From af9510afb3ce53b3dd05136fdbb9f0a5cc048205 Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Fri, 31 May 2024 16:00:06 +0200
Subject: [PATCH] fix booth destroy for arbitrators

---
 pcs/lib/commands/booth.py                 |  35 ++++---
 pcs/lib/pacemaker/live.py                 |   4 +
 pcs_test/tier0/lib/commands/test_booth.py | 110 ++++++++++++++++++++--
 3 files changed, 129 insertions(+), 20 deletions(-)

diff --git a/pcs/lib/commands/booth.py b/pcs/lib/commands/booth.py
index c961705b..f291a085 100644
--- a/pcs/lib/commands/booth.py
+++ b/pcs/lib/commands/booth.py
@@ -58,6 +58,7 @@ from pcs.lib.file.raw_file import (
 )
 from pcs.lib.interface.config import ParserErrorException
 from pcs.lib.node import get_existing_nodes_names
+from pcs.lib.pacemaker.live import has_cib_xml
 from pcs.lib.resource_agent import (
     ResourceAgentError,
     ResourceAgentFacade,
@@ -165,20 +166,30 @@ def config_destroy(
     found_instance_name = booth_env.instance_name
     _ensure_live_env(env, booth_env)
 
-    booth_resource_list = resource.find_for_config(
-        get_resources(env.get_cib()),
-        booth_env.config_path,
-    )
-    if booth_resource_list:
-        report_processor.report(
-            ReportItem.error(
-                reports.messages.BoothConfigIsUsed(
-                    found_instance_name,
-                    reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE,
-                    resource_name=str(booth_resource_list[0].get("id", "")),
+    if (
+        has_cib_xml()
+        or env.service_manager.is_running("pacemaker")
+        or env.service_manager.is_running("pacemaker_remoted")
+    ):
+        # To allow destroying booth config on arbitrators, only check CIB if:
+        # * pacemaker is running and therefore we are able to get CIB
+        # * CIB is stored on disk - pcmk is not running but the node is in a
+        #   cluster (don't checking corosync to cover remote and guest nodes)
+        # If CIB cannot be loaded in either case, fail with an error.
+        booth_resource_list = resource.find_for_config(
+            get_resources(env.get_cib()),
+            booth_env.config_path,
+        )
+        if booth_resource_list:
+            report_processor.report(
+                ReportItem.error(
+                    reports.messages.BoothConfigIsUsed(
+                        found_instance_name,
+                        reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE,
+                        resource_name=str(booth_resource_list[0].get("id", "")),
+                    )
                 )
             )
-        )
     # Only systemd is currently supported. Initd does not supports multiple
     # instances (here specified by name)
     if is_systemd(env.service_manager):
diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py
index 301ce343..43197ac1 100644
--- a/pcs/lib/pacemaker/live.py
+++ b/pcs/lib/pacemaker/live.py
@@ -151,6 +151,10 @@ def get_ticket_status_text(runner: CommandRunner) -> Tuple[str, str, int]:
 ### cib
 
 
+def has_cib_xml() -> bool:
+    return os.path.exists(os.path.join(settings.cib_dir, "cib.xml"))
+
+
 def get_cib_xml_cmd_results(
     runner: CommandRunner, scope: Optional[str] = None
 ) -> tuple[str, str, int]:
diff --git a/pcs_test/tier0/lib/commands/test_booth.py b/pcs_test/tier0/lib/commands/test_booth.py
index 4e945216..2957e378 100644
--- a/pcs_test/tier0/lib/commands/test_booth.py
+++ b/pcs_test/tier0/lib/commands/test_booth.py
@@ -524,10 +524,13 @@ class ConfigSetupAuthfileFix(TestCase, FixtureMixin):
 
 
 class ConfigDestroy(TestCase, FixtureMixin):
+    # pylint: disable=too-many-public-methods
     def setUp(self):
         self.env_assist, self.config = get_env_tools(self)
+        self.cib_path = os.path.join(settings.cib_dir, "cib.xml")
 
     def fixture_config_booth_not_used(self, instance_name="booth"):
+        self.config.fs.exists(self.cib_path, True)
         self.config.runner.cib.load()
         self.config.services.is_running(
             "booth", instance=instance_name, return_value=False
@@ -536,6 +539,44 @@ class ConfigDestroy(TestCase, FixtureMixin):
             "booth", instance=instance_name, return_value=False
         )
 
+    def fixture_config_booth_used(
+        self,
+        instance_name,
+        cib_exists=False,
+        pcmk_running=False,
+        pcmk_remote_running=False,
+        booth_running=False,
+        booth_enabled=False,
+    ):
+        cib_load_exception = False
+        self.config.fs.exists(self.cib_path, cib_exists)
+        if not cib_exists:
+            self.config.services.is_running(
+                "pacemaker",
+                return_value=pcmk_running,
+                name="services.is_running.pcmk",
+            )
+            if not pcmk_running:
+                self.config.services.is_running(
+                    "pacemaker_remoted",
+                    return_value=pcmk_remote_running,
+                    name="services.is_running.pcmk_remote",
+                )
+        if cib_exists and not pcmk_running and not pcmk_remote_running:
+            self.config.runner.cib.load(
+                returncode=1, stderr="unable to get cib, pcmk is not running"
+            )
+            cib_load_exception = True
+        elif pcmk_running or pcmk_remote_running:
+            self.config.runner.cib.load(resources=self.fixture_cib_resources())
+        if not cib_load_exception:
+            self.config.services.is_running(
+                "booth", instance=instance_name, return_value=booth_running
+            )
+            self.config.services.is_enabled(
+                "booth", instance=instance_name, return_value=booth_enabled
+            )
+
     def fixture_config_success(self, instance_name="booth"):
         self.fixture_config_booth_not_used(instance_name)
         self.config.raw_file.read(
@@ -663,17 +704,29 @@ class ConfigDestroy(TestCase, FixtureMixin):
             expected_in_processor=False,
         )
 
-    def test_booth_config_in_use(self):
+    def test_booth_config_in_use_cib_pcmk(self):
         instance_name = "booth"
+        self.fixture_config_booth_used(instance_name, pcmk_running=True)
 
-        self.config.runner.cib.load(resources=self.fixture_cib_resources())
-        self.config.services.is_running(
-            "booth", instance=instance_name, return_value=True
+        self.env_assist.assert_raise_library_error(
+            lambda: commands.config_destroy(self.env_assist.get_env()),
         )
-        self.config.services.is_enabled(
-            "booth", instance=instance_name, return_value=True
+
+        self.env_assist.assert_reports(
+            [
+                fixture.error(
+                    reports.codes.BOOTH_CONFIG_IS_USED,
+                    name=instance_name,
+                    detail=reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE,
+                    resource_name="booth_resource",
+                ),
+            ]
         )
 
+    def test_booth_config_in_use_cib_pcmk_remote(self):
+        instance_name = "booth"
+        self.fixture_config_booth_used(instance_name, pcmk_remote_running=True)
+
         self.env_assist.assert_raise_library_error(
             lambda: commands.config_destroy(self.env_assist.get_env()),
         )
@@ -686,16 +739,57 @@ class ConfigDestroy(TestCase, FixtureMixin):
                     detail=reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE,
                     resource_name="booth_resource",
                 ),
+            ]
+        )
+
+    def test_pcmk_not_running(self):
+        instance_name = "booth"
+        self.fixture_config_booth_used(instance_name, cib_exists=True)
+
+        self.env_assist.assert_raise_library_error(
+            lambda: commands.config_destroy(self.env_assist.get_env()),
+            [
+                fixture.error(
+                    reports.codes.CIB_LOAD_ERROR,
+                    reason="unable to get cib, pcmk is not running",
+                )
+            ],
+            expected_in_processor=False,
+        )
+
+    def test_booth_config_in_use_systemd_running(self):
+        instance_name = "booth"
+        self.fixture_config_booth_used(instance_name, booth_running=True)
+
+        self.env_assist.assert_raise_library_error(
+            lambda: commands.config_destroy(self.env_assist.get_env()),
+        )
+
+        self.env_assist.assert_reports(
+            [
                 fixture.error(
                     reports.codes.BOOTH_CONFIG_IS_USED,
                     name=instance_name,
-                    detail=reports.const.BOOTH_CONFIG_USED_ENABLED_IN_SYSTEMD,
+                    detail=reports.const.BOOTH_CONFIG_USED_RUNNING_IN_SYSTEMD,
                     resource_name=None,
                 ),
+            ]
+        )
+
+    def test_booth_config_in_use_systemd_enabled(self):
+        instance_name = "booth"
+        self.fixture_config_booth_used(instance_name, booth_enabled=True)
+
+        self.env_assist.assert_raise_library_error(
+            lambda: commands.config_destroy(self.env_assist.get_env()),
+        )
+
+        self.env_assist.assert_reports(
+            [
                 fixture.error(
                     reports.codes.BOOTH_CONFIG_IS_USED,
                     name=instance_name,
-                    detail=reports.const.BOOTH_CONFIG_USED_RUNNING_IN_SYSTEMD,
+                    detail=reports.const.BOOTH_CONFIG_USED_ENABLED_IN_SYSTEMD,
                     resource_name=None,
                 ),
             ]
-- 
2.25.1

