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-06-25 10:55:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.2088 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Thu Jun 25 10:55:51 2026 rev:413 rq:1361531 version:5.1.0+20260624.349562dd Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2026-06-15 19:53:51.513621227 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.2088/crmsh.changes 2026-06-25 10:58:30.796081890 +0200 @@ -1,0 +2,13 @@ +Wed Jun 24 04:47:36 UTC 2026 - [email protected] + +- Update to version 5.1.0+20260624.349562dd: + * Dev: storage_utils: Rename blkid UUID helper + * Dev: storage_utils: Refactor storage helpers out of utils.py + +------------------------------------------------------------------- +Mon Jun 22 08:17:20 UTC 2026 - [email protected] + +- Update to version 5.1.0+20260622.f844a5ab: + * Chore: unittests: reduce output verbosity and stop printing coverage report + +------------------------------------------------------------------- Old: ---- crmsh-5.1.0+20260615.8409ea5a.tar.bz2 New: ---- crmsh-5.1.0+20260624.349562dd.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.cooZyN/_old 2026-06-25 10:58:31.560108257 +0200 +++ /var/tmp/diff_new_pack.cooZyN/_new 2026-06-25 10:58:31.560108257 +0200 @@ -41,7 +41,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 5.1.0+20260615.8409ea5a +Version: 5.1.0+20260624.349562dd Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.cooZyN/_old 2026-06-25 10:58:31.608109914 +0200 +++ /var/tmp/diff_new_pack.cooZyN/_new 2026-06-25 10:58:31.612110052 +0200 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">69bca8d37b097414990f2291b69f39f6bdb1d8a2</param> + <param name="changesrevision">349562dd6312bfaef9755ef04f626b496c66cc7e</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-5.1.0+20260615.8409ea5a.tar.bz2 -> crmsh-5.1.0+20260624.349562dd.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/crmsh/cibconfig.py new/crmsh-5.1.0+20260624.349562dd/crmsh/cibconfig.py --- old/crmsh-5.1.0+20260615.8409ea5a/crmsh/cibconfig.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/crmsh/cibconfig.py 2026-06-24 06:16:33.000000000 +0200 @@ -18,6 +18,7 @@ from . import clidisplay from . import idmgmt from . import schema +from . import storage_utils from . import utils from . import cibverify from . import parse @@ -2679,7 +2680,7 @@ self.last_commit_time = t self.refresh() - utils.check_no_quorum_policy_with_dlm() + storage_utils.check_no_quorum_policy_with_dlm() return rc def _update_schema(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/crmsh/cluster_fs.py new/crmsh-5.1.0+20260624.349562dd/crmsh/cluster_fs.py --- old/crmsh-5.1.0+20260615.8409ea5a/crmsh/cluster_fs.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/crmsh/cluster_fs.py 2026-06-24 06:16:33.000000000 +0200 @@ -1,7 +1,7 @@ import logging import re from contextlib import contextmanager -from . import utils, sh +from . import utils, sh, storage_utils from . import bootstrap from . import ra from . import corosync @@ -113,7 +113,7 @@ raise Error("Without Cluster LVM2 (-C option), -o option only support one device") elif len(self.gfs2_devices) > 1: raise Error("Without Cluster LVM2 (-C option), -g option only support one device") - if self.mount_point and utils.has_mount_point_used(self.mount_point): + if self.mount_point and storage_utils.has_mount_point_used(self.mount_point): raise Error(f"Mount point {self.mount_point} already mounted") def _verify_devices(self): @@ -122,14 +122,14 @@ """ node_list = utils.list_cluster_nodes() if self.use_stage else [utils.this_node()] for dev in self.devices: - failed_nodes = utils.get_non_block_device_nodes(dev, node_list) + failed_nodes = storage_utils.get_non_block_device_nodes(dev, node_list) if failed_nodes: raise Error(f"{dev} is not a block device on {', '.join(failed_nodes)}") - if utils.is_dev_used_for_lvm(dev) and self.use_cluster_lvm2: + if storage_utils.is_dev_used_for_lvm(dev) and self.use_cluster_lvm2: raise Error(f"{dev} is a Logical Volume, cannot be used with the -C option") - if utils.has_disk_mounted(dev): + if storage_utils.has_disk_mounted(dev): raise Error(f"{dev} is already mounted") - utils.MultipathInspector.check_device_under_multipath(dev) + storage_utils.MultipathInspector.check_device_under_multipath(dev) def _check_if_already_configured(self): """ @@ -176,10 +176,10 @@ """ for dev in self.devices: msg = "" - if utils.has_dev_partitioned(dev): + if storage_utils.has_dev_partitioned(dev): msg = f"Found a partition table in {dev}" else: - fs_type = utils.get_dev_fs_type(dev) + fs_type = storage_utils.get_dev_fs_type(dev) if fs_type: msg = f"{dev} contains a {fs_type} file system" if msg and not bootstrap.confirm(f"{msg} - overwrite?"): @@ -254,13 +254,13 @@ shell.get_stdout_or_raise_error(f"pvcreate {disks_string} -y") # Create VG - self.vg_id = utils.gen_unused_id(utils.get_all_vg_name(), self.VG_ID) + self.vg_id = utils.gen_unused_id(storage_utils.get_all_vg_name(), self.VG_ID) with logger_utils.status_long(f"Creating VG {self.vg_id}"): shell.get_stdout_or_raise_error(f"vgcreate --shared {self.vg_id} {disks_string} -y") # Create LV with logger_utils.status_long(f"Creating LV {self.LV_ID} on VG {self.vg_id}"): - pe_number = utils.get_pe_number(self.vg_id) + pe_number = storage_utils.get_pe_number(self.vg_id) shell.get_stdout_or_raise_error(f"lvcreate -l {pe_number} {self.vg_id} -n {self.LV_ID} -y") return f"/dev/{self.vg_id}/{self.LV_ID}" @@ -396,8 +396,8 @@ with logger_utils.status_long(f"Verify {cluster_fs_type.upper()} environment on {device}"): use_cluster_lvm2 = xmlutil.CrmMonXmlParser(peer).is_resource_configured(constants.LVMLOCKD_RA) self._verify_packages(cluster_fs_type.upper(), use_cluster_lvm2) - if utils.is_dev_a_plain_raw_disk_or_partition(device, peer): - utils.compare_uuid_with_peer_dev([device], peer) + if storage_utils.is_dev_a_plain_raw_disk_or_partition(device, peer): + storage_utils.compare_uuid_with_peer_dev([device], peer) @classmethod def pre_verify(cls, ctx): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/crmsh/sbd.py new/crmsh-5.1.0+20260624.349562dd/crmsh/sbd.py --- old/crmsh-5.1.0+20260615.8409ea5a/crmsh/sbd.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/crmsh/sbd.py 2026-06-24 06:16:33.000000000 +0200 @@ -6,7 +6,7 @@ import shlex import logging from enum import Enum, IntEnum, auto -from . import utils, sh +from . import utils, sh, storage_utils from . import bootstrap from . import log from . import constants @@ -91,16 +91,16 @@ raise ValueError(f"Maximum number of SBD device is {SBDManager.SBD_DEVICE_MAX}") for dev in dev_list: - failed_nodes = utils.get_non_block_device_nodes(dev, node_list) + failed_nodes = storage_utils.get_non_block_device_nodes(dev, node_list) if failed_nodes: raise ValueError(f"{dev} is not a block device on {', '.join(failed_nodes)}") - utils.MultipathInspector.check_device_under_multipath(dev) + storage_utils.MultipathInspector.check_device_under_multipath(dev) if compare_uuid: SBDUtils.compare_device_uuid(dev, node_list) - utils.detect_duplicate_device_path(dev_list) + storage_utils.detect_duplicate_device_path(dev_list) @staticmethod def get_sbd_value_from_config(key): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/crmsh/storage_utils.py new/crmsh-5.1.0+20260624.349562dd/crmsh/storage_utils.py --- old/crmsh-5.1.0+20260615.8409ea5a/crmsh/storage_utils.py 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-5.1.0+20260624.349562dd/crmsh/storage_utils.py 2026-06-24 06:16:33.000000000 +0200 @@ -0,0 +1,249 @@ +import logging +import os +import re +import shlex +import typing +from collections import defaultdict +from dataclasses import dataclass +from functools import cache +from pathlib import Path + +from . import constants, sh, xmlutil, utils + + +logger = logging.getLogger(__name__) + + +def get_non_block_device_nodes(dev, node_list=None) -> list[str]: + """ + Return a list of nodes where the device is not a block device or does not exist + """ + shell = sh.cluster_shell() + cluster_nodes = node_list or [utils.this_node()] + failed_nodes = [] + for node in cluster_nodes: + rc, _, _ = shell.get_rc_stdout_stderr_without_input( + node, + f"test -b {shlex.quote(dev)}" + ) + if rc != 0: + failed_nodes.append(node) + return failed_nodes + + +def detect_duplicate_device_path(device_list: typing.List[str]): + """ + Resolve device path and check if there are duplicated device path + """ + path_dict = defaultdict(list) + for dev in device_list: + resolved_path = Path(dev).resolve() + path_dict[resolved_path].append(dev) + for path, dev_list in path_dict.items(): + if len(dev_list) > 1: + raise ValueError(f"Duplicated device path detected: {','.join(dev_list)}. They are all pointing to {path}") + + +def has_disk_mounted(dev): + """ + Check if device already mounted + """ + out = sh.cluster_shell().get_stdout_or_raise_error("mount") + return re.search("\n{} on ".format(dev), out) is not None + + +def has_mount_point_used(directory): + """ + Check if mount directory already mounted + """ + out = sh.cluster_shell().get_stdout_or_raise_error("mount") + return re.search(" on {}".format(directory), out) is not None + + +def get_all_vg_name(): + """ + Get all available VGs + """ + out = sh.cluster_shell().get_stdout_or_raise_error("vgdisplay") + return re.findall(r"VG Name\s+(.*)", out) + + +def get_pe_number(vg_id): + """ + Get pe number + """ + output = sh.cluster_shell().get_stdout_or_raise_error("vgdisplay {}".format(vg_id)) + res = re.search(r"Total PE\s+(\d+)", output) + if not res: + raise ValueError("Cannot find PE on VG({})".format(vg_id)) + return int(res.group(1)) + + +def has_dev_partitioned(dev, peer=None): + """ + Check if device has partitions + """ + return len(get_dev_info(dev, "NAME", peer=peer).splitlines()) > 1 + + +def get_dev_uuid(dev, peer=None): + """ + Get UUID of device on local or peer node + """ + out = get_dev_info(dev, "UUID", peer=peer).splitlines() + return out[0] if out else get_dev_uuid_by_blkid(dev, peer) + + +def get_dev_uuid_by_blkid(dev, peer=None): + """ + Get UUID of device using blkid + """ + out = sh.cluster_shell().get_stdout_or_raise_error("blkid {}".format(dev), peer) + res = re.search("UUID=\"(.*?)\"", out) + return res.group(1) if res else None + + +def get_dev_fs_type(dev, peer=None): + """ + Get filesystem type of device + """ + return get_dev_info(dev, "FSTYPE", peer=peer) + + +def get_dev_info(dev, *_type, peer=None): + """ + Get device info using lsblk + """ + cmd = "lsblk -fno {} {}".format(','.join(_type), dev) + return sh.cluster_shell().get_stdout_or_raise_error(cmd, peer) + + +def is_dev_used_for_lvm(dev, peer=None): + """ + Check if device is LV + """ + return "lvm" in get_dev_info(dev, "TYPE", peer=peer) + + +def is_dev_a_plain_raw_disk_or_partition(dev, peer=None): + """ + Check if device is a raw disk or partition + """ + out = get_dev_info(dev, "TYPE", peer=peer) + return re.search("(disk|part)", out) is not None + + +def compare_uuid_with_peer_dev(dev_list, peer): + """ + Check if device UUID is the same with peer's device + """ + for dev in dev_list: + local_uuid = get_dev_uuid(dev) + if not local_uuid: + raise ValueError("Cannot find UUID for {} on local".format(dev)) + peer_uuid = get_dev_uuid(dev, peer) + if not peer_uuid: + raise ValueError("Cannot find UUID for {} on {}".format(dev, peer)) + if local_uuid != peer_uuid: + raise ValueError("UUID of {} not same with peer {}".format(dev, peer)) + + +def get_dlm_option_dict(peer=None): + """ + Get dlm config option dictionary + """ + out = sh.cluster_shell().get_stdout_or_raise_error("dlm_tool dump_config", peer) + return dict(re.findall(r"(\w+)=(\w+)", out)) + + +def set_dlm_option(peer=None, **kargs): + """ + Set dlm option + """ + shell = sh.cluster_shell() + dlm_option_dict = get_dlm_option_dict(peer=peer) + for option, value in kargs.items(): + if option not in dlm_option_dict: + raise ValueError(f'"{option}" is not dlm config option') + if dlm_option_dict[option] != value: + shell.get_stdout_or_raise_error(f'dlm_tool set_config "{option}={value}"', peer) + + +def is_dlm_running(peer=None, on_node=None): + """ + Check if dlm ra controld is running + """ + return xmlutil.CrmMonXmlParser(peer).is_resource_started(constants.DLM_CONTROLD_RA, node=on_node) + + +def is_dlm_configured(peer=None): + """ + Check if dlm configured + """ + return xmlutil.CrmMonXmlParser(peer).is_resource_configured(constants.DLM_CONTROLD_RA) + + +def check_no_quorum_policy_with_dlm(): + """ + Give warning when no-quorum-policy not freeze while configured DLM + """ + if not is_dlm_configured(): + return + from . import utils + res = utils.get_property("no-quorum-policy") + if not res or res != "freeze": + logger.warning("The DLM cluster best practice suggests to set the cluster property \"no-quorum-policy=freeze\"") + + +@dataclass(frozen=True) +class DeviceInfo: + device: str + parent_device: str|None + under_multipath: bool + + +class MultipathInspector: + def __init__(self, dev): + self._shell = sh.cluster_shell() + self._device_info = self._inspect(dev) + + def _get_parent_device(self, dev) -> str: + resolved = Path(dev).resolve() + cmd = f"lsblk -dn -o PKNAME {shlex.quote(str(resolved))}" + _, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, cmd) + return out or resolved.name + + def _get_multipath_mapping(self) -> dict[str, str]: + cmd = "multipathd show paths format \"%d %m\"" + rc, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, cmd) + mapping = dict() + if rc != 0: + return mapping + for line in out.splitlines(): + parts = line.split() + if len(parts) < 2: + continue + dev_name, map_name = parts[0], parts[1] + if (dev_name, map_name) == ("dev", "multipath"): + continue + mapping[dev_name] = map_name + return mapping + + def _inspect(self, dev: str) -> DeviceInfo: + parent = self._get_parent_device(dev) + mapping = self._get_multipath_mapping() + return DeviceInfo( + device=dev, + parent_device=parent, + under_multipath=parent in mapping + ) + + def _is_under_multipath(self) -> bool: + return self._device_info.under_multipath + + @classmethod + def check_device_under_multipath(cls, dev): + inspector = cls(dev) + if inspector._is_under_multipath(): + error_msg = f"Device {dev} is under multipath, please provide the multipath device instead" + raise ValueError(error_msg) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/crmsh/ui_cluster.py new/crmsh-5.1.0+20260624.349562dd/crmsh/ui_cluster.py --- old/crmsh-5.1.0+20260615.8409ea5a/crmsh/ui_cluster.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/crmsh/ui_cluster.py 2026-06-24 06:16:33.000000000 +0200 @@ -15,7 +15,7 @@ from argparse import ArgumentParser, RawDescriptionHelpFormatter import crmsh.parallax -from . import command, sh, healthcheck, migration +from . import command, sh, healthcheck, migration, storage_utils from . import utils from . import scripts from . import completers as compl @@ -247,9 +247,9 @@ When dlm running and quorum is lost, before stop cluster service, should set enable_quorum_fencing=0, enable_quorum_lockspace=0 for dlm config option """ - if utils.is_dlm_running(node) and not utils.cluster_with_quorum(node): + if storage_utils.is_dlm_running(node) and not utils.cluster_with_quorum(node): logger.debug("Quorum is lost; Set enable_quorum_fencing=0 and enable_quorum_lockspace=0 for dlm") - utils.set_dlm_option(peer=node, enable_quorum_fencing=0, enable_quorum_lockspace=0) + storage_utils.set_dlm_option(peer=node, enable_quorum_fencing=0, enable_quorum_lockspace=0) @command.skill_level('administrator') def do_stop(self, context, *args): @@ -270,7 +270,7 @@ cluster_in_maintenance = utils.is_cluster_in_maintenance_mode() for node in node_list[:]: - if cluster_in_maintenance and utils.is_dlm_running(on_node=node): + if cluster_in_maintenance and storage_utils.is_dlm_running(on_node=node): logger.info("The cluster is in maintenance mode and dlm is running on %s", node) logger.error("Stopping pacemaker/corosync will trigger unexpected node fencing when 'dlm_controld' is running in maintenance mode.") node_list.remove(node) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/crmsh/utils.py new/crmsh-5.1.0+20260624.349562dd/crmsh/utils.py --- old/crmsh-5.1.0+20260615.8409ea5a/crmsh/utils.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/crmsh/utils.py 2026-06-24 06:16:33.000000000 +0200 @@ -29,9 +29,7 @@ import selectors import shlex from pathlib import Path -from collections import defaultdict from contextlib import contextmanager, closing -from stat import S_ISBLK from lxml import etree from packaging import version from enum import IntFlag, auto @@ -40,7 +38,7 @@ import crmsh.parallax import crmsh.user_of_host -from . import config, sh, corosync, cibquery +from . import config, sh, corosync, cibquery, storage_utils from . import userdir from . import constants from . import options @@ -54,6 +52,7 @@ from .sh import ShellUtils from .service_manager import ServiceManager + logger = logging.getLogger(__name__) logger_utils = log.LoggerUtils(logger) @@ -2623,36 +2622,6 @@ return [x for x in re.split(reg, string) if x] -def get_non_block_device_nodes(dev, node_list=None) -> list[str]: - """ - Return a list of nodes where the device is not a block device or does not exist - """ - shell = sh.cluster_shell() - cluster_nodes = node_list or [this_node()] - failed_nodes = [] - for node in cluster_nodes: - rc, _, _ = shell.get_rc_stdout_stderr_without_input( - node, - f"test -b {shlex.quote(dev)}" - ) - if rc != 0: - failed_nodes.append(node) - return failed_nodes - - -def detect_duplicate_device_path(device_list: typing.List[str]): - """ - Resolve device path and check if there are duplicated device path - """ - path_dict = defaultdict(list) - for dev in device_list: - resolved_path = Path(dev).resolve() - path_dict[resolved_path].append(dev) - for path, dev_list in path_dict.items(): - if len(dev_list) > 1: - raise ValueError(f"Duplicated device path detected: {','.join(dev_list)}. They are all pointing to {path}") - - def has_fence_device_registered(): """ Check if any fence device registered @@ -2663,22 +2632,6 @@ return has_fence_device or using_diskless_sbd -def has_disk_mounted(dev): - """ - Check if device already mounted - """ - out = sh.cluster_shell().get_stdout_or_raise_error("mount") - return re.search("\n{} on ".format(dev), out) is not None - - -def has_mount_point_used(directory): - """ - Check if mount directory already mounted - """ - out = sh.cluster_shell().get_stdout_or_raise_error("mount") - return re.search(" on {}".format(directory), out) is not None - - def all_exist_id(): """ Get current exist id list @@ -2706,94 +2659,6 @@ return unused_id -def get_all_vg_name(): - """ - Get all available VGs - """ - out = sh.cluster_shell().get_stdout_or_raise_error("vgdisplay") - return re.findall(r"VG Name\s+(.*)", out) - - -def get_pe_number(vg_id): - """ - Get pe number - """ - output = sh.cluster_shell().get_stdout_or_raise_error("vgdisplay {}".format(vg_id)) - res = re.search(r"Total PE\s+(\d+)", output) - if not res: - raise ValueError("Cannot find PE on VG({})".format(vg_id)) - return int(res.group(1)) - - -def has_dev_partitioned(dev, peer=None): - """ - Check if device has partitions - """ - return len(get_dev_info(dev, "NAME", peer=peer).splitlines()) > 1 - - -def get_dev_uuid(dev, peer=None): - """ - Get UUID of device on local or peer node - """ - out = get_dev_info(dev, "UUID", peer=peer).splitlines() - return out[0] if out else get_dev_uuid_2(dev, peer) - - -def get_dev_uuid_2(dev, peer=None): - """ - Get UUID of device using blkid - """ - out = sh.cluster_shell().get_stdout_or_raise_error("blkid {}".format(dev), peer) - res = re.search("UUID=\"(.*?)\"", out) - return res.group(1) if res else None - - -def get_dev_fs_type(dev, peer=None): - """ - Get filesystem type of device - """ - return get_dev_info(dev, "FSTYPE", peer=peer) - - -def get_dev_info(dev, *_type, peer=None): - """ - Get device info using lsblk - """ - cmd = "lsblk -fno {} {}".format(','.join(_type), dev) - return sh.cluster_shell().get_stdout_or_raise_error(cmd, peer) - - -def is_dev_used_for_lvm(dev, peer=None): - """ - Check if device is LV - """ - return "lvm" in get_dev_info(dev, "TYPE", peer=peer) - - -def is_dev_a_plain_raw_disk_or_partition(dev, peer=None): - """ - Check if device is a raw disk or partition - """ - out = get_dev_info(dev, "TYPE", peer=peer) - return re.search("(disk|part)", out) is not None - - -def compare_uuid_with_peer_dev(dev_list, peer): - """ - Check if device UUID is the same with peer's device - """ - for dev in dev_list: - local_uuid = get_dev_uuid(dev) - if not local_uuid: - raise ValueError("Cannot find UUID for {} on local".format(dev)) - peer_uuid = get_dev_uuid(dev, peer) - if not peer_uuid: - raise ValueError("Cannot find UUID for {} on {}".format(dev, peer)) - if local_uuid != peer_uuid: - raise ValueError("UUID of {} not same with peer {}".format(dev, peer)) - - def append_res_to_group(group_id, res_id): """ Append resource to exist group @@ -2829,41 +2694,6 @@ raise ValueError(error_msg) -def get_dlm_option_dict(peer=None): - """ - Get dlm config option dictionary - """ - out = sh.cluster_shell().get_stdout_or_raise_error("dlm_tool dump_config", peer) - return dict(re.findall(r"(\w+)=(\w+)", out)) - - -def set_dlm_option(peer=None, **kargs): - """ - Set dlm option - """ - shell = sh.cluster_shell() - dlm_option_dict = get_dlm_option_dict(peer=peer) - for option, value in kargs.items(): - if option not in dlm_option_dict: - raise ValueError(f'"{option}" is not dlm config option') - if dlm_option_dict[option] != value: - shell.get_stdout_or_raise_error(f'dlm_tool set_config "{option}={value}"', peer) - - -def is_dlm_running(peer=None, on_node=None): - """ - Check if dlm ra controld is running - """ - return xmlutil.CrmMonXmlParser(peer).is_resource_started(constants.DLM_CONTROLD_RA, node=on_node) - - -def is_dlm_configured(peer=None): - """ - Check if dlm configured - """ - return xmlutil.CrmMonXmlParser(peer).is_resource_configured(constants.DLM_CONTROLD_RA) - - def cluster_with_quorum(peer=None): """ Check if current cluster has quorum @@ -2994,17 +2824,6 @@ yield False -def check_no_quorum_policy_with_dlm(): - """ - Give warning when no-quorum-policy not freeze while configured DLM - """ - if not is_dlm_configured(): - return - res = get_property("no-quorum-policy") - if not res or res != "freeze": - logger.warning("The DLM cluster best practice suggests to set the cluster property \"no-quorum-policy=freeze\"") - - def set_property(property_name, property_value, property_type="crm_config", conditional=False): """ Set property for cluster, resource and operator @@ -3460,7 +3279,7 @@ if not crm_mon_parser.is_non_stonith_resource_running(): return True elif in_maintenance_mode: - if is_dlm_running(): + if storage_utils.is_dlm_running(): dlm_related_ids = crm_mon_parser.get_resource_top_parent_id_set_via_type(constants.DLM_CONTROLD_RA) logger.warning("Please stop DLM related resources (%s) and try again", ', '.join(dlm_related_ids)) return False @@ -3634,56 +3453,4 @@ return not self._resolve_res.using_deprecated -@dataclass(frozen=True) -class DeviceInfo: - device: str - parent_device: str|None - under_multipath: bool - - -class MultipathInspector: - def __init__(self, dev): - self._shell = sh.cluster_shell() - self._device_info = self._inspect(dev) - - def _get_parent_device(self, dev) -> str: - resolved = Path(dev).resolve() - cmd = f"lsblk -dn -o PKNAME {shlex.quote(str(resolved))}" - _, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, cmd) - return out or resolved.name - - def _get_multipath_mapping(self) -> dict[str, str]: - cmd = "multipathd show paths format \"%d %m\"" - rc, out, _ = self._shell.get_rc_stdout_stderr_without_input(None, cmd) - mapping = dict() - if rc != 0: - return mapping - for line in out.splitlines(): - parts = line.split() - if len(parts) < 2: - continue - dev_name, map_name = parts[0], parts[1] - if (dev_name, map_name) == ("dev", "multipath"): - continue - mapping[dev_name] = map_name - return mapping - - def _inspect(self, dev: str) -> DeviceInfo: - parent = self._get_parent_device(dev) - mapping = self._get_multipath_mapping() - return DeviceInfo( - device=dev, - parent_device=parent, - under_multipath=parent in mapping - ) - - def _is_under_multipath(self) -> bool: - return self._device_info.under_multipath - - @classmethod - def check_device_under_multipath(cls, dev): - inspector = cls(dev) - if inspector._is_under_multipath(): - error_msg = f"Device {dev} is under multipath, please provide the multipath device instead" - raise ValueError(error_msg) # vim:ts=4:sw=4:et: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/test/unittests/test_cluster_fs.py new/crmsh-5.1.0+20260624.349562dd/test/unittests/test_cluster_fs.py --- old/crmsh-5.1.0+20260615.8409ea5a/test/unittests/test_cluster_fs.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/test/unittests/test_cluster_fs.py 2026-06-24 06:16:33.000000000 +0200 @@ -81,7 +81,7 @@ self.multi_gfs2_devices_without_clvm2._verify_options() self.assertIn("Without Cluster LVM2 (-C option), -g option only support one device", str(context.exception)) - @mock.patch("crmsh.utils.has_mount_point_used") + @mock.patch("crmsh.cluster_fs.storage_utils.has_mount_point_used") def test_verify_options_mount_point(self, mock_has_mount_point_used): mock_has_mount_point_used.return_value = True with self.assertRaises(cluster_fs.Error) as context: @@ -89,7 +89,7 @@ self.assertIn("Mount point /mnt/gfs2 already mounted", str(context.exception)) @mock.patch("crmsh.utils.list_cluster_nodes") - @mock.patch("crmsh.utils.get_non_block_device_nodes") + @mock.patch("crmsh.cluster_fs.storage_utils.get_non_block_device_nodes") def test_verify_devices_not_block_device(self, mock_get_non_block_device_nodes, mock_list_cluster_nodes): mock_list_cluster_nodes.return_value = ["node1", "node2"] mock_get_non_block_device_nodes.return_value = ["node1"] @@ -97,8 +97,8 @@ self.ocfs2_instance_one_device._verify_devices() self.assertIn("/dev/sda1 is not a block device on node1", str(context.exception)) - @mock.patch("crmsh.utils.is_dev_used_for_lvm") - @mock.patch("crmsh.utils.get_non_block_device_nodes") + @mock.patch("crmsh.cluster_fs.storage_utils.is_dev_used_for_lvm") + @mock.patch("crmsh.cluster_fs.storage_utils.get_non_block_device_nodes") def test_verify_devices_clvm2_with_lv(self, mock_get_non_block_device_nodes, mock_is_dev_used_for_lvm): mock_get_non_block_device_nodes.return_value = [] mock_is_dev_used_for_lvm.return_value = True @@ -106,9 +106,9 @@ self.gfs2_instance_one_device_clvm2._verify_devices() self.assertIn("/dev/sda1 is a Logical Volume, cannot be used with the -C option", str(context.exception)) - @mock.patch("crmsh.utils.has_disk_mounted") - @mock.patch("crmsh.utils.is_dev_used_for_lvm") - @mock.patch("crmsh.utils.get_non_block_device_nodes") + @mock.patch("crmsh.cluster_fs.storage_utils.has_disk_mounted") + @mock.patch("crmsh.cluster_fs.storage_utils.is_dev_used_for_lvm") + @mock.patch("crmsh.cluster_fs.storage_utils.get_non_block_device_nodes") def test_verify_devices_already_mounted(self, mock_get_non_block_device_nodes, mock_is_dev_used_for_lvm, mock_has_disk_mounted): mock_get_non_block_device_nodes.return_value = [] mock_is_dev_used_for_lvm.return_value = False @@ -156,7 +156,7 @@ self.assertEqual(str(context.exception), '/dev/sda1 cannot be the same with SBD device') @mock.patch("crmsh.bootstrap.confirm") - @mock.patch("crmsh.utils.has_dev_partitioned") + @mock.patch("crmsh.cluster_fs.storage_utils.has_dev_partitioned") def test_confirm_to_overwrite_device_no_overwrite(self, mock_has_dev_partitioned, mock_confirm): mock_has_dev_partitioned.return_value = True mock_confirm.return_value = False @@ -166,8 +166,8 @@ @mock.patch("crmsh.sh.cluster_shell") @mock.patch("crmsh.bootstrap.confirm") - @mock.patch("crmsh.utils.get_dev_fs_type") - @mock.patch("crmsh.utils.has_dev_partitioned") + @mock.patch("crmsh.cluster_fs.storage_utils.get_dev_fs_type") + @mock.patch("crmsh.cluster_fs.storage_utils.has_dev_partitioned") def test_confirm_to_overwrite_device(self, mock_has_dev_partitioned, mock_get_dev_fs_type, mock_confirm, mock_cluster_shell): mock_has_dev_partitioned.return_value = False mock_get_dev_fs_type.return_value = "ext4" @@ -296,8 +296,8 @@ self.ocfs2_instance_one_device.join("node1") mock_status_long.assert_not_called() - @mock.patch("crmsh.utils.compare_uuid_with_peer_dev") - @mock.patch("crmsh.utils.is_dev_a_plain_raw_disk_or_partition") + @mock.patch("crmsh.cluster_fs.storage_utils.compare_uuid_with_peer_dev") + @mock.patch("crmsh.cluster_fs.storage_utils.is_dev_a_plain_raw_disk_or_partition") @mock.patch("crmsh.xmlutil.CrmMonXmlParser") @mock.patch("crmsh.log.LoggerUtils.status_long") def test_join(self, mock_status_long, mock_crmmonxmlparser, mock_is_dev_a_plain_raw_disk_or_partition, mock_compare_uuid_with_peer_dev): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/test/unittests/test_sbd.py new/crmsh-5.1.0+20260624.349562dd/test/unittests/test_sbd.py --- old/crmsh-5.1.0+20260615.8409ea5a/test/unittests/test_sbd.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/test/unittests/test_sbd.py 2026-06-24 06:16:33.000000000 +0200 @@ -59,7 +59,7 @@ with self.assertRaises(ValueError): SBDUtils.compare_device_uuid("/dev/sbd_device", ["node1"]) - @patch('crmsh.utils.get_non_block_device_nodes') + @patch('crmsh.sbd.storage_utils.get_non_block_device_nodes') @patch('crmsh.sbd.SBDUtils.compare_device_uuid') def test_verify_sbd_device_exceeds_max(self, mock_compare_device_uuid, mock_get_non_block_device_nodes): dev_list = [f"/dev/sbd_device_{i}" for i in range(SBDManager.SBD_DEVICE_MAX + 1)] @@ -67,7 +67,7 @@ SBDUtils.verify_sbd_device(dev_list) self.assertTrue(f"Maximum number of SBD device is {SBDManager.SBD_DEVICE_MAX}" in str(context.exception)) - @patch('crmsh.utils.get_non_block_device_nodes') + @patch('crmsh.sbd.storage_utils.get_non_block_device_nodes') @patch('crmsh.sbd.SBDUtils.compare_device_uuid') def test_verify_sbd_device_non_block(self, mock_compare_device_uuid, mock_get_non_block_device_nodes): mock_get_non_block_device_nodes.return_value = ["node1"] @@ -75,7 +75,7 @@ SBDUtils.verify_sbd_device(["/dev/not_a_block_device"]) self.assertTrue(f"/dev/not_a_block_device is not a block device on node1" in str(context.exception)) - @patch('crmsh.utils.get_non_block_device_nodes') + @patch('crmsh.sbd.storage_utils.get_non_block_device_nodes') @patch('crmsh.sbd.SBDUtils.compare_device_uuid') def test_verify_sbd_device_valid(self, mock_compare_device_uuid, mock_get_non_block_device_nodes): mock_get_non_block_device_nodes.return_value = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/test/unittests/test_utils.py new/crmsh-5.1.0+20260624.349562dd/test/unittests/test_utils.py --- old/crmsh-5.1.0+20260615.8409ea5a/test/unittests/test_utils.py 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/test/unittests/test_utils.py 2026-06-24 06:16:33.000000000 +0200 @@ -13,7 +13,7 @@ from itertools import chain import crmsh.utils -from crmsh import utils, config, tmpfiles, constants, options +from crmsh import utils, storage_utils, config, tmpfiles, constants, options logging.basicConfig(level=logging.DEBUG) @@ -756,31 +756,31 @@ assert utils.re_split_string('[; ]', "/dev/sda1 ") == ["/dev/sda1"] [email protected]('crmsh.utils.get_dev_info') [email protected]('crmsh.storage_utils.get_dev_info') def test_has_dev_partitioned(mock_get_dev_info): mock_get_dev_info.return_value = """ disk part """ - res = utils.has_dev_partitioned("/dev/sda1") + res = storage_utils.has_dev_partitioned("/dev/sda1") assert res is True mock_get_dev_info.assert_called_once_with("/dev/sda1", "NAME", peer=None) [email protected]('crmsh.utils.get_dev_uuid') [email protected]('crmsh.storage_utils.get_dev_uuid') def test_compare_uuid_with_peer_dev_cannot_find_local(mock_get_dev_uuid): mock_get_dev_uuid.return_value = "" with pytest.raises(ValueError) as err: - utils.compare_uuid_with_peer_dev(["/dev/sdb1"], "node2") + storage_utils.compare_uuid_with_peer_dev(["/dev/sdb1"], "node2") assert str(err.value) == "Cannot find UUID for /dev/sdb1 on local" mock_get_dev_uuid.assert_called_once_with("/dev/sdb1") [email protected]('crmsh.utils.get_dev_uuid') [email protected]('crmsh.storage_utils.get_dev_uuid') def test_compare_uuid_with_peer_dev_cannot_find_peer(mock_get_dev_uuid): mock_get_dev_uuid.side_effect = ["1234", ""] with pytest.raises(ValueError) as err: - utils.compare_uuid_with_peer_dev(["/dev/sdb1"], "node2") + storage_utils.compare_uuid_with_peer_dev(["/dev/sdb1"], "node2") assert str(err.value) == "Cannot find UUID for /dev/sdb1 on node2" mock_get_dev_uuid.assert_has_calls([ mock.call("/dev/sdb1"), @@ -788,11 +788,11 @@ ]) [email protected]('crmsh.utils.get_dev_uuid') [email protected]('crmsh.storage_utils.get_dev_uuid') def test_compare_uuid_with_peer_dev(mock_get_dev_uuid): mock_get_dev_uuid.side_effect = ["1234", "5678"] with pytest.raises(ValueError) as err: - utils.compare_uuid_with_peer_dev(["/dev/sdb1"], "node2") + storage_utils.compare_uuid_with_peer_dev(["/dev/sdb1"], "node2") assert str(err.value) == "UUID of /dev/sdb1 not same with peer node2" mock_get_dev_uuid.assert_has_calls([ mock.call("/dev/sdb1"), @@ -800,18 +800,18 @@ ]) [email protected]('crmsh.utils.get_dev_info') [email protected]('crmsh.storage_utils.get_dev_info') def test_is_dev_used_for_lvm(mock_dev_info): mock_dev_info.return_value = "lvm" - res = utils.is_dev_used_for_lvm("/dev/sda1") + res = storage_utils.is_dev_used_for_lvm("/dev/sda1") assert res is True mock_dev_info.assert_called_once_with("/dev/sda1", "TYPE", peer=None) [email protected]('crmsh.utils.get_dev_info') [email protected]('crmsh.storage_utils.get_dev_info') def test_is_dev_a_plain_raw_disk_or_partition(mock_dev_info): mock_dev_info.return_value = "raid1\nlvm" - res = utils.is_dev_a_plain_raw_disk_or_partition("/dev/md127") + res = storage_utils.is_dev_a_plain_raw_disk_or_partition("/dev/md127") assert res is False mock_dev_info.assert_called_once_with("/dev/md127", "TYPE", peer=None) @@ -819,23 +819,23 @@ @mock.patch('crmsh.sh.ClusterShell.get_stdout_or_raise_error') def test_get_dev_info(mock_run): mock_run.return_value = "data" - res = utils.get_dev_info("/dev/sda1", "TYPE") + res = storage_utils.get_dev_info("/dev/sda1", "TYPE") assert res == "data" mock_run.assert_called_once_with("lsblk -fno TYPE /dev/sda1", None) [email protected]('crmsh.utils.get_dev_info') [email protected]('crmsh.storage_utils.get_dev_info') def test_get_dev_fs_type(mock_get_info): mock_get_info.return_value = "data" - res = utils.get_dev_fs_type("/dev/sda1") + res = storage_utils.get_dev_fs_type("/dev/sda1") assert res == "data" mock_get_info.assert_called_once_with("/dev/sda1", "FSTYPE", peer=None) [email protected]('crmsh.utils.get_dev_info') [email protected]('crmsh.storage_utils.get_dev_info') def test_get_dev_uuid(mock_get_info): mock_get_info.return_value = "uuid" - res = utils.get_dev_uuid("/dev/sda1") + res = storage_utils.get_dev_uuid("/dev/sda1") assert res == "uuid" mock_get_info.assert_called_once_with("/dev/sda1", "UUID", peer=None) @@ -844,7 +844,7 @@ def test_get_pe_number_except(mock_run): mock_run.return_value = "data" with pytest.raises(ValueError) as err: - utils.get_pe_number("vg1") + storage_utils.get_pe_number("vg1") assert str(err.value) == "Cannot find PE on VG(vg1)" mock_run.assert_called_once_with("vgdisplay vg1") @@ -856,7 +856,7 @@ Total PE 1534 Alloc PE / Size 1534 / 5.99 GiB """ - res = utils.get_pe_number("vg1") + res = storage_utils.get_pe_number("vg1") assert res == 1534 mock_run.assert_called_once_with("vgdisplay vg1") @@ -868,7 +868,7 @@ VG Name ocfs2-vg System ID """ - res = utils.get_all_vg_name() + res = storage_utils.get_all_vg_name() assert res == ["ocfs2-vg"] mock_run.assert_called_once_with("vgdisplay") @@ -908,7 +908,7 @@ /dev/vda2 on /opt type btrfs (rw,relatime,space_cache,subvolid=263,subvol=/@/opt) /dev/vda2 on /var/lib/docker/btrfs type btrfs (rw,relatime,space_cache,subvolid=258,subvol=/@/var) """ - res = utils.has_mount_point_used("/opt") + res = storage_utils.has_mount_point_used("/opt") assert res is True mock_run.assert_called_once_with("mount") @@ -920,7 +920,7 @@ /dev/vda2 on /opt type btrfs (rw,relatime,space_cache,subvolid=263,subvol=/@/opt) /dev/vda2 on /var/lib/docker/btrfs type btrfs (rw,relatime,space_cache,subvolid=258,subvol=/@/var) """ - res = utils.has_disk_mounted("/dev/vda2") + res = storage_utils.has_disk_mounted("/dev/vda2") assert res is True mock_run.assert_called_once_with("mount") @@ -944,7 +944,7 @@ mock_cluster_shell_inst = mock.Mock() mock_cluster_shell.return_value = mock_cluster_shell_inst mock_cluster_shell_inst.get_rc_stdout_stderr_without_input.return_value = (1, None, None) - res = utils.get_non_block_device_nodes("/dev/sda1", ["node1"]) + res = storage_utils.get_non_block_device_nodes("/dev/sda1", ["node1"]) assert res == ["node1"] mock_cluster_shell_inst.get_rc_stdout_stderr_without_input.assert_called_once_with("node1", "test -b /dev/sda1") @@ -995,7 +995,7 @@ key1=value1 key2=value2 """ - res_dict = utils.get_dlm_option_dict() + res_dict = storage_utils.get_dlm_option_dict() assert res_dict == { "key1": "value1", "key2": "value2" @@ -1003,19 +1003,19 @@ mock_run_inst.get_stdout_or_raise_error.assert_called_once_with("dlm_tool dump_config", None) [email protected]('crmsh.utils.get_dlm_option_dict') [email protected]('crmsh.storage_utils.get_dlm_option_dict') def test_set_dlm_option_exception(mock_get_dict): mock_get_dict.return_value = { "key1": "value1", "key2": "value2" } with pytest.raises(ValueError) as err: - utils.set_dlm_option(name="xin") + storage_utils.set_dlm_option(name="xin") assert str(err.value) == '"name" is not dlm config option' @mock.patch('crmsh.sh.cluster_shell') [email protected]('crmsh.utils.get_dlm_option_dict') [email protected]('crmsh.storage_utils.get_dlm_option_dict') def test_set_dlm_option(mock_get_dict, mock_run): mock_run_inst = mock.Mock() mock_run.return_value = mock_run_inst @@ -1023,7 +1023,7 @@ "key1": "value1", "key2": "value2" } - utils.set_dlm_option(key2="test") + storage_utils.set_dlm_option(key2="test") mock_run_inst.get_stdout_or_raise_error.assert_called_once_with('dlm_tool set_config "key2=test"', None) @@ -1032,7 +1032,7 @@ mock_crmmon_inst = mock.Mock() mock_crmmon.return_value = mock_crmmon_inst mock_crmmon_inst.is_resource_configured.return_value = True - assert utils.is_dlm_configured() is True + assert storage_utils.is_dlm_configured() is True mock_crmmon_inst.is_resource_configured.assert_called_once_with(constants.DLM_CONTROLD_RA) @@ -1150,20 +1150,20 @@ mock_msec.assert_has_calls([mock.call("10s"), mock.call("10")]) [email protected]('crmsh.utils.is_dlm_configured') [email protected]('crmsh.storage_utils.is_dlm_configured') def test_check_no_quorum_policy_with_dlm_return(mock_dlm): mock_dlm.return_value = False - utils.check_no_quorum_policy_with_dlm() + storage_utils.check_no_quorum_policy_with_dlm() mock_dlm.assert_called_once_with() @mock.patch('logging.Logger.warning') @mock.patch('crmsh.utils.get_property') [email protected]('crmsh.utils.is_dlm_configured') [email protected]('crmsh.storage_utils.is_dlm_configured') def test_check_no_quorum_policy_with_dlm(mock_dlm, mock_get_property, mock_warn): mock_dlm.return_value = True mock_get_property.return_value = "stop" - utils.check_no_quorum_policy_with_dlm() + storage_utils.check_no_quorum_policy_with_dlm() mock_dlm.assert_called_once_with() mock_get_property.assert_called_once_with("no-quorum-policy") mock_warn.assert_called_once_with('The DLM cluster best practice suggests to set the cluster property "no-quorum-policy=freeze"') @@ -1492,7 +1492,7 @@ (0, "dev multipath\nsda mpatha", "") # multipathd show paths output ] - inspector = utils.MultipathInspector("/dev/sda1") + inspector = storage_utils.MultipathInspector("/dev/sda1") assert inspector._shell == mock_shell_inst assert inspector._device_info.device == "/dev/sda1" @@ -1510,7 +1510,7 @@ (0, "sda", "") # lsblk output for test call ] - inspector = utils.MultipathInspector("/dev/sda1") + inspector = storage_utils.MultipathInspector("/dev/sda1") parent = inspector._get_parent_device("/dev/sda1") assert parent == "sda" @@ -1530,7 +1530,7 @@ (0, multipathd_output, "") # multipathd for test call ] - inspector = utils.MultipathInspector("/dev/sda1") + inspector = storage_utils.MultipathInspector("/dev/sda1") mapping = inspector._get_multipath_mapping() assert mapping == {"sda": "mpatha", "sdb": "mpatha", "sdc": "mpathb"} @@ -1545,7 +1545,7 @@ (0, "dev multipath\nsda mpatha", "") ] - inspector = utils.MultipathInspector("/dev/sda1") + inspector = storage_utils.MultipathInspector("/dev/sda1") device_info = inspector._device_info assert device_info.device == "/dev/sda1" @@ -1562,7 +1562,7 @@ (0, "dev multipath\nsda mpatha", "") ] - inspector = utils.MultipathInspector("/dev/sda1") + inspector = storage_utils.MultipathInspector("/dev/sda1") assert inspector._is_under_multipath() is True @@ -1577,6 +1577,6 @@ ] with pytest.raises(ValueError) as exc_info: - utils.MultipathInspector.check_device_under_multipath("/dev/sda1") + storage_utils.MultipathInspector.check_device_under_multipath("/dev/sda1") assert str(exc_info.value) == "Device /dev/sda1 is under multipath, please provide the multipath device instead" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260615.8409ea5a/tox.ini new/crmsh-5.1.0+20260624.349562dd/tox.ini --- old/crmsh-5.1.0+20260615.8409ea5a/tox.ini 2026-06-15 11:35:01.000000000 +0200 +++ new/crmsh-5.1.0+20260624.349562dd/tox.ini 2026-06-24 06:16:33.000000000 +0200 @@ -8,7 +8,7 @@ deps = pytest pytest-cov -commands = pytest -vv --cov=crmsh --cov-config .coveragerc --cov-report term --cov-report xml {posargs} +commands = pytest --cov=crmsh --cov-config .coveragerc --cov-report xml {posargs} [testenv] changedir = {[base]changedir}
