diff --git a/RHEL-214140-01-fix-pcs-stonith-update-scsi-devices-command.patch b/RHEL-214140-01-fix-pcs-stonith-update-scsi-devices-command.patch new file mode 100644 index 0000000..035b84c --- /dev/null +++ b/RHEL-214140-01-fix-pcs-stonith-update-scsi-devices-command.patch @@ -0,0 +1,440 @@ +From 86aef9b5f6990b9258ec91a90860fcea1d37e76f Mon Sep 17 00:00:00 2001 +From: Miroslav Lisik +Date: Thu, 11 Jun 2026 16:23:08 +0200 +Subject: [PATCH] fix `pcs stonith update-scsi-devices` command + +* use new cibadmin option `--update-status` when updating scsi devices +--- + pcs/lib/commands/stonith.py | 9 ++++-- + pcs/lib/env.py | 31 +++++++++++++------ + pcs/lib/pacemaker/live.py | 12 ++++++- + .../test_stonith_update_scsi_devices.py | 27 ++++++++++++++++ + pcs_test/tier0/lib/test_env_cib.py | 21 +++++++++++++ + pcs_test/tools/command_env/config_env.py | 8 ++++- + .../tools/command_env/config_runner_cib.py | 7 ++++- + .../tools/command_env/config_runner_pcmk.py | 15 +++++++++ + pcs_test/tools/command_env/mock_push_cib.py | 30 ++++++++++++++++-- + 9 files changed, 142 insertions(+), 18 deletions(-) + +diff --git a/pcs/lib/commands/stonith.py b/pcs/lib/commands/stonith.py +index d39904b32..7419e4e37 100644 +--- a/pcs/lib/commands/stonith.py ++++ b/pcs/lib/commands/stonith.py +@@ -38,6 +38,7 @@ from pcs.lib.pacemaker.live import ( + fence_history_cleanup, + fence_history_text, + fence_history_update, ++ is_cibadmin_update_status_supported, + is_fence_history_supported_management, + is_getting_resource_digest_supported, + ) +@@ -268,7 +269,9 @@ def _update_scsi_devices_get_element_and_devices( + cib -- cib element + stonith_id -- id of stonith resource + """ +- if not is_getting_resource_digest_supported(runner): ++ if not is_getting_resource_digest_supported( ++ runner ++ ) or not is_cibadmin_update_status_supported(runner): + raise LibraryError( + ReportItem.error( + reports.messages.StonithRestartlessUpdateOfScsiDevicesNotSupported() +@@ -395,7 +398,7 @@ def update_scsi_devices( + _unfencing_scsi_devices( + env, stonith_el, current_device_list, set_device_list, force_flags + ) +- env.push_cib() ++ env.push_cib(with_status=True) + + + def update_scsi_devices_add_remove( +@@ -449,4 +452,4 @@ def update_scsi_devices_add_remove( + _unfencing_scsi_devices( + env, stonith_el, current_device_list, updated_device_set, force_flags + ) +- env.push_cib() ++ env.push_cib(with_status=True) +diff --git a/pcs/lib/env.py b/pcs/lib/env.py +index ca7effdd0..a5f48485e 100644 +--- a/pcs/lib/env.py ++++ b/pcs/lib/env.py +@@ -231,15 +231,21 @@ class LibraryEnvironment: + ReportItem.error(reports.messages.WaitForIdleNotLiveCluster()) + ) + +- def push_cib(self, custom_cib=None, wait_timeout: int = -1) -> None: ++ def push_cib( ++ self, ++ custom_cib: _Element | None = None, ++ wait_timeout: int = -1, ++ with_status: bool = False, ++ ) -> None: + """ + Push previously loaded instance of CIB or a custom CIB + +- etree custom_cib -- push a custom CIB instead of a loaded instance ++ custom_cib -- push a custom CIB instead of a loaded instance + (allows to push an externally provided CIB and replace the one in + the cluster completely) + wait_timeout -- wait timeout in seconds, if less than 0 wait will be + skipped, if 0 wait indefinitely ++ with_status -- push also status section of a CIB + """ + self._ensure_wait_satisfiable(wait_timeout) + if custom_cib is not None: +@@ -247,10 +253,14 @@ class LibraryEnvironment: + raise AssertionError( + "CIB has been loaded, cannot push custom CIB" + ) ++ if with_status: ++ raise AssertionError( ++ "Cannot push status section of a custom CIB" ++ ) + return self.__push_cib_full(custom_cib, wait_timeout) + if self.__loaded_cib_diff_source is None: + raise AssertionError("CIB has not been loaded") +- return self.__push_cib_diff(wait_timeout) ++ return self.__push_cib_diff(wait_timeout, with_status) + + def __push_cib_full(self, cib_to_push, wait_timeout: int): + self.__do_push_cib( +@@ -258,20 +268,23 @@ class LibraryEnvironment: + wait_timeout, + ) + +- def __push_cib_diff(self, wait_timeout: int): ++ def __push_cib_diff(self, wait_timeout: int, with_status: bool = False): + self.__do_push_cib( +- lambda: self.__main_push_cib_diff(self.cmd_runner()), wait_timeout ++ lambda: self.__main_push_cib_diff(self.cmd_runner(), with_status), ++ wait_timeout, + ) + +- def __main_push_cib_diff(self, cmd_runner): ++ def __main_push_cib_diff( ++ self, cmd_runner: CommandRunner, with_status: bool = False ++ ): + cib_diff_xml = diff_cibs_xml( + cmd_runner, + self.report_processor, +- self.__loaded_cib_diff_source, +- etree_to_str(self.__loaded_cib_to_modify), ++ cast(str, self.__loaded_cib_diff_source), ++ etree_to_str(cast(_Element, self.__loaded_cib_to_modify)), + ) + if cib_diff_xml: +- push_cib_diff_xml(cmd_runner, cib_diff_xml) ++ push_cib_diff_xml(cmd_runner, cib_diff_xml, with_status) + + def __do_push_cib(self, push_strategy, wait_timeout: int) -> None: + push_strategy() +diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py +index 3542af254..e6212edb4 100644 +--- a/pcs/lib/pacemaker/live.py ++++ b/pcs/lib/pacemaker/live.py +@@ -298,13 +298,17 @@ def replace_cib_configuration(runner: CommandRunner, tree: _Element) -> None: + return replace_cib_configuration_xml(runner, etree_to_str(tree)) + + +-def push_cib_diff_xml(runner: CommandRunner, cib_diff_xml: str) -> None: ++def push_cib_diff_xml( ++ runner: CommandRunner, cib_diff_xml: str, with_status: bool = False ++) -> None: + cmd = [ + settings.cibadmin_exec, + "--patch", + "--verbose", + "--xml-pipe", + ] ++ if with_status: ++ cmd.append("--update-status") + stdout, stderr, retval = runner.run(cmd, stdin_string=cib_diff_xml) + if retval != 0: + raise LibraryError( +@@ -952,6 +956,12 @@ def is_getting_resource_digest_supported(runner: CommandRunner) -> bool: + ) + + ++def is_cibadmin_update_status_supported(runner: CommandRunner) -> bool: ++ return _is_in_pcmk_tool_help( ++ runner, settings.cibadmin_exec, ["--update-status"] ++ ) ++ ++ + def get_resource_digests( + runner: CommandRunner, + resource_id: str, +diff --git a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py +index d1cf6456d..8ba5bebb0 100644 +--- a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py ++++ b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py +@@ -417,6 +417,7 @@ class UpdateScsiDevicesMixin: + ), + ) + self.config.runner.pcmk.is_resource_digests_supported() ++ self.config.runner.pcmk.is_cibadmin_update_status_supported() + self.config.runner.pcmk.load_state( + resources=fixture_crm_mon_res_running( + self.stonith_id, +@@ -518,6 +519,7 @@ class UpdateScsiDevicesMixin: + lrm_monitor_ops=lrm_monitor_ops_updated, + digests_attrs_list=digests_attrs_list_updated, + ), ++ with_status=True, + ) + kwargs = dict(devices_updated=devices_updated) + if devices_add is not None: +@@ -560,6 +562,26 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin): + expected_in_processor=False, + ) + ++ def test_cibadmin_doesnt_support_update_status(self): ++ self.config.runner.cib.load( ++ resources=fixture_scsi( ++ stonith_id=self.stonith_id, stonith_type=self.stonith_type ++ ) ++ ) ++ self.config.runner.pcmk.is_resource_digests_supported() ++ self.config.runner.pcmk.is_cibadmin_update_status_supported( ++ is_supported=False ++ ) ++ self.env_assist.assert_raise_library_error( ++ self.command(), ++ [ ++ fixture.error( ++ reports.codes.STONITH_RESTARTLESS_UPDATE_OF_SCSI_DEVICES_NOT_SUPPORTED, ++ ) ++ ], ++ expected_in_processor=False, ++ ) ++ + def test_nonexistent_id(self): + """ + lower level tested in +@@ -572,6 +594,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin): + ) + ) + self.config.runner.pcmk.is_resource_digests_supported() ++ self.config.runner.pcmk.is_cibadmin_update_status_supported() + self.env_assist.assert_raise_library_error(self.command()) + self.env_assist.assert_reports( + [ +@@ -597,6 +620,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin): + ) + ) + self.config.runner.pcmk.is_resource_digests_supported() ++ self.config.runner.pcmk.is_cibadmin_update_status_supported() + self.env_assist.assert_raise_library_error( + self.command(devices_add=[DEV_2], devices_remove=[DEV_1]) + ) +@@ -949,6 +973,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin): + lrm_start_ops=DEFAULT_LRM_START_OPS_UPDATED, + lrm_monitor_ops=DEFAULT_LRM_MONITOR_OPS_UPDATED, + ), ++ with_status=True, + ) + self.command(force_flags=[reports.codes.SKIP_OFFLINE_NODES])() + self.env_assist.assert_reports( +@@ -1240,6 +1265,7 @@ class UpdateScsiDevicesSetFailuresBaseMixin( + ) + ) + self.config.runner.pcmk.is_resource_digests_supported() ++ self.config.runner.pcmk.is_cibadmin_update_status_supported() + self.env_assist.assert_raise_library_error( + self.command(devices_updated=()) + ) +@@ -1271,6 +1297,7 @@ class UpdateScsiDevicesAddRemoveFailuresBaseMixin( + ) + ) + self.config.runner.pcmk.is_resource_digests_supported() ++ self.config.runner.pcmk.is_cibadmin_update_status_supported() + self.env_assist.assert_raise_library_error( + self.command(devices_add=(), devices_remove=()) + ) +diff --git a/pcs_test/tier0/lib/test_env_cib.py b/pcs_test/tier0/lib/test_env_cib.py +index e23a55a98..8f762e809 100644 +--- a/pcs_test/tier0/lib/test_env_cib.py ++++ b/pcs_test/tier0/lib/test_env_cib.py +@@ -52,6 +52,11 @@ class ManageCibAssertionMixin: + callable_obj, "CIB has been loaded, cannot push custom CIB" + ) + ++ def assert_raises_cannot_push_status_custom(self, callable_obj): ++ self.assert_raises_cib_error( ++ callable_obj, "Cannot push status section of a custom CIB" ++ ) ++ + + class IsCibLive(TestCase): + def test_is_live_when_no_cib_data_specified(self): +@@ -385,6 +390,16 @@ class PushLoadedCib(TestCase, ManageCibAssertionMixin): + ] + ) + ++ def test_with_status(self): ++ self.config_load_cib_files() ++ self.config.runner.cib.diff(self.tmpfile_old, self.tmpfile_new) ++ self.config.runner.cib.push_diff(with_status=True) ++ env = self.env_assist.get_env() ++ ++ env.get_cib() ++ env.push_cib(with_status=True) ++ self.env_assist.assert_reports(self.push_reports()) ++ + + class PushCustomCib(TestCase, ManageCibAssertionMixin): + custom_cib = "" +@@ -424,6 +439,12 @@ class PushCustomCib(TestCase, ManageCibAssertionMixin): + ] + ) + ++ def test_with_status(self): ++ env = self.env_assist.get_env() ++ self.assert_raises_cannot_push_status_custom( ++ partial(env.push_cib, etree.XML(self.custom_cib), with_status=True) ++ ) ++ + + class PushCibMockedWithWait(TestCase): + def setUp(self): +diff --git a/pcs_test/tools/command_env/config_env.py b/pcs_test/tools/command_env/config_env.py +index b421c0b21..4782d62db 100644 +--- a/pcs_test/tools/command_env/config_env.py ++++ b/pcs_test/tools/command_env/config_env.py +@@ -89,6 +89,7 @@ class EnvConfig: + wait=-1, + exception=None, + instead=None, ++ with_status=False, + **modifier_shortcuts, + ): + """ +@@ -115,7 +116,12 @@ class EnvConfig: + ) + self.__calls.place( + name, +- PushCibCall(cib_xml, wait_timeout=wait, exception=exception), ++ PushCibCall( ++ cib_xml, ++ wait_timeout=wait, ++ exception=exception, ++ with_status=with_status, ++ ), + instead=instead, + ) + +diff --git a/pcs_test/tools/command_env/config_runner_cib.py b/pcs_test/tools/command_env/config_runner_cib.py +index 16273b2b0..fabac5a28 100644 +--- a/pcs_test/tools/command_env/config_runner_cib.py ++++ b/pcs_test/tools/command_env/config_runner_cib.py +@@ -227,17 +227,22 @@ class CibShortcuts: + stderr="", + returncode=0, + env=None, ++ with_status=False, + ): + """ + Create a call for pushing a diff of CIBs + string name -- key of the call + string cib_diff -- the diff of CIBs + dict env -- CommandRunner environment variables ++ bool with_status -- if True, expect --update-status flag + """ ++ cmd = ["cibadmin", "--patch", "--verbose", "--xml-pipe"] ++ if with_status: ++ cmd.append("--update-status") + self.__calls.place( + name, + RunnerCall( +- ["cibadmin", "--patch", "--verbose", "--xml-pipe"], ++ cmd, + check_stdin=CheckStdinEqualXml(cib_diff), + stdout=stdout, + stderr=stderr, +diff --git a/pcs_test/tools/command_env/config_runner_pcmk.py b/pcs_test/tools/command_env/config_runner_pcmk.py +index 6c608f558..b3005abae 100644 +--- a/pcs_test/tools/command_env/config_runner_pcmk.py ++++ b/pcs_test/tools/command_env/config_runner_pcmk.py +@@ -1012,6 +1012,21 @@ class PcmkShortcuts: + ), + ) + ++ def is_cibadmin_update_status_supported( ++ self, ++ name="runner.pcmk.is_cibadmin_update_status_supported", ++ is_supported=True, ++ ): ++ self.__calls.place( ++ name, ++ RunnerCall( ++ ["cibadmin", "--help-all"], ++ stdout="--update-status" if is_supported else "", ++ stderr="", ++ returncode=0, ++ ), ++ ) ++ + def resource_digests( + self, + resource_id, +diff --git a/pcs_test/tools/command_env/mock_push_cib.py b/pcs_test/tools/command_env/mock_push_cib.py +index 186d14f69..67275d0e6 100644 +--- a/pcs_test/tools/command_env/mock_push_cib.py ++++ b/pcs_test/tools/command_env/mock_push_cib.py +@@ -8,19 +8,28 @@ class Call: + type = CALL_TYPE_PUSH_CIB + + def __init__( +- self, cib_xml, custom_cib=False, wait_timeout=-1, exception=None ++ self, ++ cib_xml, ++ custom_cib=False, ++ wait_timeout=-1, ++ exception=None, ++ with_status=False, + ): + self.cib_xml = cib_xml + self.custom_cib = custom_cib + self.wait_timeout = wait_timeout + self.exception = exception ++ self.with_status = with_status + + def __repr__(self): +- return str("").format(self.wait_timeout) ++ return ( ++ f"" ++ ) + + + def get_push_cib(call_queue): +- def push_cib(lib_env, custom_cib=None, wait_timeout=-1): ++ def push_cib(lib_env, custom_cib=None, wait_timeout=-1, with_status=False): + i, expected_call = call_queue.take(CALL_TYPE_PUSH_CIB) + + if custom_cib is None and expected_call.custom_cib: +@@ -62,6 +71,21 @@ def get_push_cib(call_queue): + expected_type=type(expected_call.wait_timeout), + ) + ) ++ if with_status != expected_call.with_status: ++ raise AssertionError( ++ ( ++ "Trying to call env.push_cib (call no. {index}) with " ++ "'with_status' == {real_value} ({real_type}) but it was " ++ "expected 'with_status' == {expected_value} " ++ "({expected_type})" ++ ).format( ++ index=i, ++ real_value=with_status, ++ real_type=type(with_status), ++ expected_value=expected_call.with_status, ++ expected_type=type(expected_call.with_status), ++ ) ++ ) + + if expected_call.exception: + raise expected_call.exception +-- +2.54.0 + diff --git a/pcs.spec b/pcs.spec index 5ea6bc1..49152f4 100644 --- a/pcs.spec +++ b/pcs.spec @@ -1,6 +1,6 @@ Name: pcs Version: 0.12.3 -Release: 1%{?dist}.alma.1 +Release: 2%{?dist}.alma.1 # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses # GPL-2.0-only: pcs @@ -130,6 +130,7 @@ Source102: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-%{e # Patch1: name.patch Patch1: do-not-support-cluster-setup-with-udp-u-transport.patch Patch2: show-info-page-instead-of-webui.patch +Patch3: RHEL-214140-01-fix-pcs-stonith-update-scsi-devices-command.patch # ui patches: >200 # Patch201: name-web-ui.patch @@ -385,6 +386,7 @@ tar xzf %{SOURCE102} -C ${_esbuild_dir} --strip-components=1 # update_times_patch %%{PATCH1} update_times_patch %{PATCH1} update_times_patch %{PATCH2} +update_times_patch %{PATCH3} # generate .tarball-version if building from an untagged commit, not a released version # autogen uses git-version-gen which uses .tarball-version for generating version number @@ -685,9 +687,13 @@ run_all_tests %changelog -* Fri Jul 03 2026 Andrew Lukoshko - 0.12.3-1.alma.1 +* Thu Aug 20 2026 Andrew Lukoshko - 0.12.3-2.alma.1 - Added @esbuild/linux-riscv64 source to fix web UI build on riscv64 +* Wed Aug 19 2026 Michal Pospíšil - 0.12.3-2 +- Fixed restartless update of SCSI devices + Resolves: RHEL-214140 + * Tue Jun 30 2026 Michal Pospíšil - 0.12.3-1 - Rebased pcs to the latest sources (see CHANGELOG.md) Resolves: RHEL-183182, RHEL-169571, RHEL-176475, RHEL-190557