Blame SOURCES/bz1781303-01-fix-safe-disabling-clones-groups-bundles.patch

bc4e95
From e56f42bf31ae0a52618fe8754fd0b2ae623e6a7a Mon Sep 17 00:00:00 2001
bc4e95
From: Tomas Jelinek <tojeline@redhat.com>
bc4e95
Date: Thu, 12 Dec 2019 14:46:44 +0100
bc4e95
Subject: [PATCH 1/7] squash bz1781303 fix safe-disabling clones, groups,
bc4e95
 bundles
bc4e95
bc4e95
fix simulate_cib_error report
bc4e95
bc4e95
Putting only one CIB in the report is not enough info. Both original and
bc4e95
changed CIB as well as crm_simulate output would be needed. All that
bc4e95
info can be seen in debug messages. So there is no need to put it in the
bc4e95
report.
bc4e95
---
bc4e95
 pcs/cli/common/console_report.py              |   7 +-
bc4e95
 pcs/lib/cib/resource/common.py                |  21 +-
bc4e95
 pcs/lib/commands/resource.py                  |  27 +-
bc4e95
 pcs/lib/pacemaker/live.py                     |   8 +-
bc4e95
 pcs/lib/reports.py                            |   4 +-
bc4e95
 .../tier0/cli/common/test_console_report.py   |  10 +-
bc4e95
 .../tier0/lib/cib/test_resource_common.py     |  60 ++++-
bc4e95
 .../resource/test_resource_enable_disable.py  | 242 +++++++++++++++++-
bc4e95
 pcs_test/tier0/lib/pacemaker/test_live.py     |   7 -
bc4e95
 9 files changed, 350 insertions(+), 36 deletions(-)
bc4e95
bc4e95
diff --git a/pcs/cli/common/console_report.py b/pcs/cli/common/console_report.py
bc4e95
index d349c823..60dbb2a0 100644
bc4e95
--- a/pcs/cli/common/console_report.py
bc4e95
+++ b/pcs/cli/common/console_report.py
bc4e95
@@ -1269,8 +1269,11 @@ CODE_TO_MESSAGE_BUILDER_MAP = {
bc4e95
     ,
bc4e95
 
bc4e95
     codes.CIB_SIMULATE_ERROR: lambda info:
bc4e95
-        "Unable to simulate changes in CIB: {reason}\n{cib}"
bc4e95
-        .format(**info)
bc4e95
+        "Unable to simulate changes in CIB{_reason}"
bc4e95
+        .format(
bc4e95
+            _reason=format_optional(info["reason"], ": {0}"),
bc4e95
+            **info
bc4e95
+        )
bc4e95
     ,
bc4e95
 
bc4e95
     codes.CIB_PUSH_FORCED_FULL_DUE_TO_CRM_FEATURE_SET: lambda info:
bc4e95
diff --git a/pcs/lib/cib/resource/common.py b/pcs/lib/cib/resource/common.py
bc4e95
index f1891003..e30c5e69 100644
bc4e95
--- a/pcs/lib/cib/resource/common.py
bc4e95
+++ b/pcs/lib/cib/resource/common.py
bc4e95
@@ -1,8 +1,9 @@
bc4e95
 from collections import namedtuple
bc4e95
 from typing import (
bc4e95
     cast,
bc4e95
+    List,
bc4e95
     Optional,
bc4e95
-    Sequence,
bc4e95
+    Set,
bc4e95
 )
bc4e95
 from xml.etree.ElementTree import Element
bc4e95
 
bc4e95
@@ -114,7 +115,23 @@ def find_primitives(resource_el):
bc4e95
         return [resource_el]
bc4e95
     return []
bc4e95
 
bc4e95
-def get_inner_resources(resource_el: Element) -> Sequence[Element]:
bc4e95
+def get_all_inner_resources(resource_el: Element) -> Set[Element]:
bc4e95
+    """
bc4e95
+    Return all inner resources (both direct and indirect) of a resource
bc4e95
+    Example: for a clone containing a group, this function will return both
bc4e95
+    the group and the resources inside the group
bc4e95
+
bc4e95
+    resource_el -- resource element to get its inner resources
bc4e95
+    """
bc4e95
+    all_inner: Set[Element] = set()
bc4e95
+    to_process = set([resource_el])
bc4e95
+    while to_process:
bc4e95
+        new_inner = get_inner_resources(to_process.pop())
bc4e95
+        to_process.update(set(new_inner) - all_inner)
bc4e95
+        all_inner.update(new_inner)
bc4e95
+    return all_inner
bc4e95
+
bc4e95
+def get_inner_resources(resource_el: Element) -> List[Element]:
bc4e95
     """
bc4e95
     Return list of inner resources (direct descendants) of a resource
bc4e95
     specified as resource_el.
bc4e95
diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py
bc4e95
index 1b652ea4..4f975c7f 100644
bc4e95
--- a/pcs/lib/commands/resource.py
bc4e95
+++ b/pcs/lib/commands/resource.py
bc4e95
@@ -802,7 +802,28 @@ def disable_safe(env, resource_ids, strict, wait):
bc4e95
     with resource_environment(
bc4e95
         env, wait, resource_ids, _ensure_disabled_after_wait(True)
bc4e95
     ) as resources_section:
bc4e95
-        _disable_validate_and_edit_cib(env, resources_section, resource_ids)
bc4e95
+        id_provider = IdProvider(resources_section)
bc4e95
+        resource_el_list = _find_resources_or_raise(
bc4e95
+            resources_section,
bc4e95
+            resource_ids
bc4e95
+        )
bc4e95
+        env.report_processor.process_list(
bc4e95
+            _resource_list_enable_disable(
bc4e95
+                resource_el_list,
bc4e95
+                resource.common.disable,
bc4e95
+                id_provider,
bc4e95
+                env.get_cluster_state()
bc4e95
+            )
bc4e95
+        )
bc4e95
+
bc4e95
+        inner_resources_names_set = set()
bc4e95
+        for resource_el in resource_el_list:
bc4e95
+            inner_resources_names_set.update({
bc4e95
+                inner_resource_el.get("id")
bc4e95
+                for inner_resource_el
bc4e95
+                    in resource.common.get_all_inner_resources(resource_el)
bc4e95
+            })
bc4e95
+
bc4e95
         plaintext_status, transitions, dummy_cib = simulate_cib(
bc4e95
             env.cmd_runner(),
bc4e95
             get_root(resources_section)
bc4e95
@@ -830,6 +851,10 @@ def disable_safe(env, resource_ids, strict, wait):
bc4e95
                     exclude=resource_ids
bc4e95
                 )
bc4e95
             )
bc4e95
+
bc4e95
+        # Stopping a clone stops all its inner resources. That should not block
bc4e95
+        # stopping the clone.
bc4e95
+        other_affected = other_affected - inner_resources_names_set
bc4e95
         if other_affected:
bc4e95
             raise LibraryError(
bc4e95
                 reports.resource_disable_affects_other_resources(
bc4e95
diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py
bc4e95
index 83274af0..233f2e2d 100644
bc4e95
--- a/pcs/lib/pacemaker/live.py
bc4e95
+++ b/pcs/lib/pacemaker/live.py
bc4e95
@@ -271,7 +271,7 @@ def simulate_cib_xml(runner, cib_xml):
bc4e95
         transitions_file = write_tmpfile(None)
bc4e95
     except OSError as e:
bc4e95
         raise LibraryError(
bc4e95
-            reports.cib_simulate_error(format_os_error(e), cib_xml)
bc4e95
+            reports.cib_simulate_error(format_os_error(e))
bc4e95
         )
bc4e95
 
bc4e95
     cmd = [
bc4e95
@@ -284,7 +284,7 @@ def simulate_cib_xml(runner, cib_xml):
bc4e95
     stdout, stderr, retval = runner.run(cmd, stdin_string=cib_xml)
bc4e95
     if retval != 0:
bc4e95
         raise LibraryError(
bc4e95
-            reports.cib_simulate_error(stderr.strip(), cib_xml)
bc4e95
+            reports.cib_simulate_error(stderr.strip())
bc4e95
         )
bc4e95
 
bc4e95
     try:
bc4e95
@@ -297,7 +297,7 @@ def simulate_cib_xml(runner, cib_xml):
bc4e95
         return stdout, transitions_xml, new_cib_xml
bc4e95
     except OSError as e:
bc4e95
         raise LibraryError(
bc4e95
-            reports.cib_simulate_error(format_os_error(e), cib_xml)
bc4e95
+            reports.cib_simulate_error(format_os_error(e))
bc4e95
         )
bc4e95
 
bc4e95
 def simulate_cib(runner, cib):
bc4e95
@@ -319,7 +319,7 @@ def simulate_cib(runner, cib):
bc4e95
         )
bc4e95
     except (etree.XMLSyntaxError, etree.DocumentInvalid) as e:
bc4e95
         raise LibraryError(
bc4e95
-            reports.cib_simulate_error(str(e), cib_xml)
bc4e95
+            reports.cib_simulate_error(str(e))
bc4e95
         )
bc4e95
 
bc4e95
 ### wait for idle
bc4e95
diff --git a/pcs/lib/reports.py b/pcs/lib/reports.py
bc4e95
index 1f081007..c9b4a25d 100644
bc4e95
--- a/pcs/lib/reports.py
bc4e95
+++ b/pcs/lib/reports.py
bc4e95
@@ -1935,18 +1935,16 @@ def cib_diff_error(reason, cib_old, cib_new):
bc4e95
         }
bc4e95
     )
bc4e95
 
bc4e95
-def cib_simulate_error(reason, cib):
bc4e95
+def cib_simulate_error(reason):
bc4e95
     """
bc4e95
     cannot simulate effects a CIB would have on a live cluster
bc4e95
 
bc4e95
     string reason -- error description
bc4e95
-    string cib -- the CIB whose effects were to be simulated
bc4e95
     """
bc4e95
     return ReportItem.error(
bc4e95
         report_codes.CIB_SIMULATE_ERROR,
bc4e95
         info={
bc4e95
             "reason": reason,
bc4e95
-            "cib": cib,
bc4e95
         }
bc4e95
     )
bc4e95
 
bc4e95
diff --git a/pcs_test/tier0/cli/common/test_console_report.py b/pcs_test/tier0/cli/common/test_console_report.py
bc4e95
index 0d0c2457..29e9614d 100644
bc4e95
--- a/pcs_test/tier0/cli/common/test_console_report.py
bc4e95
+++ b/pcs_test/tier0/cli/common/test_console_report.py
bc4e95
@@ -2238,8 +2238,14 @@ class CibDiffError(NameBuildTest):
bc4e95
 class CibSimulateError(NameBuildTest):
bc4e95
     def test_success(self):
bc4e95
         self.assert_message_from_report(
bc4e95
-            "Unable to simulate changes in CIB: error message\n<cib />",
bc4e95
-            reports.cib_simulate_error("error message", "<cib />")
bc4e95
+            "Unable to simulate changes in CIB: error message",
bc4e95
+            reports.cib_simulate_error("error message")
bc4e95
+        )
bc4e95
+
bc4e95
+    def test_empty_reason(self):
bc4e95
+        self.assert_message_from_report(
bc4e95
+            "Unable to simulate changes in CIB",
bc4e95
+            reports.cib_simulate_error("")
bc4e95
         )
bc4e95
 
bc4e95
 
bc4e95
diff --git a/pcs_test/tier0/lib/cib/test_resource_common.py b/pcs_test/tier0/lib/cib/test_resource_common.py
bc4e95
index ebba09da..cd716ba2 100644
bc4e95
--- a/pcs_test/tier0/lib/cib/test_resource_common.py
bc4e95
+++ b/pcs_test/tier0/lib/cib/test_resource_common.py
bc4e95
@@ -200,10 +200,12 @@ class FindOneOrMoreResources(TestCase):
bc4e95
 
bc4e95
 
bc4e95
 class FindResourcesMixin:
bc4e95
+    _iterable_type = list
bc4e95
+
bc4e95
     def assert_find_resources(self, input_resource_id, output_resource_ids):
bc4e95
         self.assertEqual(
bc4e95
-            output_resource_ids,
bc4e95
-            [
bc4e95
+            self._iterable_type(output_resource_ids),
bc4e95
+            self._iterable_type([
bc4e95
                 element.get("id", "")
bc4e95
                 for element in
bc4e95
                 self._tested_fn(
bc4e95
@@ -211,7 +213,7 @@ class FindResourcesMixin:
bc4e95
                         './/*[@id="{0}"]'.format(input_resource_id)
bc4e95
                     )
bc4e95
                 )
bc4e95
-            ]
bc4e95
+            ])
bc4e95
         )
bc4e95
 
bc4e95
     def test_group(self):
bc4e95
@@ -235,6 +237,27 @@ class FindResourcesMixin:
bc4e95
     def test_bundle_with_primitive(self):
bc4e95
         self.assert_find_resources("H-bundle", ["H"])
bc4e95
 
bc4e95
+    def test_primitive(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
+    def test_primitive_in_clone(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
+    def test_primitive_in_master(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
+    def test_primitive_in_group(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
+    def test_primitive_in_bundle(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
+    def test_cloned_group(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
+    def test_mastered_group(self):
bc4e95
+        raise NotImplementedError()
bc4e95
+
bc4e95
 
bc4e95
 class FindPrimitives(TestCase, FindResourcesMixin):
bc4e95
     _tested_fn = staticmethod(common.find_primitives)
bc4e95
@@ -266,6 +289,37 @@ class FindPrimitives(TestCase, FindResourcesMixin):
bc4e95
         self.assert_find_resources("F-master", ["F1", "F2"])
bc4e95
 
bc4e95
 
bc4e95
+class GetAllInnerResources(TestCase, FindResourcesMixin):
bc4e95
+    _iterable_type = set
bc4e95
+    _tested_fn = staticmethod(common.get_all_inner_resources)
bc4e95
+
bc4e95
+    def test_primitive(self):
bc4e95
+        self.assert_find_resources("A", set())
bc4e95
+
bc4e95
+    def test_primitive_in_clone(self):
bc4e95
+        self.assert_find_resources("B", set())
bc4e95
+
bc4e95
+    def test_primitive_in_master(self):
bc4e95
+        self.assert_find_resources("C", set())
bc4e95
+
bc4e95
+    def test_primitive_in_group(self):
bc4e95
+        self.assert_find_resources("D1", set())
bc4e95
+        self.assert_find_resources("D2", set())
bc4e95
+        self.assert_find_resources("E1", set())
bc4e95
+        self.assert_find_resources("E2", set())
bc4e95
+        self.assert_find_resources("F1", set())
bc4e95
+        self.assert_find_resources("F2", set())
bc4e95
+
bc4e95
+    def test_primitive_in_bundle(self):
bc4e95
+        self.assert_find_resources("H", set())
bc4e95
+
bc4e95
+    def test_cloned_group(self):
bc4e95
+        self.assert_find_resources("E-clone", {"E", "E1", "E2"})
bc4e95
+
bc4e95
+    def test_mastered_group(self):
bc4e95
+        self.assert_find_resources("F-master", {"F", "F1", "F2"})
bc4e95
+
bc4e95
+
bc4e95
 class GetInnerResources(TestCase, FindResourcesMixin):
bc4e95
     _tested_fn = staticmethod(common.get_inner_resources)
bc4e95
 
bc4e95
diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py b/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py
bc4e95
index 634f0f33..62899940 100644
bc4e95
--- a/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py
bc4e95
+++ b/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py
bc4e95
@@ -1729,12 +1729,6 @@ class DisableSimulate(TestCase):
bc4e95
                 fixture.error(
bc4e95
                     report_codes.CIB_SIMULATE_ERROR,
bc4e95
                     reason="some stderr",
bc4e95
-                    # curently, there is no way to normalize xml with our lxml
bc4e95
-                    # version 4.2.3, so this never passes equality tests
bc4e95
-                    # cib=self.config.calls.get(
bc4e95
-                    #         "runner.pcmk.simulate_cib"
bc4e95
-                    #     ).check_stdin.expected_stdin
bc4e95
-                    # ,
bc4e95
                 ),
bc4e95
             ],
bc4e95
             expected_in_processor=False
bc4e95
@@ -1988,12 +1982,6 @@ class DisableSafeMixin():
bc4e95
                 fixture.error(
bc4e95
                     report_codes.CIB_SIMULATE_ERROR,
bc4e95
                     reason="some stderr",
bc4e95
-                    # curently, there is no way to normalize xml with our lxml
bc4e95
-                    # version 4.2.3, so this never passes equality tests
bc4e95
-                    # cib=self.config.calls.get(
bc4e95
-                    #         "runner.pcmk.simulate_cib"
bc4e95
-                    #     ).check_stdin.expected_stdin
bc4e95
-                    # ,
bc4e95
                 ),
bc4e95
             ],
bc4e95
             expected_in_processor=False
bc4e95
@@ -2118,6 +2106,236 @@ class DisableSafeMixin():
bc4e95
             fixture.report_resource_not_running("B"),
bc4e95
         ])
bc4e95
 
bc4e95
+    def test_inner_resources(self, mock_write_tmpfile):
bc4e95
+        cib_xml = """
bc4e95
+            <resources>
bc4e95
+                <primitive id="A" />
bc4e95
+                <clone id="B-clone">
bc4e95
+                    <primitive id="B" />
bc4e95
+                </clone>
bc4e95
+                <master id="C-master">
bc4e95
+                    <primitive id="C" />
bc4e95
+                </master>
bc4e95
+                <group id="D">
bc4e95
+                    <primitive id="D1" />
bc4e95
+                    <primitive id="D2" />
bc4e95
+                </group>
bc4e95
+                <clone id="E-clone">
bc4e95
+                    <group id="E">
bc4e95
+                        <primitive id="E1" />
bc4e95
+                        <primitive id="E2" />
bc4e95
+                    </group>
bc4e95
+                </clone>
bc4e95
+                <master id="F-master">
bc4e95
+                    <group id="F">
bc4e95
+                        <primitive id="F1" />
bc4e95
+                        <primitive id="F2" />
bc4e95
+                    </group>
bc4e95
+                </master>
bc4e95
+                <bundle id="G-bundle" />
bc4e95
+                <bundle id="H-bundle">
bc4e95
+                    <primitive id="H" />
bc4e95
+                </bundle>
bc4e95
+            </resources>
bc4e95
+        """
bc4e95
+        status_xml = """
bc4e95
+            <resources>
bc4e95
+                <resource id="A" managed="true" />
bc4e95
+                
bc4e95
+                    unique="false"
bc4e95
+                >
bc4e95
+                    <resource id="B" managed="true" />
bc4e95
+                    <resource id="B" managed="true" />
bc4e95
+                </clone>
bc4e95
+                
bc4e95
+                    unique="false"
bc4e95
+                >
bc4e95
+                    <resource id="C" managed="true" />
bc4e95
+                    <resource id="C" managed="true" />
bc4e95
+                </clone>
bc4e95
+                <group id="D" number_resources="2">
bc4e95
+                    <resource id="D1" managed="true" />
bc4e95
+                    <resource id="D2" managed="true" />
bc4e95
+                </group>
bc4e95
+                
bc4e95
+                    unique="false"
bc4e95
+                >
bc4e95
+                    <group id="E:0" number_resources="2">
bc4e95
+                        <resource id="E1" managed="true" />
bc4e95
+                        <resource id="E2" managed="true" />
bc4e95
+                    </group>
bc4e95
+                    <group id="E:1" number_resources="2">
bc4e95
+                        <resource id="E1" managed="true" />
bc4e95
+                        <resource id="E2" managed="true" />
bc4e95
+                    </group>
bc4e95
+                </clone>
bc4e95
+                
bc4e95
+                    unique="false"
bc4e95
+                >
bc4e95
+                    <group id="F:0" number_resources="2">
bc4e95
+                        <resource id="F1" managed="true" />
bc4e95
+                        <resource id="F2" managed="true" />
bc4e95
+                    </group>
bc4e95
+                    <group id="F:1" number_resources="2">
bc4e95
+                        <resource id="F1" managed="true" />
bc4e95
+                        <resource id="F2" managed="true" />
bc4e95
+                    </group>
bc4e95
+                </clone>
bc4e95
+                
bc4e95
+                    unique="false" managed="true" failed="false"
bc4e95
+                >
bc4e95
+                    <replica id="0">
bc4e95
+                        <resource id="H" />
bc4e95
+                    </replica>
bc4e95
+                    <replica id="1">
bc4e95
+                        <resource id="H" />
bc4e95
+                    </replica>
bc4e95
+                </bundle>
bc4e95
+            </resources>
bc4e95
+        """
bc4e95
+        synapses = []
bc4e95
+        index = 0
bc4e95
+        for res_name, is_clone in [
bc4e95
+            ("A", False),
bc4e95
+            ("B", True),
bc4e95
+            ("C", True),
bc4e95
+            ("D1", False),
bc4e95
+            ("D2", False),
bc4e95
+            ("E1", True),
bc4e95
+            ("E2", True),
bc4e95
+            ("F1", True),
bc4e95
+            ("F2", True),
bc4e95
+            ("H", False),
bc4e95
+        ]:
bc4e95
+            if is_clone:
bc4e95
+                synapses.append(f"""
bc4e95
+                  <synapse>
bc4e95
+                    <action_set>
bc4e95
+                      <rsc_op id="{index}" operation="stop" on_node="node1">
bc4e95
+                        <primitive id="{res_name}" long_id="{res_name}:0" />
bc4e95
+                      </rsc_op>
bc4e95
+                    </action_set>
bc4e95
+                  </synapse>
bc4e95
+                  <synapse>
bc4e95
+                    <action_set>
bc4e95
+                      <rsc_op id="{index + 1}" operation="stop" on_node="node2">
bc4e95
+                        <primitive id="{res_name}" long_id="{res_name}:1" />
bc4e95
+                      </rsc_op>
bc4e95
+                    </action_set>
bc4e95
+                  </synapse>
bc4e95
+                """)
bc4e95
+                index += 2
bc4e95
+            else:
bc4e95
+                synapses.append(f"""
bc4e95
+                  <synapse>
bc4e95
+                    <action_set>
bc4e95
+                      <rsc_op id="{index}" operation="stop" on_node="node1">
bc4e95
+                        <primitive id="{res_name}" />
bc4e95
+                      </rsc_op>
bc4e95
+                    </action_set>
bc4e95
+                  </synapse>
bc4e95
+                """)
bc4e95
+                index += 1
bc4e95
+        transitions_xml = (
bc4e95
+            "<transition_graph>" + "\n".join(synapses) + "</transition_graph>"
bc4e95
+        )
bc4e95
+
bc4e95
+        self.tmpfile_transitions.read.return_value = transitions_xml
bc4e95
+        mock_write_tmpfile.side_effect = [
bc4e95
+            self.tmpfile_new_cib, self.tmpfile_transitions,
bc4e95
+            AssertionError("No other write_tmpfile call expected")
bc4e95
+        ]
bc4e95
+        (self.config
bc4e95
+            .runner.cib.load(resources=cib_xml)
bc4e95
+            .runner.pcmk.load_state(resources=status_xml)
bc4e95
+        )
bc4e95
+        self.config.runner.pcmk.simulate_cib(
bc4e95
+            self.tmpfile_new_cib.name,
bc4e95
+            self.tmpfile_transitions.name,
bc4e95
+            stdout="simulate output",
bc4e95
+            resources="""
bc4e95
+                <resources>
bc4e95
+                    <primitive id="A" />
bc4e95
+                    <clone id="B-clone">
bc4e95
+                        <meta_attributes id="B-clone-meta_attributes">
bc4e95
+                            
bc4e95
+                                id="B-clone-meta_attributes-target-role"
bc4e95
+                            />
bc4e95
+                        </meta_attributes>
bc4e95
+                        <primitive id="B" />
bc4e95
+                    </clone>
bc4e95
+                    <master id="C-master">
bc4e95
+                        <meta_attributes id="C-master-meta_attributes">
bc4e95
+                            
bc4e95
+                                id="C-master-meta_attributes-target-role"
bc4e95
+                            />
bc4e95
+                        </meta_attributes>
bc4e95
+                        <primitive id="C" />
bc4e95
+                    </master>
bc4e95
+                    <group id="D">
bc4e95
+                        <meta_attributes id="D-meta_attributes">
bc4e95
+                            
bc4e95
+                                id="D-meta_attributes-target-role"
bc4e95
+                            />
bc4e95
+                        </meta_attributes>
bc4e95
+                        <primitive id="D1" />
bc4e95
+                        <primitive id="D2" />
bc4e95
+                    </group>
bc4e95
+                    <clone id="E-clone">
bc4e95
+                        <meta_attributes id="E-clone-meta_attributes">
bc4e95
+                            
bc4e95
+                                id="E-clone-meta_attributes-target-role"
bc4e95
+                            />
bc4e95
+                        </meta_attributes>
bc4e95
+                        <group id="E">
bc4e95
+                            <primitive id="E1" />
bc4e95
+                            <primitive id="E2" />
bc4e95
+                        </group>
bc4e95
+                    </clone>
bc4e95
+                    <master id="F-master">
bc4e95
+                        <meta_attributes id="F-master-meta_attributes">
bc4e95
+                            
bc4e95
+                                id="F-master-meta_attributes-target-role"
bc4e95
+                            />
bc4e95
+                        </meta_attributes>
bc4e95
+                        <group id="F">
bc4e95
+                            <primitive id="F1" />
bc4e95
+                            <primitive id="F2" />
bc4e95
+                        </group>
bc4e95
+                    </master>
bc4e95
+                    <bundle id="G-bundle" />
bc4e95
+                    <bundle id="H-bundle">
bc4e95
+                        <meta_attributes id="H-bundle-meta_attributes">
bc4e95
+                            
bc4e95
+                                id="H-bundle-meta_attributes-target-role"
bc4e95
+                            />
bc4e95
+                        </meta_attributes>
bc4e95
+                        <primitive id="H" />
bc4e95
+                    </bundle>
bc4e95
+                </resources>
bc4e95
+            """
bc4e95
+        )
bc4e95
+        self.env_assist.assert_raise_library_error(
bc4e95
+            lambda: resource.disable_safe(
bc4e95
+                self.env_assist.get_env(),
bc4e95
+                ["B-clone", "C-master", "D", "E-clone", "F-master", "H-bundle"],
bc4e95
+                self.strict,
bc4e95
+                False,
bc4e95
+            ),
bc4e95
+            [
bc4e95
+                fixture.error(
bc4e95
+                    report_codes.RESOURCE_DISABLE_AFFECTS_OTHER_RESOURCES,
bc4e95
+                    disabled_resource_list=[
bc4e95
+                        "B-clone", "C-master", "D", "E-clone", "F-master",
bc4e95
+                        "H-bundle"
bc4e95
+                    ],
bc4e95
+                    affected_resource_list=["A"],
bc4e95
+                    crm_simulate_plaintext_output="simulate output",
bc4e95
+                ),
bc4e95
+            ],
bc4e95
+            expected_in_processor=False
bc4e95
+        )
bc4e95
+
bc4e95
 @mock.patch("pcs.lib.pacemaker.live.write_tmpfile")
bc4e95
 class DisableSafe(DisableSafeMixin, TestCase):
bc4e95
     strict = False
bc4e95
diff --git a/pcs_test/tier0/lib/pacemaker/test_live.py b/pcs_test/tier0/lib/pacemaker/test_live.py
bc4e95
index dfebcb17..1ea5454e 100644
bc4e95
--- a/pcs_test/tier0/lib/pacemaker/test_live.py
bc4e95
+++ b/pcs_test/tier0/lib/pacemaker/test_live.py
bc4e95
@@ -686,7 +686,6 @@ class SimulateCibXml(LibraryPacemakerTest):
bc4e95
             fixture.error(
bc4e95
                 report_codes.CIB_SIMULATE_ERROR,
bc4e95
                 reason="some error",
bc4e95
-                cib="<cib />",
bc4e95
             ),
bc4e95
         )
bc4e95
         mock_runner.run.assert_not_called()
bc4e95
@@ -703,7 +702,6 @@ class SimulateCibXml(LibraryPacemakerTest):
bc4e95
             fixture.error(
bc4e95
                 report_codes.CIB_SIMULATE_ERROR,
bc4e95
                 reason="some error",
bc4e95
-                cib="<cib />",
bc4e95
             ),
bc4e95
         )
bc4e95
         mock_runner.run.assert_not_called()
bc4e95
@@ -729,7 +727,6 @@ class SimulateCibXml(LibraryPacemakerTest):
bc4e95
             fixture.error(
bc4e95
                 report_codes.CIB_SIMULATE_ERROR,
bc4e95
                 reason="some error",
bc4e95
-                cib="<cib />",
bc4e95
             ),
bc4e95
         )
bc4e95
 
bc4e95
@@ -755,7 +752,6 @@ class SimulateCibXml(LibraryPacemakerTest):
bc4e95
             fixture.error(
bc4e95
                 report_codes.CIB_SIMULATE_ERROR,
bc4e95
                 reason="some error",
bc4e95
-                cib="<cib />",
bc4e95
             ),
bc4e95
         )
bc4e95
 
bc4e95
@@ -782,7 +778,6 @@ class SimulateCibXml(LibraryPacemakerTest):
bc4e95
             fixture.error(
bc4e95
                 report_codes.CIB_SIMULATE_ERROR,
bc4e95
                 reason="some error",
bc4e95
-                cib="<cib />",
bc4e95
             ),
bc4e95
         )
bc4e95
 
bc4e95
@@ -819,7 +814,6 @@ class SimulateCib(TestCase):
bc4e95
                     "Start tag expected, '<' not found, line 1, column 1 "
bc4e95
                     "(<string>, line 1)"
bc4e95
                 ),
bc4e95
-                cib=self.cib_xml,
bc4e95
             ),
bc4e95
         )
bc4e95
 
bc4e95
@@ -835,7 +829,6 @@ class SimulateCib(TestCase):
bc4e95
                     "Start tag expected, '<' not found, line 1, column 1 "
bc4e95
                     "(<string>, line 1)"
bc4e95
                 ),
bc4e95
-                cib=self.cib_xml,
bc4e95
             ),
bc4e95
         )
bc4e95
 
bc4e95
-- 
bc4e95
2.21.1
bc4e95