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-09 17:46:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.31432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Thu Mar 9 17:46:51 2023 rev:285 rq:1070389 version:4.5.0+20230309.a4c4192d Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2023-03-07 16:50:53.389759978 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.31432/crmsh.changes 2023-03-09 17:46:52.759271772 +0100 @@ -1,0 +2,16 @@ +Thu Mar 09 10:28:49 UTC 2023 - xli...@suse.com + +- Update to version 4.5.0+20230309.a4c4192d: + * Dev: ChangeLog: update ChangeLog for release 4.5.0-rc2 + * Dev: version: Bump crmsh version to 4.5.0 + +------------------------------------------------------------------- +Thu Mar 09 08:27:30 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230309.e15745c6: + * Dev: bootstrap: remove unused codes + * Dev: unittest: Adjust unit test for previous change + * Dev: bootstrap: Swap hacluster ssh key with other nodes + * Dev: behave: Check passwordless for hacluster between cluster nodes + +------------------------------------------------------------------- Old: ---- crmsh-4.4.1+20230307.daea9d13.tar.bz2 New: ---- crmsh-4.5.0+20230309.a4c4192d.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.Fx3W5a/_old 2023-03-09 17:46:53.423275306 +0100 +++ /var/tmp/diff_new_pack.Fx3W5a/_new 2023-03-09 17:46:53.427275328 +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+20230307.daea9d13 +Version: 4.5.0+20230309.a4c4192d Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.Fx3W5a/_old 2023-03-09 17:46:53.459275498 +0100 +++ /var/tmp/diff_new_pack.Fx3W5a/_new 2023-03-09 17:46:53.463275520 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/ClusterLabs/crmsh.git</param> <param name="scm">git</param> <param name="filename">crmsh</param> - <param name="versionformat">4.4.1+%cd.%h</param> + <param name="versionformat">4.5.0+%cd.%h</param> <param name="revision">master</param> <param name="changesgenerate">enable</param> </service> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.Fx3W5a/_old 2023-03-09 17:46:53.483275626 +0100 +++ /var/tmp/diff_new_pack.Fx3W5a/_new 2023-03-09 17:46:53.487275648 +0100 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">3b0a582ed4f2b9de2e74d86bfdd6bf7e2ca44511</param> + <param name="changesrevision">1615c26abe860045bf4e15442899e992cc29455c</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-4.4.1+20230307.daea9d13.tar.bz2 -> crmsh-4.5.0+20230309.a4c4192d.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/ChangeLog new/crmsh-4.5.0+20230309.a4c4192d/ChangeLog --- old/crmsh-4.4.1+20230307.daea9d13/ChangeLog 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/ChangeLog 2023-03-09 10:55:42.000000000 +0100 @@ -1,3 +1,11 @@ +* Thu Mar 9 2023 Xin Liang <xli...@suse.com> +- Release 4.5.0 rc2 +- Dev: version: Bump crmsh version to 4.5.0 +- Fix: bootstrap: Swap hacluster ssh key with other nodes +- Fix: report: Fix crm report issue under non-root user +- Fix: bootstrap: Don't save core.debug when saving core.hosts (bsc#1208991) +- Dev: log: Redirect debug messages into stderr + * Fri Mar 3 2023 Xin Liang <xli...@suse.com> - Release 4.5.0 rc1 - Fix: qdevice: Unable to setup qdevice under non-root user (bsc#1208770) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/configure.ac new/crmsh-4.5.0+20230309.a4c4192d/configure.ac --- old/crmsh-4.4.1+20230307.daea9d13/configure.ac 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/configure.ac 2023-03-09 10:55:42.000000000 +0100 @@ -8,7 +8,7 @@ AC_PREREQ([2.53]) -AC_INIT([crmsh],[4.4.0],[us...@clusterlabs.org]) +AC_INIT([crmsh],[4.5.0],[us...@clusterlabs.org]) AC_ARG_WITH(version, [ --with-version=version Override package version (if you're a packager needing to pretend) ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/crmsh/bootstrap.py new/crmsh-4.5.0+20230309.a4c4192d/crmsh/bootstrap.py --- old/crmsh-4.4.1+20230307.daea9d13/crmsh/bootstrap.py 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/crmsh/bootstrap.py 2023-03-09 10:55:42.000000000 +0100 @@ -864,7 +864,6 @@ for node in _context.node_list: if utils.service_is_active("pacemaker.service", remote_addr=node): utils.fatal("Cluster is currently active on {} - can't run".format(node)) - print() def init_ssh_impl(local_user: str, node_list: typing.List[str], user_list: typing.List[str]): @@ -970,27 +969,35 @@ return keyfile_dict -def is_nologin(user): +def is_nologin(user, remote=None): """ - Check if user's shell is /sbin/nologin + Check if user's shell is nologin """ - with open("/etc/passwd") as f: - return re.search("{}:.*:/sbin/nologin".format(user), f.read()) + passwd_file = "/etc/passwd" + pattern = f"{user}:.*:/.*/nologin" + if remote: + cmd = f"cat {passwd_file}|grep {pattern}" + rc, _, _ = utils.run_cmd_on_remote(cmd, remote) + return rc == 0 + else: + with open(passwd_file) as f: + return re.search(pattern, f.read()) -# FIXME! What is this all about? -def change_user_shell(user): +def change_user_shell(user, remote=None): """ To change user's login shell """ - message = "The user '{}' will have the login shell configuration changed to /bin/bash" - if user != "root" and is_nologin(user): + user_msg = f"'{user}' on {remote}" if remote else f"'{user}'" + message = f"The user {user_msg} will have the login shell configuration changed to /bin/bash" + if user != "root" and is_nologin(user, remote): if _context is not None and not _context.yes_to_all: - logger.info(message.format(user)) + logger.info(message) if not confirm("Continue?"): _context.with_other_user = False return - invoke("usermod -s /bin/bash {}".format(user)) + cmd = f"usermod -s /bin/bash {user}" + utils.get_stdout_or_raise_error(cmd, remote) def configure_ssh_key(user): @@ -1672,6 +1679,7 @@ # After this, login to remote_node is passwordless swap_public_ssh_key(seed_host, local_user, seed_user, local_user, seed_user, add=True) configure_ssh_key('hacluster') + change_user_shell('hacluster', seed_host) swap_public_ssh_key(seed_host, 'hacluster', 'hacluster', local_user, seed_user, add=True) # This makes sure the seed host has its own SSH keys in its own @@ -1735,29 +1743,6 @@ )) return result.stdout.decode('utf-8') -def fetch_public_key_from_remote_node(node, user="root"): - """ - Fetch public key file from remote node - Return a temp file contains public key - Return None if no key exist - """ - - # For dsa, might need to add PubkeyAcceptedKeyTypes=+ssh-dss to config file, see - # https://superuser.com/questions/1016989/ssh-dsa-keys-no-longer-work-for-password-less-authentication - home_dir = userdir.gethomedir(user) - for key in ("id_rsa", "id_ecdsa", "id_ed25519", "id_dsa"): - public_key_file = "{}/.ssh/{}.pub".format(home_dir, key) - cmd = "ssh {} {}@{} 'test -f {}'".format(SSH_OPTION, user, node, public_key_file) - if not invokerc(cmd): - continue - _, temp_public_key_file = tmpfiles.create() - cmd = "scp {} {}@{}:{} {}".format(SSH_OPTION, user, node, public_key_file, temp_public_key_file) - rc, _, err = invoke(cmd) - if not rc: - utils.fatal("Failed to run \"{}\": {}".format(cmd, err)) - return temp_public_key_file - raise ValueError("No ssh key exist on {}".format(node)) - def join_csync2(seed_host): """ @@ -1970,9 +1955,32 @@ 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) + if local_user != 'hacluster': + change_user_shell('hacluster', node) + swap_public_ssh_key(node, 'hacluster', 'hacluster', local_user, remote_privileged_user, add=True) + if local_user != 'hacluster': + swap_key_for_hacluster(cluster_nodes_list) + user_by_host.save_remote(cluster_nodes_list) +def swap_key_for_hacluster(other_node_list): + """ + In some cases, old cluster may not be configured passwordless for hacluster. + The new join node should check and swap the public key between the old cluster nodes. + """ + for node in other_node_list: + for n in other_node_list: + if node == n: + continue + logger.info("Checking passwordless for hacluster between %s and %s", node, n) + _, public_key, authorized_file = key_files('hacluster').values() + public_key_content = utils.get_stdout_or_raise_error(f'cat {public_key}', remote=n) + if not utils.check_text_included(public_key_content, authorized_file, remote=node): + cmd = "echo '{}' >> {}".format(public_key_content, authorized_file) + utils.get_stdout_or_raise_error(cmd, remote=node) + + def sync_files_to_disk(): """ Sync file content to disk between cluster nodes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/setup.py new/crmsh-4.5.0+20230309.a4c4192d/setup.py --- old/crmsh-4.4.1+20230307.daea9d13/setup.py 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/setup.py 2023-03-09 10:55:42.000000000 +0100 @@ -4,7 +4,7 @@ from setuptools import setup setup(name='crmsh', - version='4.4.0', + version='4.5.0', description='Command-line interface for High-Availability cluster management', author='Kristoffer Gronlund, Xin Liang', author_email='xli...@suse.com', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/test/features/bootstrap_init_join_remove.feature new/crmsh-4.5.0+20230309.a4c4192d/test/features/bootstrap_init_join_remove.feature --- old/crmsh-4.4.1+20230307.daea9d13/test/features/bootstrap_init_join_remove.feature 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/test/features/bootstrap_init_join_remove.feature 2023-03-09 10:55:42.000000000 +0100 @@ -2,10 +2,10 @@ Feature: crmsh bootstrap process - init, join and remove Test crmsh bootstrap init/join/remove process - Need nodes: hanode1 hanode2 + Need nodes: hanode1 hanode2 hanode3 Background: Setup a two nodes cluster - Given Nodes ["hanode1", "hanode2"] are cleaned up + Given Nodes ["hanode1", "hanode2", "hanode3"] are cleaned up And Cluster service is "stopped" on "hanode1" And Cluster service is "stopped" on "hanode2" When Run "crm cluster init -y" on "hanode1" @@ -150,3 +150,34 @@ Then Directory "/var/lib/pacemaker/cib/" is empty on "hanode1" Then Directory "/var/lib/pacemaker/pengine/" is empty on "hanode1" Then Directory "/var/lib/corosync/" is empty on "hanode1" + + Scenario: Check hacluster's passwordless configuration on 2 nodes + Then Check passwordless for hacluster between "hanode1 hanode2" + + Scenario: Check hacluster's passwordless configuration in old cluster, 2 nodes + When Run "crm cluster stop --all" on "hanode1" + Then Cluster service is "stopped" on "hanode1" + And Cluster service is "stopped" on "hanode2" + When Run "crm cluster init -y" on "hanode1" + Then Cluster service is "started" on "hanode1" + When Run "rm -rf /var/lib/heartbeat/cores/hacluster/.ssh" on "hanode1" + When Run "crm cluster join -c hanode1 -y" on "hanode2" + Then Cluster service is "started" on "hanode2" + And Online nodes are "hanode1 hanode2" + And Check passwordless for hacluster between "hanode1 hanode2" + + Scenario: Check hacluster's passwordless configuration on 3 nodes + Given Cluster service is "stopped" on "hanode3" + When Run "crm cluster join -c hanode1 -y" on "hanode3" + Then Cluster service is "started" on "hanode3" + And Online nodes are "hanode1 hanode2 hanode3" + And Check passwordless for hacluster between "hanode1 hanode2 hanode3" + + Scenario: Check hacluster's passwordless configuration in old cluster, 3 nodes + Given Cluster service is "stopped" on "hanode3" + When Run "rm -rf /var/lib/heartbeat/cores/hacluster/.ssh" on "hanode1" + And Run "rm -rf /var/lib/heartbeat/cores/hacluster/.ssh" on "hanode2" + When Run "crm cluster join -c hanode1 -y" on "hanode3" + Then Cluster service is "started" on "hanode3" + And Online nodes are "hanode1 hanode2 hanode3" + And Check passwordless for hacluster between "hanode1 hanode2 hanode3" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/test/features/steps/step_implementation.py new/crmsh-4.5.0+20230309.a4c4192d/test/features/steps/step_implementation.py --- old/crmsh-4.4.1+20230307.daea9d13/test/features/steps/step_implementation.py 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/test/features/steps/step_implementation.py 2023-03-09 10:55:42.000000000 +0100 @@ -6,7 +6,7 @@ import behave from behave import given, when, then -from crmsh import corosync, parallax, sbd +from crmsh import corosync, parallax, sbd, userdir from crmsh import utils as crmutils from utils import check_cluster_state, check_service_state, online, run_command, me, \ run_command_local_or_remote, file_in_archive, \ @@ -479,3 +479,18 @@ index += 1 return False +@then('Check passwordless for hacluster between "{nodelist}"') +def step_impl(context, nodelist): + if userdir.getuser() != 'root': + return True + ssh_option = "-o StrictHostKeyChecking=no -T -o Batchmode=yes" + node_list = nodelist.split() + for node in node_list: + for n in node_list: + if node == n: + continue + cmd = f"su - hacluster -c 'ssh {ssh_option} hacluster@{n} true'" + if node != me(): + cmd = f"ssh {node} \"{cmd}\"" + context.logger.info(f"\nRun cmd: {cmd}") + run_command(context, cmd) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230307.daea9d13/test/unittests/test_bootstrap.py new/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_bootstrap.py --- old/crmsh-4.4.1+20230307.daea9d13/test/unittests/test_bootstrap.py 2023-03-07 02:41:16.000000000 +0100 +++ new/crmsh-4.5.0+20230309.a4c4192d/test/unittests/test_bootstrap.py 2023-03-09 10:55:42.000000000 +0100 @@ -361,19 +361,19 @@ bootstrap.change_user_shell("hacluster") - mock_nologin.assert_called_once_with("hacluster") + mock_nologin.assert_called_once_with("hacluster", None) mock_confirm.assert_called_once_with("Continue?") - @mock.patch('crmsh.bootstrap.invoke') + @mock.patch('crmsh.utils.get_stdout_or_raise_error') @mock.patch('crmsh.bootstrap.is_nologin') - def test_change_user_shell_return(self, mock_nologin, mock_invoke): + def test_change_user_shell(self, mock_nologin, mock_invoke): bootstrap._context = mock.Mock(yes_to_all=True) mock_nologin.return_value = True bootstrap.change_user_shell("hacluster") - mock_nologin.assert_called_once_with("hacluster") - mock_invoke.assert_called_once_with("usermod -s /bin/bash hacluster") + mock_nologin.assert_called_once_with("hacluster", None) + mock_invoke.assert_called_once_with("usermod -s /bin/bash hacluster", None) @mock.patch('crmsh.utils.su_subprocess_run') def test_generate_ssh_key_pair_on_remote(self, mock_su: mock.MagicMock): @@ -441,36 +441,6 @@ cmd = "cat fromfile | ssh {} root@node1 'cat >> tofile'".format(constants.SSH_OPTION) mock_run.assert_called_once_with(cmd) - @mock.patch('crmsh.bootstrap.invokerc') - def test_fetch_public_key_from_remote_node_exception(self, mock_invoke): - mock_invoke.side_effect = [False, False, False, False] - - with self.assertRaises(ValueError) as err: - bootstrap.fetch_public_key_from_remote_node("node1") - self.assertEqual("No ssh key exist on node1", str(err.exception)) - - mock_invoke.assert_has_calls([ - mock.call("ssh {} root@node1 'test -f /root/.ssh/id_rsa.pub'".format(constants.SSH_OPTION)), - mock.call("ssh {} root@node1 'test -f /root/.ssh/id_ecdsa.pub'".format(constants.SSH_OPTION)), - mock.call("ssh {} root@node1 'test -f /root/.ssh/id_ed25519.pub'".format(constants.SSH_OPTION)), - mock.call("ssh {} root@node1 'test -f /root/.ssh/id_dsa.pub'".format(constants.SSH_OPTION)) - ]) - - @mock.patch('crmsh.tmpfiles.create') - @mock.patch('crmsh.bootstrap.invokerc') - @mock.patch('crmsh.bootstrap.invoke') - def test_fetch_public_key_from_remote_node(self, mock_invoke, mock_invokerc, mock_tmpfile): - mock_invokerc.return_value = True - mock_invoke.return_value = (True, None, None) - mock_tmpfile.return_value = (0, "temp_file_name") - - res = bootstrap.fetch_public_key_from_remote_node("node1") - self.assertEqual(res, "temp_file_name") - - mock_invokerc.assert_called_once_with("ssh {} root@node1 'test -f /root/.ssh/id_rsa.pub'".format(constants.SSH_OPTION)) - mock_invoke.assert_called_once_with("scp -o StrictHostKeyChecking=no root@node1:/root/.ssh/id_rsa.pub temp_file_name") - mock_tmpfile.assert_called_once_with() - @mock.patch('crmsh.utils.fatal') def test_join_ssh_no_seed_host(self, mock_error): mock_error.side_effect = ValueError @@ -478,12 +448,13 @@ bootstrap.join_ssh_impl(None, None) mock_error.assert_called_once_with("No existing IP/hostname specified (use -c option)") + @mock.patch('crmsh.bootstrap.change_user_shell') @mock.patch('crmsh.utils.su_get_stdout_or_raise_error') @mock.patch('crmsh.bootstrap.swap_public_ssh_key') @mock.patch('crmsh.utils.ssh_copy_id') @mock.patch('crmsh.bootstrap.configure_ssh_key') @mock.patch('crmsh.utils.start_service') - def test_join_ssh(self, mock_start_service, mock_config_ssh, mock_ssh_copy_id, mock_swap, mock_invoke): + def test_join_ssh(self, mock_start_service, mock_config_ssh, mock_ssh_copy_id, mock_swap, mock_invoke, mock_change): bootstrap._context = mock.Mock(current_user="bob", user_list=["alice"], node_list=['node1'], default_nic_list=["eth1"]) mock_invoke.return_value = '' mock_swap.return_value = None @@ -594,6 +565,8 @@ ]) mock_error.assert_called_once_with("Can't fetch hostname of node1: None") + @mock.patch('crmsh.bootstrap.swap_key_for_hacluster') + @mock.patch('crmsh.bootstrap.change_user_shell') @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.bootstrap._fetch_core_hosts') @mock.patch('crmsh.utils.ssh_copy_id') @@ -608,7 +581,8 @@ mock_ssh_copy_id: mock.MagicMock, mock_fetch_core_hosts, mock_host_user_config_class, - + mock_change_shell, + mock_swap_hacluster ): bootstrap._context = mock.Mock(current_user="carol", user_list=["alice", "bob"]) mock_fetch_core_hosts.return_value = (["alice", "bob"], ["node1", "node2"]) @@ -630,7 +604,10 @@ mock_ssh_copy_id.assert_has_calls([ mock.call('carol', 'bob', 'node2') ]) - mock_swap.assert_called_once_with('node2', "carol", "bob", "carol", "bob") + mock_swap.assert_has_calls([ + mock.call('node2', "carol", "bob", "carol", "bob"), + mock.call('node2', 'hacluster', 'hacluster', 'carol', 'bob', add=True) + ]) @mock.patch('crmsh.userdir.getuser') @mock.patch('crmsh.bootstrap.key_files')