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-21 12:44:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Tue Apr 21 12:44:27 2026 rev:405 rq:1348352 version:5.0.0+20260420.d13e03ac Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2026-04-12 17:53:08.025173387 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.11940/crmsh.changes 2026-04-21 12:47:12.588370388 +0200 @@ -1,0 +2,36 @@ +Mon Apr 20 06:53:40 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260420.d13e03ac: + * Fix: ui_cluster: rename: Sync corosync.conf when at least one peer exists + * Dev: utils: Improve check_port_open to concurrently try all addresses (bsc#1262094) + +------------------------------------------------------------------- +Fri Apr 17 09:39:19 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260417.27bde1ec: + * Dev: corosync: Check qdevice vote while checking corosync status + * Dev: ui_cluster: Sleep 1s before checking qdevice vote + +------------------------------------------------------------------- +Thu Apr 16 10:09:24 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260416.1ff83b5b: + * Fix: watchdog: use cluster_shell() for remote watchdog query (#2078) + +------------------------------------------------------------------- +Wed Apr 15 06:00:58 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260415.cbb6a8b2: + * Dev: unittests: Adjust unit test for previous commit + * Fix: qdevice: Change suggestion message when detecting cluster name conflict (bsc#1261884) + * Dev: ui_cluster: Improve 'rename' subcommand + +------------------------------------------------------------------- +Wed Apr 15 01:39:31 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260415.89daedf5: + * Dev: unittests: Adjust unit test for previous commit + * Dev: behave: Adjust functional test for previous commit + * Dev: Use xmlutil.CrmMonXmlParser.get_node_list instead of parsing output of 'crm_node -l' + +------------------------------------------------------------------- Old: ---- crmsh-5.0.0+20260412.db68d024.tar.bz2 New: ---- crmsh-5.0.0+20260420.d13e03ac.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.sKfQeX/_old 2026-04-21 12:47:14.696457833 +0200 +++ /var/tmp/diff_new_pack.sKfQeX/_new 2026-04-21 12:47:14.696457833 +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+20260412.db68d024 +Version: 5.0.0+20260420.d13e03ac Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.sKfQeX/_old 2026-04-21 12:47:14.756460322 +0200 +++ /var/tmp/diff_new_pack.sKfQeX/_new 2026-04-21 12:47:14.760460488 +0200 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">cf3a81412188c120b0b69ee59c2066143aec463f</param> + <param name="changesrevision">d13e03ac0144d910af7fdfcf06faffe71cfdffe8</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-5.0.0+20260412.db68d024.tar.bz2 -> crmsh-5.0.0+20260420.d13e03ac.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/bootstrap.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/bootstrap.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/bootstrap.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/bootstrap.py 2026-04-20 08:35:08.000000000 +0200 @@ -1487,8 +1487,8 @@ init_cluster_local() - _rc, nnodes = ShellUtils().get_stdout("crm_node -l") - nnodes = len(nnodes.splitlines()) + node_list = xmlutil.CrmMonXmlParser().get_node_list(online=True, node_type="member") + nnodes = len(node_list) if nnodes < 1: utils.fatal("No nodes found in cluster") if nnodes > 1: @@ -1837,7 +1837,8 @@ stderr=subprocess.DEVNULL, ) - hosts = utils.fetch_cluster_node_list_from_node(cluster_node) + [utils.this_node()] + node_list = xmlutil.CrmMonXmlParser(cluster_node).get_node_list(online=True, node_type="member") + hosts = node_list + [utils.this_node()] known_hosts_new: set[str] = set() cat_cmd = "[ -e ~/.ssh/known_hosts ] && cat ~/.ssh/known_hosts || true" for host in hosts: @@ -2871,7 +2872,7 @@ """ node_list = [] if peer_node: - node_list = utils.fetch_cluster_node_list_from_node(peer_node) + node_list = xmlutil.CrmMonXmlParser(peer_node).get_node_list(online=True, node_type="member") utils.cluster_copy_path(path, nodes=node_list) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/corosync.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/corosync.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/corosync.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/corosync.py 2026-04-20 08:35:08.000000000 +0200 @@ -16,6 +16,7 @@ from . import log from . import corosync_config_format from . import xmlutil +from . import qdevice from .sh import ShellUtils @@ -114,6 +115,7 @@ out = sh.cluster_shell().get_stdout_or_raise_error("crm_node -l") print(f"{out}\n") print(status_func_dict[status_type]()) + qdevice.QDevice.check_qdevice_vote() else: raise ValueError("Wrong type \"{}\" to query status".format(status_type)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/crash_test/task.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/crash_test/task.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/crash_test/task.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/crash_test/task.py 2026-04-20 08:35:08.000000000 +0200 @@ -199,8 +199,8 @@ if rc != 0 and err: raise TaskError(err) - if not utils.check_node_status(self.target_node, 'member'): - raise TaskError("Node \"{}\" not in cluster!".format(self.target_node)) + if self.target_node not in utils.online_nodes(): + raise TaskError(f"Node \"{self.target_node}\" is not in cluster!") def run(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/crash_test/utils.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/crash_test/utils.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/crash_test/utils.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/crash_test/utils.py 2026-04-20 08:35:08.000000000 +0200 @@ -135,20 +135,6 @@ return config.FENCE_TIMEOUT -def check_node_status(node, state): - """ - Check whether the node has expected state - """ - rc, stdout, stderr = ShellUtils().get_stdout_stderr('crm_node -l') - if rc != 0: - msg_error(stderr) - return False - pattern = re.compile(r'^.* {} {}'.format(node, state), re.MULTILINE) - if not pattern.search(stdout): - return False - return True - - def online_nodes(): """ Get online node list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/lock.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/lock.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/lock.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/lock.py 2026-04-20 08:35:08.000000000 +0200 @@ -9,6 +9,7 @@ from . import sh from . import config from . import log +from . import xmlutil from .sh import ShellUtils @@ -134,15 +135,6 @@ raise ValueError("Minimum value of core.lock_timeout should be {}".format(self.MIN_LOCK_TIMEOUT)) return value - def _get_online_nodelist(self): - """ - Get the online node list from remote node - """ - rc, out, err = self._run("crm_node -l") - if rc != 0 and err: - raise ValueError(err) - return re.findall('[0-9]+ (.*) member', out) - def _lock_or_wait(self): """ Try to claim lock on remote node, wait if failed to claim @@ -164,7 +156,7 @@ if self.for_join: # Might lose claiming lock again, start to wait again - online_list = self._get_online_nodelist() + online_list = xmlutil.CrmMonXmlParser(self.remote_node).get_node_list(online=True, node_type="member") if pre_online_list and pre_online_list != online_list: timeout = current_time + self.lock_timeout pre_online_list = online_list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/qdevice.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/qdevice.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/qdevice.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/qdevice.py 2026-04-20 08:35:08.000000000 +0200 @@ -271,7 +271,11 @@ cmd = f"corosync-qnetd-tool -l -c {self.cluster_name}" if shell.get_stdout_or_raise_error(cmd, self.qnetd_addr): exception_msg = f"This cluster's name \"{self.cluster_name}\" already exists on qnetd server!" - suggestion_msg = "Please consider to use the different cluster-name property" + if self.is_stage: + suggestion_msg = "Please consider to use `crm cluster rename` to change a different cluster name." + else: + suggestion_msg = "Please consider to use -n option to specify a different cluster name." + suggestion_msg += "\nOr, run `crm cluster remove --qdevice` on the existing cluster beforehand." else: exception_msg = f"Package \"corosync-qnetd\" not installed on {self.qnetd_addr}!" suggestion_msg = f"Please install \"corosync-qnetd\" on {self.qnetd_addr}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/ui_cluster.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_cluster.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/ui_cluster.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_cluster.py 2026-04-20 08:35:08.000000000 +0200 @@ -11,6 +11,7 @@ import tarfile import subprocess import glob +import time from argparse import ArgumentParser, RawDescriptionHelpFormatter import crmsh.parallax @@ -200,6 +201,7 @@ logger.info("The cluster stack started on %s", node) if start_qdevice and success_list: + time.sleep(1) qdevice.QDevice.check_qdevice_vote() return success_flag @@ -611,23 +613,19 @@ logger.info(suggestion) return - cib_factory = cibconfig.cib_factory_instance() - old_name = cib_factory.get_property('cluster-name') + old_name = corosync.get_value('totem.cluster_name') if old_name and new_name == old_name: - context.fatal_error("Expected a different name") - - # Update config file with the new name on all nodes - nodes = utils.list_cluster_nodes() + context.fatal_error("Expected a different cluster name") + logger.info("Setting totem.cluster_name to %s in corosync.conf", new_name) corosync.set_value('totem.cluster_name', new_name) - if len(nodes) > 1: - nodes.remove(utils.this_node()) - context.info("Copy cluster config file to \"{}\"".format(' '.join(nodes))) + + nodes = utils.list_cluster_nodes_except_me() + if len(nodes) >= 1: + logger.info("Syncing corosync.conf to other nodes in the cluster") corosync.push_configuration(nodes) - # Change the cluster-name property in the CIB - cib_factory.create_object("property", "cluster-name={}".format(new_name)) - if not cib_factory.commit(): - context.fatal_error("Change property cluster-name failed!") + logger.info("Setting cluster-name property to %s in CIB", new_name) + utils.set_property("cluster-name", new_name) if xmlutil.CrmMonXmlParser().is_non_stonith_resource_running(): context.info("To apply the change, restart the cluster service at convenient time") @@ -915,7 +913,6 @@ @command.completers_repeating(compl.choice(['10', '60', '600'])) def do_wait_for_startup(self, context, timeout='10'): "usage: wait_for_startup [<timeout>]" - import time t0 = time.time() timeout = float(timeout) cmd = 'crm_mon -bD1 >/dev/null 2>&1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/utils.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/utils.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/utils.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/utils.py 2026-04-20 08:35:08.000000000 +0200 @@ -25,6 +25,7 @@ import lzma import json import socket +import selectors from pathlib import Path from collections import defaultdict from contextlib import contextmanager, closing @@ -2087,22 +2088,64 @@ return rc != 0 -def check_port_open(host, port, timeout=3) -> bool: +def check_port_open(host, port, timeout=1.0, retry=3) -> bool: """ Check whether the port is open on the host Use getaddrinfo to support both IPv4 and IPv6 """ try: - addrinfo = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM) - af, socktype, proto, canonname, sa = addrinfo[0] - with closing(socket.socket(af, socktype, proto)) as sock: - sock.settimeout(timeout) - if sock.connect_ex(sa) == 0: - return True - return False + addrinfos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM) except socket.error: return False + for i in range(retry): + start_time = time.time() + sel = selectors.DefaultSelector() + sockets = [] + + for addrinfo in addrinfos: + af, socktype, proto, canonname, sa = addrinfo + sock = None + try: + sock = socket.socket(af, socktype, proto) + sock.setblocking(False) + if hasattr(socket, 'TCP_SYNCNT'): + try: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_SYNCNT, 1) + except OSError: + pass + + err = sock.connect_ex(sa) + if err == 0: + sel.close() + for s in sockets: + s.close() + sock.close() + return True + + sel.register(sock, selectors.EVENT_WRITE) + sockets.append(sock) + except socket.error: + if sock: + sock.close() + + try: + events = sel.select(timeout) + for key, mask in events: + if key.fileobj.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) == 0: + return True + finally: + sel.close() + for sock in sockets: + sock.close() + + if i < retry - 1: + elapsed = time.time() - start_time + if elapsed < timeout: + time.sleep(timeout - elapsed) + + return False + def valid_port(port): return int(port) >= 1024 and int(port) <= 65535 @@ -2149,16 +2192,10 @@ def get_nodeid_from_name(name): - if xmlutil.CrmMonXmlParser().is_node_remote(name): + crm_mon_xml_parser = xmlutil.CrmMonXmlParser() + if crm_mon_xml_parser.is_node_remote(name): return name - rc, out = ShellUtils().get_stdout('crm_node -l') - if rc != 0: - return None - res = re.search(r'^([0-9]+) {} '.format(name), out, re.M) - if res: - return res.group(1) - else: - return None + return crm_mon_xml_parser.get_node_id_from_name(name) def check_empty_option_value(options): @@ -3091,26 +3128,6 @@ return asyncio.run(asyncio.wait_for(wrapper(), timeout_sec)) -def fetch_cluster_node_list_from_node(init_node): - """ - Fetch cluster member list from one known cluster node - """ - cluster_nodes_list = [] - out = sh.cluster_shell().get_stdout_or_raise_error("crm_node -l", init_node) - for line in out.splitlines(): - # Parse line in format: <id> <nodename> <state>, and collect the nodename. - tokens = line.split() - if len(tokens) == 0: - pass # Skip any spurious empty line. - elif len(tokens) < 3: - logger.warning("The node '%s' has no known name and/or state information", tokens[0]) - elif tokens[2] != "member": - logger.warning("The node '%s'(state '%s') is not a current member", tokens[1], tokens[2]) - else: - cluster_nodes_list.append(tokens[1]) - return cluster_nodes_list - - def has_sudo_access(): """ Check if current user has sudo access diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/watchdog.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/watchdog.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/watchdog.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/watchdog.py 2026-04-20 08:35:08.000000000 +0200 @@ -1,7 +1,7 @@ import re from . import utils -from .constants import SSH_OPTION from .sh import ShellUtils +from . import sh from . import sbd @@ -90,9 +90,7 @@ """ Given watchdog device name, get driver name on remote node """ - # FIXME - cmd = "ssh {} {}@{} {}".format(SSH_OPTION, self._remote_user, self._peer_host, self.QUERY_CMD) - rc, out, err = ShellUtils().get_stdout_stderr(cmd) + rc, out, err = sh.cluster_shell().get_rc_stdout_stderr_without_input(self._peer_host, self.QUERY_CMD) if rc == 0 and out: # output format might like: # [1] /dev/watchdog\nIdentity: Software Watchdog\nDriver: softdog\n diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/crmsh/xmlutil.py new/crmsh-5.0.0+20260420.d13e03ac/crmsh/xmlutil.py --- old/crmsh-5.0.0+20260412.db68d024/crmsh/xmlutil.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/crmsh/xmlutil.py 2026-04-20 08:35:08.000000000 +0200 @@ -1681,4 +1681,9 @@ 'completed': last_event.get('completed', '') } return fence_event_info + + def get_node_id_from_name(self, name) -> str|None: + xpath = f'//node[@name="{name}"]' + res = self.xml_elem.xpath(xpath) + return res[0].get('id') if res else None # vim:ts=4:sw=4:et: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/features/qdevice_setup_remove.feature new/crmsh-5.0.0+20260420.d13e03ac/test/features/qdevice_setup_remove.feature --- old/crmsh-5.0.0+20260412.db68d024/test/features/qdevice_setup_remove.feature 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/features/qdevice_setup_remove.feature 2026-04-20 08:35:08.000000000 +0200 @@ -93,6 +93,7 @@ When Run "crm cluster restart" on "hanode1" Then Cluster service is "started" on "hanode1" And Service "corosync-qdevice" is "started" on "hanode1" + When Wait "10" seconds When Run "crm cluster join -c hanode1 -y" on "hanode2" Then Cluster service is "started" on "hanode2" And Service "corosync-qdevice" is "started" on "hanode2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_bootstrap.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_bootstrap.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_bootstrap.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_bootstrap.py 2026-04-20 08:35:08.000000000 +0200 @@ -1550,11 +1550,14 @@ mock_adj_fence.assert_called_once_with(True) @mock.patch('crmsh.utils.cluster_copy_path') - @mock.patch('crmsh.utils.fetch_cluster_node_list_from_node') - def test_sync_path_peer(self, mock_fetch_nodes, mock_cluster_copy): - mock_fetch_nodes.return_value = ["node1", "node2"] + @mock.patch('crmsh.xmlutil.CrmMonXmlParser') + def test_sync_path_peer(self, mock_parser, mock_cluster_copy): + mock_parser_inst = mock.Mock() + mock_parser.return_value = mock_parser_inst + mock_parser_inst.get_node_list.return_value = ["node1", "node2"] bootstrap.sync_path("/file1", peer_node="node3") - mock_fetch_nodes.assert_called_once_with("node3") + mock_parser.assert_called_once_with("node3") + mock_parser_inst.get_node_list.assert_called_once_with(online=True, node_type="member") mock_cluster_copy.assert_called_once_with("/file1", nodes=["node1", "node2"]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_crashtest_task.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_crashtest_task.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_crashtest_task.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_crashtest_task.py 2026-04-20 08:35:08.000000000 +0200 @@ -490,21 +490,20 @@ mock_run.assert_called_once_with("which crm_node") mock_pre_check.assert_called_once_with() - @mock.patch('crmsh.crash_test.utils.check_node_status') + @mock.patch('crmsh.crash_test.utils.online_nodes') @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') @mock.patch('crmsh.crash_test.task.Task.task_pre_check') - def test_pre_check_error(self, mock_pre_check, mock_run, mock_node_status): + def test_pre_check_error(self, mock_pre_check, mock_run, mock_online_nodes): mock_run.side_effect = [(0, None, None), (0, None, None), (0, None, None)] - mock_node_status.return_value = False + mock_online_nodes.return_value = [] with self.assertRaises(task.TaskError) as err: self.task_fence_inst.pre_check() - self.assertEqual("Node \"node1\" not in cluster!", str(err.exception)) + self.assertEqual("Node \"node1\" is not in cluster!", str(err.exception)) mock_run.assert_has_calls([ mock.call("which crm_node"), mock.call("which stonith_admin"), mock.call("which crm_attribute") ]) - mock_node_status.assert_called_once_with("node1", "member") @mock.patch('crmsh.crash_test.task.Task.fence_action_monitor') @mock.patch('threading.Thread') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_crashtest_utils.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_crashtest_utils.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_crashtest_utils.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_crashtest_utils.py 2026-04-20 08:35:08.000000000 +0200 @@ -322,35 +322,6 @@ mock.call(b'/usr/sbin/sbd\x00') ]) - @mock.patch('crmsh.crash_test.utils.msg_error') - @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') - def test_check_node_status_error_cmd(self, mock_run, mock_error): - mock_run.return_value = (1, None, "error") - res = utils.check_node_status("node1", "member") - self.assertEqual(res, False) - mock_run.assert_called_once_with("crm_node -l") - mock_error.assert_called_once_with("error") - - @mock.patch('crmsh.crash_test.utils.msg_error') - @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') - def test_check_node_status(self, mock_run, mock_error): - output = """ -1084783297 15sp2-1 member -1084783193 15sp2-2 lost - """ - mock_run.return_value = (0, output, None) - - res = utils.check_node_status("15sp2-2", "member") - self.assertEqual(res, False) - res = utils.check_node_status("15sp2-1", "member") - self.assertEqual(res, True) - - mock_run.assert_has_calls([ - mock.call("crm_node -l"), - mock.call("crm_node -l") - ]) - mock_error.assert_not_called() - @mock.patch('crmsh.xmlutil.CrmMonXmlParser') def test_online_nodes(self, mock_crmmon_parser): mock_inst = mock.Mock() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_lock.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_lock.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_lock.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_lock.py 2026-04-20 08:35:08.000000000 +0200 @@ -163,26 +163,6 @@ config.core.lock_timeout = "130" self.assertEqual(self.lock_inst.lock_timeout, 130) - @mock.patch('crmsh.lock.RemoteLock._run') - def test_get_online_nodelist_error(self, mock_run): - mock_run.return_value = (1, None, "error data") - with self.assertRaises(ValueError) as err: - self.lock_inst._get_online_nodelist() - self.assertEqual("error data", str(err.exception)) - mock_run.assert_called_once_with("crm_node -l") - - @mock.patch('crmsh.lock.RemoteLock._run') - def test_get_online_nodelist(self, mock_run): - output = """ - 1084783297 15sp2-1 member - 1084783193 15sp2-2 lost - 1084783331 15sp2-3 member - """ - mock_run.return_value = (0, output, None) - res = self.lock_inst._get_online_nodelist() - self.assertEqual(res, ["15sp2-1", "15sp2-3"]) - mock_run.assert_called_once_with("crm_node -l") - @mock.patch('crmsh.lock.Lock._create_lock_dir') @mock.patch('crmsh.lock.RemoteLock.lock_timeout', new_callable=mock.PropertyMock) @mock.patch('time.time') @@ -198,16 +178,18 @@ @mock.patch('time.sleep') @mock.patch('logging.Logger.warning') - @mock.patch('crmsh.lock.RemoteLock._get_online_nodelist') + @mock.patch('crmsh.xmlutil.CrmMonXmlParser') @mock.patch('crmsh.lock.Lock._create_lock_dir') @mock.patch('crmsh.lock.RemoteLock.lock_timeout', new_callable=mock.PropertyMock) @mock.patch('time.time') def test_lock_or_wait_timed_out(self, mock_time, mock_time_out, mock_create, - mock_get_nodelist, mock_warn, mock_sleep): + mock_parser, mock_warn, mock_sleep): mock_time.side_effect = [10000, 10121] mock_time_out.return_value = 120 mock_create.return_value = False - mock_get_nodelist.return_value = ["node2"] + mock_parser_inst = mock.Mock() + mock_parser.return_value = mock_parser_inst + mock_parser_inst.get_nodelist.return_value = ["node2"] with self.assertRaises(lock.ClaimLockError) as err: self.lock_inst._lock_or_wait() @@ -216,29 +198,31 @@ mock_time.assert_has_calls([ mock.call(), mock.call()]) mock_time_out.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_create.assert_called_once_with() - mock_get_nodelist.assert_called_once_with() mock_warn.assert_called_once_with('Might have unfinished process on other nodes, wait %ss...', 120) mock_sleep.assert_called_once_with(10) @mock.patch('time.sleep') @mock.patch('logging.Logger.warning') - @mock.patch('crmsh.lock.RemoteLock._get_online_nodelist') + @mock.patch('crmsh.xmlutil.CrmMonXmlParser') @mock.patch('crmsh.lock.Lock._create_lock_dir') @mock.patch('crmsh.lock.RemoteLock.lock_timeout', new_callable=mock.PropertyMock) @mock.patch('time.time') def test_lock_or_wait_again(self, mock_time, mock_time_out, mock_create, - mock_get_nodelist, mock_warn, mock_sleep): + mock_parser, mock_warn, mock_sleep): mock_time.side_effect = [10000, 10010, 10020] mock_time_out.side_effect = [120, 120, 120] mock_create.side_effect = [False, False, True] - mock_get_nodelist.side_effect = [["node1"], ["node1", "node2"]] + mock_parser_inst1 = mock.Mock() + mock_parser_inst2 = mock.Mock() + mock_parser.side_effect = [mock_parser_inst1, mock_parser_inst2] + mock_parser_inst1.get_nodelist.return_value = ["node1"] + mock_parser_inst2.get_nodelist.return_value = ["node1", "node2"] self.lock_inst._lock_or_wait() mock_time.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_time_out.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_create.assert_has_calls([mock.call(), mock.call(), mock.call()]) - mock_get_nodelist.assert_has_calls([mock.call(), mock.call()]) mock_warn.assert_called_once_with('Might have unfinished process on other nodes, wait %ss...', 120) mock_sleep.assert_has_calls([mock.call(10), mock.call(10)]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_qdevice.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_qdevice.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_qdevice.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_qdevice.py 2026-04-20 08:35:08.000000000 +0200 @@ -295,7 +295,7 @@ mock_cluster_shell.return_value = mock_cluster_inst mock_cluster_inst.get_stdout_or_raise_error.return_value = "data" mock_installed.return_value = True - excepted_err_string = "This cluster's name \"cluster1\" already exists on qnetd server!\nPlease consider to use the different cluster-name property" + excepted_err_string = "This cluster's name \"cluster1\" already exists on qnetd server!\nPlease consider to use `crm cluster rename` to change a different cluster name.\nOr, run `crm cluster remove --qdevice` on the existing cluster beforehand." with self.assertRaises(ValueError) as err: self.qdevice_with_stage_cluster_name.validate_and_start_qnetd() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_utils.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_utils.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_utils.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_utils.py 2026-04-20 08:35:08.000000000 +0200 @@ -26,53 +26,26 @@ mock_run.assert_called_once_with("rpm -q --quiet crmsh") [email protected]('re.search') [email protected]('crmsh.sh.ShellUtils.get_stdout') @mock.patch('crmsh.xmlutil.CrmMonXmlParser') -def test_get_nodeid_from_name_run_None1(mock_parser, mock_get_stdout, mock_re_search): +def test_get_nodeid_from_name_remote(mock_parser): mock_parser_inst = mock.Mock() mock_parser.return_value = mock_parser_inst - mock_parser_inst.is_node_remote.return_value = False - mock_get_stdout.return_value = (1, None) - mock_re_search_inst = mock.Mock() - mock_re_search.return_value = mock_re_search_inst - res = utils.get_nodeid_from_name("node1") - assert res is None - mock_get_stdout.assert_called_once_with('crm_node -l') - mock_re_search.assert_not_called() - - [email protected]('re.search') [email protected]('crmsh.sh.ShellUtils.get_stdout') [email protected]('crmsh.xmlutil.CrmMonXmlParser') -def test_get_nodeid_from_name_run_None2(mock_parser, mock_get_stdout, mock_re_search): - mock_parser_inst = mock.Mock() - mock_parser.return_value = mock_parser_inst - mock_parser_inst.is_node_remote.return_value = False - mock_get_stdout.return_value = (0, "172167901 node1 member\n172168231 node2 member") - mock_re_search.return_value = None - res = utils.get_nodeid_from_name("node111") - assert res is None - mock_get_stdout.assert_called_once_with('crm_node -l') - mock_re_search.assert_called_once_with(r'^([0-9]+) node111 ', mock_get_stdout.return_value[1], re.M) + mock_parser_inst.is_node_remote.return_value = True + assert utils.get_nodeid_from_name("node1") == "node1" + mock_parser.assert_called_once_with() + mock_parser_inst.is_node_remote.assert_called_once_with("node1") [email protected]('re.search') [email protected]('crmsh.sh.ShellUtils.get_stdout') @mock.patch('crmsh.xmlutil.CrmMonXmlParser') -def test_get_nodeid_from_name(mock_parser, mock_get_stdout, mock_re_search): +def test_get_nodeid_from_name(mock_parser): mock_parser_inst = mock.Mock() mock_parser.return_value = mock_parser_inst mock_parser_inst.is_node_remote.return_value = False - mock_get_stdout.return_value = (0, "172167901 node1 member\n172168231 node2 member") - mock_re_search_inst = mock.Mock() - mock_re_search.return_value = mock_re_search_inst - mock_re_search_inst.group.return_value = '172168231' - res = utils.get_nodeid_from_name("node2") - assert res == '172168231' - mock_get_stdout.assert_called_once_with('crm_node -l') - mock_re_search.assert_called_once_with(r'^([0-9]+) node2 ', mock_get_stdout.return_value[1], re.M) - mock_re_search_inst.group.assert_called_once_with(1) + mock_parser_inst.get_node_id_from_name.return_value = "1" + assert utils.get_nodeid_from_name("node1") == "1" + mock_parser.assert_called_once_with() + mock_parser_inst.is_node_remote.assert_called_once_with("node1") + mock_parser_inst.get_node_id_from_name.assert_called_once_with("node1") @mock.patch('crmsh.sh.LocalShell.get_rc_and_error') @@ -277,35 +250,43 @@ @mock.patch("socket.getaddrinfo") @mock.patch("socket.socket") [email protected]("crmsh.utils.closing") -def test_check_port_open_false(mock_closing, mock_socket, mock_getaddrinfo): [email protected]("selectors.DefaultSelector") [email protected]("time.sleep") +def test_check_port_open_false(mock_sleep, mock_selector_cls, mock_socket, mock_getaddrinfo): sock_inst = mock.Mock() mock_socket.return_value = sock_inst - mock_closing.return_value.__enter__.return_value = sock_inst sock_inst.connect_ex.return_value = 1 mock_getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("127.0.0.1", 22))] + mock_selector = mock.Mock() + mock_selector_cls.return_value = mock_selector + mock_selector.select.return_value = [] + assert utils.check_port_open("localhost", 22) is False - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM, 6) - mock_closing.assert_called_once_with(sock_inst) - sock_inst.connect_ex.assert_called_once_with(("127.0.0.1", 22)) + assert mock_socket.call_count == 3 + assert sock_inst.connect_ex.call_count == 3 + assert mock_selector_cls.call_count == 3 + assert mock_selector.select.call_count == 3 + assert mock_sleep.call_count == 2 @mock.patch("socket.getaddrinfo") @mock.patch("socket.socket") [email protected]("crmsh.utils.closing") -def test_check_port_open_true(mock_closing, mock_socket, mock_getaddrinfo): [email protected]("selectors.DefaultSelector") +def test_check_port_open_true(mock_selector_cls, mock_socket, mock_getaddrinfo): sock_inst = mock.Mock() mock_socket.return_value = sock_inst - mock_closing.return_value.__enter__.return_value = sock_inst sock_inst.connect_ex.return_value = 0 mock_getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("127.0.0.1", 22))] + mock_selector = mock.Mock() + mock_selector_cls.return_value = mock_selector + assert utils.check_port_open("localhost", 22) is True - mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM, 6) - mock_closing.assert_called_once_with(sock_inst) - sock_inst.connect_ex.assert_called_once_with(("127.0.0.1", 22)) + assert mock_socket.call_count == 1 + assert sock_inst.connect_ex.call_count == 1 + assert mock_selector_cls.call_count == 1 def test_valid_port(): assert utils.valid_port(1) is False @@ -1272,23 +1253,6 @@ assert utils.compatible_role("Slave", "Unpromoted") is True [email protected]('logging.Logger.warning') [email protected]('crmsh.sh.ClusterShell.get_stdout_or_raise_error') -def test_fetch_cluster_node_list_from_node(mock_run, mock_warn): - mock_run.return_value = """ - - 1 node1 - 2 node2 lost - 3 node3 member - """ - assert utils.fetch_cluster_node_list_from_node("node1") == ["node3"] - mock_run.assert_called_once_with("crm_node -l", "node1") - mock_warn.assert_has_calls([ - mock.call("The node '%s' has no known name and/or state information", "1"), - mock.call("The node '%s'(state '%s') is not a current member", "node2", "lost") - ]) - - @mock.patch('crmsh.utils.list_cluster_nodes_except_me') def test_cluster_copy_path_return(mock_list_nodes): mock_list_nodes.return_value = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_watchdog.py new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_watchdog.py --- old/crmsh-5.0.0+20260412.db68d024/test/unittests/test_watchdog.py 2026-04-12 04:54:18.000000000 +0200 +++ new/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_watchdog.py 2026-04-20 08:35:08.000000000 +0200 @@ -146,22 +146,22 @@ mock_verify.assert_called_once_with("/dev/watchdog1") @mock.patch('crmsh.utils.fatal') - @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') - def test_get_driver_through_device_remotely_error(self, mock_run, mock_error): - mock_run.return_value = (1, None, "error") + @mock.patch('crmsh.sh.cluster_shell') + def test_get_driver_through_device_remotely_error(self, mock_cluster_shell, mock_error): + mock_cluster_shell().get_rc_stdout_stderr_without_input.return_value = (1, None, "error") self.watchdog_join_inst._get_driver_through_device_remotely("test") - mock_run.assert_called_once_with("ssh {} alice@node1 sudo sbd query-watchdog".format(constants.SSH_OPTION)) + mock_cluster_shell().get_rc_stdout_stderr_without_input.assert_called_once_with("node1", watchdog.Watchdog.QUERY_CMD) mock_error.assert_called_once_with("Failed to run sudo sbd query-watchdog remotely: error") - @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') - def test_get_driver_through_device_remotely_none(self, mock_run): - mock_run.return_value = (0, "data", None) + @mock.patch('crmsh.sh.cluster_shell') + def test_get_driver_through_device_remotely_none(self, mock_cluster_shell): + mock_cluster_shell().get_rc_stdout_stderr_without_input.return_value = (0, "data", None) res = self.watchdog_join_inst._get_driver_through_device_remotely("/dev/watchdog") self.assertEqual(res, None) - mock_run.assert_called_once_with("ssh {} alice@node1 sudo sbd query-watchdog".format(constants.SSH_OPTION)) + mock_cluster_shell().get_rc_stdout_stderr_without_input.assert_called_once_with("node1", watchdog.Watchdog.QUERY_CMD) - @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') - def test_get_driver_through_device_remotely(self, mock_run): + @mock.patch('crmsh.sh.cluster_shell') + def test_get_driver_through_device_remotely(self, mock_cluster_shell): output = """ Discovered 3 watchdog devices: @@ -179,10 +179,10 @@ Identity: iTCO_wdt Driver: iTCO_wdt """ - mock_run.return_value = (0, output, None) + mock_cluster_shell().get_rc_stdout_stderr_without_input.return_value = (0, output, None) res = self.watchdog_join_inst._get_driver_through_device_remotely("/dev/watchdog") self.assertEqual(res, "softdog") - mock_run.assert_called_once_with("ssh {} alice@node1 sudo sbd query-watchdog".format(constants.SSH_OPTION)) + mock_cluster_shell().get_rc_stdout_stderr_without_input.assert_called_once_with("node1", watchdog.Watchdog.QUERY_CMD) def test_get_first_unused_device_none(self): res = self.watchdog_inst._get_first_unused_device()
