Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2026-10-27 17:08:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Tue Oct 27 17:08:38 2026 rev:424 rq:1393074 version:5.1.0+20261027.e559ffa7 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2026-10-20 12:44:18.390514775 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.2252680/crmsh.changes 2026-10-27 17:09:36.727369894 +0100 @@ -1,0 +2,23 @@ +Tue Oct 27 09:45:08 UTC 2026 - [email protected] + +- Update to version 5.1.0+20261027.e559ffa7: + * fix(sbd): allow zero fencing-watchdog-timeout (bsc#1286095) + +------------------------------------------------------------------- +Fri Oct 23 07:15:47 UTC 2026 - [email protected] + +- Update to version 5.1.0+20261023.bf7b3f40: + * fix(sbd): Don't verify SBD device while purging SBD + +------------------------------------------------------------------- +Thu Oct 22 14:00:49 UTC 2026 - [email protected] + +- Update to version 5.1.0+20261022.8a2aa2f0: + * feat(sbd): account for disk backstore in watchdog timeout (jsc#PED-13836) + * feat(sbd): show disk backstore timeouts + * feat(storage): read multipath timeout config + * feat(sbd): read async IO timeout + * feat(sbd): read servant restart interval + * fix(sbd): account for SCSI device timeout + +------------------------------------------------------------------- Old: ---- crmsh-5.1.0+20261020.5b865c90.tar.bz2 New: ---- crmsh-5.1.0+20261027.e559ffa7.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.wHZP5i/_old 2026-10-27 17:09:38.570447096 +0100 +++ /var/tmp/diff_new_pack.wHZP5i/_new 2026-10-27 17:09:38.572447179 +0100 @@ -40,7 +40,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 5.1.0+20261020.5b865c90 +Version: 5.1.0+20261027.e559ffa7 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.wHZP5i/_old 2026-10-27 17:09:38.622449274 +0100 +++ /var/tmp/diff_new_pack.wHZP5i/_new 2026-10-27 17:09:38.625449399 +0100 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">5b865c909a7c06c3bb7fb65b17319f86081f2af5</param> + <param name="changesrevision">828483ec8937b01e50ce5586452cfa56f5fd055d</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-5.1.0+20261020.5b865c90.tar.bz2 -> crmsh-5.1.0+20261027.e559ffa7.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/crmsh/sbd.py new/crmsh-5.1.0+20261027.e559ffa7/crmsh/sbd.py --- old/crmsh-5.1.0+20261020.5b865c90/crmsh/sbd.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/crmsh/sbd.py 2026-10-27 09:52:08.000000000 +0100 @@ -112,12 +112,25 @@ return utils.parse_sysconfig(SBDManager.SYSCONFIG_SBD).get(key) @staticmethod - def get_crashdump_watchdog_timeout() -> typing.Optional[int]: - res = SBDUtils.get_sbd_value_from_config("SBD_OPTS") - if not res: + def get_sbd_opts_int_value(option: str) -> typing.Optional[int]: + """ + Get the integer value of an option, like "-t 5", from SBD_OPTS in /etc/sysconfig/sbd + Return None if SBD_OPTS is not set or doesn't contain the option + """ + sbd_opts = SBDUtils.get_sbd_value_from_config("SBD_OPTS") + if not sbd_opts: return None - matched = re.search(r"-C\s+(\d+)", res) - return int(matched.group(1)) if matched else None + opts_list = shlex.split(sbd_opts) + for index, opt in enumerate(opts_list): + if opt == option: + if index + 1 >= len(opts_list): + raise ValueError(f"Cannot get the value of {option} in SBD_OPTS") + return int(opts_list[index + 1]) + return None + + @staticmethod + def get_crashdump_watchdog_timeout() -> typing.Optional[int]: + return SBDUtils.get_sbd_opts_int_value("-C") @staticmethod def get_sbd_device_from_config(): @@ -236,6 +249,9 @@ SBD_WATCHDOG_TIMEOUT_DEFAULT = 5 SBD_WATCHDOG_TIMEOUT_DEFAULT_S390 = 15 SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE = 35 + SBD_SERVANT_RESTART_INTERVAL_DEFAULT = 5 + SBD_LOOP_TIMEOUT_DEFAULT = 1 + SBD_TIMEOUT_IO_LOOP_TIMEOUT_FACTOR = 3 QDEVICE_SYNC_TIMEOUT_MARGIN = 5 SHOW_SBD_START_TIMEOUT_CMD = "systemctl daemon-reload; systemctl show -p TimeoutStartUSec sbd.service --value" SHOW_DEFAULT_START_TIMEOUT_CMD = "systemctl show -p DefaultTimeoutStartUSec --value" @@ -245,12 +261,14 @@ diskless_sbd: bool, quiet: bool, context: typing.Optional[bootstrap.GlobalVariables] = None, + sbd_devices: typing.Optional[typing.List[str]] = None, ): """ Init function """ self.context = context self.disk_based = not diskless_sbd + self.sbd_devices = sbd_devices or [] self.sbd_msgwait = None self.fencing_timeout = None self.sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT @@ -271,6 +289,7 @@ if not self.disk_based: self._adjust_sbd_watchdog_timeout_with_diskless_and_qdevice() else: + self._adjust_sbd_watchdog_timeout_with_disk_backstore() self._set_sbd_msgwait() def _set_sbd_watchdog_timeout(self): @@ -287,6 +306,26 @@ ) self.sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT_S390 + def _adjust_sbd_watchdog_timeout_with_disk_backstore(self): + """ + When using disk-based SBD, the watchdog timeout must also cover the + disk backstore (multipath or SCSI) timeout. When multipath and SCSI + devices are mixed, multipath takes priority. + """ + if not self.sbd_devices: + return + watchdog_from_disk = self.get_sbd_watchdog_timeout_from_disk( + storage_utils.pick_backstore_device(self.sbd_devices) + ) + if watchdog_from_disk is None: + return + if self.sbd_watchdog_timeout < watchdog_from_disk: + self.logger.warning( + "SBD watchdog_timeout is set to %d, it was %d", + watchdog_from_disk, self.sbd_watchdog_timeout + ) + self.sbd_watchdog_timeout = watchdog_from_disk + def _set_sbd_msgwait(self): """ Set sbd msgwait from profiles.yml if exists @@ -338,6 +377,56 @@ return int(res) @staticmethod + def get_sbd_servant_restart_interval() -> int: + """ + Get sbd servant restart interval from SBD_OPTS -t option. + """ + value = SBDUtils.get_sbd_opts_int_value("-t") + return SBDTimeout.SBD_SERVANT_RESTART_INTERVAL_DEFAULT if value is None else value + + @staticmethod + def get_sbd_loop_timeout() -> int: + """ + Get loop timeout from the first configured SBD device metadata. + """ + dev_list = SBDUtils.get_sbd_device_from_config() + if not dev_list: + return SBDTimeout.SBD_LOOP_TIMEOUT_DEFAULT + device_metadata = SBDUtils.get_sbd_device_metadata(dev_list[0], timeout_only=True) + return device_metadata.get("loop", SBDTimeout.SBD_LOOP_TIMEOUT_DEFAULT) + + @staticmethod + def get_sbd_timeout_io() -> int: + """ + Get sbd async IO read timeout from SBD_OPTS -I option. + """ + value = SBDUtils.get_sbd_opts_int_value("-I") + if value is None: + return SBDTimeout.SBD_TIMEOUT_IO_LOOP_TIMEOUT_FACTOR * SBDTimeout.get_sbd_loop_timeout() + return value + + @staticmethod + def get_sbd_watchdog_timeout_from_disk(dev) -> int|None: + """ + Get the watchdog timeout required by the SBD disk backstore. + + multipath: 1.2 * (max_polling_interval + checker_timeout + servant_restart_interval - timeout_io) + SCSI: 1.2 * (scsi_timeout + servant_restart_interval - timeout_io) + + Returns None when the device is neither multipath nor SCSI. + """ + servant_restart_interval = SBDTimeout.get_sbd_servant_restart_interval() + timeout_io = SBDTimeout.get_sbd_timeout_io() + if storage_utils.is_multipath_device(dev): + timeout = (storage_utils.get_multipath_max_polling_interval(dev) or 0) \ + + storage_utils.get_multipath_checker_timeout(dev) + elif storage_utils.is_scsi_device(dev): + timeout = storage_utils.get_scsi_timeout(dev) + else: + return None + return max(1, int(1.2 * (timeout + servant_restart_interval - timeout_io))) + + @staticmethod def get_fencing_watchdog_timeout_expected( crashdump_watchdog_timeout: int|None = None, sbd_watchdog_timeout: int|None = None @@ -353,6 +442,7 @@ self.two_node_without_qdevice = utils.is_2node_cluster_without_qdevice() self.crashdump_watchdog_timeout = SBDUtils.get_crashdump_watchdog_timeout() dev_list = SBDUtils.get_sbd_device_from_config() + self.sbd_devices = dev_list if dev_list: # disk-based self.disk_based = True first_dev = dev_list[0] @@ -375,14 +465,23 @@ logger.debug("Inspect SBDTimeout: %s", vars(self)) @classmethod - def get_sbd_metadata_expected(cls) -> tuple[int, int]: + def get_sbd_metadata_expected(cls, sbd_devices: list[str]|None = None) -> tuple[int, int]: """ Get the expected sbd watchdog timeout and msgwait for disk-based sbd Make sure the expected msgwait >= 2 * expected watchdog timeout + sbd_devices overrides the configured devices, e.g. when devices are about to be added """ instance = cls(False, True, None) instance._load_configurations_from_runtime() + if sbd_devices: + instance.sbd_devices = sbd_devices expected_watchdog_timeout = max(SBDTimeout.get_sbd_watchdog_timeout_expected(), instance.sbd_watchdog_timeout) + if instance.sbd_devices: + watchdog_from_disk = SBDTimeout.get_sbd_watchdog_timeout_from_disk( + storage_utils.pick_backstore_device(instance.sbd_devices) + ) + if watchdog_from_disk is not None: + expected_watchdog_timeout = max(expected_watchdog_timeout, watchdog_from_disk) if instance.crashdump_watchdog_timeout: msgwait_from_formula = 2 * expected_watchdog_timeout + instance.crashdump_watchdog_timeout else: @@ -472,16 +571,23 @@ return utils.get_systemd_timeout_start_in_sec(out) @staticmethod - def able_to_set_fencing_watchdog_timeout(name: str, value: int) -> bool: + def is_fencing_watchdog_timeout_valid(name: str, value: int) -> bool: """ - Check if able to set fencing-watchdog-timeout or stonith-watchdog-timeout property + Check if the fencing-watchdog-timeout or stonith-watchdog-timeout value is valid """ if not ServiceManager().service_is_active(constants.SBD_SERVICE): - logger.error("Can't set %s because sbd.service is not active", name) + if value == 0: + return True + logger.error("%s shouldn't be configured when sbd.service is not active", name) + return False + if SBDUtils.is_using_disk_based_sbd(): + if value == 0: + return True + logger.error("%s shouldn't be configured when disk-based SBD is used", name) return False expected_fencing_watchdog_timeout = 2 * SBDTimeout.get_sbd_watchdog_timeout() - if value == -1: + if value < 0: logger.warning( "It's recommended to set %s to a positive value (at least 2*SBD_WATCHDOG_TIMEOUT: %d)", name, expected_fencing_watchdog_timeout @@ -1314,7 +1420,7 @@ if not self.bootstrap_context or not self.overwrite_sysconfig: return if not self.timeout_dict: - timeout_inst = SBDTimeout(self.diskless_sbd, False, self.bootstrap_context) + timeout_inst = SBDTimeout(self.diskless_sbd, False, self.bootstrap_context, sbd_devices=self.device_list_to_init) self.timeout_dict["watchdog"] = timeout_inst.sbd_watchdog_timeout if self.diskless_sbd: self.update_dict["SBD_WATCHDOG_TIMEOUT"] = str(timeout_inst.sbd_watchdog_timeout) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/crmsh/storage_utils.py new/crmsh-5.1.0+20261027.e559ffa7/crmsh/storage_utils.py --- old/crmsh-5.1.0+20261020.5b865c90/crmsh/storage_utils.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/crmsh/storage_utils.py 2026-10-27 09:52:08.000000000 +0100 @@ -198,26 +198,44 @@ @dataclass(frozen=True) class DeviceInfo: device: str - parent_device: str|None + root_backing_device: str under_multipath: bool class MultipathInspector: + SYSFS_BLOCK = Path("/sys/class/block") + def __init__(self, dev): self._shell = sh.cluster_shell() + self._multipath_mapping = None self._device_info = self._inspect(dev) - def _get_parent_device(self, dev) -> str: - resolved = Path(dev).resolve() - cmd = f"lsblk -dn -o PKNAME {shlex.quote(str(resolved))}" - _, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, cmd) - return out or resolved.name + @classmethod + def _get_root_backing_device(cls, dev) -> str: + """ + Walk down the block device stack (partition, dm, multipath map) + to the bottom-level device, e.g. sdX. + For a multipath map, the first path is used. + """ + name = Path(dev).resolve().name + while True: + sysfs = cls.SYSFS_BLOCK / name + if (sysfs / "partition").exists(): + name = sysfs.resolve().parent.name + continue + slaves = sorted(p.name for p in (sysfs / "slaves").glob("*")) + if not slaves: + return name + name = slaves[0] def _get_multipath_mapping(self) -> dict[str, str]: + if self._multipath_mapping is not None: + return self._multipath_mapping cmd = "multipathd show paths format \"%d %m\"" rc, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, cmd) mapping = dict() if rc != 0: + self._multipath_mapping = mapping return mapping for line in out.splitlines(): parts = line.split() @@ -226,24 +244,120 @@ dev_name, map_name = parts[0], parts[1] if (dev_name, map_name) == ("dev", "multipath"): continue + # Paths not belonging to any multipath map are reported as [orphan] + if map_name.startswith("["): + continue mapping[dev_name] = map_name + self._multipath_mapping = mapping return mapping + @staticmethod + def _get_multipath_name(dev: str) -> str: + return re.sub(r"-part\d+$", "", Path(dev).name) + + def _is_multipath_device(self, dev: str) -> bool: + return self._get_multipath_name(dev) in self._get_multipath_mapping().values() + def _inspect(self, dev: str) -> DeviceInfo: - parent = self._get_parent_device(dev) + root = self._get_root_backing_device(dev) mapping = self._get_multipath_mapping() return DeviceInfo( device=dev, - parent_device=parent, - under_multipath=parent in mapping + root_backing_device=root, + under_multipath=root in mapping ) def _is_under_multipath(self) -> bool: return self._device_info.under_multipath + def _get_multipath_config_value(self, key: str) -> int|None: + rc, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, "multipath -T") + if rc != 0: + return None + for line in out.splitlines(): + parts = line.split() + if len(parts) < 2: + continue + if parts[0] == key: + return int(parts[1]) + return None + + def _get_scsi_device_name(self) -> str|None: + return self._device_info.root_backing_device + + def get_scsi_timeout(self) -> int: + """ + Get SCSI timeout from /sys/block/<sdX>/device/timeout. + """ + device = self._get_scsi_device_name() + if not device: + raise ValueError(f"Cannot find backing device for {self._device_info.device}") + timeout_path = f"/sys/block/{device}/device/timeout" + out = self._shell.get_stdout_or_raise_error(f"cat {shlex.quote(timeout_path)}") + return int(out.strip()) + + def is_scsi_device(self) -> bool: + """ + Check if the backing device exposes a SCSI device timeout. + """ + device = self._get_scsi_device_name() + if not device: + return False + timeout_path = f"/sys/block/{device}/device/timeout" + rc, _, _ = self._shell.get_rc_stdout_stderr_without_input(None, f"test -f {shlex.quote(timeout_path)}") + return rc == 0 + + def get_max_polling_interval(self) -> int|None: + """ + Get max_polling_interval from multipath effective config. + """ + return self._get_multipath_config_value("max_polling_interval") + + def get_checker_timeout(self) -> int: + """ + Get checker_timeout from multipath effective config, defaulting to SCSI timeout. + """ + value = self._get_multipath_config_value("checker_timeout") + return value if value is not None else self.get_scsi_timeout() + + @classmethod + def get_device_scsi_timeout(cls, dev) -> int: + return cls(dev).get_scsi_timeout() + @classmethod def check_device_under_multipath(cls, dev): inspector = cls(dev) - if inspector._is_under_multipath(): + if inspector._is_under_multipath() and not inspector._is_multipath_device(dev): error_msg = f"Device {dev} is under multipath, please provide the multipath device instead" raise ValueError(error_msg) + + +def is_scsi_device(dev) -> bool: + return MultipathInspector(dev).is_scsi_device() + + +def is_multipath_device(dev) -> bool: + return MultipathInspector(dev)._is_multipath_device(dev) + + +def pick_backstore_device(devs: list[str]) -> str|None: + """ + Pick the device whose timeouts should drive the configuration: + the first multipath device if any, otherwise the first device. + """ + for dev in devs: + if is_multipath_device(dev): + return dev + return devs[0] if devs else None + + +def get_multipath_max_polling_interval(dev) -> int|None: + return MultipathInspector(dev).get_max_polling_interval() + + +def get_multipath_checker_timeout(dev) -> int: + return MultipathInspector(dev).get_checker_timeout() + + +def get_scsi_timeout(dev) -> int: + return MultipathInspector.get_device_scsi_timeout(dev) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/crmsh/ui_sbd.py new/crmsh-5.1.0+20261027.e559ffa7/crmsh/ui_sbd.py --- old/crmsh-5.1.0+20261020.5b865c90/crmsh/ui_sbd.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/crmsh/ui_sbd.py 2026-10-27 09:52:08.000000000 +0100 @@ -5,6 +5,7 @@ from pathlib import Path from crmsh import sbd +from crmsh import storage_utils from crmsh import watchdog from crmsh import command from crmsh import utils, network_utils @@ -103,7 +104,7 @@ "watchdog", "allocate", "loop", "msgwait", "crashdump-watchdog" ) DISKLESS_TIMEOUT_TYPES = ("watchdog", "crashdump-watchdog") - SHOW_TYPES = ("disk_metadata", "sysconfig", "property") + SHOW_TYPES = ("disk_metadata", "disk_backstore", "sysconfig", "property") DISKLESS_SHOW_TYPES = ("sysconfig", "property") PCMK_ATTRS = ( "have-watchdog", @@ -218,6 +219,26 @@ for dev in self.device_list_from_config: print(sbd.SBDUtils.get_sbd_device_metadata_raw(dev)) + def _show_disk_backstore(self) -> None: + """ + Show SBD and storage timeout values not covered by disk metadata. + """ + if not self.device_list_from_config: + return + logger.info("crm sbd configure show disk_backstore") + dev = storage_utils.pick_backstore_device(self.device_list_from_config) + if storage_utils.is_multipath_device(dev): + max_polling_interval = storage_utils.get_multipath_max_polling_interval(dev) + if max_polling_interval is not None: + print(f"multipath max_polling_interval: {max_polling_interval}") + checker_timeout = storage_utils.get_multipath_checker_timeout(dev) + if checker_timeout is not None: + print(f"multipath checker_timeout : {checker_timeout}") + if storage_utils.is_scsi_device(dev): + print(f"SCSI timeout : {storage_utils.get_scsi_timeout(dev)}") + print(f"SBD timeout io : {sbd.SBDTimeout.get_sbd_timeout_io()}") + print(f"SBD servant restart interval : {sbd.SBDTimeout.get_sbd_servant_restart_interval()}") + def _show_property(self) -> None: ''' Show sbd-related properties from cluster and systemd @@ -252,6 +273,8 @@ self._show_disk_metadata() case "sysconfig": self._show_sysconfig() + case "disk_backstore": + self._show_disk_backstore() case "property": self._show_property() case _: @@ -260,6 +283,8 @@ self._show_disk_metadata() if self.device_list_from_config: print() + self._show_disk_backstore() + print() self._show_sysconfig() print() self._show_property() @@ -475,12 +500,21 @@ self.device_list_from_config ) + # The new devices may require larger timeouts (e.g. a multipath device + # added to SCSI ones); initialize all devices once with the expected values + timeout_dict = self.device_meta_dict_runtime + expected_watchdog, expected_msgwait = sbd.SBDTimeout.get_sbd_metadata_expected(all_device_list) + if expected_watchdog > timeout_dict.get("watchdog", 0) or expected_msgwait > timeout_dict.get("msgwait", 0): + logger.info("Adjusting sbd msgwait to %d, watchdog timeout to %d", expected_msgwait, expected_watchdog) + timeout_dict = {"watchdog": expected_watchdog, "msgwait": expected_msgwait} + devices_to_init = all_device_list + logger.info("Append devices: %s", ';'.join(devices_to_add)) update_dict = {"SBD_DEVICE": ";".join(all_device_list)} sbd_manager = sbd.SBDManager( device_list_to_init=devices_to_init, update_dict=update_dict, - timeout_dict=self.device_meta_dict_runtime + timeout_dict=timeout_dict ) sbd_manager.init_and_deploy_sbd() @@ -589,6 +623,7 @@ ''' timeout_dict, update_dict = {}, {} if self.device_list_from_config: + sbd.SBDUtils.verify_sbd_device(self.device_list_from_config, self.cluster_nodes) timeout_dict["watchdog"] = self.device_meta_dict_runtime.get("watchdog") timeout_dict["msgwait"] = 2 * timeout_dict["watchdog"] logger.info("Set msgwait-timeout to 2*watchdog-timeout: %s", timeout_dict["msgwait"]) @@ -617,11 +652,9 @@ ''' Implement sbd purge command ''' - self._load_attributes() + self.service_manager = ServiceManager() if not self._service_is_active(constants.SBD_SERVICE): return False - if self.device_list_from_config: - sbd.SBDUtils.verify_sbd_device(self.device_list_from_config, self.cluster_nodes) args = _handle_force_option(args) purge_crashdump = False @@ -639,6 +672,7 @@ msg = "purging SBD crashdump" if purge_crashdump else "purging SBD" utils.check_all_nodes_reachable(msg) if purge_crashdump: + self._load_attributes() self._purge_crashdump() else: self._purge_sbd() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/crmsh/xmlutil.py new/crmsh-5.1.0+20261027.e559ffa7/crmsh/xmlutil.py --- old/crmsh-5.1.0+20261020.5b865c90/crmsh/xmlutil.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/crmsh/xmlutil.py 2026-10-27 09:52:08.000000000 +0100 @@ -186,7 +186,7 @@ try: intval = int(value) - if not sbd.SBDTimeout.able_to_set_fencing_watchdog_timeout(name, intval): + if not sbd.SBDTimeout.is_fencing_watchdog_timeout_valid(name, intval): rc = utils.VerifyResult.FATAL_ERROR except ValueError: logger.error("%s must be an integer", name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/doc/crm.8.adoc new/crmsh-5.1.0+20261027.e559ffa7/doc/crm.8.adoc --- old/crmsh-5.1.0+20261020.5b865c90/doc/crm.8.adoc 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/doc/crm.8.adoc 2026-10-27 09:52:08.000000000 +0100 @@ -307,6 +307,33 @@ .SBD related timeouts `watchdog` timeout: Default value is 15s. This value is from the definition in +/etc/crm/profiles.xml+, can be a different value with a different profile. + For disk-based SBD, it also needs to cover the timeout of the SBD disk: +............... + # For disk-based SBD directly on top of a multipath device + value_from_disk = 1.2 * (multipath_max_polling_interval + multipath_checker_timeout + + servant_restart_interval - timeout_io) + + # For disk-based SBD directly on top of a SCSI device + value_from_disk = 1.2 * (SCSI_timeout + servant_restart_interval - timeout_io) + + watchdog = max(value_from_disk, value_from_profiles) + + # How to get the values above: + # multipath_max_polling_interval: max_polling_interval in the effective multipath + # configuration, ie. the output of "multipath -T" + + # multipath_checker_timeout: checker_timeout in the effective multipath + # configuration, ie. the output of "multipath -T"; default is SCSI_timeout + + # servant_restart_interval: option -t in SBD_OPTS of /etc/sysconfig/sbd; default is 5s + + # timeout_io: option -I in SBD_OPTS of /etc/sysconfig/sbd; + # default is 3 * loop timeout, loop timeout is from the SBD disk metadata + # and its default is 1s, so default timeout_io is 3s + + # SCSI_timeout: /sys/block/<sdX>/device/timeout, <sdX> is the SBD disk itself, + # or one of the path devices if the SBD disk is a multipath device +............... `msgwait` timeout: (only for disk-based SBD) ............... @@ -2233,6 +2260,7 @@ Main functionailities include: - Show configured disk metadata +- Show disk backstore timeout values - Show contents of /etc/sysconfig/sbd - Show SBD related cluster properties - Update the SBD related configuration parameters @@ -2244,7 +2272,7 @@ Usage: ............... # For disk-based SBD -crm sbd configure show [disk_metadata|sysconfig|property] +crm sbd configure show [disk_metadata|disk_backstore|sysconfig|property] crm sbd configure [watchdog-timeout=<integer>] [allocate-timeout=<integer>] [loop-timeout=<integer>] [msgwait-timeout=<integer>] [crashdump-watchdog-timeout=<integer>] [watchdog-device=<device|driver>] [-F|--force] # For disk-less SBD @@ -2256,6 +2284,7 @@ ............... configure show configure show disk_metadata +configure show disk_backstore configure show sysconfig configure show property configure watchdog-timeout=30 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/test/features/bootstrap_sbd_delay.feature new/crmsh-5.1.0+20261027.e559ffa7/test/features/bootstrap_sbd_delay.feature --- old/crmsh-5.1.0+20261020.5b865c90/test/features/bootstrap_sbd_delay.feature 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/test/features/bootstrap_sbd_delay.feature 2026-10-27 09:52:08.000000000 +0100 @@ -15,13 +15,13 @@ Then Cluster service is "started" on "hanode1" And Service "sbd" is "started" on "hanode1" And Resource "fencing-sbd" type "fence_sbd" is "Started" - And SBD option "SBD_DELAY_START" value is "41" + And SBD option "SBD_DELAY_START" value is "87" And SBD option "SBD_WATCHDOG_TIMEOUT" value is "5" - And SBD option "msgwait" value for "/dev/sda1" is "30" + And SBD option "msgwait" value for "/dev/sda1" is "76" # original value is 43, which is calculated by external/sbd RA # now fence_sbd doesn't calculate it, so this value is the default one # from pacemaker - And Cluster property "fencing-timeout" is "71" + And Cluster property "fencing-timeout" is "102" And Parameter "pcmk_delay_max" not configured in "fencing-sbd" Given Has disk "/dev/sda1" on "hanode2" @@ -30,12 +30,12 @@ Then Cluster service is "started" on "hanode2" And Service "sbd" is "started" on "hanode2" # SBD_DELAY_START >= (token + consensus + pcmk_delay_max + msgwait) # for disk-based sbd - And SBD option "SBD_DELAY_START" value is "71" + And SBD option "SBD_DELAY_START" value is "117" And SBD option "SBD_WATCHDOG_TIMEOUT" value is "5" - And SBD option "msgwait" value for "/dev/sda1" is "30" + And SBD option "msgwait" value for "/dev/sda1" is "76" # value_from_sbd >= 1.2 * msgwait # for disk-based sbd # fencing_timeout >= max(value_from_sbd, constants.FENCING_TIMEOUT_DEFAULT) + token + consensus - And Cluster property "fencing-timeout" is "71" + And Cluster property "fencing-timeout" is "102" And Parameter "pcmk_delay_max" configured in "fencing-sbd" Given Has disk "/dev/sda1" on "hanode3" @@ -44,14 +44,14 @@ Then Cluster service is "started" on "hanode3" And Service "sbd" is "started" on "hanode3" # SBD_DELAY_START >= (token + consensus + pcmk_delay_max + msgwait) # for disk-based sbd - # runtime value is "41", we keep the larger one here - And SBD option "SBD_DELAY_START" value is "41" + # runtime value is "87", we keep the larger one here + And SBD option "SBD_DELAY_START" value is "87" And SBD option "SBD_WATCHDOG_TIMEOUT" value is "5" - And SBD option "msgwait" value for "/dev/sda1" is "30" + And SBD option "msgwait" value for "/dev/sda1" is "76" # value_from_sbd >= 1.2 * msgwait # for disk-based sbd # fencing_timeout >= max(value_from_sbd, constants.FENCING_TIMEOUT_DEFAULT) + token + consensus - # runtime value is "71", we keep ther larger one here - And Cluster property "fencing-timeout" is "71" + # runtime value is "102", we keep ther larger one here + And Cluster property "fencing-timeout" is "102" And Parameter "pcmk_delay_max" not configured in "fencing-sbd" # Increase expected value @@ -62,19 +62,19 @@ And Start timeout for sbd.service is "121" seconds # Decrease expected value - When Run "crm sbd configure watchdog-timeout=15" on "hanode1" - Then SBD option "SBD_DELAY_START" value is "41" - And SBD option "msgwait" value for "/dev/sda1" is "30" - And Cluster property "fencing-timeout" is "71" - And Start timeout for sbd.service is "90" seconds + When Run "crm sbd configure watchdog-timeout=38" on "hanode1" + Then SBD option "SBD_DELAY_START" value is "87" + And SBD option "msgwait" value for "/dev/sda1" is "76" + And Cluster property "fencing-timeout" is "102" + And Start timeout for sbd.service is "104" seconds When Run "crm cluster remove hanode3 -y" on "hanode1" Then Cluster service is "stopped" on "hanode3" And Service "sbd" is "stopped" on "hanode3" - And SBD option "SBD_DELAY_START" value is "71" + And SBD option "SBD_DELAY_START" value is "117" And SBD option "SBD_WATCHDOG_TIMEOUT" value is "5" - And SBD option "msgwait" value for "/dev/sda1" is "30" - And Cluster property "fencing-timeout" is "71" + And SBD option "msgwait" value for "/dev/sda1" is "76" + And Cluster property "fencing-timeout" is "102" And Parameter "pcmk_delay_max" configured in "fencing-sbd" @clean @@ -113,6 +113,8 @@ And SBD option "SBD_WATCHDOG_TIMEOUT" value is "15" And Cluster property "fencing-timeout" is "71" + When Try "crm configure property fencing-watchdog-timeout=0" on "hanode1" + Then Except "It's required to set fencing-watchdog-timeout to at least 2*SBD_WATCHDOG_TIMEOUT: 30" in stderr When Try "crm configure property fencing-watchdog-timeout=1" on "hanode1" Then Except "It's required to set fencing-watchdog-timeout to at least 2*SBD_WATCHDOG_TIMEOUT: 30" in stderr # Expect successfully set stonith-watchdog-timeout=5 since already set fencing-watchdog-timeout @@ -121,6 +123,9 @@ When Try "crm -F configure filter "sed 's/fencing-watchdog-timeout=30/#fencing-watchdog-timeout=30/g'"" on "hanode1" Then Expected "It's required to set stonith-watchdog-timeout to at least 2*SBD_WATCHDOG_TIMEOUT: 30" in stderr Then Cluster property "fencing-watchdog-timeout" is "30" + # Any negative value is treated the same as -1 + When Run "crm configure property fencing-watchdog-timeout=-2" on "hanode1" + Then Cluster property "fencing-watchdog-timeout" is "-2" @clean Scenario: disk-based SBD with big sbd_watchdog_timeout @@ -204,10 +209,10 @@ When Run "crm cluster init sbd -s /dev/sda1 -y" on "hanode1" Then Service "sbd" is "started" on "hanode1" Then Service "sbd" is "started" on "hanode2" - And SBD option "SBD_DELAY_START" value is "71" + And SBD option "SBD_DELAY_START" value is "117" And SBD option "SBD_WATCHDOG_TIMEOUT" value is "5" - And SBD option "msgwait" value for "/dev/sda1" is "30" - And Cluster property "fencing-timeout" is "71" + And SBD option "msgwait" value for "/dev/sda1" is "76" + And Cluster property "fencing-timeout" is "102" And Parameter "pcmk_delay_max" configured in "fencing-sbd" @clean @@ -229,10 +234,10 @@ And Service "sbd" is "started" on "hanode1" And Service "sbd" is "started" on "hanode2" - And SBD option "SBD_DELAY_START" value is "41" + And SBD option "SBD_DELAY_START" value is "87" And SBD option "SBD_WATCHDOG_TIMEOUT" value is "5" - And SBD option "msgwait" value for "/dev/sda1" is "30" - And Cluster property "fencing-timeout" is "71" + And SBD option "msgwait" value for "/dev/sda1" is "76" + And Cluster property "fencing-timeout" is "102" And Parameter "pcmk_delay_max" not configured in "fencing-sbd" @clean @@ -316,7 +321,7 @@ Then Service "sbd" is "started" on "hanode1" And Service "sbd" is "started" on "hanode2" And Parameter "pcmk_delay_max" configured in "fencing-sbd" - And Cluster property "fencing-timeout" is "71" + And Cluster property "fencing-timeout" is "102" And Cluster property "priority-fencing-delay" is "60" When Run "crm cluster remove hanode2 -y" on "hanode1" Then Cluster service is "stopped" on "hanode2" @@ -335,38 +340,43 @@ When Run "crm cluster init sbd -s /dev/sda1 -y" on "hanode1" Then Service "sbd" is "started" on "hanode1" And Service "sbd" is "started" on "hanode2" + # check fencing-watchdog-timeout with disk-based SBD + When Try "crm configure property fencing-watchdog-timeout=30" on "hanode1" + Then Expected "fencing-watchdog-timeout shouldn't be configured when disk-based SBD is used" in stderr + When Run "crm configure property fencing-watchdog-timeout=0" on "hanode1" + Then Cluster property "fencing-watchdog-timeout" is "0" # check /etc/sysconf/sbd consistency When Run "sed -i 's/SBD_DELAY_START=.*/SBD_DELAY_START="no"/' /etc/sysconfig/sbd" on "hanode2" When Try "crm sbd configure show" Then Expected "/etc/sysconfig/sbd is not consistent across cluster nodes" in stderr When Try "crm cluster health sbd" Then Expected "/etc/sysconfig/sbd is not consistent across cluster nodes" in stderr - When Run "sed -i 's/SBD_DELAY_START=.*/SBD_DELAY_START=71/' /etc/sysconfig/sbd" on "hanode2" + When Run "sed -i 's/SBD_DELAY_START=.*/SBD_DELAY_START=117/' /etc/sysconfig/sbd" on "hanode2" When Run "crm cluster health sbd" on "hanode1" Then Expected "SBD: Check SBD-related configurations: OK." in stdout # check sbd disk metadata When Run "sbd -1 15 -4 16 -d /dev/sda1 create" on "hanode1" When Try "crm sbd configure show disk_metadata" on "hanode1" - Then Expected "ERROR: It's required that SBD msgwait(now 16) >= 30" in stderr + Then Expected "ERROR: It's required that SBD watchdog timeout(now 15) >= 38" in stderr When Try "crm cluster health sbd" on "hanode1" - Then Expected "ERROR: It's required that SBD msgwait(now 16) >= 30" in stderr + Then Expected "ERROR: It's required that SBD watchdog timeout(now 15) >= 38" in stderr When Run "crm cluster health sbd --fix" on "hanode1" Then Expected "SBD: Check SBD-related configurations: OK." in stdout # check SBD_DELAY_START When Run "sed -i 's/SBD_DELAY_START=.*/SBD_DELAY_START=40/' /etc/sysconfig/sbd" on "hanode1" When Run "sed -i 's/SBD_DELAY_START=.*/SBD_DELAY_START=40/' /etc/sysconfig/sbd" on "hanode2" When Try "crm sbd configure show" on "hanode1" - Then Expected "It's required that SBD_DELAY_START is set to 71, now is 40" in stderr + Then Expected "It's required that SBD_DELAY_START is set to 117, now is 40" in stderr When Try "crm cluster health sbd" on "hanode1" - Then Expected "It's required that SBD_DELAY_START is set to 71, now is 40" in stderr + Then Expected "It's required that SBD_DELAY_START is set to 117, now is 40" in stderr When Run "crm cluster health sbd --fix" on "hanode1" Then Expected "SBD: Check SBD-related configurations: OK." in stdout # check fencing-timeout When Run "crm configure property fencing-timeout=50" on "hanode1" When Try "crm sbd configure show" on "hanode1" - Then Expected "It's required that fencing-timeout is set to 71, now is 50" in stderr + Then Expected "It's required that fencing-timeout is set to 102, now is 50" in stderr When Try "crm cluster health sbd" on "hanode1" - Then Expected "It's required that fencing-timeout is set to 71, now is 50" in stderr + Then Expected "It's required that fencing-timeout is set to 102, now is 50" in stderr When Run "crm cluster health sbd --fix" on "hanode1" Then Expected "SBD: Check SBD-related configurations: OK." in stdout # check crashdump and pcmk_delay_max @@ -385,9 +395,9 @@ When Run "sed -i 's/token: .*/token: 10000/' /etc/corosync/corosync.conf" on "hanode2" When Run "corosync-cfgtool -R" on "hanode1" When Try "crm sbd configure show" on "hanode1" - Then Expected "It's required that SBD_DELAY_START is set to 82, now is 71" in stderr + Then Expected "It's required that SBD_DELAY_START is set to 128, now is 117" in stderr When Try "crm cluster health sbd" on "hanode1" - Then Expected "It's required that SBD_DELAY_START is set to 82, now is 71" in stderr + Then Expected "It's required that SBD_DELAY_START is set to 128, now is 117" in stderr When Run "crm cluster health sbd --fix" on "hanode1" Then Expected "SBD: Check SBD-related configurations: OK." in stdout diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/test/features/configure_bugs.feature new/crmsh-5.1.0+20261027.e559ffa7/test/features/configure_bugs.feature --- old/crmsh-5.1.0+20261020.5b865c90/test/features/configure_bugs.feature 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/test/features/configure_bugs.feature 2026-10-27 09:52:08.000000000 +0100 @@ -83,9 +83,13 @@ Then Run "crm -F configure filter "sed 's/cpu=/#cpu='/g"" OK on "hanode1" @clean - Scenario: Set fencing-watchdog-timeout when sbd.service is disactive + Scenario: Set fencing-watchdog-timeout when sbd.service is inactive Given Cluster service is "stopped" on "hanode1" When Run "crm cluster init -y" on "hanode1" Then Cluster service is "started" on "hanode1" When Try "crm configure property fencing-watchdog-timeout=20" on "hanode1" - Then Except "Can't set fencing-watchdog-timeout because sbd.service is not active" in stderr + Then Except "fencing-watchdog-timeout shouldn't be configured when sbd.service is not active" in stderr + When Try "crm configure property stonith-watchdog-timeout=20" on "hanode1" + Then Except "stonith-watchdog-timeout shouldn't be configured when sbd.service is not active" in stderr + When Run "crm configure property fencing-watchdog-timeout=0" on "hanode1" + Then Cluster property "fencing-watchdog-timeout" is "0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/test/unittests/test_sbd.py new/crmsh-5.1.0+20261027.e559ffa7/test/unittests/test_sbd.py --- old/crmsh-5.1.0+20261020.5b865c90/test/unittests/test_sbd.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/test/unittests/test_sbd.py 2026-10-27 09:52:08.000000000 +0100 @@ -11,6 +11,7 @@ TEST_DATA = """ UUID : 1234-5678 Timeout (watchdog) : 5 + Timeout (loop) : 1 Timeout (msgwait) : 10 """ @@ -18,14 +19,14 @@ def test_get_sbd_device_metadata_success(self, mock_get_sbd_device_metadata_raw): mock_get_sbd_device_metadata_raw.return_value = self.TEST_DATA result = SBDUtils.get_sbd_device_metadata("/dev/sbd_device") - expected = {'uuid': '1234-5678', 'watchdog': 5, 'msgwait': 10} + expected = {"uuid": "1234-5678", "watchdog": 5, "loop": 1, "msgwait": 10} self.assertEqual(result, expected) @patch('crmsh.sbd.SBDUtils.get_sbd_device_metadata_raw') def test_get_sbd_device_metadata_timeout_only(self, mock_get_sbd_device_metadata_raw): mock_get_sbd_device_metadata_raw.return_value = self.TEST_DATA result = SBDUtils.get_sbd_device_metadata("/dev/sbd_device", timeout_only=True) - expected = {'watchdog': 5, 'msgwait': 10} + expected = {"watchdog": 5, "loop": 1, "msgwait": 10} self.assertNotIn('uuid', result) self.assertEqual(result, expected) @@ -92,6 +93,27 @@ result = SBDUtils.get_sbd_device_from_config() self.assertEqual(result, ['/dev/sbd_device', '/dev/another_sbd_device']) + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_opts_int_value(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-S 1 -t 10 -I 7 -C 60 -Z" + self.assertEqual(SBDUtils.get_sbd_opts_int_value("-t"), 10) + self.assertEqual(SBDUtils.get_sbd_opts_int_value("-I"), 7) + self.assertEqual(SBDUtils.get_sbd_opts_int_value("-C"), 60) + + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_opts_int_value_not_found(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-S 1 -P" + self.assertIsNone(SBDUtils.get_sbd_opts_int_value("-t")) + mock_get_sbd_value_from_config.return_value = None + self.assertIsNone(SBDUtils.get_sbd_opts_int_value("-t")) + + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_opts_int_value_no_value(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-S 1 -t" + with self.assertRaises(ValueError) as err: + SBDUtils.get_sbd_opts_int_value("-t") + self.assertEqual(str(err.exception), "Cannot get the value of -t in SBD_OPTS") + @patch('crmsh.sbd.SBDUtils.get_sbd_value_from_config') def test_get_crashdump_watchdog_timeout_none(self, mock_get_sbd_value_from_config): mock_get_sbd_value_from_config.return_value = None @@ -204,6 +226,182 @@ result = sbd.SBDTimeout.get_sbd_watchdog_timeout() self.assertEqual(result, 5) + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_servant_restart_interval_default(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = None + result = sbd.SBDTimeout.get_sbd_servant_restart_interval() + self.assertEqual(result, 5) + + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_servant_restart_interval(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-S 1 -t 10 -P" + result = sbd.SBDTimeout.get_sbd_servant_restart_interval() + self.assertEqual(result, 10) + + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_servant_restart_interval_zero(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-t 0" + result = sbd.SBDTimeout.get_sbd_servant_restart_interval() + self.assertEqual(result, 0) + + @patch("crmsh.sbd.SBDUtils.get_sbd_device_metadata") + @patch("crmsh.sbd.SBDUtils.get_sbd_device_from_config") + def test_get_sbd_loop_timeout(self, mock_get_sbd_device_from_config, mock_get_sbd_device_metadata): + mock_get_sbd_device_from_config.return_value = ["/dev/sbd_device"] + mock_get_sbd_device_metadata.return_value = {"loop": 2} + result = sbd.SBDTimeout.get_sbd_loop_timeout() + self.assertEqual(result, 2) + + @patch("crmsh.sbd.SBDUtils.get_sbd_device_from_config") + def test_get_sbd_loop_timeout_default(self, mock_get_sbd_device_from_config): + mock_get_sbd_device_from_config.return_value = [] + result = sbd.SBDTimeout.get_sbd_loop_timeout() + self.assertEqual(result, 1) + + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_timeout_io(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-S 1 -I 9 -P" + result = sbd.SBDTimeout.get_sbd_timeout_io() + self.assertEqual(result, 9) + + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_timeout_io_zero(self, mock_get_sbd_value_from_config): + mock_get_sbd_value_from_config.return_value = "-I 0" + result = sbd.SBDTimeout.get_sbd_timeout_io() + self.assertEqual(result, 0) + + @patch("crmsh.sbd.SBDTimeout.get_sbd_loop_timeout") + @patch("crmsh.sbd.SBDUtils.get_sbd_value_from_config") + def test_get_sbd_timeout_io_default(self, mock_get_sbd_value_from_config, mock_get_sbd_loop_timeout): + mock_get_sbd_value_from_config.return_value = None + mock_get_sbd_loop_timeout.return_value = 2 + result = sbd.SBDTimeout.get_sbd_timeout_io() + self.assertEqual(result, 6) + + @patch("crmsh.storage_utils.get_multipath_checker_timeout") + @patch("crmsh.storage_utils.get_multipath_max_polling_interval") + @patch("crmsh.storage_utils.is_scsi_device") + @patch("crmsh.storage_utils.is_multipath_device") + @patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + def test_get_sbd_watchdog_timeout_from_disk_multipath( + self, + mock_get_servant_restart_interval, + mock_get_timeout_io, + mock_is_multipath_device, + mock_is_scsi_device, + mock_get_multipath_max_polling_interval, + mock_get_multipath_checker_timeout, + ): + mock_get_servant_restart_interval.return_value = 5 + mock_get_timeout_io.return_value = 3 + mock_is_multipath_device.return_value = True + mock_is_scsi_device.return_value = False + mock_get_multipath_max_polling_interval.return_value = 20 + mock_get_multipath_checker_timeout.return_value = 30 + + result = sbd.SBDTimeout.get_sbd_watchdog_timeout_from_disk("/dev/mapper/mpatha") + + self.assertEqual(result, 62) + + @patch("crmsh.storage_utils.get_scsi_timeout") + @patch("crmsh.storage_utils.is_scsi_device") + @patch("crmsh.storage_utils.is_multipath_device") + @patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + def test_get_sbd_watchdog_timeout_from_disk_scsi( + self, + mock_get_servant_restart_interval, + mock_get_timeout_io, + mock_is_multipath_device, + mock_is_scsi_device, + mock_get_scsi_timeout, + ): + mock_get_servant_restart_interval.return_value = 5 + mock_get_timeout_io.return_value = 3 + mock_is_multipath_device.return_value = False + mock_is_scsi_device.return_value = True + mock_get_scsi_timeout.return_value = 30 + + result = sbd.SBDTimeout.get_sbd_watchdog_timeout_from_disk("/dev/sda1") + + self.assertEqual(result, 38) + + @patch("crmsh.storage_utils.get_multipath_checker_timeout") + @patch("crmsh.storage_utils.get_multipath_max_polling_interval") + @patch("crmsh.storage_utils.is_scsi_device") + @patch("crmsh.storage_utils.is_multipath_device") + @patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + def test_get_sbd_watchdog_timeout_from_disk_clamp( + self, + mock_get_servant_restart_interval, + mock_get_timeout_io, + mock_is_multipath_device, + mock_is_scsi_device, + mock_get_multipath_max_polling_interval, + mock_get_multipath_checker_timeout, + ): + mock_get_servant_restart_interval.return_value = 0 + mock_get_timeout_io.return_value = 10 + mock_is_multipath_device.return_value = True + mock_is_scsi_device.return_value = False + mock_get_multipath_max_polling_interval.return_value = 0 + mock_get_multipath_checker_timeout.return_value = 0 + + result = sbd.SBDTimeout.get_sbd_watchdog_timeout_from_disk("/dev/mapper/mpatha") + + self.assertEqual(result, 1) + + @patch("crmsh.storage_utils.is_scsi_device") + @patch("crmsh.storage_utils.is_multipath_device") + @patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + def test_get_sbd_watchdog_timeout_from_disk_not_multipath_nor_scsi( + self, + mock_get_servant_restart_interval, + mock_get_timeout_io, + mock_is_multipath_device, + mock_is_scsi_device, + ): + mock_get_servant_restart_interval.return_value = 5 + mock_get_timeout_io.return_value = 3 + mock_is_multipath_device.return_value = False + mock_is_scsi_device.return_value = False + + self.assertIsNone(sbd.SBDTimeout.get_sbd_watchdog_timeout_from_disk("/dev/nvme0n1")) + + @patch('crmsh.sbd.SBDTimeout.get_sbd_watchdog_timeout_from_disk') + def test_adjust_sbd_watchdog_timeout_with_disk_backstore(self, mock_from_disk): + inst = sbd.SBDTimeout(diskless_sbd=False, quiet=True) + inst.sbd_devices = ["/dev/sda1"] + inst.sbd_watchdog_timeout = 5 + mock_from_disk.return_value = 38 + + inst._adjust_sbd_watchdog_timeout_with_disk_backstore() + + self.assertEqual(inst.sbd_watchdog_timeout, 38) + mock_from_disk.assert_called_once_with("/dev/sda1") + + @patch('crmsh.storage_utils.pick_backstore_device') + @patch('crmsh.sbd.SBDTimeout.get_sbd_watchdog_timeout_from_disk') + @patch('crmsh.sbd.SBDTimeout.get_sbd_watchdog_timeout_expected') + @patch('crmsh.sbd.SBDTimeout._load_configurations_from_runtime', autospec=True) + def test_get_sbd_metadata_expected_from_disk(self, mock_load, mock_watchdog_expected, mock_from_disk, mock_pick): + mock_pick.return_value = "/dev/mapper/mpatha" + mock_watchdog_expected.return_value = 5 + mock_from_disk.return_value = 38 + mock_load.side_effect = lambda inst: [ + setattr(inst, name, value) for name, value in { + "sbd_watchdog_timeout": 5, "sbd_msgwait": 10, + "crashdump_watchdog_timeout": None, "sbd_devices": ["/dev/sda1"], + }.items() + ] + + self.assertEqual(sbd.SBDTimeout.get_sbd_metadata_expected(), (38, 76)) + mock_pick.assert_called_once_with(["/dev/sda1"]) + mock_from_disk.assert_called_once_with("/dev/mapper/mpatha") + @patch('crmsh.sbd.SBDUtils.get_sbd_value_from_config') def test_is_sbd_delay_start(self, mock_get_sbd_value_from_config): mock_get_sbd_value_from_config.return_value = "yes" @@ -220,6 +418,61 @@ mock_cluster_shell.return_value.get_stdout_or_raise_error.assert_called_once_with(sbd.SBDTimeout.SHOW_SBD_START_TIMEOUT_CMD, None) mock_get_systemd_timeout_start_in_sec.assert_called_once_with("1min 30s") + @patch('logging.Logger.error') + @patch('crmsh.sbd.ServiceManager') + def test_is_fencing_watchdog_timeout_valid_sbd_inactive_zero(self, mock_service_manager, mock_logger_error): + mock_service_manager.return_value.service_is_active.return_value = False + self.assertTrue(sbd.SBDTimeout.is_fencing_watchdog_timeout_valid("fencing-watchdog-timeout", 0)) + mock_logger_error.assert_not_called() + + @patch('logging.Logger.error') + @patch('crmsh.sbd.ServiceManager') + def test_is_fencing_watchdog_timeout_valid_sbd_inactive_nonzero( + self, mock_service_manager, mock_logger_error + ): + mock_service_manager.return_value.service_is_active.return_value = False + self.assertFalse(sbd.SBDTimeout.is_fencing_watchdog_timeout_valid("fencing-watchdog-timeout", 30)) + mock_logger_error.assert_called_once() + + @patch('logging.Logger.error') + @patch('crmsh.sbd.SBDUtils.is_using_disk_based_sbd') + @patch('crmsh.sbd.ServiceManager') + def test_is_fencing_watchdog_timeout_valid_zero_disk_based( + self, mock_service_manager, mock_is_disk_based, mock_logger_error + ): + mock_service_manager.return_value.service_is_active.return_value = True + mock_is_disk_based.return_value = True + for name in ("fencing-watchdog-timeout", "stonith-watchdog-timeout"): + self.assertTrue(sbd.SBDTimeout.is_fencing_watchdog_timeout_valid(name, 0)) + self.assertFalse(sbd.SBDTimeout.is_fencing_watchdog_timeout_valid(name, 30)) + self.assertEqual(mock_logger_error.call_count, 2) + + @patch('logging.Logger.error') + @patch('crmsh.sbd.SBDTimeout.get_sbd_watchdog_timeout') + @patch('crmsh.sbd.SBDUtils.is_using_disk_based_sbd') + @patch('crmsh.sbd.ServiceManager') + def test_is_fencing_watchdog_timeout_valid_zero_diskless( + self, mock_service_manager, mock_is_disk_based, mock_get_watchdog_timeout, mock_logger_error + ): + mock_service_manager.return_value.service_is_active.return_value = True + mock_is_disk_based.return_value = False + mock_get_watchdog_timeout.return_value = 15 + self.assertFalse(sbd.SBDTimeout.is_fencing_watchdog_timeout_valid("fencing-watchdog-timeout", 0)) + mock_logger_error.assert_called_once() + + @patch('logging.Logger.warning') + @patch('crmsh.sbd.SBDTimeout.get_sbd_watchdog_timeout') + @patch('crmsh.sbd.SBDUtils.is_using_disk_based_sbd') + @patch('crmsh.sbd.ServiceManager') + def test_is_fencing_watchdog_timeout_valid_negative_diskless( + self, mock_service_manager, mock_is_disk_based, mock_get_watchdog_timeout, mock_logger_warning + ): + mock_service_manager.return_value.service_is_active.return_value = True + mock_is_disk_based.return_value = False + mock_get_watchdog_timeout.return_value = 15 + self.assertTrue(sbd.SBDTimeout.is_fencing_watchdog_timeout_valid("fencing-watchdog-timeout", -2)) + mock_logger_warning.assert_called_once() + @patch('crmsh.corosync.token_and_consensus_timeout') def test_get_sbd_delay_start_expected_diskbased(self, mock_token_and_consensus_timeout): inst = sbd.SBDTimeout(diskless_sbd=False, quiet=False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/test/unittests/test_storage_utils.py new/crmsh-5.1.0+20261027.e559ffa7/test/unittests/test_storage_utils.py --- old/crmsh-5.1.0+20261020.5b865c90/test/unittests/test_storage_utils.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/test/unittests/test_storage_utils.py 2026-10-27 09:52:08.000000000 +0100 @@ -3,6 +3,7 @@ # # unit tests for storage_utils.py +import tempfile import unittest import pytest from pathlib import Path @@ -289,13 +290,20 @@ class TestMultipathInspector(unittest.TestCase): + def setUp(self): + self._real_get_root = storage_utils.MultipathInspector._get_root_backing_device + patcher = mock.patch.object( + storage_utils.MultipathInspector, "_get_root_backing_device", return_value="sda" + ) + patcher.start() + self.addCleanup(patcher.stop) + @mock.patch('crmsh.sh.cluster_shell') def test_init(self, mock_cluster_shell): """Test MultipathInspector initialization""" mock_shell_inst = mock.Mock() mock_cluster_shell.return_value = mock_shell_inst mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ - (0, "sda", ""), # lsblk output for parent device (0, "dev multipath\nsda mpatha", "") # multipathd show paths output ] @@ -303,24 +311,26 @@ assert inspector._shell == mock_shell_inst assert inspector._device_info.device == "/dev/sda1" - assert inspector._device_info.parent_device == "sda" + assert inspector._device_info.root_backing_device == "sda" assert inspector._device_info.under_multipath is True - @mock.patch('crmsh.sh.cluster_shell') - def test_get_parent_device(self, mock_cluster_shell): - """Test _get_parent_device method""" - mock_shell_inst = mock.Mock() - mock_cluster_shell.return_value = mock_shell_inst - mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ - (0, "sda", ""), # lsblk output for __init__ - (0, "", ""), # multipathd show paths output for __init__ - (0, "sda", "") # lsblk output for test call - ] - - inspector = storage_utils.MultipathInspector("/dev/sda1") - parent = inspector._get_parent_device("/dev/sda1") - - assert parent == "sda" + def test_get_root_backing_device(self): + """Test _get_root_backing_device walks partitions and nested slaves down to sdX""" + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "sda" / "sda1").mkdir(parents=True) + (root / "sda" / "sda1" / "partition").write_text("1") + (root / "sda1").symlink_to(root / "sda" / "sda1") + (root / "sdb").mkdir() + (root / "dm-3" / "slaves").mkdir(parents=True) + (root / "dm-3" / "slaves" / "sdb").mkdir() + (root / "dm-48" / "slaves").mkdir(parents=True) + (root / "dm-48" / "slaves" / "dm-3").mkdir() + with mock.patch.object(storage_utils.MultipathInspector, "SYSFS_BLOCK", root): + get = self._real_get_root + assert get("/dev/sda1") == "sda" + assert get("/dev/sdb") == "sdb" + assert get("/dev/dm-48") == "sdb" @mock.patch('crmsh.sh.cluster_shell') def test_get_multipath_mapping(self, mock_cluster_shell): @@ -332,7 +342,6 @@ sdb mpatha sdc mpathb""" mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ - (0, "sda", ""), # lsblk for __init__ (0, multipathd_output, ""), # multipathd for __init__ (0, multipathd_output, "") # multipathd for test call ] @@ -348,7 +357,6 @@ mock_shell_inst = mock.Mock() mock_cluster_shell.return_value = mock_shell_inst mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ - (0, "sda", ""), (0, "dev multipath\nsda mpatha", "") ] @@ -356,7 +364,7 @@ device_info = inspector._device_info assert device_info.device == "/dev/sda1" - assert device_info.parent_device == "sda" + assert device_info.root_backing_device == "sda" assert device_info.under_multipath is True @mock.patch('crmsh.sh.cluster_shell') @@ -365,7 +373,6 @@ mock_shell_inst = mock.Mock() mock_cluster_shell.return_value = mock_shell_inst mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ - (0, "sda", ""), (0, "dev multipath\nsda mpatha", "") ] @@ -379,7 +386,6 @@ mock_shell_inst = mock.Mock() mock_cluster_shell.return_value = mock_shell_inst mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ - (0, "sda", ""), (0, "dev multipath\nsda mpatha", "") ] @@ -387,3 +393,134 @@ storage_utils.MultipathInspector.check_device_under_multipath("/dev/sda1") assert str(exc_info.value) == "Device /dev/sda1 is under multipath, please provide the multipath device instead" + + @mock.patch("crmsh.sh.cluster_shell") + def test_check_device_under_multipath_accepts_multipath_device(self, mock_cluster_shell): + """Test check_device_under_multipath accepts a multipath device alias""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "dev multipath\nsda mpatha", ""), + (0, "dev multipath\nsda mpatha", "") + ] + + storage_utils.MultipathInspector.check_device_under_multipath("/dev/mapper/mpatha") + + @mock.patch("crmsh.sh.cluster_shell") + def test_check_device_under_multipath_accepts_multipath_partition(self, mock_cluster_shell): + """Test check_device_under_multipath accepts a multipath partition alias""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "dev multipath\nsda shared_iscsi_lun1", ""), + (0, "dev multipath\nsda shared_iscsi_lun1", "") + ] + + storage_utils.MultipathInspector.check_device_under_multipath("/dev/mapper/shared_iscsi_lun1-part5") + + @mock.patch("crmsh.sh.cluster_shell") + def test_get_scsi_timeout_non_multipath(self, mock_cluster_shell): + """Test get_scsi_timeout for a non-multipath device""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "", ""), + (0, "", "") + ] + mock_shell_inst.get_stdout_or_raise_error.return_value = "30\n" + + timeout = storage_utils.get_scsi_timeout("/dev/sda1") + + assert timeout == 30 + mock_shell_inst.get_stdout_or_raise_error.assert_called_once_with("cat /sys/block/sda/device/timeout") + + @mock.patch("crmsh.sh.cluster_shell") + def test_get_scsi_timeout_multipath_device(self, mock_cluster_shell): + """Test get_scsi_timeout resolves multipath devices to a backing path""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "dev multipath\nsda mpatha\nsdb mpatha", ""), + (0, "dev multipath\nsda mpatha\nsdb mpatha", ""), + (0, "dev multipath\nsda mpatha\nsdb mpatha", "") + ] + mock_shell_inst.get_stdout_or_raise_error.return_value = "60\n" + + timeout = storage_utils.get_scsi_timeout("/dev/mapper/mpatha") + + assert timeout == 60 + mock_shell_inst.get_stdout_or_raise_error.assert_called_once_with("cat /sys/block/sda/device/timeout") + + @mock.patch("crmsh.sh.cluster_shell") + def test_get_scsi_timeout_multipath_partition_device(self, mock_cluster_shell): + """Test get_scsi_timeout resolves multipath partition aliases""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "dev multipath\nsda shared_iscsi_lun1\nsdb shared_iscsi_lun1", ""), + (0, "dev multipath\nsda shared_iscsi_lun1\nsdb shared_iscsi_lun1", ""), + (0, "dev multipath\nsda shared_iscsi_lun1\nsdb shared_iscsi_lun1", "") + ] + mock_shell_inst.get_stdout_or_raise_error.return_value = "30\n" + + timeout = storage_utils.get_scsi_timeout("/dev/mapper/shared_iscsi_lun1-part5") + + assert timeout == 30 + mock_shell_inst.get_stdout_or_raise_error.assert_called_once_with("cat /sys/block/sda/device/timeout") + + @mock.patch("crmsh.sh.cluster_shell") + def test_get_scsi_timeout_under_multipath_path(self, mock_cluster_shell): + """Test get_scsi_timeout accepts an underlying path directly""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "dev multipath\nsda mpatha", ""), + (0, "dev multipath\nsda mpatha", "") + ] + mock_shell_inst.get_stdout_or_raise_error.return_value = "60\n" + + timeout = storage_utils.get_scsi_timeout("/dev/sda1") + + assert timeout == 60 + mock_shell_inst.get_stdout_or_raise_error.assert_called_once_with("cat /sys/block/sda/device/timeout") + + @mock.patch("crmsh.sh.cluster_shell") + def test_get_multipath_runtime_timeouts(self, mock_cluster_shell): + """Test multipath runtime timeout values are parsed from effective config""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "dev multipath\nsda mpatha", ""), + (0, "defaults {\n max_polling_interval 20\n checker_timeout 30\n}", ""), + (0, "defaults {\n max_polling_interval 20\n checker_timeout 30\n}", "") + ] + + inspector = storage_utils.MultipathInspector("/dev/mapper/mpatha") + + assert inspector.get_max_polling_interval() == 20 + assert inspector.get_checker_timeout() == 30 + + @mock.patch("crmsh.sh.cluster_shell") + def test_get_checker_timeout_defaults_to_scsi_timeout(self, mock_cluster_shell): + """Test checker_timeout defaults to SCSI timeout when absent""" + mock_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_shell_inst + mock_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (0, "", ""), + (0, "defaults {\n max_polling_interval 20\n}", ""), + (0, "", "") + ] + mock_shell_inst.get_stdout_or_raise_error.return_value = "30\n" + + timeout = storage_utils.get_multipath_checker_timeout("/dev/sda1") + + assert timeout == 30 + mock_shell_inst.get_stdout_or_raise_error.assert_called_once_with("cat /sys/block/sda/device/timeout") + + [email protected]("crmsh.storage_utils.is_multipath_device") +def test_pick_backstore_device(mock_is_mp): + mock_is_mp.side_effect = lambda d: d.startswith("/dev/mapper") + assert storage_utils.pick_backstore_device(["/dev/sdc7", "/dev/mapper/m-part5"]) == "/dev/mapper/m-part5" + assert storage_utils.pick_backstore_device(["/dev/sdc7", "/dev/sdd1"]) == "/dev/sdc7" + assert storage_utils.pick_backstore_device([]) is None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20261020.5b865c90/test/unittests/test_ui_sbd.py new/crmsh-5.1.0+20261027.e559ffa7/test/unittests/test_ui_sbd.py --- old/crmsh-5.1.0+20261020.5b865c90/test/unittests/test_ui_sbd.py 2026-10-20 09:53:48.000000000 +0200 +++ new/crmsh-5.1.0+20261027.e559ffa7/test/unittests/test_ui_sbd.py 2026-10-27 09:52:08.000000000 +0100 @@ -197,6 +197,115 @@ res = self.sbd_instance_diskbased.do_configure(mock.Mock(), "test") self.assertFalse(res) + @mock.patch("crmsh.storage_utils.get_multipath_checker_timeout") + @mock.patch("crmsh.storage_utils.get_multipath_max_polling_interval") + @mock.patch("crmsh.storage_utils.is_multipath_device") + @mock.patch("crmsh.storage_utils.is_scsi_device") + @mock.patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @mock.patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + @mock.patch("crmsh.storage_utils.get_scsi_timeout") + @mock.patch("logging.Logger.info") + @mock.patch("builtins.print") + def test_show_disk_backstore( + self, + mock_print, + mock_logger_info, + mock_get_scsi_timeout, + mock_get_sbd_servant_restart_interval, + mock_get_sbd_timeout_io, + mock_is_scsi_device, + mock_is_multipath_device, + mock_get_multipath_max_polling_interval, + mock_get_multipath_checker_timeout, + ): + mock_get_scsi_timeout.return_value = 30 + mock_is_scsi_device.return_value = True + mock_get_sbd_servant_restart_interval.return_value = 5 + mock_get_sbd_timeout_io.return_value = 3 + mock_is_multipath_device.return_value = True + mock_get_multipath_max_polling_interval.return_value = 20 + mock_get_multipath_checker_timeout.return_value = 30 + + self.sbd_instance_diskbased._show_disk_backstore() + + mock_logger_info.assert_called_once_with("crm sbd configure show disk_backstore") + mock_print.assert_has_calls([ + mock.call("multipath max_polling_interval: 20"), + mock.call("multipath checker_timeout : 30"), + mock.call("SCSI timeout : 30"), + mock.call("SBD timeout io : 3"), + mock.call("SBD servant restart interval : 5"), + ]) + + @mock.patch("crmsh.storage_utils.get_multipath_checker_timeout") + @mock.patch("crmsh.storage_utils.get_multipath_max_polling_interval") + @mock.patch("crmsh.storage_utils.is_multipath_device") + @mock.patch("crmsh.storage_utils.is_scsi_device") + @mock.patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @mock.patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + @mock.patch("crmsh.storage_utils.get_scsi_timeout") + @mock.patch("logging.Logger.info") + @mock.patch("builtins.print") + def test_show_disk_backstore_non_multipath_device( + self, + mock_print, + mock_logger_info, + mock_get_scsi_timeout, + mock_get_sbd_servant_restart_interval, + mock_get_sbd_timeout_io, + mock_is_scsi_device, + mock_is_multipath_device, + mock_get_multipath_max_polling_interval, + mock_get_multipath_checker_timeout, + ): + mock_get_scsi_timeout.return_value = 30 + mock_is_scsi_device.return_value = True + mock_get_sbd_servant_restart_interval.return_value = 5 + mock_get_sbd_timeout_io.return_value = 3 + mock_is_multipath_device.return_value = False + + self.sbd_instance_diskbased._show_disk_backstore() + + mock_logger_info.assert_called_once_with("crm sbd configure show disk_backstore") + mock_get_multipath_max_polling_interval.assert_not_called() + mock_get_multipath_checker_timeout.assert_not_called() + mock_print.assert_has_calls([ + mock.call("SCSI timeout : 30"), + mock.call("SBD timeout io : 3"), + mock.call("SBD servant restart interval : 5"), + ]) + + @mock.patch("crmsh.storage_utils.is_multipath_device") + @mock.patch("crmsh.storage_utils.is_scsi_device") + @mock.patch("crmsh.sbd.SBDTimeout.get_sbd_timeout_io") + @mock.patch("crmsh.sbd.SBDTimeout.get_sbd_servant_restart_interval") + @mock.patch("crmsh.storage_utils.get_scsi_timeout") + @mock.patch("logging.Logger.info") + @mock.patch("builtins.print") + def test_show_disk_backstore_non_scsi_device( + self, + mock_print, + mock_logger_info, + mock_get_scsi_timeout, + mock_get_sbd_servant_restart_interval, + mock_get_sbd_timeout_io, + mock_is_scsi_device, + mock_is_multipath_device, + ): + mock_get_sbd_servant_restart_interval.return_value = 5 + mock_get_sbd_timeout_io.return_value = 3 + mock_is_scsi_device.return_value = False + mock_is_multipath_device.return_value = False + + self.sbd_instance_diskbased._show_disk_backstore() + + mock_logger_info.assert_called_once_with("crm sbd configure show disk_backstore") + mock_get_scsi_timeout.assert_not_called() + mock_print.assert_has_calls([ + mock.call("SBD timeout io : 3"), + mock.call("SBD servant restart interval : 5"), + ]) + @mock.patch('crmsh.sbd.SBDTimeout.get_sbd_systemd_start_timeout') @mock.patch('logging.Logger.info') @mock.patch('builtins.print') @@ -262,6 +371,17 @@ mock_sbd_timeout_checker_instance.check_and_fix.assert_called_once() @mock.patch('crmsh.sbd.SBDConfigChecker') + def test_configure_show_disk_backstore(self, mock_sbd_timeout_checker): + mock_sbd_timeout_checker_instance = mock.Mock() + mock_sbd_timeout_checker.return_value = mock_sbd_timeout_checker_instance + mock_sbd_timeout_checker_instance.check_and_fix = mock.Mock() + self.sbd_instance_diskbased._show_disk_backstore = mock.Mock() + self.sbd_instance_diskbased._configure_show(["show", "disk_backstore"]) + self.sbd_instance_diskbased._show_disk_backstore.assert_called_once() + mock_sbd_timeout_checker.assert_called_once_with() + mock_sbd_timeout_checker_instance.check_and_fix.assert_called_once() + + @mock.patch('crmsh.sbd.SBDConfigChecker') def test_configure_show_property(self, mock_sbd_timeout_checker): mock_sbd_timeout_checker_instance = mock.Mock() mock_sbd_timeout_checker.return_value = mock_sbd_timeout_checker_instance @@ -293,9 +413,10 @@ mock_sbd_timeout_checker.return_value = mock_sbd_timeout_checker_instance mock_sbd_timeout_checker_instance.check_and_fix = mock.Mock() self.sbd_instance_diskbased._show_disk_metadata = mock.Mock() + self.sbd_instance_diskbased._show_disk_backstore = mock.Mock() self.sbd_instance_diskbased._show_property = mock.Mock() self.sbd_instance_diskbased._configure_show(["show"]) - mock_print.assert_has_calls([mock.call(), mock.call()]) + mock_print.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_sbd_timeout_checker_instance.check_and_fix.assert_called_once() def test_parse_args_invalid_args(self): @@ -417,11 +538,13 @@ self.sbd_instance_diskless._configure_diskless(parameter_dict) mock_logger_info.assert_called_once_with("No change in SBD configuration") + @mock.patch('crmsh.sbd.SBDTimeout.get_sbd_metadata_expected') @mock.patch('crmsh.sbd.SBDManager') @mock.patch('logging.Logger.info') @mock.patch('crmsh.sbd.SBDUtils.handle_input_sbd_devices') @mock.patch('crmsh.sbd.SBDUtils.verify_sbd_device') - def test_device_add(self, mock_verify_sbd_device, mock_handle_input_sbd_devices, mock_logger_info, mock_SBDManager): + def test_device_add(self, mock_verify_sbd_device, mock_handle_input_sbd_devices, mock_logger_info, mock_SBDManager, mock_expected): + mock_expected.return_value = (0, 0) mock_handle_input_sbd_devices.return_value = [["/dev/sda2"], ["/dev/sda1"]] mock_SBDManager.return_value.init_and_deploy_sbd = mock.Mock() self.sbd_instance_diskbased._device_add(["/dev/sda2"]) @@ -434,6 +557,22 @@ ) mock_logger_info.assert_called_once_with("Append devices: %s", "/dev/sda2") + @mock.patch('crmsh.sbd.SBDTimeout.get_sbd_metadata_expected') + @mock.patch('crmsh.sbd.SBDManager') + @mock.patch('logging.Logger.info') + @mock.patch('crmsh.sbd.SBDUtils.handle_input_sbd_devices') + @mock.patch('crmsh.sbd.SBDUtils.verify_sbd_device') + def test_device_add_larger_timeout(self, mock_verify, mock_handle, mock_logger_info, mock_SBDManager, mock_expected): + mock_handle.return_value = [["/dev/sda2"], ["/dev/sda1"]] + mock_expected.return_value = (62, 124) + self.sbd_instance_diskbased._device_add(["/dev/sda2"]) + mock_expected.assert_called_once_with(["/dev/sda1", "/dev/sda2"]) + mock_SBDManager.assert_called_once_with( + device_list_to_init=["/dev/sda1", "/dev/sda2"], + update_dict={"SBD_DEVICE": "/dev/sda1;/dev/sda2"}, + timeout_dict={"watchdog": 62, "msgwait": 124} + ) + @mock.patch('crmsh.sbd.SBDUtils.verify_sbd_device') def test_device_remove_dev_not_in_config(self, mock_verify_sbd_device): with self.assertRaises(ValueError) as e: @@ -611,18 +750,41 @@ self.assertFalse(res) mock_purge_sbd_from_cluster.assert_not_called() + @mock.patch('crmsh.ui_sbd.ServiceManager') @mock.patch('crmsh.sbd.SBDUtils.verify_sbd_device') @mock.patch('crmsh.utils.check_all_nodes_reachable') - def test_do_purge(self, mock_check_all_nodes_reachable, mock_verify_sbd_device): + def test_do_purge(self, mock_check_all_nodes_reachable, mock_verify_sbd_device, mock_ServiceManager): self.sbd_instance_diskbased._load_attributes = mock.Mock() self.sbd_instance_diskbased._service_is_active = mock.Mock(return_value=True) self.sbd_instance_diskbased._purge_sbd = mock.Mock() res = self.sbd_instance_diskbased.do_purge(mock.Mock()) self.assertTrue(res) self.sbd_instance_diskbased._purge_sbd.assert_called_once_with() - self.sbd_instance_diskbased._load_attributes.assert_called_once() + self.sbd_instance_diskbased._load_attributes.assert_not_called() self.sbd_instance_diskbased._service_is_active.assert_called_once_with(constants.SBD_SERVICE) mock_check_all_nodes_reachable.assert_called_once_with("purging SBD") + mock_verify_sbd_device.assert_not_called() + + @mock.patch('crmsh.ui_sbd.ServiceManager') + @mock.patch('crmsh.utils.check_all_nodes_reachable') + def test_do_purge_crashdump(self, mock_check_all_nodes_reachable, mock_ServiceManager): + self.sbd_instance_diskbased._load_attributes = mock.Mock() + self.sbd_instance_diskbased._service_is_active = mock.Mock(return_value=True) + self.sbd_instance_diskbased._is_crashdump_configured = mock.Mock(return_value=True) + self.sbd_instance_diskbased._purge_crashdump = mock.Mock() + res = self.sbd_instance_diskbased.do_purge(mock.Mock(), "crashdump") + self.assertTrue(res) + self.sbd_instance_diskbased._load_attributes.assert_called_once_with() + self.sbd_instance_diskbased._purge_crashdump.assert_called_once_with() + mock_check_all_nodes_reachable.assert_called_once_with("purging SBD crashdump") + + @mock.patch('crmsh.sbd.SBDManager') + @mock.patch('crmsh.sbd.SBDUtils.verify_sbd_device') + def test_purge_crashdump_verifies_device(self, mock_verify_sbd_device, mock_SBDManager): + self.sbd_instance_diskbased._set_sbd_opts = mock.Mock(return_value={}) + self.sbd_instance_diskbased._purge_crashdump() + mock_verify_sbd_device.assert_called_once_with(["/dev/sda1"], ["node1", "node2"]) + mock_SBDManager.return_value.init_and_deploy_sbd.assert_called_once_with() @mock.patch('crmsh.xmlutil.CrmMonXMLParser') def test_print_sbd_agent_status(self, mock_CrmMonXMLParser):
