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-21 17:41:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crmsh (Old)
 and      /work/SRC/openSUSE:Factory/.crmsh.new.31432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Tue Mar 21 17:41:18 2023 rev:286 rq:1073057 version:4.5.0+20230320.5e777809

Changes:
--------
--- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes      2023-03-09 
17:46:52.759271772 +0100
+++ /work/SRC/openSUSE:Factory/.crmsh.new.31432/crmsh.changes   2023-03-21 
17:41:22.765787616 +0100
@@ -1,0 +2,16 @@
+Mon Mar 20 08:53:40 UTC 2023 - xli...@suse.com
+
+- Update to version 4.5.0+20230320.5e777809:
+  * Dev: unittest: Adjust unit test for previous changes
+  * Fix: validate ssh session when the users is determined by guessing 
(bsc#1209193)
+
+-------------------------------------------------------------------
+Tue Mar 14 06:42:32 UTC 2023 - xli...@suse.com
+
+- Update to version 4.5.0+20230314.c7422396:
+  * Dev: unittest: Adjust unit test for previous changes
+  * Fix: parallax: Use 'sudo bash -c' when executing commands via sudoer 
(bsc#1209192)
+  * Dev: qdevice: Add more debug messages for running commands
+  * Dev: log: For the log_only_to_file method, show debug log in debug mode
+
+-------------------------------------------------------------------

Old:
----
  crmsh-4.5.0+20230309.a4c4192d.tar.bz2

New:
----
  crmsh-4.5.0+20230320.5e777809.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.ucqFnk/_old  2023-03-21 17:41:23.509791175 +0100
+++ /var/tmp/diff_new_pack.ucqFnk/_new  2023-03-21 17:41:23.513791194 +0100
@@ -36,7 +36,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0-or-later
 Group:          %{pkg_group}
-Version:        4.5.0+20230309.a4c4192d
+Version:        4.5.0+20230320.5e777809
 Release:        0
 URL:            http://crmsh.github.io
 Source0:        %{name}-%{version}.tar.bz2

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.ucqFnk/_old  2023-03-21 17:41:23.593791577 +0100
+++ /var/tmp/diff_new_pack.ucqFnk/_new  2023-03-21 17:41:23.597791596 +0100
@@ -9,7 +9,7 @@
 </service>
 <service name="tar_scm">
   <param name="url">https://github.com/ClusterLabs/crmsh.git</param>
-  <param 
name="changesrevision">1615c26abe860045bf4e15442899e992cc29455c</param>
+  <param 
name="changesrevision">4697fb58457de1acf83640116e145612d97e593f</param>
 </service>
 </servicedata>
 (No newline at EOF)

++++++ crmsh-4.5.0+20230309.a4c4192d.tar.bz2 -> 
crmsh-4.5.0+20230320.5e777809.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.5.0+20230309.a4c4192d/crmsh/corosync.py 
new/crmsh-4.5.0+20230320.5e777809/crmsh/corosync.py
--- old/crmsh-4.5.0+20230309.a4c4192d/crmsh/corosync.py 2023-03-09 
10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/crmsh/corosync.py 2023-03-20 
08:49:02.000000000 +0100
@@ -107,11 +107,7 @@
         raise ValueError("host for qnetd not configured!")
 
     # Configure ssh passwordless to qnetd if detect password is needed
-    local_user = utils.user_of(utils.this_node())
-    try:
-        remote_user = utils.user_of(qnetd_addr)
-    except ValueError:
-        remote_user = 'root'
+    local_user, remote_user = utils.user_pair_for_ssh(qnetd_addr)
     if utils.check_ssh_passwd_need(local_user, remote_user, qnetd_addr):
         utils.ssh_copy_id(local_user, remote_user, qnetd_addr)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.5.0+20230309.a4c4192d/crmsh/healthcheck.py 
new/crmsh-4.5.0+20230320.5e777809/crmsh/healthcheck.py
--- old/crmsh-4.5.0+20230309.a4c4192d/crmsh/healthcheck.py      2023-03-09 
10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/crmsh/healthcheck.py      2023-03-20 
08:49:02.000000000 +0100
@@ -145,8 +145,8 @@
         remote_nodes = set(nodes)
         remote_nodes.remove(local_node)
         remote_nodes = list(remote_nodes)
-        local_user = crmsh.utils.user_of(local_node)
-        remote_users = [crmsh.utils.user_of(node) for node in remote_nodes]
+        local_user = crmsh.utils.user_pair_for_ssh(remote_nodes[0])[0]
+        remote_users = [crmsh.utils.user_pair_for_ssh(node)[1] for node in 
remote_nodes]
         crmsh.bootstrap.init_ssh_impl(local_user, remote_nodes, remote_users)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.5.0+20230309.a4c4192d/crmsh/log.py 
new/crmsh-4.5.0+20230320.5e777809/crmsh/log.py
--- old/crmsh-4.5.0+20230309.a4c4192d/crmsh/log.py      2023-03-09 
10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/crmsh/log.py      2023-03-20 
08:49:02.000000000 +0100
@@ -273,8 +273,12 @@
             self.logger.addHandler(console_handler)
 
     def log_only_to_file(self, msg, level=logging.INFO):
-        with self.only_file():
-            self.logger.log(level, msg)
+        from .config import core
+        if core.debug:
+            self.logger.log(logging.DEBUG, msg)
+        else:
+            with self.only_file():
+                self.logger.log(level, msg)
 
     @contextmanager
     def buffer(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.5.0+20230309.a4c4192d/crmsh/parallax.py 
new/crmsh-4.5.0+20230320.5e777809/crmsh/parallax.py
--- old/crmsh-4.5.0+20230309.a4c4192d/crmsh/parallax.py 2023-03-09 
10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/crmsh/parallax.py 2023-03-20 
08:49:02.000000000 +0100
@@ -43,7 +43,7 @@
             self.ssh_options = ['StrictHostKeyChecking=no',
                     'ConnectTimeout=10',
                     'LogLevel=error']
-        sudoer = crmsh.utils.user_of(crmsh.utils.this_node())
+        sudoer, _ = crmsh.utils.user_pair_for_ssh(self.nodes[0])
         if sudoer is not None:
             # FIXME: this is really unreliable
             
self.ssh_options.append('IdentityFile={}/.ssh/id_rsa'.format(userdir.gethomedir(sudoer)))
@@ -65,9 +65,12 @@
     def call(self):
         host_port_user = []
         for host in self.nodes:
-            host_port_user.append([host, None, crmsh.utils.user_of(host)])
+            _, remote_user = crmsh.utils.user_pair_for_ssh(host)
+            host_port_user.append([host, None, remote_user])
         # FIXME: this is really unreliable
-        results = parallax.call(host_port_user, 'sudo {}'.format(self.cmd), 
self.opts)
+        sudoer = userdir.get_sudoer()
+        cmd = f'sudo bash -c "{self.cmd}"' if sudoer else self.cmd
+        results = parallax.call(host_port_user, cmd, self.opts)
         return self.handle(list(results.items()))
 
     def slurp(self):
@@ -79,11 +82,13 @@
         results = parallax.copy(self.nodes, self.src, self.dst, self.opts)
         return self.handle(list(results.items()))
     def run(self):
-        return parallax.run(
-            [[node, None, crmsh.utils.user_of(node)] for node in self.nodes],
-            'sudo {}'.format(self.cmd),
-            self.opts,
-        )
+        sudoer = userdir.get_sudoer()
+        cmd = f'sudo bash -c "{self.cmd}"' if sudoer else self.cmd
+        host_port_user = []
+        for host in self.nodes:
+            _, remote_user = crmsh.utils.user_pair_for_ssh(host)
+            host_port_user.append([host, None, remote_user])
+        return parallax.run(host_port_user, cmd, self.opts)
 
 
 def parallax_call(nodes, cmd, askpass=False, ssh_options=None, strict=True):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.5.0+20230309.a4c4192d/crmsh/qdevice.py 
new/crmsh-4.5.0+20230320.5e777809/crmsh/qdevice.py
--- old/crmsh-4.5.0+20230309.a4c4192d/crmsh/qdevice.py  2023-03-09 
10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/crmsh/qdevice.py  2023-03-20 
08:49:02.000000000 +0100
@@ -358,7 +358,7 @@
 
         cmd = "corosync-qnetd-certutil -i"
         desc = "Step 1: Initialize database on {}".format(self.qnetd_addr)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc, cmd)
         parallax.parallax_call([self.qnetd_addr], cmd)
 
     def fetch_qnetd_crt_from_qnetd(self):
@@ -371,7 +371,7 @@
             return
 
         desc = "Step 2: Fetch {} from {}".format(self.qnetd_cacert_filename, 
self.qnetd_addr)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc)
         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):
@@ -385,19 +385,19 @@
             return
 
         desc = "Step 3: Copy exported {} to 
{}".format(self.qnetd_cacert_filename, node_list)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc)
         
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)
+        local_user, remote_user = utils.user_pair_for_ssh(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()),
+                local_user,
                 # FIXME: sftp-server is not always in /usr/lib/ssh
                 tmp.name,
                 remote_user, cls._enclose_inet6_addr(remote_host),
@@ -433,7 +433,7 @@
         node_list = utils.list_cluster_nodes()
         cmd = "corosync-qdevice-net-certutil -i -c 
{}".format(self.qnetd_cacert_on_local)
         desc = "Step 4: Initialize database on {}".format(node_list)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc, cmd)
         parallax.parallax_call(node_list, cmd)
 
     def create_ca_request(self):
@@ -444,8 +444,8 @@
         /usr/sbin/corosync-qdevice-net-certutil -r -n Cluster
         (Cluster name must match cluster_name key in the corosync.conf)
         """
-        logger_utils.log_only_to_file("Step 5: Generate certificate request 
{}".format(self.qdevice_crq_filename))
         cmd = "corosync-qdevice-net-certutil -r -n 
{}".format(self.cluster_name)
+        QDevice.log_only_to_file("Step 5: Generate certificate request 
{}".format(self.qdevice_crq_filename), cmd)
         utils.get_stdout_or_raise_error(cmd)
 
     def copy_crq_to_qnetd(self):
@@ -455,7 +455,7 @@
         Copy exported CRQ to QNetd server
         """
         desc = "Step 6: Copy {} to {}".format(self.qdevice_crq_filename, 
self.qnetd_addr)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc)
         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):
@@ -466,9 +466,9 @@
         corosync-qnetd-certutil -s -c qdevice-net-node.crq -n Cluster
         """
         desc = "Step 7: Sign and export cluster certificate on 
{}".format(self.qnetd_addr)
-        logger_utils.log_only_to_file(desc)
         cmd = "corosync-qnetd-certutil -s -c {} -n {}".\
                 format(self.qdevice_crq_on_qnetd, self.cluster_name)
+        QDevice.log_only_to_file(desc, cmd)
         parallax.parallax_call([self.qnetd_addr], cmd)
 
     def fetch_cluster_crt_from_qnetd(self):
@@ -478,7 +478,7 @@
         Copy exported CRT to node where certificate request was created
         """
         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)
+        QDevice.log_only_to_file(desc)
         self._fetch_file_to_local(self.qnetd_addr, 
self.qnetd_cluster_crt_on_qnetd, '{}/{}'.format(self.qdevice_path, 
self.qnetd_addr))
 
     @classmethod
@@ -502,8 +502,10 @@
         Import certificate on node where certificate request was created by
         running /usr/sbin/corosync-qdevice-net-certutil -M -c 
cluster-Cluster.crt
         """
-        logger_utils.log_only_to_file("Step 9: Import certificate file {} on 
local".format(os.path.basename(self.qnetd_cluster_crt_on_local)))
         cmd = "corosync-qdevice-net-certutil -M -c 
{}".format(self.qnetd_cluster_crt_on_local)
+        QDevice.log_only_to_file(
+                "Step 9: Import certificate file {} on 
local".format(os.path.basename(self.qnetd_cluster_crt_on_local)),
+                cmd)
         utils.get_stdout_or_raise_error(cmd)
 
     def copy_p12_to_cluster(self):
@@ -517,7 +519,7 @@
             return
 
         desc = "Step 10: Copy {} to {}".format(self.qdevice_p12_filename, 
node_list)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc)
         self._copy_file_to_remote_hosts(self.qdevice_p12_on_local, node_list, 
self.qdevice_p12_on_local)
 
     def import_p12_on_cluster(self):
@@ -532,8 +534,8 @@
             return
 
         desc = "Step 11: Import {} on {}".format(self.qdevice_p12_filename, 
node_list)
-        logger_utils.log_only_to_file(desc)
         cmd = "corosync-qdevice-net-certutil -m -c 
{}".format(self.qdevice_p12_on_local)
+        QDevice.log_only_to_file(desc, cmd)
         parallax.parallax_call(node_list, cmd)
 
     def certificate_process_on_init(self):
@@ -562,7 +564,7 @@
             return
 
         desc = "Step 1: Fetch {} from {}".format(self.qnetd_cacert_filename, 
self.cluster_node)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc)
         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):
@@ -575,8 +577,8 @@
         if os.path.exists(self.qdevice_db_path):
             utils.rmdir_r(self.qdevice_db_path)
 
-        logger_utils.log_only_to_file("Step 2: Initialize database on local")
         cmd = "corosync-qdevice-net-certutil -i -c 
{}".format(self.qnetd_cacert_on_cluster)
+        QDevice.log_only_to_file("Step 2: Initialize database on local", cmd)
         utils.get_stdout_or_raise_error(cmd)
 
     def fetch_p12_from_cluster(self):
@@ -589,7 +591,7 @@
             return
 
         desc = "Step 3: Fetch {} from {}".format(self.qdevice_p12_filename, 
self.cluster_node)
-        logger_utils.log_only_to_file(desc)
+        QDevice.log_only_to_file(desc)
         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):
@@ -598,8 +600,8 @@
         Step 4
         Import cluster certificate and key
         """
-        logger_utils.log_only_to_file("Step 4: Import cluster certificate and 
key")
         cmd = "corosync-qdevice-net-certutil -m -c 
{}".format(self.qdevice_p12_on_cluster)
+        QDevice.log_only_to_file("Step 4: Import cluster certificate and key", 
cmd)
         utils.get_stdout_or_raise_error(cmd)
 
     def certificate_process_on_join(self):
@@ -653,6 +655,7 @@
             return
         node_list = addr_list if addr_list else utils.list_cluster_nodes()
         cmd = "rm -rf {}/*".format(QDevice.qdevice_path)
+        QDevice.log_only_to_file("Remove qdevice database", cmd)
         parallax.parallax_call(node_list, cmd)
 
     @classmethod
@@ -742,3 +745,9 @@
         if res:
             qnetd_host = corosync.get_value('quorum.device.net.host')
             logger.warning("Qdevice's vote is 0, which simply means Qdevice 
can't talk to Qnetd({}) for various reasons.".format(qnetd_host))
+
+    @staticmethod
+    def log_only_to_file(desc, cmd=None):
+        logger_utils.log_only_to_file(desc)
+        if cmd:
+            logger_utils.log_only_to_file(f"Run: {cmd}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.5.0+20230309.a4c4192d/crmsh/utils.py 
new/crmsh-4.5.0+20230320.5e777809/crmsh/utils.py
--- old/crmsh-4.5.0+20230309.a4c4192d/crmsh/utils.py    2023-03-09 
10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/crmsh/utils.py    2023-03-20 
08:49:02.000000000 +0100
@@ -109,27 +109,46 @@
 
 
 class UserOfHost:
+    class UserNotFoundError(Exception):
+        pass
+
     def __init__(self):
-        self._cache = dict()
+        self._user_cache = dict()
+        self._user_pair_cache = dict()
 
     def user_of(self, host):
-        cached = self._cache.get(host)
+        cached = self._user_cache.get(host)
         if cached is None:
-            ret = self._user_of_impl(host)
+            ret = self._get_user_of_host_from_config(host)
             if ret is None:
-                user = userdir.get_sudoer()
-                if user:
-                    return user
-                else:
-                    return userdir.getuser()
+                raise self.UserNotFoundError
             else:
-                self._cache[host] = ret
+                self._user_cache[host] = ret
                 return ret
         else:
             return cached
 
+    def user_pair_for_ssh(self, host: str) -> typing.Tuple[str, str]:
+        """Return (local_user, remote_user) pair for ssh connection"""
+        try:
+            local_user = self.user_of(this_node())
+            remote_user = self.user_of(host)
+            return local_user, remote_user
+        except self.UserNotFoundError:
+            cached = self._user_pair_cache.get(host)
+            if cached is None:
+                ret = self._guess_user_for_ssh(host)
+                if ret is None:
+                    raise ValueError('Can not create ssh session from {} to 
{}.'.format(this_node(), host))
+                else:
+                    self._user_pair_cache[host] = ret
+                    return ret
+            else:
+                return cached
+
+
     @staticmethod
-    def _user_of_impl(host):
+    def _get_user_of_host_from_config(host):
         try:
             canonical, aliases, _ = socket.gethostbyaddr(host)
             aliases = set(aliases)
@@ -151,6 +170,35 @@
         logger.debug('Failed to get the user of host %s (aliases: %s). Known 
hosts are %s', host, aliases, hosts)
         return None
 
+    @staticmethod
+    def _guess_user_for_ssh(host: str) -> typing.Tuple[str, str]:
+        args = ['ssh']
+        args.extend(constants.SSH_OPTION_ARGS)
+        args.extend(['-o', 'BatchMode=yes', host, 'true'])
+        rc = subprocess.call(
+            args,
+            stdin=subprocess.DEVNULL,
+            stdout=subprocess.DEVNULL,
+            stderr=subprocess.DEVNULL,
+        )
+        if rc == 0:
+            user = userdir.getuser()
+            return user, user
+        sudoer = userdir.get_sudoer()
+        if sudoer is None:
+            return None
+        result = su_subprocess_run(
+            sudoer,
+            'ssh {} -o BatchMode=yes {}@{} sudo -u {} 
true'.format(constants.SSH_OPTION, sudoer, host, sudoer),
+            stdin=subprocess.DEVNULL,
+            stdout=subprocess.DEVNULL,
+            stderr=subprocess.DEVNULL,
+        )
+        if result.returncode == 0:
+            return sudoer, sudoer
+        return None
+
+
 
 _user_of_host_instance = UserOfHost()
 
@@ -159,6 +207,10 @@
     return _user_of_host_instance.user_of(host)
 
 
+def user_pair_for_ssh(host):
+    return _user_of_host_instance.user_pair_for_ssh(host)
+
+
 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))
@@ -1039,7 +1091,11 @@
 
 
 def get_stdout_stderr_as_local_sudoer(cmd, input_s=None, raw=False):
-    return su_get_stdout_stderr(user_of(this_node()), cmd, input_s, raw)
+    try:
+        user = user_of(this_node())
+    except UserOfHost.UserNotFoundError:
+        user = 'root'
+    return su_get_stdout_stderr(user, cmd, input_s, raw)
 
 
 def get_stdout_stderr_auto_ssh_no_input(host, cmd, raw=False):
@@ -2307,8 +2363,6 @@
     Check whether access to host need password
     """
     ssh_options = "-o StrictHostKeyChecking=no -o EscapeChar=none -o 
ConnectTimeout=15"
-    if remote_user is None:
-        remote_user = user_of(host)
     ssh_cmd = "ssh {} -T -o Batchmode=yes {}@{} true".format(ssh_options, 
remote_user, host)
     rc, _, _ = su_get_stdout_stderr(local_user, ssh_cmd)
     return rc != 0
@@ -2880,13 +2934,12 @@
             **kwargs,
         )
     else:
-        remote_sudoer = user_of(remote)
-        local_user = user_of(this_node())
+        local_user, remote_user = user_pair_for_ssh(remote)
         if user is None:
             user = 'root'
         return su_subprocess_run(
             local_user,
-            'ssh {} {}@{} sudo -H -u {} /bin/sh'.format(constants.SSH_OPTION, 
remote_sudoer, remote, user),
+            'ssh {} {}@{} sudo -H -u {} /bin/sh'.format(constants.SSH_OPTION, 
remote_user, remote, user),
             input=cmd.encode('utf-8'),
             **kwargs,
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_corosync.py 
new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_corosync.py
--- old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_corosync.py   
2023-03-09 10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_corosync.py   
2023-03-20 08:49:02.000000000 +0100
@@ -177,18 +177,15 @@
         ])
 
 
-@mock.patch('crmsh.utils.user_of')
+@mock.patch('crmsh.utils.user_pair_for_ssh')
 @mock.patch("crmsh.parallax.parallax_call")
 @mock.patch("crmsh.utils.ssh_copy_id")
 @mock.patch("crmsh.utils.check_ssh_passwd_need")
 @mock.patch("crmsh.corosync.get_value")
 @mock.patch("crmsh.utils.is_qdevice_configured")
 def test_query_qnetd_status_copy_id_failed(mock_qdevice_configured,
-        mock_get_value, mock_check_passwd, mock_ssh_copy_id, 
mock_parallax_call, mock_userof):
-    mock_userof.side_effect = [
-        "alice",
-        "root",
-    ]
+        mock_get_value, mock_check_passwd, mock_ssh_copy_id, 
mock_parallax_call, mock_user_pair_for_ssh):
+    mock_user_pair_for_ssh.return_value = "alice", "root"
     mock_parallax_call.side_effect = ValueError("Failed on 10.10.10.123: foo")
     mock_qdevice_configured.return_value = True
     mock_get_value.side_effect = ["hacluster", "10.10.10.123"]
@@ -205,7 +202,7 @@
     mock_ssh_copy_id.assert_called_once_with('alice', 'root', '10.10.10.123')
 
 
-@mock.patch('crmsh.utils.user_of')
+@mock.patch('crmsh.utils.user_pair_for_ssh')
 @mock.patch("crmsh.utils.print_cluster_nodes")
 @mock.patch("crmsh.parallax.parallax_call")
 @mock.patch("crmsh.utils.ssh_copy_id")
@@ -214,11 +211,8 @@
 @mock.patch("crmsh.utils.is_qdevice_configured")
 def test_query_qnetd_status_copy(mock_qdevice_configured, mock_get_value,
         mock_check_passwd, mock_ssh_copy_id, mock_parallax_call, 
mock_print_nodes,
-        mock_userof):
-    mock_userof.side_effect = [
-        "alice",
-        "root",
-    ]
+        mock_user_pair_for_ssh):
+    mock_user_pair_for_ssh.return_value = "alice", "root"
     mock_qdevice_configured.return_value = True
     mock_get_value.side_effect = ["hacluster", "10.10.10.123"]
     mock_check_passwd.return_value = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_parallax.py 
new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_parallax.py
--- old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_parallax.py   
2023-03-09 10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_parallax.py   
2023-03-20 08:49:02.000000000 +0100
@@ -13,121 +13,124 @@
 
 
 class TestParallax(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
-        """
-        Global setUp.
-        """
-
     def setUp(self):
         """
         Test setUp.
         """
         # Use the setup to create a fresh instance for each test
-        self.parallax_call_instance = cparallax.Parallax(["node1"], cmd="ls")
-        self.parallax_slurp_instance = cparallax.Parallax(["node1"], 
localdir="/opt", filename="/opt/file.c")
-        self.parallax_copy_instance = cparallax.Parallax(["node1", "node2"], 
src="/opt/file.c", dst="/tmp")
-
-    def tearDown(self):
-        """
-        Test tearDown.
-        """
-
-    @classmethod
-    def tearDownClass(cls):
-        """
-        Global tearDown.
-        """
-
-    @mock.patch("crmsh.utils.user_of")
+    @mock.patch("crmsh.utils.user_pair_for_ssh")
     @mock.patch("parallax.call")
     @mock.patch("crmsh.parallax.Parallax.handle")
-    def test_call(self, mock_handle, mock_call, mock_userof):
+    def test_call(self, mock_handle, mock_call, mock_user_pair_for_ssh):
+        mock_user_pair_for_ssh.return_value = "alice", "alice"
+        parallax_call_instance = cparallax.Parallax(["node1"], cmd="ls")
         mock_call.return_value = {"node1": (0, None, None)}
-        mock_userof.return_value = "alice"
         mock_handle.return_value = [("node1", (0, None, None))]
 
-        result = self.parallax_call_instance.call()
+        result = parallax_call_instance.call()
         self.assertEqual(result, mock_handle.return_value)
 
-        mock_userof.assert_called_once_with("node1")
-        mock_call.assert_called_once_with([["node1", None, "alice"]], "sudo 
ls", self.parallax_call_instance.opts)
+        mock_user_pair_for_ssh.assert_has_calls([
+            mock.call("node1"),
+            mock.call("node1"),
+        ])
+        mock_call.assert_called_once_with([["node1", None, "alice"]], "ls", 
parallax_call_instance.opts)
         
mock_handle.assert_called_once_with(list(mock_call.return_value.items()))
 
     @mock.patch("parallax.Error")
-    @mock.patch("crmsh.utils.user_of")
+    @mock.patch("crmsh.utils.user_pair_for_ssh")
     @mock.patch("parallax.call")
     @mock.patch("crmsh.parallax.Parallax.handle")
-    def test_call_exception(self, mock_handle, mock_call, mock_userof, 
mock_error):
+    def test_call_exception(self, mock_handle, mock_call, 
mock_user_pair_for_ssh, mock_error):
+        mock_user_pair_for_ssh.return_value = "alice", "alice"
+        parallax_call_instance = cparallax.Parallax(["node1"], cmd="ls")
         mock_error = mock.Mock()
         mock_call.return_value = {"node1": mock_error}
-        mock_userof.return_value = "alice"
         mock_handle.side_effect = ValueError("error happen")
 
         with self.assertRaises(ValueError) as err:
-            self.parallax_call_instance.call()
+            parallax_call_instance.call()
         self.assertEqual("error happen", str(err.exception))
 
-        mock_userof.assert_called_once_with("node1")
-        mock_call.assert_called_once_with([["node1", None, "alice"]], "sudo 
ls", self.parallax_call_instance.opts)
+        mock_user_pair_for_ssh.assert_has_calls([
+            mock.call("node1"),
+            mock.call("node1"),
+        ])
+        mock_call.assert_called_once_with([["node1", None, "alice"]], "ls", 
parallax_call_instance.opts)
         
mock_handle.assert_called_once_with(list(mock_call.return_value.items()))
 
+    @mock.patch("crmsh.utils.user_pair_for_ssh")
     @mock.patch("crmsh.parallax.Parallax.handle")
     @mock.patch("parallax.slurp")
     @mock.patch("os.path.basename")
-    def test_slurp(self, mock_basename, mock_slurp, mock_handle):
+    def test_slurp(self, mock_basename, mock_slurp, mock_handle, 
mock_user_pair_for_ssh):
+        mock_user_pair_for_ssh.return_value = "alice", "alice"
+        parallax_slurp_instance = cparallax.Parallax(["node1"], 
localdir="/opt", filename="/opt/file.c")
         mock_basename.return_value = "file.c"
         mock_slurp.return_value = {"node1": (0, None, None, "/opt")}
         mock_handle.return_value = [("node1", (0, None, None, "/opt"))]
 
-        result = self.parallax_slurp_instance.slurp()
+        result = parallax_slurp_instance.slurp()
         self.assertEqual(result, mock_handle.return_value)
 
         mock_basename.assert_called_once_with("/opt/file.c")
-        mock_slurp.assert_called_once_with(["node1"], "/opt/file.c", "file.c", 
self.parallax_slurp_instance.opts)
+        mock_slurp.assert_called_once_with(["node1"], "/opt/file.c", "file.c", 
parallax_slurp_instance.opts)
         
mock_handle.assert_called_once_with(list(mock_slurp.return_value.items()))
+        mock_user_pair_for_ssh.assert_called_once_with("node1")
 
+    @mock.patch("crmsh.utils.user_pair_for_ssh")
     @mock.patch("parallax.Error")
     @mock.patch("crmsh.parallax.Parallax.handle")
     @mock.patch("parallax.slurp")
     @mock.patch("os.path.basename")
-    def test_slurp_exception(self, mock_basename, mock_slurp, mock_handle, 
mock_error):
+    def test_slurp_exception(self, mock_basename, mock_slurp, mock_handle, 
mock_error, mock_user_pair_for_ssh):
+        mock_user_pair_for_ssh.return_value = "alice", "alice"
+        parallax_slurp_instance = cparallax.Parallax(["node1"], 
localdir="/opt", filename="/opt/file.c")
         mock_basename.return_value = "file.c"
         mock_error = mock.Mock()
         mock_slurp.return_value = {"node1": mock_error}
         mock_handle.side_effect = ValueError("error happen")
 
         with self.assertRaises(ValueError) as err:
-            self.parallax_slurp_instance.slurp()
+            parallax_slurp_instance.slurp()
         self.assertEqual("error happen", str(err.exception))
 
         mock_basename.assert_called_once_with("/opt/file.c")
-        mock_slurp.assert_called_once_with(["node1"], "/opt/file.c", "file.c", 
self.parallax_slurp_instance.opts)
+        mock_slurp.assert_called_once_with(["node1"], "/opt/file.c", "file.c", 
parallax_slurp_instance.opts)
         
mock_handle.assert_called_once_with(list(mock_slurp.return_value.items()))
+        mock_user_pair_for_ssh.assert_called_once_with("node1")
 
+    @mock.patch("crmsh.utils.user_pair_for_ssh")
     @mock.patch("parallax.copy")
     @mock.patch("crmsh.parallax.Parallax.handle")
-    def test_copy(self, mock_handle, mock_copy):
+    def test_copy(self, mock_handle, mock_copy, mock_user_pair_for_ssh):
+        mock_user_pair_for_ssh.return_value = "alice", "alice"
+        parallax_copy_instance = cparallax.Parallax(["node1", "node2"], 
src="/opt/file.c", dst="/tmp")
         mock_copy.return_value = {"node1": (0, None, None), "node2": (0, None, 
None)}
         mock_handle.return_value = [("node1", (0, None, None)), ("node2", (0, 
None, None))]
 
-        result = self.parallax_copy_instance.copy()
+        result = parallax_copy_instance.copy()
         self.assertEqual(result, mock_handle.return_value)
 
-        mock_copy.assert_called_once_with(["node1", "node2"], "/opt/file.c", 
"/tmp", self.parallax_copy_instance.opts)
+        mock_copy.assert_called_once_with(["node1", "node2"], "/opt/file.c", 
"/tmp", parallax_copy_instance.opts)
         
mock_handle.assert_called_once_with(list(mock_copy.return_value.items()))
+        mock_user_pair_for_ssh.assert_called_once_with("node1")
 
+    @mock.patch("crmsh.utils.user_pair_for_ssh")
     @mock.patch("parallax.Error")
     @mock.patch("parallax.copy")
     @mock.patch("crmsh.parallax.Parallax.handle")
-    def test_copy_exception(self, mock_handle, mock_copy, mock_error):
+    def test_copy_exception(self, mock_handle, mock_copy, mock_error, 
mock_user_pair_for_ssh):
+        mock_user_pair_for_ssh.return_value = "alice", "alice"
+        parallax_copy_instance = cparallax.Parallax(["node1", "node2"], 
src="/opt/file.c", dst="/tmp")
         mock_error = mock.Mock()
         mock_copy.return_value = {"node1": mock_error, "node2": (0, None, 
None)}
         mock_handle.side_effect = ValueError("error happen")
 
         with self.assertRaises(ValueError) as err:
-            self.parallax_copy_instance.copy()
+            parallax_copy_instance.copy()
         self.assertEqual("error happen", str(err.exception))
 
-        mock_copy.assert_called_once_with(["node1", "node2"], "/opt/file.c", 
"/tmp", self.parallax_copy_instance.opts)
+        mock_copy.assert_called_once_with(["node1", "node2"], "/opt/file.c", 
"/tmp", parallax_copy_instance.opts)
         
mock_handle.assert_called_once_with(list(mock_copy.return_value.items()))
+        mock_user_pair_for_ssh.assert_called_once_with("node1")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_qdevice.py 
new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_qdevice.py
--- old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_qdevice.py    
2023-03-09 10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_qdevice.py    
2023-03-20 08:49:02.000000000 +0100
@@ -391,7 +391,7 @@
         self.qdevice_with_ip.stop_qnetd()
         mock_stop.assert_called_once_with("corosync-qnetd.service", 
remote_addr="10.10.10.123")
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.parallax.parallax_call")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_qnetd", 
new_callable=mock.PropertyMock)
     def test_init_db_on_qnetd_already_exists(self, mock_qnetd_cacert, 
mock_call, mock_log):
@@ -403,7 +403,7 @@
         mock_qnetd_cacert.assert_called_once_with()
         mock_log.assert_not_called()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.parallax.parallax_call")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_qnetd", 
new_callable=mock.PropertyMock)
     def test_init_db_on_qnetd(self, mock_qnetd_cacert, mock_call, mock_log):
@@ -418,9 +418,10 @@
             mock.call(["10.10.10.123"], "corosync-qnetd-certutil -i")
         ])
         mock_qnetd_cacert.assert_called_once_with()
-        mock_log.assert_called_once_with("Step 1: Initialize database on 
10.10.10.123")
+        mock_log.assert_called_once_with("Step 1: Initialize database on 
10.10.10.123",
+                'corosync-qnetd-certutil -i')
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("os.path.exists")
     @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", 
new_callable=mock.PropertyMock)
@@ -436,7 +437,7 @@
         mock_fetch.assert_not_called()
         mock_log.assert_not_called()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("os.path.exists")
     @mock.patch("crmsh.qdevice.QDevice._fetch_file_to_local")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", 
new_callable=mock.PropertyMock)
@@ -452,7 +453,7 @@
         mock_log.assert_called_once_with("Step 2: Fetch qnetd-cacert.crt from 
10.10.10.123")
         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.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.list_cluster_nodes")
     @mock.patch("crmsh.utils.this_node")
     @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host")
@@ -467,7 +468,7 @@
         mock_copy.assert_not_called()
         mock_log.assert_not_called()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.list_cluster_nodes")
     @mock.patch("crmsh.utils.this_node")
     @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host")
@@ -488,7 +489,7 @@
         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")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.parallax.parallax_call")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_local", 
new_callable=mock.PropertyMock)
     @mock.patch("crmsh.utils.list_cluster_nodes")
@@ -501,21 +502,23 @@
 
         mock_list_nodes.assert_called_once_with()
         mock_qnetd_cacert_local.assert_called_once_with()
-        mock_log.assert_called_once_with("Step 4: Initialize database on 
['node1', 'node2']")
+        mock_log.assert_called_once_with("Step 4: Initialize database on 
['node1', 'node2']",
+                'corosync-qdevice-net-certutil -i -c 
/etc/corosync/qdevice/net/10.10.10.123/qnetd-cacert.crt')
         mock_call.assert_called_once_with(mock_list_nodes.return_value,
                                           "corosync-qdevice-net-certutil -i -c 
{}".format(mock_qnetd_cacert_local.return_value))
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.get_stdout_or_raise_error")
     def test_create_ca_request(self, mock_stdout_stderr, mock_log):
         mock_stdout_stderr.return_value = (0, None, None)
 
         self.qdevice_with_cluster_name.create_ca_request()
 
-        mock_log.assert_called_once_with("Step 5: Generate certificate request 
qdevice-net-node.crq")
+        mock_log.assert_called_once_with("Step 5: Generate certificate request 
qdevice-net-node.crq",
+                'corosync-qdevice-net-certutil -r -n hacluster1')
         
mock_stdout_stderr.assert_called_once_with("corosync-qdevice-net-certutil -r -n 
hacluster1")
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.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.qdevice.QDevice._copy_file_to_remote_host")
@@ -532,7 +535,7 @@
         mock_qdevice_crq_local.assert_called_once_with()
         mock_qdevice_crq_qnetd.assert_called_once_with()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.parallax.parallax_call")
     @mock.patch("crmsh.qdevice.QDevice.qdevice_crq_on_qnetd", 
new_callable=mock.PropertyMock)
     def test_sign_crq_on_qnetd(self, mock_qdevice_crq_qnetd, mock_call, 
mock_log):
@@ -542,12 +545,13 @@
         self.qdevice_with_ip.cluster_name = "hacluster"
         self.qdevice_with_ip.sign_crq_on_qnetd()
 
-        mock_log.assert_called_once_with("Step 7: Sign and export cluster 
certificate on 10.10.10.123")
+        mock_log.assert_called_once_with("Step 7: Sign and export cluster 
certificate on 10.10.10.123",
+                'corosync-qnetd-certutil -s -c 
/etc/corosync/qnetd/nssdb/qdevice-net-node.crq -n hacluster')
         mock_qdevice_crq_qnetd.assert_called_once_with()
         mock_call.assert_called_once_with(["10.10.10.123"],
                                           "corosync-qnetd-certutil -s -c {} -n 
hacluster".format(mock_qdevice_crq_qnetd.return_value))
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cluster_crt_on_qnetd", 
new_callable=mock.PropertyMock)
     @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):
@@ -560,7 +564,7 @@
         mock_crt_on_qnetd.assert_has_calls([mock.call(), mock.call()])
         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.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.get_stdout_or_raise_error")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cluster_crt_on_local", 
new_callable=mock.PropertyMock)
     def test_import_cluster_crt(self, mock_crt_on_local, mock_stdout_stderr, 
mock_log):
@@ -568,11 +572,12 @@
 
         self.qdevice_with_ip.import_cluster_crt()
 
-        mock_log.assert_called_once_with("Step 9: Import certificate file 
cluster-hacluster.crt on local")
+        mock_log.assert_called_once_with("Step 9: Import certificate file 
cluster-hacluster.crt on local",
+                'corosync-qdevice-net-certutil -M -c 
/etc/corosync/qdevice/net/10.10.10.123/cluster-hacluster.crt')
         mock_crt_on_local.assert_has_calls([mock.call(), mock.call()])
         
mock_stdout_stderr.assert_called_once_with("corosync-qdevice-net-certutil -M -c 
{}".format(mock_crt_on_local.return_value))
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.list_cluster_nodes")
     @mock.patch("crmsh.utils.this_node")
     @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_host")
@@ -587,7 +592,7 @@
         mock_list_nodes.assert_called_once_with()
         mock_copy.assert_not_called()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.list_cluster_nodes")
     @mock.patch("crmsh.utils.this_node")
     @mock.patch("crmsh.qdevice.QDevice._copy_file_to_remote_hosts")
@@ -607,7 +612,7 @@
                                           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")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.parallax.parallax_call")
     @mock.patch("crmsh.utils.list_cluster_nodes_except_me")
     def test_import_p12_on_cluster_one_node(self, mock_list_nodes, mock_call, 
mock_log):
@@ -620,7 +625,7 @@
         mock_call.assert_not_called()
 
     @mock.patch("crmsh.parallax.parallax_call")
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_local", 
new_callable=mock.PropertyMock)
     @mock.patch("crmsh.utils.list_cluster_nodes_except_me")
     def test_import_p12_on_cluster(self, mock_list_nodes, mock_p12_on_local, 
mock_log, mock_call):
@@ -630,7 +635,8 @@
 
         self.qdevice_with_ip.import_p12_on_cluster()
 
-        mock_log.assert_called_once_with("Step 11: Import qdevice-net-node.p12 
on ['node2', 'node3']")
+        mock_log.assert_called_once_with("Step 11: Import qdevice-net-node.p12 
on ['node2', 'node3']",
+                'corosync-qdevice-net-certutil -m -c 
/etc/corosync/qdevice/net/nssdb/qdevice-net-node.p12')
         mock_list_nodes.assert_called_once_with()
         mock_call.assert_called_once_with(
                 ["node2", "node3"],
@@ -666,7 +672,7 @@
         mock_copy_p12_to_cluster.assert_called_once_with()
         mock_import_p12_on_cluster.assert_called_once_with()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.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)
@@ -684,7 +690,7 @@
         mock_qnetd_cacert_local.assert_not_called()
         mock_fetch.assert_not_called()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.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)
@@ -703,7 +709,7 @@
         mock_qnetd_cacert_local.assert_called_once_with()
         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.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.get_stdout_or_raise_error")
     @mock.patch("crmsh.qdevice.QDevice.qnetd_cacert_on_cluster", 
new_callable=mock.PropertyMock)
     def test_init_db_on_local(self, mock_qnetd_cacert_cluster, 
mock_stdout_stderr, mock_log):
@@ -712,11 +718,12 @@
 
         self.qdevice_with_ip_cluster_node.init_db_on_local()
 
-        mock_log.assert_called_once_with("Step 2: Initialize database on 
local")
+        mock_log.assert_called_once_with("Step 2: Initialize database on 
local",
+                'corosync-qdevice-net-certutil -i -c 
/etc/corosync/qdevice/net/node1.com/qnetd-cacert.crt')
         mock_qnetd_cacert_cluster.assert_called_once_with()
         
mock_stdout_stderr.assert_called_once_with("corosync-qdevice-net-certutil -i -c 
{}".format(mock_qnetd_cacert_cluster.return_value))
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.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)
@@ -734,7 +741,7 @@
         mock_p12_on_local.assert_not_called()
         mock_fetch.assert_not_called()
 
-    @mock.patch("crmsh.log.LoggerUtils.log_only_to_file")
+    @mock.patch("crmsh.qdevice.QDevice.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)
@@ -753,7 +760,7 @@
         mock_p12_on_local.assert_called_once_with()
         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.qdevice.QDevice.log_only_to_file")
     @mock.patch("crmsh.utils.get_stdout_or_raise_error")
     @mock.patch("crmsh.qdevice.QDevice.qdevice_p12_on_cluster", 
new_callable=mock.PropertyMock)
     def test_import_p12_on_local(self, mock_p12_on_cluster, 
mock_stdout_stderr, mock_log):
@@ -761,7 +768,8 @@
 
         self.qdevice_with_ip_cluster_node.import_p12_on_local()
 
-        mock_log.assert_called_once_with("Step 4: Import cluster certificate 
and key")
+        mock_log.assert_called_once_with("Step 4: Import cluster certificate 
and key",
+                'corosync-qdevice-net-certutil -m -c 
/etc/corosync/qdevice/net/node1.com/qdevice-net-node.p12')
         mock_p12_on_cluster.assert_called_once_with()
         
mock_stdout_stderr.assert_called_once_with("corosync-qdevice-net-certutil -m -c 
{}".format(mock_p12_on_cluster.return_value))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_utils.py 
new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_utils.py
--- old/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_utils.py      
2023-03-09 10:55:42.000000000 +0100
+++ new/crmsh-4.5.0+20230320.5e777809/test/unittests/test_utils.py      
2023-03-20 08:49:02.000000000 +0100
@@ -1227,28 +1227,25 @@
         self.assertEqual(b'bar', result.stdout)
 
     @mock.patch("crmsh.utils.this_node")
-    @mock.patch("crmsh.utils.user_of")
+    @mock.patch('crmsh.utils.user_pair_for_ssh')
     @mock.patch("crmsh.utils.su_subprocess_run")
     @mock.patch("subprocess.run")
     def test_subprocess_run_auto_ssh_no_input_remote_no_user(
             self,
             mock_subprocess_run: mock.MagicMock,
             mock_su_subprocess_run: mock.MagicMock,
-            mock_user_of: mock.MagicMock,
+            mock_user_pair_for_ssh: mock.MagicMock,
             mock_this_node: mock.MagicMock,
     ):
         mock_this_node.return_value = 'node1'
-        mock_user_of.return_value = 'alice'
+        mock_user_pair_for_ssh.return_value = "alice", "bob"
         mock_su_subprocess_run.return_value = mock.Mock(returncode=0, 
stdout=b'bar', stderr=b'')
         result = utils.subprocess_run_auto_ssh_no_input("foo", "node2", 
stderr=subprocess.DEVNULL)
-        mock_user_of.assert_has_calls([
-            mock.call('node1'),
-            mock.call('node2'),
-        ], any_order=True)
+        mock_user_pair_for_ssh.assert_called_once_with('node2')
         mock_subprocess_run.assert_not_called()
         mock_su_subprocess_run.assert_called_once_with(
             'alice',
-            'ssh -o StrictHostKeyChecking=no alice@node2 sudo -H -u root 
/bin/sh',
+            'ssh -o StrictHostKeyChecking=no bob@node2 sudo -H -u root 
/bin/sh',
             input=b'foo',
             stderr=subprocess.DEVNULL,
         )

Reply via email to