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):

Reply via email to