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