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-28 11:57:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Tue Apr 28 11:57:23 2026 rev:406 rq:1349510 version:5.0.0+20260427.5d7e4bd6 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2026-04-21 12:47:12.588370388 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.11940/crmsh.changes 2026-04-28 12:01:18.185808642 +0200 @@ -1,0 +2,29 @@ +Mon Apr 27 07:48:19 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260427.5d7e4bd6: + * Dev: log: Sync the file formatter for NO_COLOR_FORMATTERS + +------------------------------------------------------------------- +Mon Apr 27 04:17:40 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260427.d586b0f4: + * Dev: unittests: Adjust unit test for previous commit + * Dev: ui_cluster: Add --now option for crm cluster enable/disable commands + +------------------------------------------------------------------- +Fri Apr 24 06:50:33 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260424.abe22392: + * Dev: unittests: Adjust unit test for previous commit + * Dev: doc: Adjust crm.8.adoc about -F/--force option + * Dev: ui_sbd: Add -F/--force option for crm sbd configure/device/purge commands + * Dev: behave: Adjust functional test for previous commit + * Dev: ui_cluster: Add -F/--force option for 'crm cluster init' command + +------------------------------------------------------------------- +Thu Apr 23 06:36:25 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260423.d501c2ea: + * Dev: qdevice: Improve local address check for qnetd (bsc#1262094) + +------------------------------------------------------------------- Old: ---- crmsh-5.0.0+20260420.d13e03ac.tar.bz2 New: ---- crmsh-5.0.0+20260427.5d7e4bd6.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.T9dGYf/_old 2026-04-28 12:01:18.957840612 +0200 +++ /var/tmp/diff_new_pack.T9dGYf/_new 2026-04-28 12:01:18.961840778 +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+20260420.d13e03ac +Version: 5.0.0+20260427.5d7e4bd6 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.T9dGYf/_old 2026-04-28 12:01:19.021843262 +0200 +++ /var/tmp/diff_new_pack.T9dGYf/_new 2026-04-28 12:01:19.025843429 +0200 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">d13e03ac0144d910af7fdfcf06faffe71cfdffe8</param> + <param name="changesrevision">5d7e4bd6fb3ad447a1526a65c096f02563e4fb3e</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-5.0.0+20260420.d13e03ac.tar.bz2 -> crmsh-5.0.0+20260427.5d7e4bd6.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/constants.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/constants.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/constants.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/constants.py 2026-04-27 09:16:52.000000000 +0200 @@ -434,6 +434,7 @@ If a nic name is provided, the first IP of that nic will be used. Use multiple -i for more links. Note: Only one link is allowed for the non knet transport type """ +FORCE_HELP = "Make `crm` proceed with applying changes where it would normally ask the user to confirm before proceeding, or the operation risks interfering HA protection. This option is intended primarily for scripts and must be used with care." COROSYNC_STATUS_TYPES = ("ring", "quorum", "qdevice", "qnetd", "cpg") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/log.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/log.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/log.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/log.py 2026-04-27 09:16:52.000000000 +0200 @@ -203,6 +203,13 @@ return f"{s}.{int(record.msecs):03d}{tz}" +LOGFILE_FORMATTER = { + "()": ISO8601Formatter, + "format": "%(asctime)s {} %(name)s: %(levelname)s: %(message)s".format(socket.gethostname()), + "datefmt": "%Y-%m-%dT%H:%M:%S", +} + + LOGGING_CFG = { "version": 1, "disable_existing_loggers": "False", @@ -223,11 +230,7 @@ DEBUG2: "%(levelname)s: %(funcName)s %(message)s", }, }, - "file": { - "()": ISO8601Formatter, - "format": "%(asctime)s {} %(name)s: %(levelname)s: %(message)s".format(socket.gethostname()), - "datefmt": "%Y-%m-%dT%H:%M:%S", - } + "file": LOGFILE_FORMATTER }, "filters": { "filter": { @@ -298,10 +301,7 @@ DEBUG2: "%(levelname)s: %(funcName)s %(message)s", }, }, - "file": { - "format": "%(asctime)s {} %(name)s: %(levelname)s: %(message)s".format(socket.gethostname()), - "datefmt": "%b %d %H:%M:%S", - } + "file": LOGFILE_FORMATTER } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/main.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/main.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/main.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/main.py 2026-04-27 09:16:52.000000000 +0200 @@ -113,9 +113,7 @@ "The default is color if the terminal emulation supports colors, " + "else plain.") parser.add_argument("-F", "--force", action="store_true", default=False, dest="force", - help="Make crm proceed with applying changes where it would normally " + - "ask the user to confirm before proceeding. This option is mainly useful " + - "in scripts, and should be used with care.") + help=constants.FORCE_HELP) parser.add_argument("-n", "--no", action="store_true", default=False, dest="ask_no", help="Automatically answer no when prompted") parser.add_argument("-w", "--wait", action="store_true", default=False, dest="wait", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/qdevice.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/qdevice.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/qdevice.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/qdevice.py 2026-04-27 09:16:52.000000000 +0200 @@ -1,3 +1,5 @@ +import ipaddress +import json import os import re import socket @@ -6,7 +8,7 @@ from enum import Enum import crmsh.parallax -from . import sh +from . import sh, iproute2 from . import utils from . import parallax from . import corosync @@ -185,22 +187,25 @@ @staticmethod def check_qnetd_addr(qnetd_addr): - qnetd_ip = None - try: - # socket.getaddrinfo works for both ipv4 and ipv6 address - # The function returns a list of 5-tuples with the following structure: - # (family, type, proto, canonname, sockaddr) - # sockaddr is a tuple describing a socket address, whose format depends on the returned family - # (a (address, port) 2-tuple for AF_INET, a (address, port, flow info, scope id) 4-tuple for AF_INET6) - res = socket.getaddrinfo(qnetd_addr, None) - qnetd_ip = res[0][-1][0] - except socket.error: - raise ValueError("host \"{}\" is unreachable".format(qnetd_addr)) - utils.ssh_port_reachable_check(qnetd_addr) + try: + qnetd_ip_addresses = [ + ipaddress.ip_address(sockaddr[0]) + for af, tpe, proto, canonname, sockaddr in socket.getaddrinfo(qnetd_addr, None) + ] + except socket.error as e: + raise ValueError(f"{e}: {qnetd_addr}") + try: + local_interfaces = iproute2.IPAddr(json.loads( + sh.LocalShell().get_stdout_or_raise_error(None, 'ip -j addr show') + )) + except ValueError: + return + local_ip_addresses = set(addr_info.ip for interface in local_interfaces.interfaces() for addr_info in interface.addr_info) + for ip_addr in qnetd_ip_addresses: + if ip_addr in local_ip_addresses: + raise ValueError("host for qnetd must be a remote one") - if utils.InterfacesInfo.ip_in_local(qnetd_ip): - raise ValueError("host for qnetd must be a remote one") @staticmethod def check_qdevice_port(qdevice_port): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/service_manager.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/service_manager.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/service_manager.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/service_manager.py 2026-04-27 09:16:52.000000000 +0200 @@ -81,18 +81,20 @@ cmd = "systemctl stop '{}'".format(name) return self._call(remote_addr, node_list, cmd) - def enable_service(self, name, remote_addr=None, node_list=[]): + def enable_service(self, name, remote_addr=None, node_list=[], now=False): """ Enable service Return success node list """ - cmd = "systemctl enable '{}'".format(name) + now_flag = "--now " if now else "" + cmd = f"systemctl {now_flag}enable {name}" return self._call(remote_addr, node_list, cmd) - def disable_service(self, name, remote_addr=None, node_list=[]): + def disable_service(self, name, remote_addr=None, node_list=[], now=False): """ Disable service Return success node list """ - cmd = "systemctl disable '{}'".format(name) + now_flag = "--now " if now else "" + cmd = f"systemctl {now_flag}disable {name}" return self._call(remote_addr, node_list, cmd) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_cluster.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_cluster.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_cluster.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_cluster.py 2026-04-27 09:16:52.000000000 +0200 @@ -32,6 +32,7 @@ from . import sbd from . import log from .utils import TerminateSubCommand +import crmsh.options logger = log.setup_logger(__name__) @@ -168,7 +169,7 @@ Starts the cluster stack on all nodes or specific node(s) ''' try: - node_list = ui_utils.parse_and_validate_node_args("start", *args) + node_list, _ = ui_utils.parse_and_validate_node_args("start", *args) except utils.NoSSHError as msg: logger.error('%s', msg) logger.info("Please try 'crm cluster start' on each node") @@ -251,7 +252,7 @@ Stops the cluster stack on all nodes or specific node(s) ''' try: - node_list = ui_utils.parse_and_validate_node_args("stop", *args) + node_list, _ = ui_utils.parse_and_validate_node_args("stop", *args) except utils.NoSSHError as msg: logger.error('%s', msg) logger.info("Please try 'crm cluster stop' on each node") @@ -304,25 +305,29 @@ ''' Enable the cluster services on this node ''' - node_list = ui_utils.parse_and_validate_node_args("enable", *args) + node_list, now_flag = ui_utils.parse_and_validate_node_args("enable", *args) service_manager = ServiceManager() - node_list = service_manager.enable_service("pacemaker.service", node_list=node_list) + node_list = service_manager.enable_service("pacemaker.service", node_list=node_list, now=now_flag) if service_manager.service_is_available("corosync-qdevice.service") and corosync.is_qdevice_configured(): - service_manager.enable_service("corosync-qdevice.service", node_list=node_list) + service_manager.enable_service("corosync-qdevice.service", node_list=node_list, now=now_flag) + + action_msg = "enabled and started" if now_flag else "enabled" for node in node_list: - logger.info("Cluster services enabled on %s", node) + logger.info("Cluster services %s on %s", action_msg, node) @command.skill_level('administrator') def do_disable(self, context, *args): ''' Disable the cluster services on this node ''' - node_list = ui_utils.parse_and_validate_node_args("disable", *args) + node_list, now_flag = ui_utils.parse_and_validate_node_args("disable", *args) service_manager = ServiceManager() - node_list = service_manager.disable_service("pacemaker.service", node_list=node_list) - service_manager.disable_service("corosync-qdevice.service", node_list=node_list) + node_list = service_manager.disable_service("pacemaker.service", node_list=node_list, now=now_flag) + service_manager.disable_service("corosync-qdevice.service", node_list=node_list, now=now_flag) + + action_msg = "stopped and disabled" if now_flag else "disabled" for node in node_list: - logger.info("Cluster services disabled on %s", node) + logger.info("Cluster services %s on %s", action_msg, node) def _args_implicit(self, context, args, name): ''' @@ -434,6 +439,8 @@ help="Skip csync2 initialization (default, deprecated)") parser.add_argument('--use-ssh-agent', action=argparse.BooleanOptionalAction, dest='use_ssh_agent', default=True, help="Try to use an existing key from ssh-agent (default)") + parser.add_argument("-F", "--force", dest="force", action="store_true", default=False, + help=constants.FORCE_HELP) network_group = parser.add_argument_group("Network configuration", "Options for configuring the network and messaging layer.") network_group.add_argument("-i", "--interface", dest="nic_addr_list", metavar="IF", action=CustomAppendAction, default=[], help=constants.INTERFACE_HELP) @@ -476,6 +483,8 @@ if options is None or args is None: return + crmsh.options.force |= options.force + stage = "" if len(args): stage = args[0] @@ -582,6 +591,8 @@ if options is None or args is None: return + crmsh.options.force |= options.force + if options.cluster_node is not None and options.cluster_node not in args: args = list(args) + [options.cluster_node] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_node.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_node.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_node.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_node.py 2026-04-27 09:16:52.000000000 +0200 @@ -301,7 +301,7 @@ args = args[:-1] # Parse node option - node_list = ui_utils.parse_and_validate_node_args("standby", *args) + node_list, _ = ui_utils.parse_and_validate_node_args("standby", *args) if not node_list: return @@ -389,7 +389,7 @@ To avoid race condition for --all option, melt all online values into one cib replace session """ # Parse node option - node_list = ui_utils.parse_and_validate_node_args("online", *args) + node_list, _ = ui_utils.parse_and_validate_node_args("online", *args) if not node_list: return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_sbd.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_sbd.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_sbd.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_sbd.py 2026-04-27 09:16:52.000000000 +0200 @@ -12,6 +12,7 @@ from crmsh import sh from crmsh import xmlutil from crmsh import constants +from crmsh import options from crmsh.service_manager import ServiceManager @@ -77,6 +78,15 @@ return parameters_pool +def _handle_force_option(args): + _force = False + if args and args[-1] in ("-F", "--force"): + _force = True + args = args[:-1] + options.force |= _force + return args + + class SBD(command.UI): ''' Class for sbd sub-level @@ -179,7 +189,7 @@ timeout_usage_str = " ".join([f"[{t}-timeout=<integer>]" for t in timeout_types]) show_usage = f"crm sbd configure show [{'|'.join(show_types)}]" - return f"Usage:\n{show_usage}\ncrm sbd configure {timeout_usage_str} [watchdog-device=<device|driver>]\n" + return f"Usage:\n{show_usage}\ncrm sbd configure {timeout_usage_str} [watchdog-device=<device|driver>] [-F|--force]\n" def _show_sysconfig(self) -> None: ''' @@ -506,6 +516,7 @@ logger.info("Please use 'crm cluster init sbd -s <dev1> [-s <dev2> [-s <dev3>]]' to configure the disk-based SBD first") return False + args = _handle_force_option(args) try: if not args: raise self.SyntaxError("No argument") @@ -538,6 +549,7 @@ ''' Implement sbd configure command ''' + args = _handle_force_option(args) try: self._load_attributes() if not args: @@ -602,6 +614,7 @@ if not self._service_is_active(constants.SBD_SERVICE): return False + args = _handle_force_option(args) purge_crashdump = False if args: if args[0] == "crashdump": @@ -611,7 +624,7 @@ purge_crashdump = True else: logger.error("Invalid argument: %s", ' '.join(args)) - logger.info("Usage: crm sbd purge [crashdump]") + logger.info("Usage: crm sbd purge [crashdump] [-F|--force]") return False msg = "purging SBD crashdump" if purge_crashdump else "purging SBD" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_utils.py new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_utils.py --- old/crmsh-5.0.0+20260420.d13e03ac/crmsh/ui_utils.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/crmsh/ui_utils.py 2026-04-27 09:16:52.000000000 +0200 @@ -165,10 +165,10 @@ (mknamed(), max_args-nskip, len(args)-nskip)) -def parse_and_validate_node_args(command_name, *args) -> list: +def parse_and_validate_node_args(command_name, *args) -> tuple[list[str], bool]: ''' Parses option for node-related commands - Then validates and returns the reachable node list + Then validates and returns the reachable node list and now flag ''' action_target = "node" if command_name in ["standby", "online"] else "cluster service" action = f"{command_name} {action_target}" @@ -193,6 +193,9 @@ ) parser.add_argument("-h", "--help", action="store_true", dest="help", help="Show this help message") parser.add_argument("--all", help=f"To {action} on all nodes", action="store_true", dest="all") + if command_name in ["enable", "disable"]: + help_str = "start" if command_name == "enable" else "stop" + parser.add_argument("--now", action="store_true", dest="now", help=f"To {help_str} cluster service immediately") options, args = parser.parse_known_args(args) if options.help: @@ -204,4 +207,6 @@ raise ValueError("Should either use --all or specific node(s)") include_remote = command_name in ["standby", "online"] - return utils.validate_and_get_reachable_nodes(args, options.all, include_remote) + nodes = utils.validate_and_get_reachable_nodes(args, options.all, include_remote) + + return nodes, getattr(options, "now", False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/doc/crm.8.adoc new/crmsh-5.0.0+20260427.5d7e4bd6/doc/crm.8.adoc --- old/crmsh-5.0.0+20260420.d13e03ac/doc/crm.8.adoc 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/doc/crm.8.adoc 2026-04-27 09:16:52.000000000 +0200 @@ -67,8 +67,9 @@ *-F, --force*:: Make `crm` proceed with applying changes where it would normally - ask the user to confirm before proceeding. This option is mainly - useful in scripts, and should be used with care. + ask the user to confirm before proceeding, or the operation risks + interfering HA protection. This option is intended primarily for + scripts and must be used with care *-w, --wait*:: Make `crm` wait for the cluster transition to finish (for the @@ -2235,11 +2236,11 @@ ............... # For disk-based SBD crm sbd configure show [disk_metadata|sysconfig|property] -crm sbd configure [watchdog-timeout=<integer>] [allocate-timeout=<integer>] [loop-timeout=<integer>] [msgwait-timeout=<integer>] [crashdump-watchdog-timeout=<integer>] [watchdog-device=<device|driver>] +crm sbd configure [watchdog-timeout=<integer>] [allocate-timeout=<integer>] [loop-timeout=<integer>] [msgwait-timeout=<integer>] [crashdump-watchdog-timeout=<integer>] [watchdog-device=<device|driver>] [-F|--force] # For disk-less SBD crm sbd configure show [sysconfig|property] -crm sbd configure [watchdog-timeout=<integer>] [crashdump-watchdog-timeout=<integer>] [watchdog-device=<device|driver>] +crm sbd configure [watchdog-timeout=<integer>] [crashdump-watchdog-timeout=<integer>] [watchdog-device=<device|driver>] [-F|--force] ............... example: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/test/features/steps/const.py new/crmsh-5.0.0+20260427.5d7e4bd6/test/features/steps/const.py --- old/crmsh-5.0.0+20260420.d13e03ac/test/features/steps/const.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/test/features/steps/const.py 2026-04-27 09:16:52.000000000 +0200 @@ -25,10 +25,11 @@ Choose one of the output options: plain, color-always, color, or uppercase. The default is color if the terminal emulation supports colors, else plain. - -F, --force Make crm proceed with applying changes where it would - normally ask the user to confirm before proceeding. - This option is mainly useful in scripts, and should be - used with care. + -F, --force Make `crm` proceed with applying changes where it + would normally ask the user to confirm before + proceeding, or the operation risks interfering HA + protection. This option is intended primarily for + scripts and must be used with care. -n, --no Automatically answer no when prompted -w, --wait Make crm wait for the cluster transition to finish (for the changes to take effect) after each processed @@ -82,6 +83,11 @@ Skip csync2 initialization (default, deprecated) --use-ssh-agent, --no-use-ssh-agent Try to use an existing key from ssh-agent (default) + -F, --force Make `crm` proceed with applying changes where it + would normally ask the user to confirm before + proceeding, or the operation risks interfering HA + protection. This option is intended primarily for + scripts and must be used with care. Network configuration: Options for configuring the network and messaging layer. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_qdevice.py new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_qdevice.py --- old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_qdevice.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_qdevice.py 2026-04-27 09:16:52.000000000 +0200 @@ -1,5 +1,6 @@ import unittest import socket +import json try: from unittest import mock @@ -184,25 +185,46 @@ res = self.qdevice_with_ip.qdevice_p12_on_local self.assertEqual(res, "/etc/corosync/qdevice/net/nssdb/qdevice-net-node.p12") - @mock.patch('crmsh.utils.InterfacesInfo.ip_in_local') + @mock.patch('crmsh.sh.LocalShell') @mock.patch('crmsh.utils.ssh_port_reachable_check') @mock.patch('socket.getaddrinfo') - def test_check_qnetd_addr_local(self, mock_getaddrinfo, mock_reachable, mock_in_local): - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_in_local.return_value = True + def test_check_qnetd_addr_local(self, mock_getaddrinfo, mock_reachable, mock_shell): + mock_getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '', ("10.10.10.123", 0)),] + mock_shell.return_value.get_stdout_or_raise_error.return_value = json.dumps([ + { + "ifname": "eth0", + "flags": ["UP", "LOWER_UP"], + "addr_info": [{"local": "10.10.10.123", "prefixlen": 24}] + } + ]) with self.assertRaises(ValueError) as err: qdevice.QDevice.check_qnetd_addr("qnetd-node") excepted_err_string = "host for qnetd must be a remote one" self.assertEqual(excepted_err_string, str(err.exception)) + @mock.patch('crmsh.utils.ssh_port_reachable_check') @mock.patch('socket.getaddrinfo') - def test_check_qnetd_addr(self, mock_getaddrinfo): - mock_getaddrinfo.side_effect = socket.error + def test_check_qnetd_addr(self, mock_getaddrinfo, mock_reachable): + mock_getaddrinfo.side_effect = socket.error("getaddrinfo failed") with self.assertRaises(ValueError) as err: qdevice.QDevice.check_qnetd_addr("qnetd-node") - excepted_err_string = "host \"qnetd-node\" is unreachable" + excepted_err_string = "getaddrinfo failed: qnetd-node" self.assertEqual(excepted_err_string, str(err.exception)) + @mock.patch('crmsh.sh.LocalShell') + @mock.patch('crmsh.utils.ssh_port_reachable_check') + @mock.patch('socket.getaddrinfo') + def test_check_qnetd_addr_success(self, mock_getaddrinfo, mock_reachable, mock_shell): + mock_getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '', ("10.10.10.124", 0)),] + mock_shell.return_value.get_stdout_or_raise_error.return_value = json.dumps([ + { + "ifname": "eth0", + "flags": ["UP", "LOWER_UP"], + "addr_info": [{"local": "10.10.10.123", "prefixlen": 24}] + } + ]) + qdevice.QDevice.check_qnetd_addr("qnetd-node") + @mock.patch('crmsh.utils.valid_port') def test_check_qdevice_port(self, mock_port): mock_port.return_value = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_service_manager.py new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_service_manager.py --- old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_service_manager.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_service_manager.py 2026-04-27 09:16:52.000000000 +0200 @@ -76,9 +76,9 @@ def test_enable_service(self, mock_call_with_parallax: mock.MagicMock): self.service_manager._call.return_value = ['node1'] self.assertEqual(['node1'], self.service_manager.enable_service('service1', remote_addr='node1')) - self.service_manager._call.assert_called_once_with('node1', [], "systemctl enable 'service1'") + self.service_manager._call.assert_called_once_with('node1', [], "systemctl enable service1") def test_disable_service(self, mock_call_with_parallax: mock.MagicMock): self.service_manager._call.return_value = ['node1'] self.assertEqual(['node1'], self.service_manager.disable_service('service1', remote_addr='node1')) - self.service_manager._call.assert_called_once_with('node1', [], "systemctl disable 'service1'") + self.service_manager._call.assert_called_once_with('node1', [], "systemctl disable service1") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_ui_cluster.py new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_ui_cluster.py --- old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_ui_cluster.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_ui_cluster.py 2026-04-27 09:16:52.000000000 +0200 @@ -43,7 +43,7 @@ def test_do_start_already_started(self, mock_qdevice_configured, mock_parse_nodes, mock_active, mock_info): mock_qdevice_configured.return_value = False context_inst = mock.Mock() - mock_parse_nodes.return_value = ["node1", "node2"] + mock_parse_nodes.return_value = ["node1", "node2"], False mock_active.side_effect = [True, True] self.ui_cluster_inst.do_start(context_inst, "node1", "node2") mock_parse_nodes.assert_called_once_with("start", "node1", "node2") @@ -67,7 +67,7 @@ def test_do_start(self, mock_parse_nodes, mock_active, mock_start, mock_qdevice_configured, mock_info, mock_error, mock_start_pacemaker, mock_check_qdevice): context_inst = mock.Mock() mock_start_pacemaker.return_value = ["node1"] - mock_parse_nodes.return_value = ["node1", "node2"] + mock_parse_nodes.return_value = ["node1", "node2"], False mock_active.side_effect = [False, False, False, False] mock_qdevice_configured.return_value = True @@ -88,7 +88,7 @@ @mock.patch('crmsh.ui_cluster.Cluster._node_ready_to_stop_cluster_service') @mock.patch('crmsh.ui_utils.parse_and_validate_node_args') def test_do_stop_return(self, mock_parse_nodes, mock_node_ready_to_stop_cluster_service, mock_dc): - mock_parse_nodes.return_value = ["node1", "node2"] + mock_parse_nodes.return_value = ["node1", "node2"], False mock_node_ready_to_stop_cluster_service.side_effect = [False, False] context_inst = mock.Mock() @@ -108,7 +108,7 @@ @mock.patch('crmsh.ui_utils.parse_and_validate_node_args') def test_do_stop(self, mock_parse_nodes, mock_node_ready_to_stop_cluster_service, mock_dc, mock_set_dlm, mock_service_manager, mock_info, mock_debug, mock_is_in_maintenance_mode): - mock_parse_nodes.return_value = ["node1", "node2"] + mock_parse_nodes.return_value = ["node1", "node2"], False mock_node_ready_to_stop_cluster_service.side_effect = [True, False] mock_service_manager_inst = mock.Mock() mock_service_manager.return_value = mock_service_manager_inst diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_ui_sbd.py new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_ui_sbd.py --- old/crmsh-5.0.0+20260420.d13e03ac/test/unittests/test_ui_sbd.py 2026-04-20 08:35:08.000000000 +0200 +++ new/crmsh-5.0.0+20260427.5d7e4bd6/test/unittests/test_ui_sbd.py 2026-04-27 09:16:52.000000000 +0200 @@ -155,7 +155,7 @@ mock_is_using_disk_based_sbd.return_value = True timeout_usage_str = " ".join([f"[{t}-timeout=<integer>]" for t in ui_sbd.SBD.DISKBASED_TIMEOUT_TYPES]) show_usage = f"crm sbd configure show [{'|'.join(ui_sbd.SBD.SHOW_TYPES)}]" - expected = f"Usage:\n{show_usage}\ncrm sbd configure {timeout_usage_str} [watchdog-device=<device|driver>]\n" + expected = f"Usage:\n{show_usage}\ncrm sbd configure {timeout_usage_str} [watchdog-device=<device|driver>] [-F|--force]\n" self.assertEqual(self.sbd_instance_diskbased.configure_usage, expected) mock_is_using_disk_based_sbd.assert_called_once() mock_is_using_diskless_sbd.assert_not_called() @@ -167,7 +167,7 @@ mock_is_using_diskless_sbd.return_value = True timeout_usage_str = " ".join([f"[{t}-timeout=<integer>]" for t in ui_sbd.SBD.DISKLESS_TIMEOUT_TYPES]) show_usage = f"crm sbd configure show [{'|'.join(ui_sbd.SBD.DISKLESS_SHOW_TYPES)}]" - expected = f"Usage:\n{show_usage}\ncrm sbd configure {timeout_usage_str} [watchdog-device=<device|driver>]\n" + expected = f"Usage:\n{show_usage}\ncrm sbd configure {timeout_usage_str} [watchdog-device=<device|driver>] [-F|--force]\n" self.assertEqual(self.sbd_instance_diskless.configure_usage, expected) mock_is_using_disk_based_sbd.assert_called_once() mock_is_using_diskless_sbd.assert_called_once()
