Blame SOURCES/bz1990787-01-Multiple-fixes-in-pcs-resource-move-command.patch

15e8c6
From 4effb249590c6da5eeb13d9106f36e210e6fe5c2 Mon Sep 17 00:00:00 2001
15e8c6
From: Ondrej Mular <omular@redhat.com>
15e8c6
Date: Mon, 10 Jan 2022 16:04:54 +0100
15e8c6
Subject: [PATCH 3/4] Multiple fixes in `pcs resource move` command
15e8c6
15e8c6
---
15e8c6
 pcs/common/reports/codes.py                   |   1 +
15e8c6
 pcs/common/reports/messages.py                |  21 ++
15e8c6
 pcs/lib/cib/node.py                           |  14 +-
15e8c6
 pcs/lib/commands/resource.py                  | 105 ++++++-
15e8c6
 pcs/lib/node.py                               |   7 +-
15e8c6
 pcs/resource.py                               |   7 +
15e8c6
 .../tier0/common/reports/test_messages.py     |  12 +
15e8c6
 .../resource/test_resource_move_autoclean.py  | 280 +++++++++++++++++-
15e8c6
 .../resource/test_resource_move_ban.py        |  45 ++-
15e8c6
 .../tools/command_env/config_runner_pcmk.py   |   2 +
15e8c6
 pcs_test/tools/command_env/mock_runner.py     |   2 +-
15e8c6
 pcs_test/tools/fixture_cib.py                 |   1 +
15e8c6
 12 files changed, 463 insertions(+), 34 deletions(-)
15e8c6
15e8c6
diff --git a/pcs/common/reports/codes.py b/pcs/common/reports/codes.py
15e8c6
index 8ed83d84..8887fd6d 100644
15e8c6
--- a/pcs/common/reports/codes.py
15e8c6
+++ b/pcs/common/reports/codes.py
15e8c6
@@ -421,6 +421,7 @@ RESOURCE_UNMOVE_UNBAN_PCMK_EXPIRED_NOT_SUPPORTED = M(
15e8c6
 )
15e8c6
 RESOURCE_MOVE_CONSTRAINT_CREATED = M("RESOURCE_MOVE_CONSTRAINT_CREATED")
15e8c6
 RESOURCE_MOVE_CONSTRAINT_REMOVED = M("RESOURCE_MOVE_CONSTRAINT_REMOVED")
15e8c6
+RESOURCE_MOVE_NOT_AFFECTING_RESOURCE = M("RESOURCE_MOVE_NOT_AFFECTING_RESOURCE")
15e8c6
 RESOURCE_MOVE_AFFECTS_OTRHER_RESOURCES = M(
15e8c6
     "RESOURCE_MOVE_AFFECTS_OTRHER_RESOURCES"
15e8c6
 )
15e8c6
diff --git a/pcs/common/reports/messages.py b/pcs/common/reports/messages.py
15e8c6
index 16171fd0..37c79ba1 100644
15e8c6
--- a/pcs/common/reports/messages.py
15e8c6
+++ b/pcs/common/reports/messages.py
15e8c6
@@ -6140,6 +6140,27 @@ class ResourceMoveConstraintRemoved(ReportItemMessage):
15e8c6
         )
15e8c6
 
15e8c6
 
15e8c6
+@dataclass(frozen=True)
15e8c6
+class ResourceMoveNotAffectingResource(ReportItemMessage):
15e8c6
+    """
15e8c6
+    Creating a location constraint to move a resource has no effect on the
15e8c6
+    resource.
15e8c6
+
15e8c6
+    resource_id -- id of the resource to be moved
15e8c6
+    """
15e8c6
+
15e8c6
+    resource_id: str
15e8c6
+    _code = codes.RESOURCE_MOVE_NOT_AFFECTING_RESOURCE
15e8c6
+
15e8c6
+    @property
15e8c6
+    def message(self) -> str:
15e8c6
+        return (
15e8c6
+            f"Unable to move resource '{self.resource_id}' using a location "
15e8c6
+            "constraint. Current location of the resource may be affected by "
15e8c6
+            "some other constraint."
15e8c6
+        )
15e8c6
+
15e8c6
+
15e8c6
 @dataclass(frozen=True)
15e8c6
 class ResourceMoveAffectsOtherResources(ReportItemMessage):
15e8c6
     """
15e8c6
diff --git a/pcs/lib/cib/node.py b/pcs/lib/cib/node.py
15e8c6
index 20a41ca0..df2ffbaa 100644
15e8c6
--- a/pcs/lib/cib/node.py
15e8c6
+++ b/pcs/lib/cib/node.py
15e8c6
@@ -1,12 +1,17 @@
15e8c6
 from collections import namedtuple
15e8c6
+from typing import Set
15e8c6
 from lxml import etree
15e8c6
+from lxml.etree import _Element
15e8c6
 
15e8c6
 from pcs.common import reports
15e8c6
 from pcs.common.reports.item import ReportItem
15e8c6
 from pcs.lib.cib.nvpair import update_nvset
15e8c6
 from pcs.lib.cib.tools import get_nodes
15e8c6
 from pcs.lib.errors import LibraryError
15e8c6
-from pcs.lib.xml_tools import append_when_useful
15e8c6
+from pcs.lib.xml_tools import (
15e8c6
+    append_when_useful,
15e8c6
+    get_root,
15e8c6
+)
15e8c6
 
15e8c6
 
15e8c6
 class PacemakerNode(namedtuple("PacemakerNode", "name addr")):
15e8c6
@@ -58,6 +63,13 @@ def update_node_instance_attrs(
15e8c6
     append_when_useful(cib_nodes, node_el)
15e8c6
 
15e8c6
 
15e8c6
+def get_node_names(cib: _Element) -> Set[str]:
15e8c6
+    return {
15e8c6
+        str(node.attrib["uname"])
15e8c6
+        for node in get_nodes(get_root(cib)).iterfind("./node")
15e8c6
+    }
15e8c6
+
15e8c6
+
15e8c6
 def _ensure_node_exists(tree, node_name, state_nodes=None):
15e8c6
     """
15e8c6
     Make sure node with specified name exists
15e8c6
diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py
15e8c6
index d0e8f4db..82ce73e0 100644
15e8c6
--- a/pcs/lib/commands/resource.py
15e8c6
+++ b/pcs/lib/commands/resource.py
15e8c6
@@ -50,12 +50,16 @@ from pcs.lib.cib.tools import (
15e8c6
 from pcs.lib.env import LibraryEnvironment, WaitType
15e8c6
 from pcs.lib.errors import LibraryError
15e8c6
 from pcs.lib.external import CommandRunner
15e8c6
-from pcs.lib.node import get_existing_nodes_names_addrs
15e8c6
+from pcs.lib.node import (
15e8c6
+    get_existing_nodes_names_addrs,
15e8c6
+    get_pacemaker_node_names,
15e8c6
+)
15e8c6
 from pcs.lib.pacemaker import simulate as simulate_tools
15e8c6
 from pcs.lib.pacemaker.live import (
15e8c6
     diff_cibs_xml,
15e8c6
     get_cib,
15e8c6
     get_cib_xml,
15e8c6
+    get_cluster_status_dom,
15e8c6
     has_resource_unmove_unban_expired_support,
15e8c6
     push_cib_diff_xml,
15e8c6
     resource_ban,
15e8c6
@@ -1589,6 +1593,16 @@ def move(
15e8c6
     )
15e8c6
 
15e8c6
 
15e8c6
+def _nodes_exist_reports(
15e8c6
+    cib: _Element, node_names: Iterable[str]
15e8c6
+) -> ReportItemList:
15e8c6
+    existing_node_names = get_pacemaker_node_names(cib)
15e8c6
+    return [
15e8c6
+        reports.ReportItem.error(reports.messages.NodeNotFound(node_name))
15e8c6
+        for node_name in (set(node_names) - existing_node_names)
15e8c6
+    ]
15e8c6
+
15e8c6
+
15e8c6
 def move_autoclean(
15e8c6
     env: LibraryEnvironment,
15e8c6
     resource_id: str,
15e8c6
@@ -1626,6 +1640,9 @@ def move_autoclean(
15e8c6
     if resource_el is not None:
15e8c6
         report_list.extend(resource.common.validate_move(resource_el, master))
15e8c6
 
15e8c6
+    if node:
15e8c6
+        report_list.extend(_nodes_exist_reports(cib, [node]))
15e8c6
+
15e8c6
     if env.report_processor.report_list(report_list).has_errors:
15e8c6
         raise LibraryError()
15e8c6
 
15e8c6
@@ -1659,8 +1676,32 @@ def move_autoclean(
15e8c6
     add_constraint_cib_diff = diff_cibs_xml(
15e8c6
         env.cmd_runner(), env.report_processor, cib_xml, rsc_moved_cib_xml
15e8c6
     )
15e8c6
+    with get_tmp_cib(
15e8c6
+        env.report_processor, rsc_moved_cib_xml
15e8c6
+    ) as rsc_moved_constraint_cleared_cib_file:
15e8c6
+        stdout, stderr, retval = resource_unmove_unban(
15e8c6
+            env.cmd_runner(
15e8c6
+                dict(CIB_file=rsc_moved_constraint_cleared_cib_file.name)
15e8c6
+            ),
15e8c6
+            resource_id,
15e8c6
+            node,
15e8c6
+            master,
15e8c6
+        )
15e8c6
+        if retval != 0:
15e8c6
+            raise LibraryError(
15e8c6
+                ReportItem.error(
15e8c6
+                    reports.messages.ResourceUnmoveUnbanPcmkError(
15e8c6
+                        resource_id, stdout, stderr
15e8c6
+                    )
15e8c6
+                )
15e8c6
+            )
15e8c6
+        rsc_moved_constraint_cleared_cib_file.seek(0)
15e8c6
+        constraint_removed_cib = rsc_moved_constraint_cleared_cib_file.read()
15e8c6
     remove_constraint_cib_diff = diff_cibs_xml(
15e8c6
-        env.cmd_runner(), env.report_processor, rsc_moved_cib_xml, cib_xml
15e8c6
+        env.cmd_runner(),
15e8c6
+        env.report_processor,
15e8c6
+        rsc_moved_cib_xml,
15e8c6
+        constraint_removed_cib,
15e8c6
     )
15e8c6
 
15e8c6
     if not (add_constraint_cib_diff and remove_constraint_cib_diff):
15e8c6
@@ -1689,13 +1730,15 @@ def move_autoclean(
15e8c6
                     )
15e8c6
                 )
15e8c6
             )
15e8c6
-    _ensure_resource_is_not_moved(
15e8c6
+    _ensure_resource_moved_and_not_moved_back(
15e8c6
         env.cmd_runner,
15e8c6
         env.report_processor,
15e8c6
         etree_to_str(after_move_simulated_cib),
15e8c6
         remove_constraint_cib_diff,
15e8c6
         resource_id,
15e8c6
         strict,
15e8c6
+        resource_state_before,
15e8c6
+        node,
15e8c6
     )
15e8c6
     push_cib_diff_xml(env.cmd_runner(), add_constraint_cib_diff)
15e8c6
     env.report_processor.report(
15e8c6
@@ -1704,13 +1747,15 @@ def move_autoclean(
15e8c6
         )
15e8c6
     )
15e8c6
     env.wait_for_idle(wait_timeout)
15e8c6
-    _ensure_resource_is_not_moved(
15e8c6
+    _ensure_resource_moved_and_not_moved_back(
15e8c6
         env.cmd_runner,
15e8c6
         env.report_processor,
15e8c6
         get_cib_xml(env.cmd_runner()),
15e8c6
         remove_constraint_cib_diff,
15e8c6
         resource_id,
15e8c6
         strict,
15e8c6
+        resource_state_before,
15e8c6
+        node,
15e8c6
     )
15e8c6
     push_cib_diff_xml(env.cmd_runner(), remove_constraint_cib_diff)
15e8c6
     env.report_processor.report(
15e8c6
@@ -1730,16 +1775,35 @@ def move_autoclean(
15e8c6
         raise LibraryError()
15e8c6
 
15e8c6
 
15e8c6
-def _ensure_resource_is_not_moved(
15e8c6
+def _ensure_resource_moved_and_not_moved_back(
15e8c6
     runner_factory: Callable[[Optional[Mapping[str, str]]], CommandRunner],
15e8c6
     report_processor: reports.ReportProcessor,
15e8c6
     cib_xml: str,
15e8c6
     remove_constraint_cib_diff: str,
15e8c6
     resource_id: str,
15e8c6
     strict: bool,
15e8c6
+    resource_state_before: Dict[str, List[str]],
15e8c6
+    node: Optional[str],
15e8c6
 ) -> None:
15e8c6
     # pylint: disable=too-many-locals
15e8c6
     with get_tmp_cib(report_processor, cib_xml) as rsc_unmove_cib_file:
15e8c6
+        if not _was_resource_moved(
15e8c6
+            node,
15e8c6
+            resource_state_before,
15e8c6
+            get_resource_state(
15e8c6
+                get_cluster_status_dom(
15e8c6
+                    runner_factory(dict(CIB_file=rsc_unmove_cib_file.name))
15e8c6
+                ),
15e8c6
+                resource_id,
15e8c6
+            ),
15e8c6
+        ):
15e8c6
+            raise LibraryError(
15e8c6
+                reports.ReportItem.error(
15e8c6
+                    reports.messages.ResourceMoveNotAffectingResource(
15e8c6
+                        resource_id
15e8c6
+                    )
15e8c6
+                )
15e8c6
+            )
15e8c6
         push_cib_diff_xml(
15e8c6
             runner_factory(dict(CIB_file=rsc_unmove_cib_file.name)),
15e8c6
             remove_constraint_cib_diff,
15e8c6
@@ -1809,20 +1873,31 @@ def _resource_running_on_nodes(
15e8c6
     return frozenset()
15e8c6
 
15e8c6
 
15e8c6
+def _was_resource_moved(
15e8c6
+    node: Optional[str],
15e8c6
+    resource_state_before: Dict[str, List[str]],
15e8c6
+    resource_state_after: Dict[str, List[str]],
15e8c6
+) -> bool:
15e8c6
+    running_on_nodes = _resource_running_on_nodes(resource_state_after)
15e8c6
+    return not bool(
15e8c6
+        resource_state_before
15e8c6
+        and (  # running resource moved
15e8c6
+            not running_on_nodes
15e8c6
+            or (node and node not in running_on_nodes)
15e8c6
+            or (resource_state_before == resource_state_after)
15e8c6
+        )
15e8c6
+    )
15e8c6
+
15e8c6
+
15e8c6
 def _move_wait_report(
15e8c6
     resource_id: str,
15e8c6
     node: Optional[str],
15e8c6
     resource_state_before: Dict[str, List[str]],
15e8c6
     resource_state_after: Dict[str, List[str]],
15e8c6
 ) -> ReportItem:
15e8c6
-    allowed_nodes = frozenset([node] if node else [])
15e8c6
-    running_on_nodes = _resource_running_on_nodes(resource_state_after)
15e8c6
-
15e8c6
     severity = reports.item.ReportItemSeverity.info()
15e8c6
-    if resource_state_before and (  # running resource moved
15e8c6
-        not running_on_nodes
15e8c6
-        or (allowed_nodes and allowed_nodes.isdisjoint(running_on_nodes))
15e8c6
-        or (resource_state_before == resource_state_after)
15e8c6
+    if not _was_resource_moved(
15e8c6
+        node, resource_state_before, resource_state_after
15e8c6
     ):
15e8c6
         severity = reports.item.ReportItemSeverity.error()
15e8c6
     if not resource_state_after:
15e8c6
@@ -1873,14 +1948,18 @@ class _MoveBanTemplate:
15e8c6
         lifetime=None,
15e8c6
         wait: WaitType = False,
15e8c6
     ):
15e8c6
+        # pylint: disable=too-many-locals
15e8c6
         # validate
15e8c6
         wait_timeout = env.ensure_wait_satisfiable(wait)  # raises on error
15e8c6
 
15e8c6
+        cib = env.get_cib()
15e8c6
         resource_el, report_list = resource.common.find_one_resource(
15e8c6
-            get_resources(env.get_cib()), resource_id
15e8c6
+            get_resources(cib), resource_id
15e8c6
         )
15e8c6
         if resource_el is not None:
15e8c6
             report_list.extend(self._validate(resource_el, master))
15e8c6
+        if node:
15e8c6
+            report_list.extend(_nodes_exist_reports(cib, [node]))
15e8c6
         if env.report_processor.report_list(report_list).has_errors:
15e8c6
             raise LibraryError()
15e8c6
 
15e8c6
diff --git a/pcs/lib/node.py b/pcs/lib/node.py
15e8c6
index ff08f747..3a7f236e 100644
15e8c6
--- a/pcs/lib/node.py
15e8c6
+++ b/pcs/lib/node.py
15e8c6
@@ -3,6 +3,7 @@ from typing import (
15e8c6
     List,
15e8c6
     Optional,
15e8c6
     Tuple,
15e8c6
+    Set,
15e8c6
 )
15e8c6
 
15e8c6
 from lxml.etree import _Element
15e8c6
@@ -11,7 +12,7 @@ from pcs.common import reports
15e8c6
 from pcs.common.reports import ReportItemList
15e8c6
 from pcs.common.reports import ReportItemSeverity
15e8c6
 from pcs.common.reports.item import ReportItem
15e8c6
-from pcs.lib.cib.node import PacemakerNode
15e8c6
+from pcs.lib.cib.node import PacemakerNode, get_node_names
15e8c6
 from pcs.lib.cib.resource import remote_node, guest_node
15e8c6
 from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfigFacade
15e8c6
 from pcs.lib.corosync.node import CorosyncNode
15e8c6
@@ -28,6 +29,10 @@ def get_existing_nodes_names(
15e8c6
     )
15e8c6
 
15e8c6
 
15e8c6
+def get_pacemaker_node_names(cib: _Element) -> Set[str]:
15e8c6
+    return get_node_names(cib) | set(get_existing_nodes_names(None, cib)[0])
15e8c6
+
15e8c6
+
15e8c6
 def get_existing_nodes_names_addrs(
15e8c6
     corosync_conf=None, cib=None, error_on_missing_name=False
15e8c6
 ):
15e8c6
diff --git a/pcs/resource.py b/pcs/resource.py
15e8c6
index c7cf4c7e..f4f2c093 100644
15e8c6
--- a/pcs/resource.py
15e8c6
+++ b/pcs/resource.py
15e8c6
@@ -24,6 +24,7 @@ from pcs.settings import (
15e8c6
     pacemaker_wait_timeout_status as PACEMAKER_WAIT_TIMEOUT_STATUS,
15e8c6
 )
15e8c6
 from pcs.cli.common.errors import (
15e8c6
+    SEE_MAN_CHANGES,
15e8c6
     raise_command_replaced,
15e8c6
     CmdLineInputError,
15e8c6
 )
15e8c6
@@ -868,6 +869,12 @@ def resource_move(lib: Any, argv: List[str], modifiers: InputModifiers):
15e8c6
     node = None
15e8c6
     if argv:
15e8c6
         node = argv.pop(0)
15e8c6
+        if node.startswith("lifetime="):
15e8c6
+            deprecation_warning(
15e8c6
+                "Option 'lifetime' has been removed. {}".format(
15e8c6
+                    SEE_MAN_CHANGES.format("0.11")
15e8c6
+                )
15e8c6
+            )
15e8c6
     if argv:
15e8c6
         raise CmdLineInputError()
15e8c6
 
15e8c6
diff --git a/pcs_test/tier0/common/reports/test_messages.py b/pcs_test/tier0/common/reports/test_messages.py
15e8c6
index a6f92395..a56ef382 100644
15e8c6
--- a/pcs_test/tier0/common/reports/test_messages.py
15e8c6
+++ b/pcs_test/tier0/common/reports/test_messages.py
15e8c6
@@ -4515,6 +4515,18 @@ class ResourceMoveConstraintRemoved(NameBuildTest):
15e8c6
         )
15e8c6
 
15e8c6
 
15e8c6
+class ResourceMoveNotAffectingResource(NameBuildTest):
15e8c6
+    def test_success(self):
15e8c6
+        self.assert_message_from_report(
15e8c6
+            (
15e8c6
+                "Unable to move resource 'R1' using a location constraint. "
15e8c6
+                "Current location of the resource may be affected by some "
15e8c6
+                "other constraint."
15e8c6
+            ),
15e8c6
+            reports.ResourceMoveNotAffectingResource("R1"),
15e8c6
+        )
15e8c6
+
15e8c6
+
15e8c6
 class ResourceMoveAffectsOtherResources(NameBuildTest):
15e8c6
     def test_multiple(self):
15e8c6
         self.assert_message_from_report(
15e8c6
diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_move_autoclean.py b/pcs_test/tier0/lib/commands/resource/test_resource_move_autoclean.py
15e8c6
index 32d758de..1bd4ee82 100644
15e8c6
--- a/pcs_test/tier0/lib/commands/resource/test_resource_move_autoclean.py
15e8c6
+++ b/pcs_test/tier0/lib/commands/resource/test_resource_move_autoclean.py
15e8c6
@@ -20,6 +20,25 @@ from pcs_test.tools.command_env import get_env_tools
15e8c6
 from pcs_test.tools.misc import get_test_resource as rc
15e8c6
 
15e8c6
 
15e8c6
+def _node_fixture(name, node_id):
15e8c6
+    return f'<node id="{node_id}" uname="{name}"/>'
15e8c6
+
15e8c6
+
15e8c6
+def _node_list_fixture(nodes):
15e8c6
+    return "\n".join(
15e8c6
+        _node_fixture(node_name, node_id)
15e8c6
+        for node_id, node_name in enumerate(nodes)
15e8c6
+    )
15e8c6
+
15e8c6
+
15e8c6
+def _nodes_section_fixture(content):
15e8c6
+    return f"""
15e8c6
+    <nodes>
15e8c6
+    {content}
15e8c6
+    </nodes>
15e8c6
+    """
15e8c6
+
15e8c6
+
15e8c6
 def _rsc_primitive_fixture(res_id):
15e8c6
     return f'<primitive id="{res_id}"/>'
15e8c6
 
15e8c6
@@ -145,11 +164,17 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
             resources=_resources_tag(
15e8c6
                 _resource_primitive + _resource_promotable_clone
15e8c6
             ),
15e8c6
+            nodes=_nodes_section_fixture(
15e8c6
+                _node_list_fixture([self.orig_node, self.new_node])
15e8c6
+            ),
15e8c6
         )
15e8c6
         self.orig_cib = etree_to_str(
15e8c6
             xml_fromstring(self.config.calls.get(config_load_cib_name).stdout)
15e8c6
         )
15e8c6
         self.cib_with_constraint = '<updated_cib with_constraint="True"/>'
15e8c6
+        self.cib_without_constraint = (
15e8c6
+            '<cib with_constraint="False" updated="True"/>'
15e8c6
+        )
15e8c6
         self.cib_simulate_constraint = (
15e8c6
             '<cib simulate="True" with_constraint="True"/>'
15e8c6
         )
15e8c6
@@ -160,6 +185,9 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
         self.cib_diff_add_constraint_updated_tmp_file_name = (
15e8c6
             "cib_diff_add_constraint_updated"
15e8c6
         )
15e8c6
+        self.cib_constraint_removed_by_unmove_file_name = (
15e8c6
+            "cib_constraint_removed_by_unmove"
15e8c6
+        )
15e8c6
         self.cib_diff_remove_constraint_orig_tmp_file_name = (
15e8c6
             "cib_diff_remove_constraint_orig"
15e8c6
         )
15e8c6
@@ -220,13 +248,18 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
                 self.cib_diff_add_constraint_updated_tmp_file_name,
15e8c6
                 orig_content=self.cib_with_constraint,
15e8c6
             ),
15e8c6
+            TmpFileCall(
15e8c6
+                self.cib_constraint_removed_by_unmove_file_name,
15e8c6
+                orig_content=self.cib_with_constraint,
15e8c6
+                new_content=self.cib_without_constraint,
15e8c6
+            ),
15e8c6
             TmpFileCall(
15e8c6
                 self.cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
                 orig_content=self.cib_with_constraint,
15e8c6
             ),
15e8c6
             TmpFileCall(
15e8c6
                 self.cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
-                orig_content=self.orig_cib,
15e8c6
+                orig_content=self.cib_without_constraint,
15e8c6
             ),
15e8c6
             TmpFileCall(
15e8c6
                 self.simulated_cib_add_constraint_tmp_file_name,
15e8c6
@@ -296,6 +329,12 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
             stdout=self.cib_diff_add_constraint,
15e8c6
             name="runner.cib.diff.add_constraint",
15e8c6
         )
15e8c6
+        self.config.runner.pcmk.resource_clear(
15e8c6
+            resource=resource_id,
15e8c6
+            master=is_promotable,
15e8c6
+            node=self.new_node if with_node else None,
15e8c6
+            env=dict(CIB_file=self.cib_constraint_removed_by_unmove_file_name),
15e8c6
+        )
15e8c6
         self.config.runner.cib.diff(
15e8c6
             self.cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
             self.cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
@@ -308,6 +347,13 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
             cib_xml=self.cib_with_constraint,
15e8c6
             name="pcmk.simulate.rsc.move",
15e8c6
         )
15e8c6
+        self.config.runner.pcmk.load_state(
15e8c6
+            resources=status_after,
15e8c6
+            name="runner.pcmk.load_state.mid_simulation",
15e8c6
+            env=dict(
15e8c6
+                CIB_file=self.cib_apply_diff_remove_constraint_from_simulated_cib_tmp_file_name
15e8c6
+            ),
15e8c6
+        )
15e8c6
         self.config.runner.cib.push_diff(
15e8c6
             cib_diff=self.cib_diff_remove_constraint,
15e8c6
             name="pcmk.push_cib_diff.simulation.remove_constraint",
15e8c6
@@ -335,6 +381,13 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
             self.cib_with_constraint,
15e8c6
             name="load_cib_after_move",
15e8c6
         )
15e8c6
+        self.config.runner.pcmk.load_state(
15e8c6
+            resources=status_after,
15e8c6
+            name="runner.pcmk.load_state.after_push",
15e8c6
+            env=dict(
15e8c6
+                CIB_file=self.cib_apply_diff_remove_constraint_after_push_tmp_file_name
15e8c6
+            ),
15e8c6
+        )
15e8c6
         self.config.runner.cib.push_diff(
15e8c6
             cib_diff=self.cib_diff_remove_constraint,
15e8c6
             name="pcmk.push_cib_diff.simulation.remove_constraint_after_move",
15e8c6
@@ -380,6 +433,11 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
                 file_path=self.cib_diff_add_constraint_updated_tmp_file_name,
15e8c6
                 content=self.cib_with_constraint,
15e8c6
             ),
15e8c6
+            fixture.debug(
15e8c6
+                reports.codes.TMP_FILE_WRITE,
15e8c6
+                file_path=self.cib_constraint_removed_by_unmove_file_name,
15e8c6
+                content=self.cib_with_constraint,
15e8c6
+            ),
15e8c6
             fixture.debug(
15e8c6
                 reports.codes.TMP_FILE_WRITE,
15e8c6
                 file_path=self.cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
@@ -388,7 +446,7 @@ class MoveAutocleanSuccess(MoveAutocleanCommonSetup):
15e8c6
             fixture.debug(
15e8c6
                 reports.codes.TMP_FILE_WRITE,
15e8c6
                 file_path=self.cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
-                content=self.orig_cib,
15e8c6
+                content=self.cib_without_constraint,
15e8c6
             ),
15e8c6
             fixture.debug(
15e8c6
                 reports.codes.TMP_FILE_WRITE,
15e8c6
@@ -758,9 +816,7 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
             resources=_state_resource_fixture(resource_id, "Stopped"),
15e8c6
         )
15e8c6
         self.env_assist.assert_raise_library_error(
15e8c6
-            lambda: move_autoclean(
15e8c6
-                self.env_assist.get_env(), resource_id, node="node"
15e8c6
-            ),
15e8c6
+            lambda: move_autoclean(self.env_assist.get_env(), resource_id),
15e8c6
             [
15e8c6
                 fixture.error(
15e8c6
                     reports.codes.CANNOT_MOVE_RESOURCE_NOT_RUNNING,
15e8c6
@@ -770,11 +826,33 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
             expected_in_processor=False,
15e8c6
         )
15e8c6
 
15e8c6
+    def test_node_not_found(self):
15e8c6
+        resource_id = "A"
15e8c6
+        node = "non_existing_node"
15e8c6
+        self.config.runner.cib.load(
15e8c6
+            resources=_resources_tag(_rsc_primitive_fixture(resource_id)),
15e8c6
+        )
15e8c6
+        self.env_assist.assert_raise_library_error(
15e8c6
+            lambda: move_autoclean(
15e8c6
+                self.env_assist.get_env(), resource_id, node
15e8c6
+            ),
15e8c6
+        )
15e8c6
+        self.env_assist.assert_reports(
15e8c6
+            [
15e8c6
+                fixture.error(
15e8c6
+                    reports.codes.NODE_NOT_FOUND,
15e8c6
+                    node=node,
15e8c6
+                    searched_types=[],
15e8c6
+                )
15e8c6
+            ],
15e8c6
+        )
15e8c6
+
15e8c6
     def test_constraint_already_exist(self):
15e8c6
         resource_id = "A"
15e8c6
         config_load_cib_name = "load_cib"
15e8c6
         node = "node1"
15e8c6
         cib_with_constraint = '<cib with_constraint="True"/>'
15e8c6
+        cib_without_constraint = '<cib with_constraint="False" updated="True"/>'
15e8c6
         cib_rsc_move_tmp_file_name = "cib_rsc_move_tmp_file"
15e8c6
         cib_diff_add_constraint_orig_tmp_file_name = (
15e8c6
             "cib_diff_add_constraint_orig"
15e8c6
@@ -788,6 +866,9 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
         cib_diff_remove_constraint_updated_tmp_file_name = (
15e8c6
             "cib_diff_remove_constraint_updated"
15e8c6
         )
15e8c6
+        cib_constraint_removed_by_unmove_file_name = (
15e8c6
+            "cib_constraint_removed_by_unmove"
15e8c6
+        )
15e8c6
         self.config.runner.cib.load(
15e8c6
             resources=_resources_tag(_rsc_primitive_fixture(resource_id)),
15e8c6
             constraints=f"""
15e8c6
@@ -795,6 +876,7 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
                   <rsc_location id="prefer-{resource_id}" rsc="{resource_id}" role="Started" node="{node}" score="INFINITY"/>
15e8c6
               </constraints>
15e8c6
             """,
15e8c6
+            nodes=_nodes_section_fixture(_node_list_fixture([node])),
15e8c6
             name=config_load_cib_name,
15e8c6
         )
15e8c6
         orig_cib = etree_to_str(
15e8c6
@@ -815,13 +897,18 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
                     cib_diff_add_constraint_updated_tmp_file_name,
15e8c6
                     orig_content=cib_with_constraint,
15e8c6
                 ),
15e8c6
+                TmpFileCall(
15e8c6
+                    cib_constraint_removed_by_unmove_file_name,
15e8c6
+                    orig_content=cib_with_constraint,
15e8c6
+                    new_content=cib_without_constraint,
15e8c6
+                ),
15e8c6
                 TmpFileCall(
15e8c6
                     cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
                     orig_content=cib_with_constraint,
15e8c6
                 ),
15e8c6
                 TmpFileCall(
15e8c6
                     cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
-                    orig_content=orig_cib,
15e8c6
+                    orig_content=cib_without_constraint,
15e8c6
                 ),
15e8c6
             ]
15e8c6
         )
15e8c6
@@ -839,6 +926,11 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
             stdout="",
15e8c6
             name="runner.cib.diff.add_constraint",
15e8c6
         )
15e8c6
+        self.config.runner.pcmk.resource_clear(
15e8c6
+            resource=resource_id,
15e8c6
+            node=node,
15e8c6
+            env=dict(CIB_file=cib_constraint_removed_by_unmove_file_name),
15e8c6
+        )
15e8c6
         self.config.runner.cib.diff(
15e8c6
             cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
             cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
@@ -863,6 +955,11 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
                     file_path=cib_diff_add_constraint_updated_tmp_file_name,
15e8c6
                     content=cib_with_constraint,
15e8c6
                 ),
15e8c6
+                fixture.debug(
15e8c6
+                    reports.codes.TMP_FILE_WRITE,
15e8c6
+                    file_path=cib_constraint_removed_by_unmove_file_name,
15e8c6
+                    content=cib_with_constraint,
15e8c6
+                ),
15e8c6
                 fixture.debug(
15e8c6
                     reports.codes.TMP_FILE_WRITE,
15e8c6
                     file_path=cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
@@ -871,7 +968,7 @@ class MoveAutocleanValidations(MoveAutocleanCommonSetup):
15e8c6
                 fixture.debug(
15e8c6
                     reports.codes.TMP_FILE_WRITE,
15e8c6
                     file_path=cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
-                    content=orig_cib,
15e8c6
+                    content=cib_without_constraint,
15e8c6
                 ),
15e8c6
                 fixture.info(
15e8c6
                     reports.codes.NO_ACTION_NECESSARY,
15e8c6
@@ -896,6 +993,9 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
         self.cib_diff_add_constraint = "diff_add_constraint"
15e8c6
         self.cib_diff_remove_constraint = "diff_remove_constraint"
15e8c6
         self.cib_with_constraint = '<cib with_constraint="True"/>'
15e8c6
+        self.cib_without_constraint = (
15e8c6
+            '<cib with_constraint="False" updated="True"/>'
15e8c6
+        )
15e8c6
         self.cib_rsc_move_tmp_file_name = "cib_rsc_move_tmp_file"
15e8c6
         self.cib_diff_add_constraint_orig_tmp_file_name = (
15e8c6
             "cib_diff_add_constraint_orig"
15e8c6
@@ -903,6 +1003,9 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
         self.cib_diff_add_constraint_updated_tmp_file_name = (
15e8c6
             "cib_diff_add_constraint_updated"
15e8c6
         )
15e8c6
+        self.cib_constraint_removed_by_unmove_file_name = (
15e8c6
+            "cib_constraint_removed_by_unmove"
15e8c6
+        )
15e8c6
         self.cib_diff_remove_constraint_orig_tmp_file_name = (
15e8c6
             "cib_diff_remove_constraint_orig"
15e8c6
         )
15e8c6
@@ -951,6 +1054,9 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
 
15e8c6
         self.config.runner.cib.load(
15e8c6
             resources=_resources_tag(_rsc_primitive_fixture(self.resource_id)),
15e8c6
+            nodes=_nodes_section_fixture(
15e8c6
+                _node_list_fixture(["node1", "node2"])
15e8c6
+            ),
15e8c6
             name=self.config_load_cib_name,
15e8c6
         )
15e8c6
         self.orig_cib = etree_to_str(
15e8c6
@@ -979,13 +1085,18 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
                 self.cib_diff_add_constraint_updated_tmp_file_name,
15e8c6
                 orig_content=self.cib_with_constraint,
15e8c6
             ),
15e8c6
+            TmpFileCall(
15e8c6
+                self.cib_constraint_removed_by_unmove_file_name,
15e8c6
+                orig_content=self.cib_with_constraint,
15e8c6
+                new_content=self.cib_without_constraint,
15e8c6
+            ),
15e8c6
             TmpFileCall(
15e8c6
                 self.cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
                 orig_content=self.cib_with_constraint,
15e8c6
             ),
15e8c6
             TmpFileCall(
15e8c6
                 self.cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
-                orig_content=self.orig_cib,
15e8c6
+                orig_content=self.cib_without_constraint,
15e8c6
             ),
15e8c6
             TmpFileCall(
15e8c6
                 self.simulated_cib_add_constraint_tmp_file_name,
15e8c6
@@ -1067,6 +1178,11 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             stdout=self.cib_diff_add_constraint,
15e8c6
             name="runner.cib.diff.add_constraint",
15e8c6
         )
15e8c6
+        self.config.runner.pcmk.resource_clear(
15e8c6
+            resource=self.resource_id,
15e8c6
+            node=node,
15e8c6
+            env=dict(CIB_file=self.cib_constraint_removed_by_unmove_file_name),
15e8c6
+        )
15e8c6
         self.config.runner.cib.diff(
15e8c6
             self.cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
             self.cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
@@ -1081,6 +1197,15 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
         )
15e8c6
         if stage <= 1:
15e8c6
             return
15e8c6
+        self.config.runner.pcmk.load_state(
15e8c6
+            resources=_state_resource_fixture(
15e8c6
+                self.resource_id, "Started", node if node else "node2"
15e8c6
+            ),
15e8c6
+            name="runner.pcmk.load_state.mid_simulation",
15e8c6
+            env=dict(
15e8c6
+                CIB_file=self.cib_apply_diff_remove_constraint_from_simulated_cib_tmp_file_name
15e8c6
+            ),
15e8c6
+        )
15e8c6
         self.config.runner.cib.push_diff(
15e8c6
             cib_diff=self.cib_diff_remove_constraint,
15e8c6
             name="pcmk.push_cib_diff.simulation.remove_constraint",
15e8c6
@@ -1110,6 +1235,17 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             self.cib_with_constraint,
15e8c6
             name="load_cib_after_move",
15e8c6
         )
15e8c6
+        if stage <= 3:
15e8c6
+            return
15e8c6
+        self.config.runner.pcmk.load_state(
15e8c6
+            resources=_state_resource_fixture(
15e8c6
+                self.resource_id, "Started", node if node else "node2"
15e8c6
+            ),
15e8c6
+            name="runner.pcmk.load_state.after_push",
15e8c6
+            env=dict(
15e8c6
+                CIB_file=self.cib_apply_diff_remove_constraint_after_push_tmp_file_name
15e8c6
+            ),
15e8c6
+        )
15e8c6
         self.config.runner.cib.push_diff(
15e8c6
             cib_diff=self.cib_diff_remove_constraint,
15e8c6
             name="pcmk.push_cib_diff.simulation.remove_constraint_after_move",
15e8c6
@@ -1126,7 +1262,7 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             ),
15e8c6
             name="pcmk.simulate.rsc.unmove.after_push",
15e8c6
         )
15e8c6
-        if stage <= 3:
15e8c6
+        if stage <= 4:
15e8c6
             return
15e8c6
         self.config.runner.cib.push_diff(
15e8c6
             cib_diff=self.cib_diff_remove_constraint,
15e8c6
@@ -1153,6 +1289,11 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
                 file_path=self.cib_diff_add_constraint_updated_tmp_file_name,
15e8c6
                 content=self.cib_with_constraint,
15e8c6
             ),
15e8c6
+            fixture.debug(
15e8c6
+                reports.codes.TMP_FILE_WRITE,
15e8c6
+                file_path=self.cib_constraint_removed_by_unmove_file_name,
15e8c6
+                content=self.cib_with_constraint,
15e8c6
+            ),
15e8c6
             fixture.debug(
15e8c6
                 reports.codes.TMP_FILE_WRITE,
15e8c6
                 file_path=self.cib_diff_remove_constraint_orig_tmp_file_name,
15e8c6
@@ -1161,7 +1302,7 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             fixture.debug(
15e8c6
                 reports.codes.TMP_FILE_WRITE,
15e8c6
                 file_path=self.cib_diff_remove_constraint_updated_tmp_file_name,
15e8c6
-                content=self.orig_cib,
15e8c6
+                content=self.cib_without_constraint,
15e8c6
             ),
15e8c6
             fixture.debug(
15e8c6
                 reports.codes.TMP_FILE_WRITE,
15e8c6
@@ -1199,7 +1340,7 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
                 reports.codes.WAIT_FOR_IDLE_STARTED,
15e8c6
                 timeout=0,
15e8c6
             ),
15e8c6
-        ][: {None: None, 3: -2, 2: 7, 1: 5}[stage]]
15e8c6
+        ][: {None: None, 4: -2, 3: 10, 2: 8, 1: 6}[stage]]
15e8c6
 
15e8c6
     def test_move_affects_other_resources_strict(self):
15e8c6
         self.tmp_file_mock_obj.set_calls(
15e8c6
@@ -1304,7 +1445,8 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
                 ),
15e8c6
             )
15e8c6
         )
15e8c6
-        self.set_up_testing_env(stage=3)
15e8c6
+        setup_stage = 4
15e8c6
+        self.set_up_testing_env(stage=setup_stage)
15e8c6
         self.env_assist.assert_raise_library_error(
15e8c6
             lambda: move_autoclean(self.env_assist.get_env(), self.resource_id),
15e8c6
             [
15e8c6
@@ -1316,7 +1458,7 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             ],
15e8c6
             expected_in_processor=False,
15e8c6
         )
15e8c6
-        self.env_assist.assert_reports(self.get_reports(stage=3))
15e8c6
+        self.env_assist.assert_reports(self.get_reports(stage=setup_stage))
15e8c6
 
15e8c6
     def test_unmove_after_push_affects_other_resources_strict(self):
15e8c6
         self.tmp_file_mock_obj.set_calls(
15e8c6
@@ -1330,7 +1472,8 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
                 ),
15e8c6
             )
15e8c6
         )
15e8c6
-        self.set_up_testing_env(stage=3)
15e8c6
+        setup_stage = 4
15e8c6
+        self.set_up_testing_env(stage=setup_stage)
15e8c6
         self.env_assist.assert_raise_library_error(
15e8c6
             lambda: move_autoclean(
15e8c6
                 self.env_assist.get_env(),
15e8c6
@@ -1346,7 +1489,7 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             ],
15e8c6
             expected_in_processor=False,
15e8c6
         )
15e8c6
-        self.env_assist.assert_reports(self.get_reports(stage=3))
15e8c6
+        self.env_assist.assert_reports(self.get_reports(stage=setup_stage))
15e8c6
 
15e8c6
     def test_resource_not_runnig_after_move(self):
15e8c6
         self.tmp_file_mock_obj.set_calls(
15e8c6
@@ -1381,8 +1524,113 @@ class MoveAutocleanFailures(MoveAutocleanCommonSetup):
15e8c6
             ]
15e8c6
         )
15e8c6
 
15e8c6
+    def test_simulation_resource_not_moved(self):
15e8c6
+        node = "node2"
15e8c6
+        different_node = f"different-{node}"
15e8c6
+        setup_stage = 1
15e8c6
+        self.tmp_file_mock_obj.set_calls(
15e8c6
+            self.get_tmp_files_mocks(
15e8c6
+                _simulation_transition_fixture(
15e8c6
+                    _simulation_synapses_fixture(self.resource_id)
15e8c6
+                ),
15e8c6
+            )
15e8c6
+            + [
15e8c6
+                TmpFileCall(
15e8c6
+                    self.cib_apply_diff_remove_constraint_from_simulated_cib_tmp_file_name,
15e8c6
+                    orig_content=self.cib_simulate_constraint,
15e8c6
+                ),
15e8c6
+            ]
15e8c6
+        )
15e8c6
+        self.set_up_testing_env(node=node, stage=setup_stage)
15e8c6
+        self.config.runner.pcmk.load_state(
15e8c6
+            resources=_state_resource_fixture(
15e8c6
+                self.resource_id, "Started", different_node
15e8c6
+            ),
15e8c6
+            name="runner.pcmk.load_state.final",
15e8c6
+            env=dict(
15e8c6
+                CIB_file=self.cib_apply_diff_remove_constraint_from_simulated_cib_tmp_file_name
15e8c6
+            ),
15e8c6
+        )
15e8c6
+        self.env_assist.assert_raise_library_error(
15e8c6
+            lambda: move_autoclean(
15e8c6
+                self.env_assist.get_env(),
15e8c6
+                self.resource_id,
15e8c6
+                node=node,
15e8c6
+            ),
15e8c6
+            [
15e8c6
+                fixture.error(
15e8c6
+                    reports.codes.RESOURCE_MOVE_NOT_AFFECTING_RESOURCE,
15e8c6
+                    resource_id=self.resource_id,
15e8c6
+                )
15e8c6
+            ],
15e8c6
+            expected_in_processor=False,
15e8c6
+        )
15e8c6
+        self.env_assist.assert_reports(
15e8c6
+            self.get_reports(stage=setup_stage)
15e8c6
+            + [
15e8c6
+                fixture.debug(
15e8c6
+                    reports.codes.TMP_FILE_WRITE,
15e8c6
+                    file_path=self.cib_apply_diff_remove_constraint_from_simulated_cib_tmp_file_name,
15e8c6
+                    content=self.cib_simulate_constraint,
15e8c6
+                ),
15e8c6
+            ]
15e8c6
+        )
15e8c6
+
15e8c6
+    def test_after_push_resource_not_moved(self):
15e8c6
+        node = "node2"
15e8c6
+        different_node = f"different-{node}"
15e8c6
+        setup_stage = 3
15e8c6
+        self.tmp_file_mock_obj.set_calls(
15e8c6
+            self.get_tmp_files_mocks(
15e8c6
+                _simulation_transition_fixture(
15e8c6
+                    _simulation_synapses_fixture(self.resource_id)
15e8c6
+                ),
15e8c6
+                _simulation_transition_fixture(),
15e8c6
+            )
15e8c6
+            + [
15e8c6
+                TmpFileCall(
15e8c6
+                    self.cib_apply_diff_remove_constraint_after_push_tmp_file_name,
15e8c6
+                    orig_content=self.cib_with_constraint,
15e8c6
+                ),
15e8c6
+            ]
15e8c6
+        )
15e8c6
+        self.set_up_testing_env(node=node, stage=setup_stage)
15e8c6
+        self.config.runner.pcmk.load_state(
15e8c6
+            resources=_state_resource_fixture(
15e8c6
+                self.resource_id, "Started", different_node
15e8c6
+            ),
15e8c6
+            name="runner.pcmk.load_state.final",
15e8c6
+            env=dict(
15e8c6
+                CIB_file=self.cib_apply_diff_remove_constraint_after_push_tmp_file_name,
15e8c6
+            ),
15e8c6
+        )
15e8c6
+        self.env_assist.assert_raise_library_error(
15e8c6
+            lambda: move_autoclean(
15e8c6
+                self.env_assist.get_env(),
15e8c6
+                self.resource_id,
15e8c6
+                node=node,
15e8c6
+            ),
15e8c6
+            [
15e8c6
+                fixture.error(
15e8c6
+                    reports.codes.RESOURCE_MOVE_NOT_AFFECTING_RESOURCE,
15e8c6
+                    resource_id=self.resource_id,
15e8c6
+                )
15e8c6
+            ],
15e8c6
+            expected_in_processor=False,
15e8c6
+        )
15e8c6
+        self.env_assist.assert_reports(
15e8c6
+            self.get_reports(stage=setup_stage)
15e8c6
+            + [
15e8c6
+                fixture.debug(
15e8c6
+                    reports.codes.TMP_FILE_WRITE,
15e8c6
+                    file_path=self.cib_apply_diff_remove_constraint_after_push_tmp_file_name,
15e8c6
+                    content=self.cib_with_constraint,
15e8c6
+                ),
15e8c6
+            ]
15e8c6
+        )
15e8c6
+
15e8c6
     def test_resource_running_on_a_different_node(self):
15e8c6
-        node = "node1"
15e8c6
+        node = "node2"
15e8c6
         different_node = f"different-{node}"
15e8c6
         self.tmp_file_mock_obj.set_calls(
15e8c6
             self.get_tmp_files_mocks(
15e8c6
diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_move_ban.py b/pcs_test/tier0/lib/commands/resource/test_resource_move_ban.py
15e8c6
index 5d57fa06..28dd1cd1 100644
15e8c6
--- a/pcs_test/tier0/lib/commands/resource/test_resource_move_ban.py
15e8c6
+++ b/pcs_test/tier0/lib/commands/resource/test_resource_move_ban.py
15e8c6
@@ -10,6 +10,29 @@ from pcs.common.reports import ReportItemSeverity as severities
15e8c6
 from pcs.common.reports import codes as report_codes
15e8c6
 from pcs.lib.commands import resource
15e8c6
 
15e8c6
+
15e8c6
+def _node_fixture(name, node_id):
15e8c6
+    return f'<node id="{node_id}" uname="{name}"/>'
15e8c6
+
15e8c6
+
15e8c6
+def _node_list_fixture(nodes):
15e8c6
+    return "\n".join(
15e8c6
+        _node_fixture(node_name, node_id)
15e8c6
+        for node_id, node_name in enumerate(nodes)
15e8c6
+    )
15e8c6
+
15e8c6
+
15e8c6
+def _nodes_section_fixture(content):
15e8c6
+    return f"""
15e8c6
+    <nodes>
15e8c6
+    {content}
15e8c6
+    </nodes>
15e8c6
+    """
15e8c6
+
15e8c6
+
15e8c6
+nodes_section = _nodes_section_fixture(
15e8c6
+    _node_list_fixture(["node", "node1", "node2"])
15e8c6
+)
15e8c6
 resources_primitive = """
15e8c6
     <resources>
15e8c6
         <primitive id="A" />
15e8c6
@@ -128,8 +151,24 @@ class MoveBanBaseMixin(MoveBanClearBaseMixin):
15e8c6
             expected_in_processor=False,
15e8c6
         )
15e8c6
 
15e8c6
+    def test_node_not_found(self):
15e8c6
+        self.config.runner.cib.load(resources=resources_primitive)
15e8c6
+        node = "node"
15e8c6
+        self.env_assist.assert_raise_library_error(
15e8c6
+            lambda: self.lib_action(self.env_assist.get_env(), "A", node)
15e8c6
+        )
15e8c6
+        self.env_assist.assert_reports(
15e8c6
+            [
15e8c6
+                fixture.error(
15e8c6
+                    report_codes.NODE_NOT_FOUND, node=node, searched_types=[]
15e8c6
+                )
15e8c6
+            ]
15e8c6
+        )
15e8c6
+
15e8c6
     def test_all_options(self):
15e8c6
-        self.config.runner.cib.load(resources=resources_promotable)
15e8c6
+        self.config.runner.cib.load(
15e8c6
+            resources=resources_promotable, nodes=nodes_section
15e8c6
+        )
15e8c6
         self.config_pcmk_action(
15e8c6
             resource="A-clone",
15e8c6
             master=True,
15e8c6
@@ -274,7 +313,9 @@ class MoveBanWaitMixin:
15e8c6
     def setUp(self):
15e8c6
         self.timeout = 10
15e8c6
         self.env_assist, self.config = get_env_tools(self)
15e8c6
-        self.config.runner.cib.load(resources=resources_primitive)
15e8c6
+        self.config.runner.cib.load(
15e8c6
+            resources=resources_primitive, nodes=nodes_section
15e8c6
+        )
15e8c6
 
15e8c6
     @mock.patch.object(
15e8c6
         settings,
15e8c6
diff --git a/pcs_test/tools/command_env/config_runner_pcmk.py b/pcs_test/tools/command_env/config_runner_pcmk.py
15e8c6
index 6df3dff3..7a88cf96 100644
15e8c6
--- a/pcs_test/tools/command_env/config_runner_pcmk.py
15e8c6
+++ b/pcs_test/tools/command_env/config_runner_pcmk.py
15e8c6
@@ -695,6 +695,7 @@ class PcmkShortcuts:
15e8c6
         stdout="",
15e8c6
         stderr="",
15e8c6
         returncode=0,
15e8c6
+        env=None,
15e8c6
     ):
15e8c6
         """
15e8c6
         Create a call for crm_resource --clear
15e8c6
@@ -711,6 +712,7 @@ class PcmkShortcuts:
15e8c6
         string stdout -- crm_resource's stdout
15e8c6
         string stderr -- crm_resource's stderr
15e8c6
         int returncode -- crm_resource's returncode
15e8c6
+        dict env -- CommandRunner environment variables
15e8c6
         """
15e8c6
         # arguments are used via locals()
15e8c6
         # pylint: disable=unused-argument
15e8c6
diff --git a/pcs_test/tools/command_env/mock_runner.py b/pcs_test/tools/command_env/mock_runner.py
15e8c6
index f7871fc2..8520ce02 100644
15e8c6
--- a/pcs_test/tools/command_env/mock_runner.py
15e8c6
+++ b/pcs_test/tools/command_env/mock_runner.py
15e8c6
@@ -143,6 +143,6 @@ class Runner:
15e8c6
             env.update(env_extend)
15e8c6
         if env != call.env:
15e8c6
             raise self.__call_queue.error_with_context(
15e8c6
-                f"ENV doesn't match. Expected: {call.env}; Real: {env}"
15e8c6
+                f"Command #{i}: ENV doesn't match. Expected: {call.env}; Real: {env}"
15e8c6
             )
15e8c6
         return call.stdout, call.stderr, call.returncode
15e8c6
diff --git a/pcs_test/tools/fixture_cib.py b/pcs_test/tools/fixture_cib.py
15e8c6
index 602491c8..bf02bacc 100644
15e8c6
--- a/pcs_test/tools/fixture_cib.py
15e8c6
+++ b/pcs_test/tools/fixture_cib.py
15e8c6
@@ -310,6 +310,7 @@ MODIFIER_GENERATORS = {
15e8c6
     "replace": replace_all,
15e8c6
     "append": append_all,
15e8c6
     "resources": lambda xml: replace_all({"./configuration/resources": xml}),
15e8c6
+    "nodes": lambda xml: replace_all({"./configuration/nodes": xml}),
15e8c6
     "constraints": lambda xml: replace_all(
15e8c6
         {"./configuration/constraints": xml}
15e8c6
     ),
15e8c6
-- 
15e8c6
2.31.1
15e8c6