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-04-07 16:33:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crmsh (Old)
 and      /work/SRC/openSUSE:Factory/.crmsh.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Tue Apr  7 16:33:56 2026 rev:403 rq:1344869 version:5.0.0+20260407.ce740ef1

Changes:
--------
--- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes      2026-04-02 
17:46:10.447708173 +0200
+++ /work/SRC/openSUSE:Factory/.crmsh.new.21863/crmsh.changes   2026-04-07 
16:50:04.243423622 +0200
@@ -1,0 +2,14 @@
+Tue Apr 07 06:44:04 UTC 2026 - [email protected]
+
+- Update to version 5.0.0+20260407.ce740ef1:
+  * Dev: behave: Adjust functional test for previous commit
+  * Dev: unittests: Adjust unit test for previous commit
+  * Dev: ui_sbd: Refactor SBD crashdump set/purge flow to avoid inconsistent 
/etc/sysconfig/sbd
+
+-------------------------------------------------------------------
+Fri Apr 03 09:37:47 UTC 2026 - [email protected]
+
+- Update to version 5.0.0+20260403.4ebee742:
+  * Fix: bootstrap: Ensure robust node identification when removing from 
cluster (bsc#1259683)
+
+-------------------------------------------------------------------

Old:
----
  crmsh-5.0.0+20260402.90d08295.tar.bz2

New:
----
  crmsh-5.0.0+20260407.ce740ef1.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.KqyBFE/_old  2026-04-07 16:50:05.287466842 +0200
+++ /var/tmp/diff_new_pack.KqyBFE/_new  2026-04-07 16:50:05.287466842 +0200
@@ -41,7 +41,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0-or-later
 Group:          %{pkg_group}
-Version:        5.0.0+20260402.90d08295
+Version:        5.0.0+20260407.ce740ef1
 Release:        0
 URL:            http://crmsh.github.io
 Source0:        %{name}-%{version}.tar.bz2

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.KqyBFE/_old  2026-04-07 16:50:05.351469492 +0200
+++ /var/tmp/diff_new_pack.KqyBFE/_new  2026-04-07 16:50:05.359469823 +0200
@@ -9,7 +9,7 @@
 </service>
 <service name="tar_scm">
   <param name="url">https://github.com/ClusterLabs/crmsh.git</param>
-  <param 
name="changesrevision">0b408da8bf1caaf7e3471f62ff5f1a8f6aab41e3</param>
+  <param 
name="changesrevision">ce740ef16bab35ffcedf5eadd8dcbf27ba57bdb6</param>
 </service>
 </servicedata>
 (No newline at EOF)

++++++ crmsh-5.0.0+20260402.90d08295.tar.bz2 -> 
crmsh-5.0.0+20260407.ce740ef1.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-5.0.0+20260402.90d08295/crmsh/bootstrap.py 
new/crmsh-5.0.0+20260407.ce740ef1/crmsh/bootstrap.py
--- old/crmsh-5.0.0+20260402.90d08295/crmsh/bootstrap.py        2026-04-02 
11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/crmsh/bootstrap.py        2026-04-07 
07:59:59.000000000 +0200
@@ -12,6 +12,7 @@
 import codecs
 import dataclasses
 import io
+import json
 import os
 import subprocess
 import sys
@@ -39,6 +40,7 @@
 from . import tmpfiles
 from . import lock
 from . import userdir
+from . import iproute2
 from .constants import QDEVICE_HELP_INFO, FENCING_TIMEOUT_DEFAULT,\
         REJOIN_COUNT, REJOIN_INTERVAL, PCMK_DELAY_MAX, CSYNC2_SERVICE, 
WAIT_TIMEOUT_MS_DEFAULT
 from . import cluster_fs
@@ -2108,23 +2110,24 @@
         utils.set_property("priority-fencing-delay", 0)
 
 
-def get_cluster_node_ip(node: str) -> str:
+def get_cluster_node_ips(node: str) -> list[str]:
     """
-    ringx_addr might be hostname or IP
-    _context.cluster_node by now is always hostname
-
-    If ring0_addr is IP, we should get the configured iplist which belong 
_context.cluster_node
-    Then filter out which one is configured as ring0_addr
-    At last assign that ip to _context.cluster_node_ip which will be removed 
later
+    Get all IP addresses of the target node remotely.
+    If it fails, fall back to utils.get_iplist_from_name(node).
     """
-    addr_list = corosync.get_values('nodelist.node.ring0_addr')
-    if node in addr_list:
-        return
+    rc, out, err = sh.cluster_shell().get_rc_stdout_stderr_without_input(node, 
"ip -j addr show")
+    if rc == 0:
+        try:
+            addr_info = iproute2.IPAddr(json.loads(out))
+            ips = []
+            for iface in addr_info.interfaces():
+                for addr in iface.addr_info:
+                    ips.append(str(addr.ip))
+            return ips
+        except Exception as e:
+            logger.warning("Failed to parse ip output from node {}: 
{}".format(node, e))
 
-    ip_list = utils.get_iplist_from_name(node)
-    for ip in ip_list:
-        if ip in addr_list:
-            return ip
+    return utils.get_iplist_from_name(node)
 
 
 def stop_and_disable_services(remote_addr=None):
@@ -2174,7 +2177,7 @@
         remove_pacemaker_remote_node_from_cluster(node)
         return
 
-    node_ip = get_cluster_node_ip(node)
+    nodeid = utils.get_nodeid_from_name(node)
     if not dead_node:
         stop_and_disable_services(remote_addr=node)
         qdevice.QDevice.remove_qdevice_db([node])
@@ -2187,7 +2190,15 @@
 
     # Remove node from nodelist
     if corosync.get_values("nodelist.node.ring0_addr"):
-        corosync.del_node(node_ip if node_ip is not None else node)
+        if corosync.del_node_by_name(node):
+            pass
+        elif nodeid and corosync.del_node_by_nodeid(nodeid):
+            pass
+        else:
+            node_ips = get_cluster_node_ips(node)
+            node_ips.append(node)
+            if not corosync.del_node(node_ips):
+                utils.fatal("Failed to remove node {} from corosync 
configuration".format(node))
 
     corosync.configure_two_node(removing=True)
     logger.info("Propagating configuration changes across the remaining nodes")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-5.0.0+20260402.90d08295/crmsh/corosync.py 
new/crmsh-5.0.0+20260407.ce740ef1/crmsh/corosync.py
--- old/crmsh-5.0.0+20260402.90d08295/crmsh/corosync.py 2026-04-02 
11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/crmsh/corosync.py 2026-04-07 
07:59:59.000000000 +0200
@@ -300,15 +300,56 @@
     inst.save()
 
 
-def del_node(addr: str) -> None:
+def del_node_by_name(name: str) -> bool:
+    '''
+    Remove node from corosync by node name
+    '''
+    try:
+        inst = ConfParser()
+        name_list = inst.get_all("nodelist.node.name")
+        index = name_list.index(name)
+        inst.remove("nodelist.node", index)
+        inst.save()
+        return True
+    except (ValueError, KeyError, IndexError):
+        return False
+
+
+def del_node_by_nodeid(nodeid: int | str) -> bool:
+    '''
+    Remove node from corosync by nodeid
+    '''
+    try:
+        inst = ConfParser()
+        nodeid_list = inst.get_all("nodelist.node.nodeid")
+        index = nodeid_list.index(str(nodeid))
+        inst.remove("nodelist.node", index)
+        inst.save()
+        return True
+    except (ValueError, KeyError, IndexError):
+        return False
+
+
+def del_node(addrs: list[str]) -> bool:
     '''
     Remove node from corosync
     '''
-    inst = ConfParser()
-    name_list = inst.get_all("nodelist.node.ring0_addr")
-    index = name_list.index(addr)
-    inst.remove("nodelist.node", index)
-    inst.save()
+    try:
+        inst = ConfParser()
+        addr_list = inst.get_all("nodelist.node.ring0_addr")
+        index = -1
+        for addr in addrs:
+            if addr in addr_list:
+                index = addr_list.index(addr)
+                break
+        if index == -1:
+            return False
+        inst.remove("nodelist.node", index)
+        inst.save()
+        return True
+    except (ValueError, KeyError, IndexError):
+        return False
+
 
 
 def get_corosync_value(key, cmapctl_prefix="runtime.config"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-5.0.0+20260402.90d08295/crmsh/sbd.py 
new/crmsh-5.0.0+20260407.ce740ef1/crmsh/sbd.py
--- old/crmsh-5.0.0+20260402.90d08295/crmsh/sbd.py      2026-04-02 
11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/crmsh/sbd.py      2026-04-07 
07:59:59.000000000 +0200
@@ -1171,6 +1171,7 @@
     SBD_RA = "stonith:fence_sbd"
     SBD_RA_ID = "fencing-sbd"
     SBD_DEVICE_MAX = 3
+    SBD_CRASHDUMP_ACTION = "flush,crashdump"
 
     class NotConfigSBD(Exception):
         pass
@@ -1181,7 +1182,8 @@
         timeout_dict: typing.Dict[str, int] | None = None,
         update_dict: typing.Dict[str, str] | None = None,
         diskless_sbd: bool = False,
-        bootstrap_context: 'bootstrap.Context | None' = None
+        bootstrap_context: 'bootstrap.Context | None' = None,
+        crashdump_mode: str | None = None
     ):
         '''
         Init function which can be called from crm sbd subcommand or bootstrap
@@ -1193,6 +1195,7 @@
         self.cluster_is_running = 
ServiceManager().service_is_active(constants.PCMK_SERVICE)
         self.bootstrap_context = bootstrap_context
         self.overwrite_sysconfig = False
+        self.crashdump_mode = crashdump_mode
 
         # From bootstrap init or join process, override the values
         if self.bootstrap_context:
@@ -1233,7 +1236,10 @@
             utils.copy_local_file(self.SYSCONFIG_SBD_TEMPLATE, 
self.SYSCONFIG_SBD)
 
         for key, value in self.update_dict.items():
-            logger.info("Update %s in %s: %s", key, self.SYSCONFIG_SBD, value)
+            if value:
+                logger.info("Update %s in %s: %s", key, self.SYSCONFIG_SBD, 
value)
+            else:
+                logger.info("Set %s in %s to empty", key, self.SYSCONFIG_SBD)
         utils.sysconfig_set(self.SYSCONFIG_SBD, **self.update_dict)
         if self.cluster_is_running:
             bootstrap.sync_path(self.SYSCONFIG_SBD)
@@ -1395,6 +1401,7 @@
                 return
 
             self.initialize_sbd()
+            self.set_crashdump_action()
             self.update_configuration()
             self.enable_sbd_service()
 
@@ -1454,6 +1461,34 @@
                   "systemctl daemon-reload"
             utils.cluster_run_cmd(cmd, node_list)
 
+    def set_crashdump_action(self):
+        '''
+        Set crashdump timeout action in /etc/sysconfig/sbd
+        '''
+        if not self.crashdump_mode or self.crashdump_mode not in ("set", 
"restore"):
+            return
+
+        comment_action_line = f"sed -i 
'/^SBD_TIMEOUT_ACTION/s/^/#__sbd_crashdump_backup__ /' {self.SYSCONFIG_SBD}"
+        add_action_line = f"sed -i '/^#__sbd_crashdump_backup__/a 
SBD_TIMEOUT_ACTION={self.SBD_CRASHDUMP_ACTION}' {self.SYSCONFIG_SBD}"
+        comment_out_action_line = f"sed -i 's/^#__sbd_crashdump_backup__ 
SBD_TIMEOUT_ACTION/SBD_TIMEOUT_ACTION/' {self.SYSCONFIG_SBD}"
+        delete_action_line = f"sed -i '/^SBD_TIMEOUT_ACTION/d' 
{self.SYSCONFIG_SBD}"
+
+        sbd_timeout_action_configured = 
SBDUtils.get_sbd_value_from_config("SBD_TIMEOUT_ACTION")
+        shell = sh.cluster_shell()
+
+        if self.crashdump_mode == "set":
+            if not sbd_timeout_action_configured:
+                logger.info("Set SBD_TIMEOUT_ACTION in %s: %s", 
self.SYSCONFIG_SBD, self.SBD_CRASHDUMP_ACTION)
+                self.update_dict["SBD_TIMEOUT_ACTION"] = 
self.SBD_CRASHDUMP_ACTION
+            elif sbd_timeout_action_configured != self.SBD_CRASHDUMP_ACTION:
+                logger.info("Update SBD_TIMEOUT_ACTION in %s: %s", 
self.SYSCONFIG_SBD, self.SBD_CRASHDUMP_ACTION)
+                shell.get_stdout_or_raise_error(f"{comment_action_line} && 
{add_action_line}")
+
+        elif self.crashdump_mode == "restore":
+            if sbd_timeout_action_configured and sbd_timeout_action_configured 
== self.SBD_CRASHDUMP_ACTION:
+                logger.info("Restore SBD_TIMEOUT_ACTION in %s", 
self.SYSCONFIG_SBD)
+                shell.get_stdout_or_raise_error(f"{delete_action_line} && 
{comment_out_action_line}")
+
 
 def cleanup_existing_sbd_resource():
     if xmlutil.CrmMonXmlParser().is_resource_configured(SBDManager.SBD_RA):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-5.0.0+20260402.90d08295/crmsh/ui_sbd.py 
new/crmsh-5.0.0+20260407.ce740ef1/crmsh/ui_sbd.py
--- old/crmsh-5.0.0+20260402.90d08295/crmsh/ui_sbd.py   2026-04-02 
11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/crmsh/ui_sbd.py   2026-04-07 
07:59:59.000000000 +0200
@@ -12,7 +12,6 @@
 from crmsh import sh
 from crmsh import xmlutil
 from crmsh import constants
-from crmsh import cibquery
 from crmsh.service_manager import ServiceManager
 
 
@@ -324,60 +323,16 @@
             return timeout_dict
         return timeout_dict
 
-    def _set_crashdump_option(self, delete=False):
-        '''
-        Set crashdump option for fence_sbd resource
-        '''
-        cib = 
xmlutil.text2elem(self.cluster_shell.get_stdout_or_raise_error('crm configure 
show xml'))
-        ra = cibquery.ResourceAgent("stonith", "", "fence_sbd")
-        res_id_list = cibquery.get_primitives_with_ra(cib, ra)
-        if not res_id_list:
-            if delete:
-                return
-            logger.error("No fence_sbd resource found")
-            raise utils.TerminateSubCommand
-
-        crashdump_value = cibquery.get_parameter_value(cib, res_id_list[0], 
"crashdump")
-        cmd = ""
-        if utils.is_boolean_false(crashdump_value):
-            if delete:
-                return
-            cmd = f"crm resource param {res_id_list[0]} set crashdump 1"
-            logger.info("Set crashdump option for fence_sbd resource")
-        elif delete:
-            cmd = f"crm resource param {res_id_list[0]} delete crashdump"
-            logger.info("Delete crashdump option for fence_sbd resource")
-        if cmd:
-            self.cluster_shell.get_stdout_or_raise_error(cmd)
-
-    def _set_crashdump_in_sysconfig(self, crashdump_watchdog_timeout=None, 
restore=False, diskless=False) -> dict:
+    def _set_sbd_opts(self, crashdump_watchdog_timeout=None, restore=False, 
diskless=False) -> dict:
         update_dict = {}
-        sbd_timeout_action_for_crashdump = "flush,crashdump"
-        comment_action_line = f"sed -i 
'/^SBD_TIMEOUT_ACTION/s/^/#__sbd_crashdump_backup__ /' 
{sbd.SBDManager.SYSCONFIG_SBD}"
-        add_action_line = f"sed -i '/^#__sbd_crashdump_backup__/a 
SBD_TIMEOUT_ACTION={sbd_timeout_action_for_crashdump}' 
{sbd.SBDManager.SYSCONFIG_SBD}"
-        comment_out_action_line = f"sed -i 's/^#__sbd_crashdump_backup__ 
SBD_TIMEOUT_ACTION/SBD_TIMEOUT_ACTION/' {sbd.SBDManager.SYSCONFIG_SBD}"
-        delete_action_line = f"sed -i '/^SBD_TIMEOUT_ACTION/d' 
{sbd.SBDManager.SYSCONFIG_SBD}"
 
-        sbd_timeout_action_configured = 
sbd.SBDUtils.get_sbd_value_from_config("SBD_TIMEOUT_ACTION")
         if restore:
-            if sbd_timeout_action_configured and sbd_timeout_action_configured 
== sbd_timeout_action_for_crashdump:
-                cmd_delete_and_comment_out = f"{delete_action_line} && 
{comment_out_action_line}"
-                logger.info("Delete SBD_TIMEOUT_ACTION: %s and restore 
original value", sbd_timeout_action_for_crashdump)
-                
self.cluster_shell.get_stdout_or_raise_error(cmd_delete_and_comment_out)
-
             sbd_opts = sbd.SBDUtils.get_sbd_value_from_config("SBD_OPTS")
             if sbd_opts and re.search(self.SBD_OPTS_RE, sbd_opts):
                 sbd_opts = re.sub(self.SBD_OPTS_RE, '', sbd_opts)
                 update_dict["SBD_OPTS"] = ' '.join(sbd_opts.split())
 
         elif crashdump_watchdog_timeout:
-            if not sbd_timeout_action_configured:
-                update_dict["SBD_TIMEOUT_ACTION"] = 
sbd_timeout_action_for_crashdump
-            elif sbd_timeout_action_configured != 
sbd_timeout_action_for_crashdump:
-                cmd_comment_and_add = f"{comment_action_line} && 
{add_action_line}"
-                
self.cluster_shell.get_stdout_or_raise_error(cmd_comment_and_add)
-                logger.info("Update SBD_TIMEOUT_ACTION in %s: %s", 
sbd.SBDManager.SYSCONFIG_SBD, sbd_timeout_action_for_crashdump)
-
             value_for_diskless = " -Z" if diskless else ""
             value_for_sbd_opts = f"-C 
{crashdump_watchdog_timeout}{value_for_diskless}"
             sbd_opts = sbd.SBDUtils.get_sbd_value_from_config("SBD_OPTS")
@@ -432,13 +387,14 @@
         # merge runtime timeout dict into parameter timeout dict without 
overwriting
         timeout_dict = {**self.device_meta_dict_runtime, **timeout_dict}
 
+        configure_crashdump = False
         crashdump_watchdog_timeout = parameter_dict.get("crashdump-watchdog", 
self.crashdump_watchdog_timeout_from_config)
         if self._should_configure_crashdump(crashdump_watchdog_timeout, 
timeout_dict.get("watchdog")):
+            configure_crashdump = True
             self._check_kdump_service()
-            self._set_crashdump_option()
             timeout_dict["msgwait"] = 2*timeout_dict["watchdog"] + 
crashdump_watchdog_timeout
             logger.info("Set msgwait-timeout to 2*watchdog-timeout + 
crashdump-watchdog-timeout: %s", timeout_dict["msgwait"])
-            result_dict = 
self._set_crashdump_in_sysconfig(crashdump_watchdog_timeout)
+            result_dict = self._set_sbd_opts(crashdump_watchdog_timeout)
             update_dict = {**update_dict, **result_dict}
 
         device_list = sbd.SBDUtils.get_sbd_device_from_config()
@@ -450,7 +406,8 @@
         sbd_manager = sbd.SBDManager(
             device_list_to_init=self.device_list_from_config,
             timeout_dict=timeout_dict,
-            update_dict=update_dict
+            update_dict=update_dict,
+            crashdump_mode="set" if configure_crashdump else None
         )
         sbd_manager.init_and_deploy_sbd()
         
@@ -467,10 +424,12 @@
         if watchdog_device != self.watchdog_device_from_config:
             update_dict["SBD_WATCHDOG_DEV"] = watchdog_device
 
+        configure_crashdump = False
         crashdump_watchdog_timeout = parameter_dict.get("crashdump-watchdog", 
self.crashdump_watchdog_timeout_from_config)
         if self._should_configure_crashdump(crashdump_watchdog_timeout, 
watchdog_timeout, diskless=True):
+            configure_crashdump = True
             self._check_kdump_service()
-            result_dict = 
self._set_crashdump_in_sysconfig(crashdump_watchdog_timeout, diskless=True)
+            result_dict = self._set_sbd_opts(crashdump_watchdog_timeout, 
diskless=True)
             update_dict = {**update_dict, **result_dict}
         if not update_dict:
             logger.info("No change in SBD configuration")
@@ -488,7 +447,8 @@
 
         sbd_manager = sbd.SBDManager(
             update_dict=update_dict,
-            diskless_sbd=True
+            diskless_sbd=True,
+            crashdump_mode="set" if configure_crashdump else None
         )
         sbd_manager.init_and_deploy_sbd(restart_first)
 
@@ -604,6 +564,35 @@
                 print(usage)
             return False
 
+    def _purge_crashdump(self):
+        '''
+        Purge crashdump configuration from SBD by leveraging maintenance mode
+        '''
+        timeout_dict, update_dict = {}, {}
+        if self.device_list_from_config:
+            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"])
+        update_dict = self._set_sbd_opts(restore=True)
+
+        sbd_manager = sbd.SBDManager(
+            device_list_to_init=self.device_list_from_config if 
self.device_list_from_config else None,
+            timeout_dict=timeout_dict,
+            update_dict=update_dict,
+            crashdump_mode="restore"
+        )
+        sbd_manager.init_and_deploy_sbd()
+
+    def _purge_sbd(self):
+        '''
+        Purge SBD from cluster by leveraging maintenance mode
+        '''
+        with utils.leverage_maintenance_mode() as enabled:
+            if not utils.able_to_restart_cluster(enabled):
+                return False
+            sbd.purge_sbd_from_cluster()
+            bootstrap.restart_cluster()
+
     @command.completers(completers.choice(['crashdump']))
     def do_purge(self, context, *args) -> bool:
         '''
@@ -625,22 +614,14 @@
                 logger.info("Usage: crm sbd purge [crashdump]")
                 return False
 
-        utils.check_all_nodes_reachable("purging SBD")
-
-        with utils.leverage_maintenance_mode() as enabled:
-            if not utils.able_to_restart_cluster(enabled):
-                return False
-
-            if purge_crashdump:
-                self._set_crashdump_option(delete=True)
-                update_dict = self._set_crashdump_in_sysconfig(restore=True)
-                if update_dict:
-                    sbd.SBDManager.update_sbd_configuration(update_dict)
-            else:
-                sbd.purge_sbd_from_cluster()
+        msg = "purging SBD crashdump" if purge_crashdump else "purging SBD"
+        utils.check_all_nodes_reachable(msg)
+        if purge_crashdump:
+            self._purge_crashdump()
+        else:
+            self._purge_sbd()
 
-            bootstrap.restart_cluster()
-            return True
+        return True
 
     def _print_sbd_type(self):
         if not self.service_manager.service_is_active(constants.SBD_SERVICE):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-5.0.0+20260402.90d08295/test/features/bootstrap_bugs.feature 
new/crmsh-5.0.0+20260407.ce740ef1/test/features/bootstrap_bugs.feature
--- old/crmsh-5.0.0+20260402.90d08295/test/features/bootstrap_bugs.feature      
2026-04-02 11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/test/features/bootstrap_bugs.feature      
2026-04-07 07:59:59.000000000 +0200
@@ -307,3 +307,25 @@
     When    Run "crm cluster join -c hanode1 -y" on "hanode2"
     Then    Cluster service is "started" on "hanode1"
     Then    Cluster service is "started" on "hanode2"
+
+  @clean
+  Scenario: Avoid mixed up of nodeid and ip addresses after node removal and 
rejoin (bsc#1259683)
+    Given   Cluster service is "stopped" on "hanode1"
+    And     Cluster service is "stopped" on "hanode2"
+    And     Cluster service is "stopped" on "hanode3"
+    When    Run "crm cluster init -y" on "hanode1"
+    Then    Cluster service is "started" on "hanode1"
+    When    Run "crm cluster join -c hanode1 -y" on "hanode2,hanode3"
+    Then    Cluster service is "started" on "hanode2"
+    And     Cluster service is "started" on "hanode3"
+    When    Run "crm cluster remove hanode2 -y" on "hanode1"
+    Then    Cluster service is "stopped" on "hanode2"
+    When    Run "crm cluster remove hanode3 -y" on "hanode1"
+    Then    Cluster service is "stopped" on "hanode3"
+    When    Run "crm cluster join -c hanode1 -y" on "hanode3"
+    Then    Cluster service is "started" on "hanode3"
+    When    Run "crm cluster join -c hanode1 -y" on "hanode2"
+    Then    Cluster service is "started" on "hanode2"
+    When    Run "crm cluster remove hanode3 -y" on "hanode1"
+    Then    Cluster service is "stopped" on "hanode3"
+    And     Online nodes are "hanode1 hanode2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-5.0.0+20260402.90d08295/test/features/sbd_ui.feature 
new/crmsh-5.0.0+20260407.ce740ef1/test/features/sbd_ui.feature
--- old/crmsh-5.0.0+20260402.90d08295/test/features/sbd_ui.feature      
2026-04-02 11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/test/features/sbd_ui.feature      
2026-04-07 07:59:59.000000000 +0200
@@ -77,26 +77,19 @@
     Then    Run "crm sbd configure show sysconfig|grep -E 
"SBD_DEVICE=\"/dev/sda5;/dev/sda6\""" OK
     Then    Run "crm sbd configure show sysconfig|grep -E 
"SBD_DEVICE=\"/dev/sda5;/dev/sda6\""" OK on "hanode2"
     And     Run "crm sbd configure show disk_metadata |grep -A 8 
'/dev/sda6'|grep -E "watchdog.*30"" OK
-    And     Run "crm sbd configure show disk_metadata |grep -A 8 
'/dev/sda6'|grep -E "msgwait.*120"" OK
-    When    Run "crm cluster restart --all" on "hanode1"
-    And     Wait for DC
+    And     Run "crm sbd configure show disk_metadata |grep -A 8 
'/dev/sda6'|grep -E "msgwait.*60"" OK
     # Remove a sbd disk
     When    Run "crm sbd device remove /dev/sda5" on "hanode1"
     Then    Run "crm sbd configure show sysconfig|grep "SBD_DEVICE=/dev/sda6"" 
OK
     Then    Run "crm sbd configure show sysconfig|grep "SBD_DEVICE=/dev/sda6"" 
OK on "hanode2"
-    When    Run "crm cluster restart --all" on "hanode1"
-    And     Wait for DC
     # Replace a sbd disk
     When    Run "crm -F sbd device add /dev/sda7" on "hanode1"
     Then    Run "crm sbd configure show sysconfig|grep -E 
"SBD_DEVICE=\"/dev/sda6;/dev/sda7\""" OK
     Then    Run "crm sbd configure show sysconfig|grep -E 
"SBD_DEVICE=\"/dev/sda6;/dev/sda7\""" OK on "hanode2"
     And     Run "crm sbd configure show disk_metadata |grep -A 8 
'/dev/sda7'|grep -E "watchdog.*30"" OK
-    And     Run "crm sbd configure show disk_metadata |grep -A 8 
'/dev/sda7'|grep -E "msgwait.*120"" OK
-    When    Run "crm cluster restart --all" on "hanode1"
-    And     Wait for DC
+    And     Run "crm sbd configure show disk_metadata |grep -A 8 
'/dev/sda7'|grep -E "msgwait.*60"" OK
     # Purge sbd from cluster
     When    Run "crm sbd purge" on "hanode1"
-    And     Run "crm cluster restart --all" on "hanode1"
     Then    Service "sbd.service" is "stopped" on "hanode1"
     Then    Service "sbd.service" is "stopped" on "hanode2"
 
@@ -131,7 +124,6 @@
     Then    Expected return code is "1"
     # Purge sbd from cluster
     When    Run "crm sbd purge" on "hanode1"
-    And     Run "crm cluster restart --all" on "hanode1"
     Then    Service "sbd.service" is "stopped" on "hanode1"
     Then    Service "sbd.service" is "stopped" on "hanode2"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-5.0.0+20260402.90d08295/test/unittests/test_bootstrap.py 
new/crmsh-5.0.0+20260407.ce740ef1/test/unittests/test_bootstrap.py
--- old/crmsh-5.0.0+20260402.90d08295/test/unittests/test_bootstrap.py  
2026-04-02 11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/test/unittests/test_bootstrap.py  
2026-04-07 07:59:59.000000000 +0200
@@ -1924,23 +1924,28 @@
             ])
         mock_rm_sbd.assert_called_once_with(None)
 
+    @mock.patch('crmsh.sh.cluster_shell')
     @mock.patch('crmsh.utils.get_iplist_from_name')
-    @mock.patch('crmsh.corosync.get_values')
-    def test_get_cluster_node_ip_host(self, mock_get_values, mock_get_iplist):
-        mock_get_values.return_value = ["node1", "node2"]
-        self.assertIsNone(bootstrap.get_cluster_node_ip('node1'))
-        mock_get_values.assert_called_once_with("nodelist.node.ring0_addr")
-        mock_get_iplist.assert_not_called()
-
-    @mock.patch('crmsh.utils.get_iplist_from_name')
-    @mock.patch('crmsh.corosync.get_values')
-    def test_get_cluster_node_ip(self, mock_get_values, mock_get_iplist):
-        mock_get_values.return_value = ["10.10.10.1", "10.10.10.2"]
+    def test_get_cluster_node_ips_fallback(self, mock_get_iplist, 
mock_cluster_shell):
+        mock_shell_inst = mock.Mock()
+        mock_cluster_shell.return_value = mock_shell_inst
+        mock_shell_inst.get_rc_stdout_stderr_without_input.return_value = (1, 
"", "")
         mock_get_iplist.return_value = ["10.10.10.1"]
-        self.assertEqual("10.10.10.1", bootstrap.get_cluster_node_ip('node1'))
-        mock_get_values.assert_called_once_with("nodelist.node.ring0_addr")
+        self.assertEqual(["10.10.10.1"], 
bootstrap.get_cluster_node_ips('node1'))
+        
mock_shell_inst.get_rc_stdout_stderr_without_input.assert_called_once_with('node1',
 'ip -j addr show')
         mock_get_iplist.assert_called_once_with('node1')
 
+    @mock.patch('crmsh.sh.cluster_shell')
+    @mock.patch('crmsh.utils.get_iplist_from_name')
+    def test_get_cluster_node_ips_success(self, mock_get_iplist, 
mock_cluster_shell):
+        mock_shell_inst = mock.Mock()
+        mock_cluster_shell.return_value = mock_shell_inst
+        json_out = 
'[{"ifname":"eth0","flags":[],"addr_info":[{"local":"10.10.10.1","prefixlen":24}]}]'
+        mock_shell_inst.get_rc_stdout_stderr_without_input.return_value = (0, 
json_out, "")
+        self.assertEqual(["10.10.10.1"], 
bootstrap.get_cluster_node_ips('node1'))
+        
mock_shell_inst.get_rc_stdout_stderr_without_input.assert_called_once_with('node1',
 'ip -j addr show')
+        mock_get_iplist.assert_not_called()
+
     @mock.patch('crmsh.service_manager.ServiceManager.disable_service')
     @mock.patch('crmsh.service_manager.ServiceManager.service_is_enabled')
     @mock.patch('crmsh.utils.this_node')
@@ -1976,13 +1981,15 @@
     @mock.patch('crmsh.bootstrap.invoke')
     @mock.patch('logging.Logger.info')
     @mock.patch('crmsh.bootstrap.stop_and_disable_services')
-    @mock.patch('crmsh.bootstrap.get_cluster_node_ip')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_ips')
+    @mock.patch('crmsh.utils.get_nodeid_from_name')
     @mock.patch('crmsh.xmlutil.CrmMonXmlParser')
-    def test_remove_node_from_cluster_rm_node_failed(self, 
mock_crm_mon_parser, mock_get_ip, mock_stop, mock_status, mock_invoke, 
mock_error, mock_rm_conf_files, mock_call_delnode):
+    def test_remove_node_from_cluster_rm_node_failed(self, 
mock_crm_mon_parser, mock_get_nodeid, mock_get_ips, mock_stop, mock_status, 
mock_invoke, mock_error, mock_rm_conf_files, mock_call_delnode):
         mock_crm_mon_parser_inst = mock.Mock()
         mock_crm_mon_parser.return_value = mock_crm_mon_parser_inst
         mock_crm_mon_parser_inst.is_node_remote.return_value = False
-        mock_get_ip.return_value = '192.0.2.100'
+        mock_get_nodeid.return_value = '1'
+        mock_get_ips.return_value = ['192.0.2.100']
         mock_call_delnode.return_value = False
         mock_error.side_effect = SystemExit
 
@@ -1990,7 +1997,8 @@
             bootstrap._context = mock.Mock(rm_list=["file1", "file2"])
             bootstrap.remove_node_from_cluster('node1')
 
-        mock_get_ip.assert_called_once_with('node1')
+        mock_get_nodeid.assert_called_once_with('node1')
+        mock_get_ips.assert_not_called()
         mock_status.assert_called_once_with("Removing node %s from CIB", 
"node1")
         mock_stop.assert_called_once_with(remote_addr="node1")
         mock_invoke.assert_not_called()
@@ -2006,24 +2014,29 @@
     @mock.patch('crmsh.corosync.configure_two_node')
     @mock.patch('crmsh.bootstrap.adjust_properties')
     @mock.patch('crmsh.bootstrap.sync_path')
-    @mock.patch('crmsh.corosync.del_node')
+    @mock.patch('crmsh.corosync.del_node_by_nodeid')
+    @mock.patch('crmsh.corosync.del_node_by_name')
     @mock.patch('crmsh.corosync.get_values')
     @mock.patch('crmsh.utils.fatal')
     @mock.patch('crmsh.bootstrap.invoke')
     @mock.patch('logging.Logger.info')
     @mock.patch('crmsh.bootstrap.stop_and_disable_services')
-    @mock.patch('crmsh.bootstrap.get_cluster_node_ip')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_ips')
+    @mock.patch('crmsh.utils.get_nodeid_from_name')
     @mock.patch('crmsh.xmlutil.CrmMonXmlParser')
-    def test_remove_node_from_cluster_hostname(self, mock_crm_mon_parser, 
mock_get_ip, mock_stop, mock_status,
-            mock_invoke, mock_error, mock_get_values, mock_del, mock_csync2,
+    def test_remove_node_from_cluster_hostname(self, mock_crm_mon_parser, 
mock_get_nodeid, mock_get_ips, mock_stop, mock_status,
+            mock_invoke, mock_error, mock_get_values, mock_del_by_name, 
mock_del_by_id, mock_csync2,
             mock_adjust_priority, mock_adjust_fence_delay, mock_rm_conf_files, 
mock_is_active, mock_cal_delnode, mock_firewall, mock_cluster_shell, 
mock_host_user_config):
         mock_crm_mon_parser_inst = mock.Mock()
         mock_crm_mon_parser.return_value = mock_crm_mon_parser_inst
         mock_crm_mon_parser_inst.is_node_remote.return_value = False
-        mock_get_ip.return_value = "10.10.10.1"
+        mock_get_nodeid.return_value = "1"
+        mock_get_ips.return_value = ["10.10.10.1"]
         mock_cal_delnode.return_value = True
         mock_invoke.side_effect = [(True, None, None)]
         mock_get_values.return_value = ["10.10.10.1"]
+        mock_del_by_name.return_value = True
+        mock_del_by_id.return_value = True
         mock_is_active.return_value = False
         mock_firewall_inst = mock.Mock()
         mock_firewall.return_value = mock_firewall_inst
@@ -2034,7 +2047,8 @@
         bootstrap._context = mock.Mock(cluster_node="node1", rm_list=["file1", 
"file2"])
         bootstrap.remove_node_from_cluster('node1')
 
-        mock_get_ip.assert_called_once_with('node1')
+        mock_get_nodeid.assert_called_once_with('node1')
+        mock_get_ips.assert_not_called()
         mock_status.assert_has_calls([
             mock.call("Removing node %s from CIB", "node1"),
             mock.call("Propagating configuration changes across the remaining 
nodes")
@@ -2044,10 +2058,149 @@
         
mock_cluster_shell_inst.get_stdout_or_raise_error.assert_called_once_with("corosync-cfgtool
 -R")
         mock_error.assert_not_called()
         mock_get_values.assert_called_once_with("nodelist.node.ring0_addr")
-        mock_del.assert_called_once_with("10.10.10.1")
-        mock_csync2.assert_has_calls([
-            mock.call("/etc/corosync/corosync.conf")
-            ])
+        mock_del_by_name.assert_called_once_with("node1")
+        mock_csync2.assert_called_once_with("/etc/corosync/corosync.conf")
+
+    @mock.patch('crmsh.utils.HostUserConfig')
+    @mock.patch('crmsh.sh.cluster_shell')
+    @mock.patch('crmsh.bootstrap.FirewallManager')
+    @mock.patch.object(NodeMgmt, 'call_delnode')
+    @mock.patch('crmsh.service_manager.ServiceManager.service_is_active')
+    @mock.patch('crmsh.bootstrap.rm_configuration_files')
+    @mock.patch('crmsh.corosync.configure_two_node')
+    @mock.patch('crmsh.bootstrap.adjust_properties')
+    @mock.patch('crmsh.bootstrap.sync_path')
+    @mock.patch('crmsh.corosync.del_node_by_nodeid')
+    @mock.patch('crmsh.corosync.del_node_by_name')
+    @mock.patch('crmsh.corosync.get_values')
+    @mock.patch('crmsh.utils.fatal')
+    @mock.patch('crmsh.bootstrap.invoke')
+    @mock.patch('logging.Logger.info')
+    @mock.patch('crmsh.bootstrap.stop_and_disable_services')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_ips')
+    @mock.patch('crmsh.utils.get_nodeid_from_name')
+    @mock.patch('crmsh.xmlutil.CrmMonXmlParser')
+    def test_remove_node_from_cluster_by_nodeid(self, mock_crm_mon_parser, 
mock_get_nodeid, mock_get_ips, mock_stop, mock_status,
+            mock_invoke, mock_error, mock_get_values, mock_del_by_name, 
mock_del_by_id, mock_csync2,
+            mock_adjust_priority, mock_adjust_fence_delay, mock_rm_conf_files, 
mock_is_active, mock_cal_delnode, mock_firewall, mock_cluster_shell, 
mock_host_user_config):
+        mock_crm_mon_parser_inst = mock.Mock()
+        mock_crm_mon_parser.return_value = mock_crm_mon_parser_inst
+        mock_crm_mon_parser_inst.is_node_remote.return_value = False
+        mock_get_nodeid.return_value = "1"
+        mock_get_ips.return_value = ["10.10.10.1"]
+        mock_cal_delnode.return_value = True
+        mock_invoke.side_effect = [(True, None, None)]
+        mock_get_values.return_value = ["10.10.10.1"]
+        mock_del_by_name.return_value = False
+        mock_del_by_id.return_value = True
+        mock_is_active.return_value = False
+        mock_firewall_inst = mock.Mock()
+        mock_firewall.return_value = mock_firewall_inst
+        mock_firewall_inst.remove_service = mock.Mock()
+        mock_cluster_shell_inst = mock.Mock()
+        mock_cluster_shell.return_value = mock_cluster_shell_inst
+
+        bootstrap._context = mock.Mock(cluster_node="node1", rm_list=["file1", 
"file2"])
+        bootstrap.remove_node_from_cluster('node1')
+
+        mock_get_nodeid.assert_called_once_with('node1')
+        mock_get_ips.assert_not_called()
+        mock_get_values.assert_called_once_with("nodelist.node.ring0_addr")
+        mock_del_by_name.assert_called_once_with("node1")
+        mock_del_by_id.assert_called_once_with("1")
+        mock_csync2.assert_called_once_with("/etc/corosync/corosync.conf")
+
+    @mock.patch('crmsh.bootstrap.get_cluster_node_ips')
+    @mock.patch('crmsh.corosync.del_node')
+    @mock.patch('crmsh.utils.HostUserConfig')
+    @mock.patch('crmsh.sh.cluster_shell')
+    @mock.patch('crmsh.bootstrap.FirewallManager')
+    @mock.patch.object(NodeMgmt, 'call_delnode')
+    @mock.patch('crmsh.service_manager.ServiceManager.service_is_active')
+    @mock.patch('crmsh.bootstrap.rm_configuration_files')
+    @mock.patch('crmsh.corosync.configure_two_node')
+    @mock.patch('crmsh.bootstrap.adjust_properties')
+    @mock.patch('crmsh.bootstrap.sync_path')
+    @mock.patch('crmsh.corosync.del_node_by_nodeid')
+    @mock.patch('crmsh.corosync.del_node_by_name')
+    @mock.patch('crmsh.corosync.get_values')
+    @mock.patch('crmsh.utils.fatal')
+    @mock.patch('crmsh.bootstrap.invoke')
+    @mock.patch('logging.Logger.info')
+    @mock.patch('crmsh.bootstrap.stop_and_disable_services')
+    @mock.patch('crmsh.utils.get_nodeid_from_name')
+    @mock.patch('crmsh.xmlutil.CrmMonXmlParser')
+    def test_remove_node_from_cluster_fallback_success(self, 
mock_crm_mon_parser, mock_get_nodeid, mock_stop, mock_status,
+            mock_invoke, mock_error, mock_get_values, mock_del_by_name, 
mock_del_by_id, mock_csync2,
+            mock_adjust_priority, mock_adjust_fence_delay, mock_rm_conf_files, 
mock_is_active, mock_cal_delnode, mock_firewall, mock_cluster_shell, 
mock_host_user_config,
+            mock_del_node, mock_get_ips):
+        mock_crm_mon_parser_inst = mock.Mock()
+        mock_crm_mon_parser.return_value = mock_crm_mon_parser_inst
+        mock_crm_mon_parser_inst.is_node_remote.return_value = False
+        mock_get_nodeid.return_value = "1"
+        mock_get_ips.return_value = ["10.10.10.2"]
+        mock_del_by_name.return_value = False
+        mock_del_by_id.return_value = False
+        mock_del_node.return_value = True
+        mock_is_active.return_value = False
+        mock_firewall_inst = mock.Mock()
+        mock_firewall.return_value = mock_firewall_inst
+        mock_firewall_inst.remove_service = mock.Mock()
+        mock_cluster_shell_inst = mock.Mock()
+        mock_cluster_shell.return_value = mock_cluster_shell_inst
+
+        bootstrap._context = mock.Mock(cluster_node="node1", rm_list=["file1", 
"file2"])
+        bootstrap.remove_node_from_cluster('node1')
+
+        mock_get_values.assert_called_once_with("nodelist.node.ring0_addr")
+        mock_del_by_name.assert_called_once_with("node1")
+        mock_del_by_id.assert_called_once_with("1")
+        mock_get_ips.assert_called_once_with("node1")
+        mock_del_node.assert_called_once_with(["10.10.10.2", "node1"])
+        mock_error.assert_not_called()
+
+    @mock.patch('crmsh.bootstrap.get_cluster_node_ips')
+    @mock.patch('crmsh.corosync.del_node')
+    @mock.patch('crmsh.utils.HostUserConfig')
+    @mock.patch('crmsh.sh.cluster_shell')
+    @mock.patch('crmsh.bootstrap.FirewallManager')
+    @mock.patch.object(NodeMgmt, 'call_delnode')
+    @mock.patch('crmsh.service_manager.ServiceManager.service_is_active')
+    @mock.patch('crmsh.bootstrap.rm_configuration_files')
+    @mock.patch('crmsh.corosync.configure_two_node')
+    @mock.patch('crmsh.bootstrap.adjust_properties')
+    @mock.patch('crmsh.bootstrap.sync_path')
+    @mock.patch('crmsh.corosync.del_node_by_nodeid')
+    @mock.patch('crmsh.corosync.del_node_by_name')
+    @mock.patch('crmsh.corosync.get_values')
+    @mock.patch('crmsh.utils.fatal')
+    @mock.patch('crmsh.bootstrap.invoke')
+    @mock.patch('logging.Logger.info')
+    @mock.patch('crmsh.bootstrap.stop_and_disable_services')
+    @mock.patch('crmsh.utils.get_nodeid_from_name')
+    @mock.patch('crmsh.xmlutil.CrmMonXmlParser')
+    def test_remove_node_from_cluster_del_node_failed(self, 
mock_crm_mon_parser, mock_get_nodeid, mock_stop, mock_status,
+            mock_invoke, mock_error, mock_get_values, mock_del_by_name, 
mock_del_by_id, mock_csync2,
+            mock_adjust_priority, mock_adjust_fence_delay, mock_rm_conf_files, 
mock_is_active, mock_cal_delnode, mock_firewall, mock_cluster_shell, 
mock_host_user_config,
+            mock_del_node, mock_get_ips):
+        mock_crm_mon_parser_inst = mock.Mock()
+        mock_crm_mon_parser.return_value = mock_crm_mon_parser_inst
+        mock_crm_mon_parser_inst.is_node_remote.return_value = False
+        mock_get_nodeid.return_value = None
+        mock_get_ips.return_value = []
+        mock_del_by_name.return_value = False
+        mock_del_node.return_value = False
+        mock_error.side_effect = SystemExit
+
+        bootstrap._context = mock.Mock(cluster_node="node1", rm_list=["file1", 
"file2"])
+        with self.assertRaises(SystemExit):
+            bootstrap.remove_node_from_cluster('node1')
+
+        mock_get_values.assert_called_once_with("nodelist.node.ring0_addr")
+        mock_del_by_id.assert_not_called()
+        mock_get_ips.assert_called_once_with("node1")
+        mock_del_node.assert_called_once_with(["node1"])
+        mock_error.assert_called_once_with("Failed to remove node node1 from 
corosync configuration")
 
 
 class TestFirewallManager(unittest.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-5.0.0+20260402.90d08295/test/unittests/test_corosync.py 
new/crmsh-5.0.0+20260407.ce740ef1/test/unittests/test_corosync.py
--- old/crmsh-5.0.0+20260402.90d08295/test/unittests/test_corosync.py   
2026-04-02 11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/test/unittests/test_corosync.py   
2026-04-07 07:59:59.000000000 +0200
@@ -710,5 +710,113 @@
         mock_update_link.assert_not_called()
 
 
+F2 = """
+nodelist {
+    node {
+        ring0_addr: 10.16.35.101
+        nodeid: 1
+    }
+    node {
+        ring0_addr: 10.16.35.102
+        nodeid: 2
+    }
+    node {
+        ring0_addr: 10.16.35.103
+    }
+    node {
+        ring0_addr: 10.16.35.104
+    }
+    node {
+        ring0_addr: 10.16.35.105
+    }
+}
+"""
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_by_name(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["node1", "node2"]
+
+    assert corosync.del_node_by_name("node2") is True
+    mock_inst.get_all.assert_called_once_with("nodelist.node.name")
+    mock_inst.remove.assert_called_once_with("nodelist.node", 1)
+    mock_inst.save.assert_called_once()
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_by_name_not_found(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["node1", "node2"]
+
+    assert corosync.del_node_by_name("node3") is False
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_by_nodeid(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["1", "2"]
+
+    assert corosync.del_node_by_nodeid(2) is True
+    mock_inst.get_all.assert_called_once_with("nodelist.node.nodeid")
+    mock_inst.remove.assert_called_once_with("nodelist.node", 1)
+    mock_inst.save.assert_called_once()
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_by_nodeid_not_found(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["1", "2"]
+
+    assert corosync.del_node_by_nodeid(99) is False
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_list(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["10.16.35.101", "10.16.35.102", 
"10.16.35.103"]
+
+    assert corosync.del_node(["10.16.35.200", "10.16.35.103"]) is True
+    mock_inst.get_all.assert_called_once_with("nodelist.node.ring0_addr")
+    mock_inst.remove.assert_called_once_with("nodelist.node", 2)
+    mock_inst.save.assert_called_once()
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_not_found(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["10.16.35.101", "10.16.35.102"]
+
+    assert corosync.del_node(["10.16.35.200", "10.16.35.201"]) is False
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_two_node_update(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["1", "2", "3"]
+
+    assert corosync.del_node_by_nodeid(3) is True
+    mock_inst.remove.assert_called_once_with("nodelist.node", 2)
+    mock_inst.save.assert_called_once()
+
+
[email protected]("crmsh.corosync.ConfParser")
+def test_del_node_qdevice_net(mock_parser):
+    mock_inst = mock.Mock()
+    mock_parser.return_value = mock_inst
+    mock_inst.get_all.return_value = ["1", "2", "3"]
+
+    assert corosync.del_node_by_nodeid(3) is True
+    mock_inst.remove.assert_called_once_with("nodelist.node", 2)
+    mock_inst.save.assert_called_once()
+
+
 if __name__ == '__main__':
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-5.0.0+20260402.90d08295/test/unittests/test_ui_sbd.py 
new/crmsh-5.0.0+20260407.ce740ef1/test/unittests/test_ui_sbd.py
--- old/crmsh-5.0.0+20260402.90d08295/test/unittests/test_ui_sbd.py     
2026-04-02 11:19:24.000000000 +0200
+++ new/crmsh-5.0.0+20260407.ce740ef1/test/unittests/test_ui_sbd.py     
2026-04-07 07:59:59.000000000 +0200
@@ -331,59 +331,6 @@
         res = ui_sbd.SBD._adjust_timeout_dict(timeout_dict)
         self.assertEqual(res, {"watchdog": 5, "msgwait": 10})
 
-    @mock.patch('logging.Logger.error')
-    @mock.patch('crmsh.cibquery.get_primitives_with_ra')
-    @mock.patch('crmsh.cibquery.ResourceAgent')
-    @mock.patch('crmsh.xmlutil.text2elem')
-    def test_set_crashdump_option_exception(self, mock_text2elem, 
mock_ResourceAgent, mock_get_primitives_with_ra, mock_logger_error):
-        self.sbd_instance_diskbased.cluster_shell.get_stdout_or_raise_error = 
mock.Mock(return_value="<dummy></dummy>")
-        mock_text2elem.return_value = "dummy"
-        mock_ra_instance = mock.Mock()
-        mock_ResourceAgent.return_value = mock_ra_instance
-        mock_get_primitives_with_ra.return_value = []
-
-        with self.assertRaises(utils.TerminateSubCommand):
-            self.sbd_instance_diskbased._set_crashdump_option()
-
-        
self.sbd_instance_diskbased.cluster_shell.get_stdout_or_raise_error.assert_called_once_with("crm
 configure show xml")
-        mock_logger_error.assert_called_once_with("No fence_sbd resource 
found")
-
-    @mock.patch('logging.Logger.info')
-    @mock.patch('crmsh.utils.is_boolean_false')
-    @mock.patch('crmsh.cibquery.get_parameter_value')
-    @mock.patch('crmsh.cibquery.get_primitives_with_ra')
-    @mock.patch('crmsh.cibquery.ResourceAgent')
-    @mock.patch('crmsh.xmlutil.text2elem')
-    def test_set_crashdump_option(self, mock_text2elem, mock_ResourceAgent, 
mock_get_primitives_with_ra, mock_get_parameter_value, mock_is_boolean_false, 
mock_logger_info):
-        self.sbd_instance_diskbased.cluster_shell.get_stdout_or_raise_error = 
mock.Mock(side_effect=["<dummy></dummy>", ""])
-        mock_text2elem.return_value = "dummy"
-        mock_ra_instance = mock.Mock()
-        mock_ResourceAgent.return_value = mock_ra_instance
-        mock_get_primitives_with_ra.return_value = ["fence_sbd"]
-        mock_get_parameter_value.return_value = None
-        mock_is_boolean_false.return_value = True
-
-        self.sbd_instance_diskbased._set_crashdump_option()
-        mock_logger_info.assert_called_once_with("Set crashdump option for 
fence_sbd resource")
-
-    @mock.patch('logging.Logger.info')
-    @mock.patch('crmsh.utils.is_boolean_false')
-    @mock.patch('crmsh.cibquery.get_parameter_value')
-    @mock.patch('crmsh.cibquery.get_primitives_with_ra')
-    @mock.patch('crmsh.cibquery.ResourceAgent')
-    @mock.patch('crmsh.xmlutil.text2elem')
-    def test_set_crashdump_option_delete(self, mock_text2elem, 
mock_ResourceAgent, mock_get_primitives_with_ra, mock_get_parameter_value, 
mock_is_boolean_false, mock_logger_info):
-        self.sbd_instance_diskbased.cluster_shell.get_stdout_or_raise_error = 
mock.Mock(side_effect=["<dummy></dummy>", ""])
-        mock_text2elem.return_value = "dummy"
-        mock_ra_instance = mock.Mock()
-        mock_ResourceAgent.return_value = mock_ra_instance
-        mock_get_primitives_with_ra.return_value = ["fence_sbd"]
-        mock_get_parameter_value.return_value = None
-        mock_is_boolean_false.return_value = False
-
-        self.sbd_instance_diskbased._set_crashdump_option(delete=True)
-        mock_logger_info.assert_called_once_with("Delete crashdump option for 
fence_sbd resource")
-
     @mock.patch('logging.Logger.warning')
     def test_check_kdump_service(self, mock_logger_warning):
         self.sbd_instance_diskbased.service_manager.service_is_active = 
mock.Mock(side_effect=[True, False])
@@ -420,18 +367,19 @@
         parameter_dict = {"watchdog": 12, "watchdog-device": 
"/dev/watchdog100", "crashdump-watchdog": 12}
         self.sbd_instance_diskbased._should_configure_crashdump = 
mock.Mock(return_value=True)
         self.sbd_instance_diskbased._check_kdump_service = mock.Mock()
-        self.sbd_instance_diskbased._set_crashdump_option = mock.Mock()
-        self.sbd_instance_diskbased._set_crashdump_in_sysconfig = 
mock.Mock(return_value={"SBD_TIMEOUT_ACTION": "flush,crashdump", "SBD_OPTS": 
"-C 12"})
+        self.sbd_instance_diskbased._set_sbd_opts = mock.Mock()
+        self.sbd_instance_diskbased._set_sbd_opts = 
mock.Mock(return_value={"SBD_TIMEOUT_ACTION": "flush,crashdump", "SBD_OPTS": 
"-C 12"})
         mock_SBDManager.return_value.init_and_deploy_sbd = mock.Mock()
         self.sbd_instance_diskbased._configure_diskbase(parameter_dict)
         mock_SBDManager.assert_called_once_with(
             
device_list_to_init=self.sbd_instance_diskbased.device_list_from_config,
             timeout_dict={'watchdog': 12, 'allocate': 5, 'loop': 5, 'msgwait': 
36},
-            update_dict={'SBD_TIMEOUT_ACTION': 'flush,crashdump', 'SBD_OPTS': 
'-C 12', 'SBD_WATCHDOG_DEV': '/dev/watchdog100'}
+            update_dict={'SBD_TIMEOUT_ACTION': 'flush,crashdump', 'SBD_OPTS': 
'-C 12', 'SBD_WATCHDOG_DEV': '/dev/watchdog100'},
+            crashdump_mode="set"
         )
         mock_SBDManager.return_value.init_and_deploy_sbd.assert_called_once()
         self.sbd_instance_diskbased._check_kdump_service.assert_called_once()
-        self.sbd_instance_diskbased._set_crashdump_option.assert_called_once()
+        self.sbd_instance_diskbased._set_sbd_opts.assert_called_once()
 
     @mock.patch('logging.Logger.info')
     @mock.patch('crmsh.sbd.SBDManager')
@@ -448,12 +396,13 @@
         self.sbd_instance_diskless._should_configure_crashdump = 
mock.Mock(return_value=True)
         self.sbd_instance_diskless._check_kdump_service = mock.Mock()
         self.sbd_instance_diskless._check_kdump_service = mock.Mock()
-        self.sbd_instance_diskless._set_crashdump_in_sysconfig = 
mock.Mock(return_value={"SBD_TIMEOUT_ACTION": "flush,crashdump", "SBD_OPTS": 
"-C 12 -Z"})
+        self.sbd_instance_diskless._set_sbd_opts = 
mock.Mock(return_value={"SBD_TIMEOUT_ACTION": "flush,crashdump", "SBD_OPTS": 
"-C 12 -Z"})
         mock_SBDManager.return_value.init_and_deploy_sbd = mock.Mock()
         self.sbd_instance_diskless._configure_diskless(parameter_dict)
         mock_SBDManager.assert_called_once_with(
             update_dict={'SBD_WATCHDOG_TIMEOUT': '12', 'SBD_WATCHDOG_DEV': 
'/dev/watchdog100', 'SBD_TIMEOUT_ACTION': 'flush,crashdump', 'SBD_OPTS': '-C 12 
-Z'},
-            diskless_sbd=True
+            diskless_sbd=True,
+            crashdump_mode="set"
         )
         mock_SBDManager.return_value.init_and_deploy_sbd.assert_called_once()
         self.sbd_instance_diskless._check_kdump_service.assert_called_once()
@@ -603,26 +552,16 @@
         self.assertFalse(res)
         mock_purge_sbd_from_cluster.assert_not_called()
 
-    @mock.patch('crmsh.utils.able_to_restart_cluster')
-    @mock.patch('crmsh.utils.leverage_maintenance_mode')
-    @mock.patch('crmsh.bootstrap.restart_cluster')
     @mock.patch('crmsh.utils.check_all_nodes_reachable')
-    @mock.patch('crmsh.sbd.purge_sbd_from_cluster')
-    def test_do_purge(self, mock_purge_sbd_from_cluster, 
mock_check_all_nodes_reachable, mock_restart_cluster, 
mock_leverage_maintenance_mode, mock_able_to_restart_cluster):
-        enable_value = True
-        cm = mock.Mock()
-        cm.__enter__ = mock.Mock(return_value=enable_value)
-        cm.__exit__ = mock.Mock(return_value=True)
-        mock_leverage_maintenance_mode.return_value = cm
-        mock_able_to_restart_cluster.return_value = True
+    def test_do_purge(self, mock_check_all_nodes_reachable):
         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)
-        mock_purge_sbd_from_cluster.assert_called_once()
+        self.sbd_instance_diskbased._purge_sbd.assert_called_once_with()
         self.sbd_instance_diskbased._load_attributes.assert_called_once()
         
self.sbd_instance_diskbased._service_is_active.assert_called_once_with(constants.SBD_SERVICE)
-        mock_purge_sbd_from_cluster.assert_called_once_with()
         mock_check_all_nodes_reachable.assert_called_once_with("purging SBD")
 
     @mock.patch('crmsh.xmlutil.CrmMonXmlParser')

Reply via email to