Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2023-03-02 23:04:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.31432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Thu Mar 2 23:04:05 2023 rev:282 rq:1068615 version:4.4.1+20230302.2b5310b9 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2023-02-24 18:08:27.793532494 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.31432/crmsh.changes 2023-03-02 23:04:29.864151935 +0100 @@ -1,0 +2,34 @@ +Thu Mar 02 06:37:38 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230302.2b5310b9: + * Dev: unittest: Adjust unit test for previous change + * Dev: bootstrap: Add sudo before crm_node under non-root user on remote node + +------------------------------------------------------------------- +Thu Mar 02 02:23:26 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230302.fc282490: + * Dev: behave: Create user alice on qnetd node + * Dev: behave: don't build crmsh code on qnetd node + +------------------------------------------------------------------- +Thu Mar 02 01:36:34 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230302.2ed0ab14: + * Dev: unittest: Adjust unit test for previous changes + * Fix: qdevice: Unable to setup qdevice under non-root user (bsc#1208770) + +------------------------------------------------------------------- +Tue Feb 28 10:07:22 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230228.2f852310: + * Dev: utils: Suppress the output of ssh-copy-id for non-root user case + +------------------------------------------------------------------- +Mon Feb 27 01:58:59 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230227.b420cbf5: + * Dev: unittest: Adjust unit test for previous change + * Dev: utils: Avoid using magic number + +------------------------------------------------------------------- Old: ---- crmsh-4.4.1+20230224.498677ab.tar.bz2 New: ---- crmsh-4.4.1+20230302.2b5310b9.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.WYsAMV/_old 2023-03-02 23:04:30.856156890 +0100 +++ /var/tmp/diff_new_pack.WYsAMV/_new 2023-03-02 23:04:30.860156911 +0100 @@ -36,7 +36,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 4.4.1+20230224.498677ab +Version: 4.4.1+20230302.2b5310b9 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.WYsAMV/_old 2023-03-02 23:04:30.916157190 +0100 +++ /var/tmp/diff_new_pack.WYsAMV/_new 2023-03-02 23:04:30.920157210 +0100 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">498677abb1bf88b1490339d307e925742854080b</param> + <param name="changesrevision">e2961b896d4155fb6205fe9f6c628a572ae61f93</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-4.4.1+20230224.498677ab.tar.bz2 -> crmsh-4.4.1+20230302.2b5310b9.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/crmsh/bootstrap.py new/crmsh-4.4.1+20230302.2b5310b9/crmsh/bootstrap.py --- old/crmsh-4.4.1+20230224.498677ab/crmsh/bootstrap.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/crmsh/bootstrap.py 2023-03-02 07:05:25.000000000 +0100 @@ -157,12 +157,20 @@ """ if not self.qnetd_addr: return + parts = self.qnetd_addr.split('@', 2) + if len(parts) == 2: + ssh_user = parts[0] + qnetd_host = parts[1] + else: + ssh_user = None + qnetd_host = self.qnetd_addr self.qdevice_inst = qdevice.QDevice( - self.qnetd_addr, + qnetd_host, port=self.qdevice_port, algo=self.qdevice_algo, tie_breaker=self.qdevice_tie_breaker, tls=self.qdevice_tls, + ssh_user=ssh_user, cmds=self.qdevice_heuristics, mode=self.qdevice_heuristics_mode, is_stage=self.stage == "qdevice") @@ -867,7 +875,9 @@ # If not use -N/--nodes option if not node_list: - _save_core_hosts([local_user], [utils.this_node()], sync_to_remote=False) + user_by_host = utils.HostUserConfig() + user_by_host.add(local_user, utils.this_node()) + user_by_host.save_local() return print() @@ -903,14 +913,11 @@ ) if result.returncode != 0: utils.fatal('Failed to add public keys to {}@{}: {}'.format(remote_user, node, result.stdout)) - if utils.this_node() not in node_list: - _user_list = [local_user] - _user_list.extend(user_list) - _node_list = [utils.this_node()] - _node_list.extend(node_list) - _save_core_hosts(_user_list, _node_list, sync_to_remote=True) - else: - _save_core_hosts(user_list, node_list, sync_to_remote=True) + user_by_host = utils.HostUserConfig() + for user, node in zip(user_list, node_list): + user_by_host.add(user, node) + user_by_host.add(local_user, utils.this_node()) + user_by_host.save_remote(node_list) def _merge_authorized_keys(keys: typing.List[str]) -> bytes: @@ -925,33 +932,7 @@ return buf -def _save_core_hosts(user_list: typing.List[str], host_list: typing.List[str], sync_to_remote: bool): - value = [f'{user}@{host}' for user, host in zip(user_list, host_list)] - config.set_option('core', 'hosts', value) - # TODO: it is saved in ~root/.config/crm/crm.conf, is it as suitable path? - config.save() - if sync_to_remote: - assert "'" not in value - crmsh.parallax.parallax_call(host_list, "crm options set core.hosts '{}'".format(', '.join(value))) - - -def _load_core_hosts() -> typing.Optional[typing.Tuple[typing.List[str], typing.List[str]]]: - users = list() - hosts = list() - li = config.get_option('core', 'hosts') - if li == ['']: - return users, hosts - for s in li: - parts = s.split('@', 2) - if len(parts) != 2: - utils.fatal('Malformed config core.hosts: {}'.format(s)) - users.append(parts[0]) - hosts.append(parts[1]) - return users, hosts - - def _fetch_core_hosts(local_user, remote_user, remote_host) -> typing.Tuple[typing.List[str], typing.List[str]]: - # FIXME: when the remote_host is initialized with -d, this cmd will print extra debug log to stdout cmd = 'crm options show core.hosts' result = utils.su_subprocess_run( local_user, @@ -1604,12 +1585,21 @@ qdevice_heuristics_mode = prompt_for_string("MODE of operation of heuristics (on/sync/off)", default="sync", valid_func=qdevice.QDevice.check_qdevice_heuristics_mode) if qdevice_heuristics else None + parts = qnetd_addr.split('@', 2) + if len(parts) == 2: + ssh_user = parts[0] + qnetd_host = parts[1] + else: + ssh_user = None + qnetd_host = qnetd_addr + _context.qdevice_inst = qdevice.QDevice( - qnetd_addr, + qnetd_host, port=qdevice_port, algo=qdevice_algo, tie_breaker=qdevice_tie_breaker, tls=qdevice_tls, + ssh_user=ssh_user, cmds=qdevice_heuristics, mode=qdevice_heuristics_mode, is_stage=_context.stage == "qdevice") @@ -1626,24 +1616,20 @@ utils.disable_service("corosync-qdevice.service") return logger.info("""Configure Qdevice/Qnetd:""") - for node in utils.list_cluster_nodes(): + cluster_node_list = utils.list_cluster_nodes() + for node in cluster_node_list: if not utils.service_is_available("corosync-qdevice.service", node): utils.fatal("corosync-qdevice.service is not available on {}".format(node)) qdevice_inst = _context.qdevice_inst qnetd_addr = qdevice_inst.qnetd_addr + ssh_user = qdevice_inst.ssh_user if qdevice_inst.ssh_user is not None else _context.current_user # Configure ssh passwordless to qnetd if detect password is needed local_user = utils.user_of(utils.this_node()) - # TODO: ssh to qnetd with a non-root user - assert '@' not in qnetd_addr - remote_user = 'root' - remote_host = qnetd_addr - if utils.check_ssh_passwd_need(local_user, remote_user, remote_host): - logger.info("Copy ssh key to qnetd node({}@{})".format(remote_user, remote_host)) - utils.ssh_copy_id(local_user, remote_user, remote_host) - user_list, host_list = _load_core_hosts() - user_list.append(remote_user) - host_list.append(remote_host) - _save_core_hosts(user_list, host_list, sync_to_remote=True) + if utils.check_ssh_passwd_need(local_user, ssh_user, qnetd_addr): + utils.ssh_copy_id(local_user, ssh_user, qnetd_addr) + user_by_host = utils.HostUserConfig() + user_by_host.add(ssh_user, qnetd_addr) + user_by_host.save_remote(cluster_node_list) # Start qdevice service if qdevice already configured if utils.is_qdevice_configured() and not confirm("Qdevice is already configured - overwrite?"): qdevice_inst.start_qdevice_service() @@ -1698,11 +1684,11 @@ ), local_user, ) - user_list = [_context.current_user] - user_list.extend(_context.user_list) - node_list = [utils.this_node()] - node_list.extend(_context.node_list) - _save_core_hosts(user_list, node_list, sync_to_remote=False) + user_by_host = utils.HostUserConfig() + for user, node in zip(_context.user_list, _context.node_list): + user_by_host.add(user, node) + user_by_host.add(local_user, utils.this_node()) + user_by_host.save_local() def swap_public_ssh_key( @@ -1941,7 +1927,7 @@ # Fetch cluster nodes list remote_user = _context.user_list[0] local_user = _context.current_user - cmd = 'ssh {} {}@{} PATH=\\"\\$PATH\\":/usr/sbin:/sbin crm_node -l'.format(SSH_OPTION, remote_user, init_node) + cmd = f'ssh {SSH_OPTION} {remote_user}@{init_node} sudo crm_node -l' rc, out, err = utils.su_get_stdout_stderr(local_user, cmd) if rc != 0: utils.fatal("Can't fetch cluster nodes list from {}: {}".format(init_node, err)) @@ -1962,31 +1948,29 @@ tokens[1], tokens[2])) else: cluster_nodes_list.append(tokens[1]) + user_by_host = utils.HostUserConfig() + user_by_host.add(local_user, utils.this_node()) try: user_list, host_list = _fetch_core_hosts(local_user, remote_user, init_node) + for user, host in zip(user_list, host_list): + user_by_host.add(user, host) except ValueError: # No core.hosts on the seed host, may be a cluster upgraded from previous version - user_list = list() - host_list = list() - user_list.append(local_user) - host_list.append(utils.this_node()) - _save_core_hosts(user_list, host_list, sync_to_remote=False) + pass + user_by_host.save_local() # Filter out init node from cluster_nodes_list cmd = "ssh {} {}@{} hostname".format(SSH_OPTION, remote_user , init_node) rc, out, err = utils.su_get_stdout_stderr(local_user, cmd) if rc != 0: utils.fatal("Can't fetch hostname of {}: {}".format(init_node, err)) - if out in cluster_nodes_list: - cluster_nodes_list.remove(out) - # Swap ssh public key between join node and other cluster nodes - for node in cluster_nodes_list: + for node in (node for node in cluster_nodes_list if node != out): remote_user_to_swap = utils.user_of(node) remote_privileged_user = remote_user_to_swap utils.ssh_copy_id(local_user, remote_privileged_user, node) swap_public_ssh_key(node, local_user, remote_user_to_swap, local_user, remote_privileged_user) - _save_core_hosts(user_list, host_list, sync_to_remote=True) + user_by_host.save_remote(cluster_nodes_list) def sync_files_to_disk(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/crmsh/corosync.py new/crmsh-4.4.1+20230302.2b5310b9/crmsh/corosync.py --- old/crmsh-4.4.1+20230224.498677ab/crmsh/corosync.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/crmsh/corosync.py 2023-03-02 07:05:25.000000000 +0100 @@ -113,7 +113,6 @@ except ValueError: remote_user = 'root' if utils.check_ssh_passwd_need(local_user, remote_user, qnetd_addr): - print("Copy ssh key to qnetd node({})".format(qnetd_addr)) utils.ssh_copy_id(local_user, remote_user, qnetd_addr) cmd = "corosync-qnetd-tool -lv -c {}".format(cluster_name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/crmsh/qdevice.py new/crmsh-4.4.1+20230302.2b5310b9/crmsh/qdevice.py --- old/crmsh-4.4.1+20230224.498677ab/crmsh/qdevice.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/crmsh/qdevice.py 2023-03-02 07:05:25.000000000 +0100 @@ -2,6 +2,9 @@ import re import socket import functools +import subprocess +import tempfile +import typing from enum import Enum from . import constants from . import utils @@ -127,7 +130,7 @@ qdevice_db_path = "/etc/corosync/qdevice/net/nssdb" def __init__(self, qnetd_addr, port=5403, algo="ffsplit", tie_breaker="lowest", - tls="on", cluster_node=None, cmds=None, mode=None, cluster_name=None, is_stage=False): + tls="on", ssh_user=None, cluster_node=None, cmds=None, mode=None, cluster_name=None, is_stage=False): """ Init function """ @@ -136,6 +139,7 @@ self.algo = algo self.tie_breaker = tie_breaker self.tls = tls + self.ssh_user = ssh_user self.cluster_node = cluster_node self.cmds = cmds self.mode = mode @@ -368,7 +372,7 @@ desc = "Step 2: Fetch {} from {}".format(self.qnetd_cacert_filename, self.qnetd_addr) logger_utils.log_only_to_file(desc) - parallax.parallax_slurp([self.qnetd_addr], self.qdevice_path, self.qnetd_cacert_on_qnetd) + self._fetch_file_to_local(self.qnetd_addr, self.qnetd_cacert_on_qnetd, '{}/{}'.format(self.qdevice_path, self.qnetd_addr)) def copy_qnetd_crt_to_cluster(self): """ @@ -382,10 +386,42 @@ desc = "Step 3: Copy exported {} to {}".format(self.qnetd_cacert_filename, node_list) logger_utils.log_only_to_file(desc) - parallax.parallax_copy( - node_list, - os.path.dirname(self.qnetd_cacert_on_local), - self.qdevice_path) + self._copy_file_to_remote_hosts(os.path.dirname(self.qnetd_cacert_on_local), node_list, self.qdevice_path) + + @classmethod + def _copy_file_to_remote_host(cls, local_file, remote_host: str, remote_path): + remote_user = utils.user_of(remote_host) + with tempfile.NamedTemporaryFile('w', encoding='utf-8') as tmp: + tmp.write("put -pr '{}' '{}'\n".format(local_file, remote_path)) + tmp.flush() + # we can not su to a non-root user, since reading the source file may need privilege. + cmd = "sftp {} -o IdentityFile=~{}/.ssh/id_rsa -o BatchMode=yes -s 'sudo PATH=/usr/lib/ssh:/usr/libexec/ssh /bin/sh -c \"exec sftp-server\"' -b {} {}@{}".format( + constants.SSH_OPTION, + utils.user_of(utils.this_node()), + # FIXME: sftp-server is not always in /usr/lib/ssh + tmp.name, + remote_user, cls._enclose_inet6_addr(remote_host), + ) + result = subprocess.run( + ['/bin/sh', '-c', cmd], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + if result.returncode != 0: + utils.fatal('Failed to copy file from {} to {}:{}: {}: {}'.format(local_file, remote_host, remote_path, cmd, utils.to_ascii(result.stdout))) + + @staticmethod + def _enclose_inet6_addr(addr: str): + if ':' in addr: + return f'[{addr}]' + else: + return addr + + @classmethod + def _copy_file_to_remote_hosts(cls, local_file, remote_hosts: typing.Iterable[str], remote_path): + for host in remote_hosts: + cls._copy_file_to_remote_host(local_file, host, remote_path) def init_db_on_cluster(self): """ @@ -420,10 +456,7 @@ """ desc = "Step 6: Copy {} to {}".format(self.qdevice_crq_filename, self.qnetd_addr) logger_utils.log_only_to_file(desc) - parallax.parallax_copy( - [self.qnetd_addr], - self.qdevice_crq_on_local, - self.qdevice_crq_on_qnetd) + self._copy_file_to_remote_host(self.qdevice_crq_on_local, self.qnetd_addr, self.qdevice_crq_on_qnetd) def sign_crq_on_qnetd(self): """ @@ -446,10 +479,21 @@ """ desc = "Step 8: Fetch {} from {}".format(os.path.basename(self.qnetd_cluster_crt_on_qnetd), self.qnetd_addr) logger_utils.log_only_to_file(desc) - parallax.parallax_slurp( - [self.qnetd_addr], - self.qdevice_path, - self.qnetd_cluster_crt_on_qnetd) + self._fetch_file_to_local(self.qnetd_addr, self.qnetd_cluster_crt_on_qnetd, '{}/{}'.format(self.qdevice_path, self.qnetd_addr)) + + @classmethod + def _fetch_file_to_local(cls, remote_host: str, remote_path: str, local_dir: str): + basename = os.path.basename(remote_path) + rc, stdout, stderr = utils.get_stdout_stderr_auto_ssh_no_input( + remote_host, + "cat '{}'".format(remote_path), + raw=True, + ) + if rc != 0: + utils.fatal("Failed to fetch file {} from {}: {}".format(remote_path, remote_host, stderr)) + os.makedirs(local_dir, exist_ok=True) + with open('{}/{}'.format(local_dir, basename), 'wb') as f: + f.write(stdout) def import_cluster_crt(self): """ @@ -474,10 +518,7 @@ desc = "Step 10: Copy {} to {}".format(self.qdevice_p12_filename, node_list) logger_utils.log_only_to_file(desc) - parallax.parallax_copy( - node_list, - self.qdevice_p12_on_local, - os.path.dirname(self.qdevice_p12_on_local)) + self._copy_file_to_remote_hosts(self.qdevice_p12_on_local, node_list, self.qdevice_p12_on_local) def import_p12_on_cluster(self): """ @@ -522,10 +563,7 @@ desc = "Step 1: Fetch {} from {}".format(self.qnetd_cacert_filename, self.cluster_node) logger_utils.log_only_to_file(desc) - parallax.parallax_slurp( - [self.cluster_node], - self.qdevice_path, - self.qnetd_cacert_on_local) + self._fetch_file_to_local(self.cluster_node, self.qnetd_cacert_on_local, '{}/{}'.format(self.qdevice_path, self.cluster_node)) def init_db_on_local(self): """ @@ -552,10 +590,7 @@ desc = "Step 3: Fetch {} from {}".format(self.qdevice_p12_filename, self.cluster_node) logger_utils.log_only_to_file(desc) - parallax.parallax_slurp( - [self.cluster_node], - self.qdevice_path, - self.qdevice_p12_on_local) + self._fetch_file_to_local(self.cluster_node, self.qdevice_p12_on_local, '{}/{}'.format(self.qdevice_path, self.cluster_node)) def import_p12_on_local(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/crmsh/utils.py new/crmsh-4.4.1+20230302.2b5310b9/crmsh/utils.py --- old/crmsh-4.4.1+20230224.498677ab/crmsh/utils.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/crmsh/utils.py 2023-03-02 07:05:25.000000000 +0100 @@ -21,6 +21,7 @@ import argparse import random import string +import grp from pathlib import Path from contextlib import contextmanager, closing from stat import S_ISBLK @@ -116,7 +117,11 @@ if cached is None: ret = self._user_of_impl(host) if ret is None: - return userdir.getuser() + user = userdir.get_sudoer() + if user: + return user + else: + return userdir.getuser() else: self._cache[host] = ret return ret @@ -125,10 +130,13 @@ @staticmethod def _user_of_impl(host): - canonical, aliases, _ = socket.gethostbyaddr(host) - aliases = set(aliases) - aliases.add(canonical) - aliases.add(host) + try: + canonical, aliases, _ = socket.gethostbyaddr(host) + aliases = set(aliases) + aliases.add(canonical) + aliases.add(host) + except socket.herror: + aliases = {host} hosts = config.get_option('core', 'hosts') if hosts == ['']: return None @@ -140,7 +148,7 @@ node = item if node in aliases: return user - logger.warning('Failed to get the user of host %s (aliases: %s). Known hosts are %s', host, aliases, hosts) + logger.debug('Failed to get the user of host %s (aliases: %s). Known hosts are %s', host, aliases, hosts) return None @@ -154,13 +162,8 @@ def ssh_copy_id(local_user, remote_user, remote_node): if check_ssh_passwd_need(local_user, remote_user, remote_node): logger.info("Configuring SSH passwordless with {}@{}".format(remote_user, remote_node)) - cmd = "ssh-copy-id -i ~/.ssh/id_rsa.pub '{}@{}'".format(remote_user, remote_node) - result = su_subprocess_run( - local_user, - cmd, - tty=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + cmd = "ssh-copy-id -i ~/.ssh/id_rsa.pub '{}@{}' &> /dev/null".format(remote_user, remote_node) + result = su_subprocess_run(local_user, cmd, tty=True) if result.returncode != 0: fatal("Failed to login to remote host {}@{}".format(remote_user, remote_node)) @@ -2815,10 +2818,7 @@ cmd = "rpm -q --quiet {}".format(pkg) if remote_addr: # check on remote - print("Check whether {} is installed on {}".format(pkg, remote_addr)) - # FIXME - cmd = "ssh {} {}@{} \"{}\"".format(SSH_OPTION, user_of(remote_addr), remote_addr, cmd) - rc, _, _ = get_stdout_stderr(cmd, no_reg=True) + rc, _, _ = get_stdout_stderr_auto_ssh_no_input(remote_addr, cmd) else: # check on local rc, _ = get_stdout(cmd) @@ -3427,7 +3427,7 @@ """ Check if current user is in haclient group """ - return 90 in os.getgroups() + return constants.HA_GROUP in [grp.getgrgid(g).gr_name for g in os.getgroups()] def check_user_access(level_name): @@ -3456,4 +3456,47 @@ else: logger.error("Please run this command starting with \"sudo\"") raise TerminateSubCommand + + +class HostUserConfig: + """Keep the username used for ssh connection corresponding to each host. + + The data is saved in configuration option `core.hosts`. + """ + def __init__(self): + self._hosts_users = dict() + self.load() + + def load(self): + users = list() + hosts = list() + li = config.get_option('core', 'hosts') + if li == ['']: + self._hosts_users = dict() + return + for s in li: + parts = s.split('@', 2) + if len(parts) != 2: + raise ValueError('Malformed config core.hosts: {}'.format(s)) + users.append(parts[0]) + hosts.append(parts[1]) + self._hosts_users = {host: user for user, host in zip(users, hosts)} + + def save_local(self): + value = [f'{user}@{host}' for host, user in sorted(self._hosts_users.items(), key=lambda x: x[0])] + config.set_option('core', 'hosts', value) + # TODO: it is saved in ~root/.config/crm/crm.conf, is it as suitable path? + config.save() + + def save_remote(self, remote_hosts: typing.Iterable[str]): + self.save_local() + value = [f'{user}@{host}' for host, user in sorted(self._hosts_users.items(), key=lambda x: x[0])] + crmsh.parallax.parallax_call(remote_hosts, "crm options set core.hosts '{}'".format(', '.join(value))) + + def get(self, host): + return self._hosts_users[host] + + def add(self, user, host): + self._hosts_users[host] = user + # vim:ts=4:sw=4:et: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/test/run-functional-tests new/crmsh-4.4.1+20230302.2b5310b9/test/run-functional-tests --- old/crmsh-4.4.1+20230224.498677ab/test/run-functional-tests 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/test/run-functional-tests 2023-03-02 07:05:25.000000000 +0100 @@ -222,12 +222,19 @@ fi docker_exec $node_name "rm -rf /run/nologin" docker_exec $node_name "echo 'StrictHostKeyChecking no' >> /etc/ssh/ssh_config" - docker cp $PROJECT_PATH $node_name:/opt/crmsh - info "Building crmsh on \"$node_name\"..." - docker_exec $node_name "$make_cmd" 1> /dev/null || \ + + if [ "$node_name" != "qnetd-node" ];then + docker cp $PROJECT_PATH $node_name:/opt/crmsh + info "Building crmsh on \"$node_name\"..." + docker_exec $node_name "$make_cmd" 1> /dev/null || \ fatal "Building failed on $node_name!" - create_alice_bob_carol + create_alice_bob_carol + else + docker_exec $node_name "useradd -m -s /bin/bash alice 2>/dev/null" + docker_exec $node_name "echo \"alice ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/alice" + docker_exec $node_name "cp -r /root/.ssh ~alice/ && chown alice:users -R ~alice/.ssh" + fi } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/test/unittests/test_bootstrap.py new/crmsh-4.4.1+20230302.2b5310b9/test/unittests/test_bootstrap.py --- old/crmsh-4.4.1+20230224.498677ab/test/unittests/test_bootstrap.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/test/unittests/test_bootstrap.py 2023-03-02 07:05:25.000000000 +0100 @@ -16,6 +16,7 @@ import yaml import socket +import crmsh.utils from crmsh.ui_node import NodeMgmt try: @@ -73,7 +74,14 @@ options = mock.Mock(qnetd_addr="node3", qdevice_port=123, stage="") ctx = self.ctx_inst.set_context(options) ctx.initialize_qdevice() - mock_qdevice.assert_called_once_with('node3', port=123, algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, is_stage=False) + mock_qdevice.assert_called_once_with('node3', port=123, ssh_user=None, algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, is_stage=False) + + @mock.patch('crmsh.qdevice.QDevice') + def test_initialize_qdevice_with_user(self, mock_qdevice): + options = mock.Mock(qnetd_addr="alice@node3", qdevice_port=123, stage="") + ctx = self.ctx_inst.set_context(options) + ctx.initialize_qdevice() + mock_qdevice.assert_called_once_with('node3', port=123, ssh_user='alice', algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, is_stage=False) @mock.patch('crmsh.utils.fatal') def test_validate_sbd_option_error_together(self, mock_error): @@ -553,18 +561,18 @@ with self.assertRaises(SystemExit): bootstrap.setup_passwordless_with_other_nodes("node1") - mock_run.assert_called_once_with('carol', 'ssh {} alice@node1 PATH=\\"\\$PATH\\":/usr/sbin:/sbin crm_node -l'.format(constants.SSH_OPTION)) + mock_run.assert_called_once_with('carol', 'ssh {} alice@node1 sudo crm_node -l'.format(constants.SSH_OPTION)) mock_error.assert_called_once_with("Can't fetch cluster nodes list from node1: None") @mock.patch('crmsh.utils.fatal') - @mock.patch('crmsh.bootstrap._save_core_hosts') + @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.bootstrap._fetch_core_hosts') @mock.patch('crmsh.utils.su_get_stdout_stderr') def test_setup_passwordless_with_other_nodes_failed_fetch_hostname( self, mock_run, mock_fetch_core_hosts, - mock_save_core_hosts, + mock_host_user_config_class, mock_error, ): bootstrap._context = mock.Mock(user_list=["alice"], current_user="carol") @@ -581,12 +589,12 @@ bootstrap.setup_passwordless_with_other_nodes("node1") mock_run.assert_has_calls([ - mock.call('carol', 'ssh {} alice@node1 PATH=\\"\\$PATH\\":/usr/sbin:/sbin crm_node -l'.format(constants.SSH_OPTION)), + mock.call('carol', 'ssh {} alice@node1 sudo crm_node -l'.format(constants.SSH_OPTION)), mock.call('carol', 'ssh {} alice@node1 hostname'.format(constants.SSH_OPTION)) ]) mock_error.assert_called_once_with("Can't fetch hostname of node1: None") - @mock.patch('crmsh.bootstrap._save_core_hosts') + @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.bootstrap._fetch_core_hosts') @mock.patch('crmsh.utils.ssh_copy_id') @mock.patch('crmsh.utils.user_of') @@ -599,7 +607,7 @@ mock_userof, mock_ssh_copy_id: mock.MagicMock, mock_fetch_core_hosts, - mock_save_core_hosts, + mock_host_user_config_class, ): bootstrap._context = mock.Mock(current_user="carol", user_list=["alice", "bob"]) @@ -615,7 +623,7 @@ bootstrap.setup_passwordless_with_other_nodes("node1") mock_run.assert_has_calls([ - mock.call('carol', 'ssh {} alice@node1 PATH=\\"\\$PATH\\":/usr/sbin:/sbin crm_node -l'.format(constants.SSH_OPTION)), + mock.call('carol', 'ssh {} alice@node1 sudo crm_node -l'.format(constants.SSH_OPTION)), mock.call('carol', 'ssh {} alice@node1 hostname'.format(constants.SSH_OPTION)) ]) mock_userof.assert_called_once_with("node2") @@ -812,8 +820,7 @@ mock_status.assert_not_called() mock_disable.assert_called_once_with("corosync-qdevice.service") - @mock.patch('crmsh.bootstrap._save_core_hosts') - @mock.patch('crmsh.bootstrap._load_core_hosts') + @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.utils.user_of') @mock.patch('crmsh.utils.list_cluster_nodes') @mock.patch('crmsh.utils.ssh_copy_id') @@ -823,11 +830,10 @@ self, mock_status, mock_check_ssh_passwd_need, mock_ssh_copy_id, mock_list_nodes, mock_userof, - mock_load_core_hosts, mock_save_core_hosts, + mock_host_user_config_class, ): mock_list_nodes.return_value = [] bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, current_user="bob", user_list=["alice"]) - mock_load_core_hosts.return_value = ([], []) mock_check_ssh_passwd_need.return_value = True mock_ssh_copy_id.side_effect = ValueError('foo') mock_userof.return_value = "bob" @@ -837,13 +843,11 @@ mock_status.assert_has_calls([ mock.call("Configure Qdevice/Qnetd:"), - mock.call("Copy ssh key to qnetd node(root@10.10.10.123)") - ]) - mock_check_ssh_passwd_need.assert_called_once_with("bob", "root", "10.10.10.123") - mock_ssh_copy_id.assert_called_once_with('bob', 'root', '10.10.10.123') + ]) + mock_check_ssh_passwd_need.assert_called_once_with("bob", "bob", "10.10.10.123") + mock_ssh_copy_id.assert_called_once_with('bob', 'bob', '10.10.10.123') - @mock.patch('crmsh.bootstrap._save_core_hosts') - @mock.patch('crmsh.bootstrap._load_core_hosts') + @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.utils.user_of') @mock.patch('crmsh.utils.list_cluster_nodes') @mock.patch('crmsh.bootstrap.confirm') @@ -854,11 +858,10 @@ self, mock_status, mock_ssh, mock_qdevice_configured, mock_confirm, mock_list_nodes, mock_userof, - mock_load_core_hosts, mock_save_core_hosts, + mock_host_user_config_class, ): mock_list_nodes.return_value = [] bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, current_user="bob", user_list=["alice"]) - mock_load_core_hosts.return_value = ([], []) mock_ssh.return_value = False mock_userof.return_value = "bob" mock_qdevice_configured.return_value = True @@ -868,14 +871,13 @@ bootstrap.init_qdevice() mock_status.assert_called_once_with("Configure Qdevice/Qnetd:") - mock_ssh.assert_called_once_with("bob", "root", "10.10.10.123") - mock_save_core_hosts.assert_called_once_with(['root'], ['10.10.10.123'], sync_to_remote=True) + mock_ssh.assert_called_once_with("bob", "bob", "10.10.10.123") + mock_host_user_config_class.return_value.save_remote.assert_called_once_with(mock_list_nodes.return_value) mock_qdevice_configured.assert_called_once_with() mock_confirm.assert_called_once_with("Qdevice is already configured - overwrite?") self.qdevice_with_ip.start_qdevice_service.assert_called_once_with() - @mock.patch('crmsh.bootstrap._save_core_hosts') - @mock.patch('crmsh.bootstrap._load_core_hosts') + @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.utils.user_of') @mock.patch('crmsh.bootstrap.adjust_priority_fencing_delay') @mock.patch('crmsh.bootstrap.adjust_priority_in_rsc_defaults') @@ -884,9 +886,8 @@ @mock.patch('crmsh.utils.check_ssh_passwd_need') @mock.patch('logging.Logger.info') def test_init_qdevice(self, mock_info, mock_ssh, mock_qdevice_configured, mock_list_nodes, - mock_adjust_priority, mock_adjust_fence_delay, mock_userof, mock_load_core_hosts, mock_save_core_hosts): + mock_adjust_priority, mock_adjust_fence_delay, mock_userof, mock_host_user_config_class): bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, current_user="bob", user_list=["alice"]) - mock_load_core_hosts.return_value = ([], []) mock_list_nodes.return_value = [] mock_ssh.return_value = False mock_userof.return_value = "bob" @@ -898,27 +899,26 @@ bootstrap.init_qdevice() mock_info.assert_called_once_with("Configure Qdevice/Qnetd:") - mock_ssh.assert_called_once_with("bob", "root", "10.10.10.123") - mock_save_core_hosts.assert_called_once_with(['root'], ['10.10.10.123'], sync_to_remote=True) + mock_ssh.assert_called_once_with("bob", "bob", "10.10.10.123") + mock_host_user_config_class.return_value.add.assert_called_once_with('bob', '10.10.10.123') + mock_host_user_config_class.return_value.save_remote.assert_called_once_with(mock_list_nodes.return_value) mock_qdevice_configured.assert_called_once_with() self.qdevice_with_ip.set_cluster_name.assert_called_once_with() self.qdevice_with_ip.valid_qnetd.assert_called_once_with() self.qdevice_with_ip.config_and_start_qdevice.assert_called_once_with() @mock.patch('crmsh.utils.fatal') - @mock.patch('crmsh.bootstrap._save_core_hosts') - @mock.patch('crmsh.bootstrap._load_core_hosts') + @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.utils.service_is_available') @mock.patch('crmsh.utils.list_cluster_nodes') @mock.patch('logging.Logger.info') def test_init_qdevice_service_not_available( self, mock_info, mock_list_nodes, mock_available, - mock_load_core_hosts, mock_save_core_hosts, + mock_host_user_config_class, mock_fatal, ): bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip) - mock_load_core_hosts.return_value = ([], []) mock_list_nodes.return_value = ["node1"] mock_available.return_value = False mock_fatal.side_effect = SystemExit @@ -926,7 +926,8 @@ with self.assertRaises(SystemExit): bootstrap.init_qdevice() - mock_save_core_hosts.assert_not_called() + mock_host_user_config_class.return_value.save_local.assert_not_called() + mock_host_user_config_class.return_value.save_remote.assert_not_called() mock_fatal.assert_called_once_with("corosync-qdevice.service is not available on node1") mock_available.assert_called_once_with("corosync-qdevice.service", "node1") mock_info.assert_called_once_with("Configure Qdevice/Qnetd:") @@ -967,7 +968,7 @@ def test_configure_qdevice_interactive(self, mock_confirm, mock_info, mock_installed, mock_prompt, mock_qdevice): bootstrap._context = mock.Mock(yes_to_all=False) mock_confirm.return_value = True - mock_prompt.side_effect = ["qnetd-node", 5403, "ffsplit", "lowest", "on", None] + mock_prompt.side_effect = ["alice@qnetd-node", 5403, "ffsplit", "lowest", "on", None] mock_qdevice_inst = mock.Mock() mock_qdevice.return_value = mock_qdevice_inst @@ -988,7 +989,7 @@ valid_func=qdevice.QDevice.check_qdevice_heuristics, allow_empty=True) ]) - mock_qdevice.assert_called_once_with('qnetd-node', port=5403, algo='ffsplit', tie_breaker='lowest', tls='on', cmds=None, mode=None, is_stage=False) + mock_qdevice.assert_called_once_with('qnetd-node', port=5403, ssh_user='alice', algo='ffsplit', tie_breaker='lowest', tls='on', cmds=None, mode=None, is_stage=False) @mock.patch('crmsh.utils.fatal') @mock.patch('crmsh.utils.is_qdevice_configured') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/test/unittests/test_qdevice.py new/crmsh-4.4.1+20230302.2b5310b9/test/unittests/test_qdevice.py --- old/crmsh-4.4.1+20230224.498677ab/test/unittests/test_qdevice.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/test/unittests/test_qdevice.py 2023-03-02 07:05:25.000000000 +0100 @@ -422,10 +422,10 @@ @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("os.path.exists") - @mock.patch("crmsh.parallax.parallax_slurp") + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", new_callable=mock.PropertyMock) def test_fetch_qnetd_crt_from_qnetd_exist(self, mock_qnetd_cacert_local, - mock_slurp, mock_exists, mock_log): + mock_fetch, mock_exists, mock_log): mock_qnetd_cacert_local.return_value = "/etc/corosync/qdevice/net/10.10.10.123/qnetd-cacert.crt" mock_exists.return_value = True @@ -433,31 +433,29 @@ mock_exists.assert_called_once_with(mock_qnetd_cacert_local.return_value) mock_qnetd_cacert_local.assert_called_once_with() - mock_slurp.assert_not_called() + mock_fetch.assert_not_called() mock_log.assert_not_called() @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("os.path.exists") - @mock.patch("crmsh.parallax.parallax_slurp") + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", new_callable=mock.PropertyMock) def test_fetch_qnetd_crt_from_qnetd(self, mock_qnetd_cacert_local, - mock_slurp, mock_exists, mock_log): + mock_fetch, mock_exists, mock_log): mock_qnetd_cacert_local.return_value = "/etc/corosync/qdevice/net/10.10.10.123/qnetd-cacert.crt" mock_exists.return_value = False - mock_slurp.return_value = [("10.10.10.123", (0, None, None, "test"))] self.qdevice_with_ip.fetch_qnetd_crt_from_qnetd() mock_exists.assert_called_once_with(mock_qnetd_cacert_local.return_value) mock_qnetd_cacert_local.assert_called_once_with() mock_log.assert_called_once_with("Step 2: Fetch qnetd-cacert.crt from 10.10.10.123") - mock_slurp.assert_called_once_with(["10.10.10.123"], "/etc/corosync/qdevice/net", - "/etc/corosync/qnetd/nssdb/qnetd-cacert.crt") + mock_fetch.assert_called_once_with("10.10.10.123", "/etc/corosync/qnetd/nssdb/qnetd-cacert.crt", "/etc/corosync/qdevice/net/10.10.10.123") @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.list_cluster_nodes") @mock.patch("crmsh.utils.this_node") - @mock.patch("crmsh.parallax.parallax_copy") + @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host") def test_copy_qnetd_crt_to_cluster_one_node(self, mock_copy, mock_this_node, mock_list_nodes, mock_log): mock_this_node.return_value = "node1.com" mock_list_nodes.return_value = ["node1.com"] @@ -472,7 +470,7 @@ @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.list_cluster_nodes") @mock.patch("crmsh.utils.this_node") - @mock.patch("crmsh.parallax.parallax_copy") + @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host") @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", new_callable=mock.PropertyMock) @mock.patch("os.path.dirname") def test_copy_qnetd_crt_to_cluster(self, mock_dirname, mock_qnetd_cacert_local, @@ -481,14 +479,13 @@ mock_dirname.return_value = "/etc/corosync/qdevice/net/10.10.10.123" mock_this_node.return_value = "node1.com" mock_list_nodes.return_value = ["node1.com", "node2.com"] - mock_copy.return_value = [("node1.com", (0, None, None)), ("node2.com", (0, None, None))] self.qdevice_with_ip.copy_qnetd_crt_to_cluster() mock_this_node.assert_called_once_with() mock_list_nodes.assert_called_once_with() mock_log.assert_called_once_with("Step 3: Copy exported qnetd-cacert.crt to ['node2.com']") - mock_copy.assert_called_once_with(["node2.com"], mock_dirname.return_value, + mock_copy.assert_called_once_with(mock_dirname.return_value, "node2.com", "/etc/corosync/qdevice/net") @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @@ -521,17 +518,16 @@ @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.qdevice.QDevice.qdevice_crq_on_qnetd", new_callable=mock.PropertyMock) @mock.patch("crmsh.qdevice.QDevice.qdevice_crq_on_local", new_callable=mock.PropertyMock) - @mock.patch("crmsh.parallax.parallax_copy") + @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host") def test_copy_crq_to_qnetd(self, mock_copy, mock_qdevice_crq_local, mock_qdevice_crq_qnetd, mock_log): - mock_copy.return_value = [("10.10.10.123", (0, None, None))] mock_qdevice_crq_local.return_value = "/etc/corosync/qdevice/net/nssdb/qdevice-net-node.crq" mock_qdevice_crq_qnetd.return_value = "/etc/corosync/qnetd/nssdb/qdevice-net-node.crq" self.qdevice_with_ip.copy_crq_to_qnetd() mock_log.assert_called_once_with("Step 6: Copy qdevice-net-node.crq to 10.10.10.123") - mock_copy.assert_called_once_with(["10.10.10.123"], mock_qdevice_crq_local.return_value, + mock_copy.assert_called_once_with(mock_qdevice_crq_local.return_value, "10.10.10.123", mock_qdevice_crq_qnetd.return_value) mock_qdevice_crq_local.assert_called_once_with() mock_qdevice_crq_qnetd.assert_called_once_with() @@ -553,18 +549,16 @@ @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.qdevice.QDevice.qnetd_cluster_crt_on_qnetd", new_callable=mock.PropertyMock) - @mock.patch("crmsh.parallax.parallax_slurp") - def test_fetch_cluster_crt_from_qnetd(self, mock_slurp, mock_crt_on_qnetd, mock_log): + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") + def test_fetch_cluster_crt_from_qnetd(self, mock_fetch, mock_crt_on_qnetd, mock_log): mock_crt_on_qnetd.return_value = "/etc/corosync/qnetd/nssdb/cluster-hacluster.crt" - mock_slurp.return_value = [("10.10.10.123", (0, None, None, "test"))] self.qdevice_with_ip.cluster_name = "hacluster" self.qdevice_with_ip.fetch_cluster_crt_from_qnetd() mock_log.assert_called_once_with("Step 8: Fetch cluster-hacluster.crt from 10.10.10.123") mock_crt_on_qnetd.assert_has_calls([mock.call(), mock.call()]) - mock_slurp.assert_called_once_with(["10.10.10.123"], "/etc/corosync/qdevice/net", - mock_crt_on_qnetd.return_value) + mock_fetch.assert_called_once_with("10.10.10.123", mock_crt_on_qnetd.return_value, "/etc/corosync/qdevice/net/10.10.10.123",) @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.get_stdout_or_raise_error") @@ -581,7 +575,7 @@ @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.list_cluster_nodes") @mock.patch("crmsh.utils.this_node") - @mock.patch("crmsh.parallax.parallax_copy") + @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host") def test_copy_p12_to_cluster_one_node(self, mock_copy, mock_this_node, mock_list_nodes, mock_log): mock_this_node.return_value = "node1.com" mock_list_nodes.return_value = ["node1.com"] @@ -596,25 +590,21 @@ @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.list_cluster_nodes") @mock.patch("crmsh.utils.this_node") - @mock.patch("crmsh.parallax.parallax_copy") + @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_hosts") @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_local", new_callable=mock.PropertyMock) - @mock.patch("os.path.dirname") - def test_copy_p12_to_cluster(self, mock_dirname, mock_p12_on_local, + def test_copy_p12_to_cluster(self, mock_p12_on_local, mock_copy, mock_this_node, mock_list_nodes, mock_log): mock_this_node.return_value = "node1.com" mock_list_nodes.return_value = ["node1.com", "node2.com"] mock_p12_on_local.return_value = "/etc/corosync/qdevice/net/nssdb/qdevice-net-node.p12" - mock_dirname.return_value = "/etc/corosync/qdevice/net/nssdb" - mock_copy.return_value = [("node1.com", (0, None, None)), ("node2.com", (0, None, None))] self.qdevice_with_ip.copy_p12_to_cluster() mock_log.assert_called_once_with("Step 10: Copy qdevice-net-node.p12 to ['node2.com']") mock_this_node.assert_called_once_with() mock_list_nodes.assert_called_once_with() - mock_copy.assert_called_once_with(["node2.com"], mock_p12_on_local.return_value, - mock_dirname.return_value) - mock_dirname.assert_called_once_with(mock_p12_on_local.return_value) + mock_copy.assert_called_once_with(mock_p12_on_local.return_value, ["node2.com"], + mock_p12_on_local.return_value) mock_p12_on_local.assert_has_calls([mock.call(), mock.call()]) @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @@ -680,8 +670,8 @@ @mock.patch("os.path.exists") @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_cluster", new_callable=mock.PropertyMock) @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", new_callable=mock.PropertyMock) - @mock.patch("crmsh.parallax.parallax_slurp") - def test_fetch_qnetd_crt_from_cluster_exist(self, mock_slurp, mock_qnetd_cacert_local, + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") + def test_fetch_qnetd_crt_from_cluster_exist(self, mock_fetch, mock_qnetd_cacert_local, mock_qnetd_cacert_cluster, mock_exists, mock_log): mock_exists.return_value = True mock_qnetd_cacert_cluster.return_value = "/etc/corosync/qdevice/net/node1.com/qnetd-cacert.crt" @@ -692,19 +682,18 @@ mock_exists.assert_called_once_with(mock_qnetd_cacert_cluster.return_value) mock_qnetd_cacert_cluster.assert_called_once_with() mock_qnetd_cacert_local.assert_not_called() - mock_slurp.assert_not_called() + mock_fetch.assert_not_called() @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("os.path.exists") @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_cluster", new_callable=mock.PropertyMock) @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", new_callable=mock.PropertyMock) - @mock.patch("crmsh.parallax.parallax_slurp") - def test_fetch_qnetd_crt_from_cluster(self, mock_slurp, mock_qnetd_cacert_local, + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") + def test_fetch_qnetd_crt_from_cluster(self, mock_fetch, mock_qnetd_cacert_local, mock_qnetd_cacert_cluster, mock_exists, mock_log): mock_exists.return_value = False mock_qnetd_cacert_cluster.return_value = "/etc/corosync/qdevice/net/node1.com/qnetd-cacert.crt" mock_qnetd_cacert_local.return_value = "/etc/corosync/qdevice/net/10.10.10.123/qnetd-cacert.crt" - mock_slurp.return_value = [("node1.com", (0, None, None, "test"))] self.qdevice_with_ip_cluster_node.fetch_qnetd_crt_from_cluster() @@ -712,8 +701,7 @@ mock_exists.assert_called_once_with(mock_qnetd_cacert_cluster.return_value) mock_qnetd_cacert_cluster.assert_called_once_with() mock_qnetd_cacert_local.assert_called_once_with() - mock_slurp.assert_called_once_with(["node1.com"], "/etc/corosync/qdevice/net", - mock_qnetd_cacert_local.return_value) + mock_fetch.assert_called_once_with("node1.com", mock_qnetd_cacert_local.return_value, '/etc/corosync/qdevice/net/node1.com') @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.get_stdout_or_raise_error") @@ -732,8 +720,8 @@ @mock.patch("os.path.exists") @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_cluster", new_callable=mock.PropertyMock) @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_local", new_callable=mock.PropertyMock) - @mock.patch("crmsh.parallax.parallax_slurp") - def test_fetch_p12_from_cluster_exist(self, mock_slurp, mock_p12_on_local, + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") + def test_fetch_p12_from_cluster_exist(self, mock_fetch, mock_p12_on_local, mock_p12_on_cluster, mock_exists, mock_log): mock_exists.return_value = True mock_p12_on_cluster.return_value = "/etc/corosync/qdevice/net/node1.com/qdevice-net-node.p12" @@ -744,19 +732,18 @@ mock_exists.assert_called_once_with(mock_p12_on_cluster.return_value) mock_p12_on_cluster.assert_called_once_with() mock_p12_on_local.assert_not_called() - mock_slurp.assert_not_called() + mock_fetch.assert_not_called() @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("os.path.exists") @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_cluster", new_callable=mock.PropertyMock) @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_local", new_callable=mock.PropertyMock) - @mock.patch("crmsh.parallax.parallax_slurp") - def test_fetch_p12_from_cluster(self, mock_slurp, mock_p12_on_local, + @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local") + def test_fetch_p12_from_cluster(self, mock_fetch, mock_p12_on_local, mock_p12_on_cluster, mock_exists, mock_log): mock_exists.return_value = False mock_p12_on_cluster.return_value = "/etc/corosync/qdevice/net/node1.com/qdevice-net-node.p12" mock_p12_on_local.return_value = "/etc/corosync/qdevice/net/nssdb/qdevice-net-node.p12" - mock_slurp.return_value = [("node1.com", (0, None, None, "test"))] self.qdevice_with_ip_cluster_node.fetch_p12_from_cluster() @@ -764,8 +751,7 @@ mock_exists.assert_called_once_with(mock_p12_on_cluster.return_value) mock_p12_on_cluster.assert_called_once_with() mock_p12_on_local.assert_called_once_with() - mock_slurp.assert_called_once_with(["node1.com"], "/etc/corosync/qdevice/net", - mock_p12_on_local.return_value) + mock_fetch.assert_called_once_with("node1.com", mock_p12_on_local.return_value, "/etc/corosync/qdevice/net/node1.com") @mock.patch("crmsh.log.LoggerUtils.log_only_to_file") @mock.patch("crmsh.utils.get_stdout_or_raise_error") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230224.498677ab/test/unittests/test_utils.py new/crmsh-4.4.1+20230302.2b5310b9/test/unittests/test_utils.py --- old/crmsh-4.4.1+20230224.498677ab/test/unittests/test_utils.py 2023-02-24 10:04:38.000000000 +0100 +++ new/crmsh-4.4.1+20230302.2b5310b9/test/unittests/test_utils.py 2023-03-02 07:05:25.000000000 +0100 @@ -1843,9 +1843,13 @@ mock_run.assert_called_once_with("sudo -S -k -n id -u") +@mock.patch('grp.getgrgid') @mock.patch('os.getgroups') -def test_in_haclient(mock_group): +def test_in_haclient(mock_group, mock_getgrgid): mock_group.return_value = [90, 100] + mock_getgrgid_inst1 = mock.Mock(gr_name=constants.HA_GROUP) + mock_getgrgid_inst2 = mock.Mock(gr_name="other") + mock_getgrgid.side_effect = [mock_getgrgid_inst1, mock_getgrgid_inst2] assert utils.in_haclient() is True mock_group.assert_called_once_with()