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-02-24 18:08:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.31432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Fri Feb 24 18:08:19 2023 rev:281 rq:1067567 version:4.4.1+20230224.498677ab Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2023-02-21 15:36:34.648471625 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.31432/crmsh.changes 2023-02-24 18:08:27.793532494 +0100 @@ -1,0 +2,16 @@ +Fri Feb 24 09:36:48 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230224.498677ab: + * Dev: utils: Suppress the output of ssh-copy-id + +------------------------------------------------------------------- +Fri Feb 24 02:32:31 UTC 2023 - xli...@suse.com + +- Update to version 4.4.1+20230224.83dcde63: + * Dev: unittest: remove test_healthcheck.py since the new implementation is trivial + * Fix: bootstrap: fail to join a cluster initialized by previous version + * Dev: healthcheck: refine the implementation to fix ssh key problems by calling function init_ssh instead of calling command `cluster init ssh` + * Dev: upgradeutil: do upgrade silently (bsc#1208327) + * Fix: bootstrap: `crm cluster join ssh` raises TypeError (bsc#1208327) + +------------------------------------------------------------------- Old: ---- crmsh-4.4.1+20230221.eb38cb6e.tar.bz2 New: ---- crmsh-4.4.1+20230224.498677ab.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.l4rkzU/_old 2023-02-24 18:08:28.557537047 +0100 +++ /var/tmp/diff_new_pack.l4rkzU/_new 2023-02-24 18:08:28.561537071 +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+20230221.eb38cb6e +Version: 4.4.1+20230224.498677ab Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.l4rkzU/_old 2023-02-24 18:08:28.617537405 +0100 +++ /var/tmp/diff_new_pack.l4rkzU/_new 2023-02-24 18:08:28.621537429 +0100 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">61f8d99e061ce5bed9285b846e9494e556a7763c</param> + <param name="changesrevision">498677abb1bf88b1490339d307e925742854080b</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-4.4.1+20230221.eb38cb6e.tar.bz2 -> crmsh-4.4.1+20230224.498677ab.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/bootstrap.py new/crmsh-4.4.1+20230224.498677ab/crmsh/bootstrap.py --- old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/bootstrap.py 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/crmsh/bootstrap.py 2023-02-24 10:04:38.000000000 +0100 @@ -851,33 +851,40 @@ def init_ssh(): - """ - Configure passwordless SSH. - """ + init_ssh_impl(_context.current_user, _context.node_list, _context.user_list) + if _context.node_list: + 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]): + """ Configure passwordless SSH.""" utils.start_service("sshd.service", enable=True) - local_user = _context.current_user configure_ssh_key(local_user) configure_ssh_key('hacluster') # If not use -N/--nodes option - if not _context.node_list: - _save_core_hosts([_context.current_user], [utils.this_node()], sync_to_remote=False) + if not node_list: + _save_core_hosts([local_user], [utils.this_node()], sync_to_remote=False) return print() - node_list = _context.node_list # Swap public ssh key between remote node and local public_key_list = list() + hacluster_public_key_list = list() for i, node in enumerate(node_list): - remote_user = _context.user_list[i] + remote_user = user_list[i] utils.ssh_copy_id(local_user, remote_user, node) # After this, login to remote_node is passwordless - public_key = swap_public_ssh_key(node, local_user, remote_user, local_user, remote_user, add=True) - public_key_list.append(public_key) + public_key_list.append(swap_public_ssh_key(node, local_user, remote_user, local_user, remote_user, add=True)) + hacluster_public_key_list.append(swap_public_ssh_key(node, 'hacluster', 'hacluster', local_user, remote_user, add=True)) if len(node_list) > 1: shell_script = _merge_authorized_keys(public_key_list) + hacluster_shell_script = _merge_authorized_keys(hacluster_public_key_list) for i, node in enumerate(node_list): - remote_user = _context.user_list[i] + remote_user = user_list[i] result = utils.su_subprocess_run( local_user, 'ssh {} {}@{} /bin/sh'.format(constants.SSH_OPTION, remote_user, node), @@ -887,17 +894,23 @@ ) if result.returncode != 0: utils.fatal('Failed to add public keys to {}@{}: {}'.format(remote_user, node, result.stdout)) - if utils.this_node() not in _context.node_list: - 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=True) + result = utils.su_subprocess_run( + local_user, + 'ssh {} {}@{} sudo -H -u {} /bin/sh'.format(constants.SSH_OPTION, remote_user, node, 'hacluster'), + input=hacluster_shell_script, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + 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(_context.user_list, _context.node_list, sync_to_remote=True) - if utils.service_is_active("pacemaker.service", remote_addr=node): - utils.fatal("Cluster is currently active on {} - can't run".format(node)) - print() + _save_core_hosts(user_list, node_list, sync_to_remote=True) def _merge_authorized_keys(keys: typing.List[str]) -> bytes: @@ -991,7 +1004,7 @@ """ message = "The user '{}' will have the login shell configuration changed to /bin/bash" if user != "root" and is_nologin(user): - if not _context.yes_to_all: + if _context is not None and not _context.yes_to_all: logger.info(message.format(user)) if not confirm("Continue?"): _context.with_other_user = False @@ -1655,7 +1668,11 @@ init_network() -def join_ssh(seed_host, seed_user): +def join_ssh(seed_host): + join_ssh_impl(seed_host, _context.user_list[0]) + + +def join_ssh_impl(seed_host, seed_user): """ SSH configuration for joining node. """ @@ -1945,7 +1962,12 @@ tokens[1], tokens[2])) else: cluster_nodes_list.append(tokens[1]) - user_list, host_list = _fetch_core_hosts(local_user, remote_user, init_node) + try: + user_list, host_list = _fetch_core_hosts(local_user, remote_user, init_node) + 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) @@ -2435,7 +2457,7 @@ init_upgradeutil() utils.ping_node(cluster_node) - join_ssh(cluster_node, _context.user_list[0]) + join_ssh_impl(cluster_node, _context.user_list[0]) remote_user = utils.user_of(cluster_node) n = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/healthcheck.py new/crmsh-4.4.1+20230224.498677ab/crmsh/healthcheck.py --- old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/healthcheck.py 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/crmsh/healthcheck.py 2023-02-24 10:04:38.000000000 +0100 @@ -139,47 +139,15 @@ return False def fix_cluster(self, nodes: typing.Iterable[str], ask: typing.Callable[[str], None]) -> None: + import crmsh.bootstrap # import bootstrap lazily here to avoid circular dependency logger.debug("setup passwordless ssh authentication for user hacluster") - try: - nodes_without_keys = [ - node for node, result in - crmsh.parallax.parallax_run( - nodes, - 'sudo test [ -f ~hacluster/.ssh/id_rsa ] || [ -f ~hacluster/.ssh/id_ecdsa ] || [ -f ~hacluster/.ssh/id_ed25519 ]' - ).items() - if result[0] != 0 - ] - except parallax.Error: - raise FixFailure() - if nodes_without_keys: - ask("Setup passwordless ssh authentication for user hacluster?") - if len(nodes_without_keys) == len(nodes): - # pick one node to run init ssh on it - init_node = nodes_without_keys[0] - # and run join ssh on other nodes - join_nodes = list() - join_nodes.extend(nodes) - join_nodes.remove(init_node) - join_target_node = init_node - else: - nodes_with_keys = set(nodes) - set(nodes_without_keys) - # no need to init ssh - init_node = None - join_nodes = nodes_without_keys - # pick one node as join target - join_target_node = next(iter(nodes_with_keys)) - if init_node is not None: - try: - crmsh.parallax.parallax_call([init_node], 'crm cluster init ssh -y') - except ValueError as e: - logger.error('Failed to initialize passwordless ssh authentication on node %s.', init_node, exc_info=e) - raise FixFailure from None - try: - for node in join_nodes: - crmsh.parallax.parallax_call([node], 'crm cluster join ssh -c {} -y'.format(join_target_node)) - except ValueError as e: - logger.error('Failed to initialize passwordless ssh authentication.', exc_info=e) - raise FixFailure from None + local_node = crmsh.utils.this_node() + 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] + crmsh.bootstrap.init_ssh_impl(local_user, remote_nodes, remote_users) def main_check_local(args) -> int: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/upgradeutil.py new/crmsh-4.4.1+20230224.498677ab/crmsh/upgradeutil.py --- old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/upgradeutil.py 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/crmsh/upgradeutil.py 2023-02-24 10:04:38.000000000 +0100 @@ -51,6 +51,15 @@ return default +def _parallax_run(nodes: str, cmd: str) -> typing.Dict[str, typing.Tuple[int, bytes, bytes]]: + ret = crmsh.parallax.parallax_run(nodes, cmd) + for node, value in ret.items(): + if isinstance(value, parallax.Error): + logger.warning("SSH connection to remote node %s failed.", node, exc_info=value) + raise value + return ret + + def _is_upgrade_needed(nodes): """decide whether upgrading is needed by checking local sequence file""" needed = False @@ -71,7 +80,7 @@ def _is_cluster_target_seq_consistent(nodes): cmd = '/usr/bin/env python3 -m crmsh.upgradeutil get-seq' try: - results = list(crmsh.parallax.parallax_run(nodes, cmd).values()) + results = list(_parallax_run(nodes, cmd).values()) except parallax.Error as e: raise _SkipUpgrade() from None try: @@ -85,7 +94,7 @@ try: return min( _parse_upgrade_seq(stdout.strip()) if rc == 0 else (0, 0) - for rc, stdout, stderr in crmsh.parallax.parallax_run(nodes, 'cat {}'.format(SEQ_FILE_PATH)).values() + for rc, stdout, stderr in _parallax_run(nodes, 'cat {}'.format(SEQ_FILE_PATH)).values() ) except ValueError: return 0, 0 @@ -93,19 +102,18 @@ def _upgrade(nodes, seq): def ask(msg: str): - if not crmsh.utils.ask('Upgrade of crmsh configuration: ' + msg, background_wait=False): - raise crmsh.healthcheck.AskDeniedByUser() + pass try: for key in VERSION_FEATURES.keys(): if seq < key <= CURRENT_UPGRADE_SEQ: for feature_class in VERSION_FEATURES[key]: feature = feature_class() if crmsh.healthcheck.feature_full_check(feature, nodes): - logger.info("Upgrade: feature %s is already functional.") + logger.debug("upgradeutil: feature %s is already functional.") else: - logger.debug("Upgrade: fixing feature %s...") + logger.debug("upgradeutil: fixing feature %s...") crmsh.healthcheck.feature_fix(feature, nodes, ask) - logger.info("Upgrade of crmsh configuration succeeded.") + logger.debug("upgradeutil: upgrade succeeded.") except crmsh.healthcheck.AskDeniedByUser: raise _SkipUpgrade() from None @@ -113,32 +121,36 @@ def upgrade_if_needed(): if os.geteuid() != 0: return + if not crmsh.utils.can_ask(background_wait=False): + return nodes = crmsh.utils.list_cluster_nodes(no_reg=True) if nodes and _is_upgrade_needed(nodes): - logger.info("crmsh version is newer than its configuration. Configuration upgrade is needed.") + logger.debug("upgradeutil: configuration upgrade needed") try: if not _is_cluster_target_seq_consistent(nodes): logger.warning("crmsh version is inconsistent in cluster.") raise _SkipUpgrade() seq = _get_minimal_seq_in_cluster(nodes) logger.debug( - "Upgrading crmsh configuration from seq %s to %s.", + "Upgrading crmsh from seq %s to %s.", seq, _format_upgrade_seq(CURRENT_UPGRADE_SEQ), ) _upgrade(nodes, seq) except _SkipUpgrade: - logger.warning("Upgrade of crmsh configuration skipped.") + logger.debug("upgradeutil: upgrade skipped") return - crmsh.parallax.parallax_call( - nodes, - "mkdir -p '{}' && echo '{}' > '{}'".format( - DATA_DIR, - _format_upgrade_seq(CURRENT_UPGRADE_SEQ), - SEQ_FILE_PATH, - ), - ) + # TODO: replace with parallax_copy when it is ready + for node in nodes: + crmsh.utils.get_stdout_or_raise_error( + "mkdir -p '{}' && echo '{}' > '{}'".format( + DATA_DIR, + _format_upgrade_seq(CURRENT_UPGRADE_SEQ), + SEQ_FILE_PATH, + ), + node, + ) crmsh.parallax.parallax_call(nodes, 'rm -f {}'.format(FORCE_UPGRADE_FILE_PATH)) - logger.debug("Upgrade of crmsh configuration finished.", seq) + logger.debug("upgrade finished") def force_set_local_upgrade_seq(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/utils.py new/crmsh-4.4.1+20230224.498677ab/crmsh/utils.py --- old/crmsh-4.4.1+20230221.eb38cb6e/crmsh/utils.py 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/crmsh/utils.py 2023-02-24 10:04:38.000000000 +0100 @@ -155,7 +155,12 @@ 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) + result = su_subprocess_run( + local_user, + cmd, + tty=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) if result.returncode != 0: fatal("Failed to login to remote host {}@{}".format(remote_user, remote_node)) @@ -268,7 +273,11 @@ """ can_ask = (not options.ask_no) and sys.stdin.isatty() if not background_wait: - can_ask = can_ask and os.tcgetpgrp(sys.stdin.fileno()) == os.getpgrp() + try: + can_ask = can_ask and os.tcgetpgrp(sys.stdin.fileno()) == os.getpgrp() + except OSError as e: + if e.errno == errno.ENOTTY: + can_ask = False return can_ask diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/data-manifest new/crmsh-4.4.1+20230224.498677ab/data-manifest --- old/crmsh-4.4.1+20230221.eb38cb6e/data-manifest 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/data-manifest 2023-02-24 10:04:38.000000000 +0100 @@ -189,7 +189,6 @@ test/unittests/test_crashtest_utils.py test/unittests/test_gv.py test/unittests/test_handles.py -test/unittests/test_healthcheck.py test/unittests/test_lock.py test/unittests/test_objset.py test/unittests/test_ocfs2.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/test/unittests/test_bootstrap.py new/crmsh-4.4.1+20230224.498677ab/test/unittests/test_bootstrap.py --- old/crmsh-4.4.1+20230221.eb38cb6e/test/unittests/test_bootstrap.py 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/test/unittests/test_bootstrap.py 2023-02-24 10:04:38.000000000 +0100 @@ -467,7 +467,7 @@ def test_join_ssh_no_seed_host(self, mock_error): mock_error.side_effect = ValueError with self.assertRaises(ValueError): - bootstrap.join_ssh(None, None) + bootstrap.join_ssh_impl(None, None) mock_error.assert_called_once_with("No existing IP/hostname specified (use -c option)") @mock.patch('crmsh.utils.su_get_stdout_or_raise_error') @@ -480,7 +480,7 @@ mock_invoke.return_value = '' mock_swap.return_value = None - bootstrap.join_ssh("node1", "alice") + bootstrap.join_ssh_impl("node1", "alice") mock_start_service.assert_called_once_with("sshd.service", enable=True) mock_config_ssh.assert_has_calls([ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20230221.eb38cb6e/test/unittests/test_healthcheck.py new/crmsh-4.4.1+20230224.498677ab/test/unittests/test_healthcheck.py --- old/crmsh-4.4.1+20230221.eb38cb6e/test/unittests/test_healthcheck.py 2023-02-21 03:39:22.000000000 +0100 +++ new/crmsh-4.4.1+20230224.498677ab/test/unittests/test_healthcheck.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,75 +0,0 @@ -import unittest -from unittest import mock -import sys - -from crmsh import healthcheck - - -class _Py37MockCallShim: - def __init__(self, mock_call): - self._mock_call = mock_call - - def __getattr__(self, item): - f = getattr(self._mock_call, item) - - def g(*args, **kwargs): - return f(*((self._mock_call, ) + args[1:]), **kwargs) - return g - - @property - def args(self): - if sys.version_info.major == 3 and sys.version_info.major < 8: - return self._mock_call[0] - else: - return self._mock_call.args - - @property - def kwargs(self): - def args(self): - if sys.version_info.major == 3 and sys.version_info.major < 8: - return self._mock_call[1] - else: - return self._mock_call.kwargs - - -class TestPasswordlessHaclusterAuthenticationFeature(unittest.TestCase): - @mock.patch('crmsh.parallax.parallax_call') - @mock.patch('crmsh.utils.ask') - @mock.patch('crmsh.parallax.parallax_run') - def test_upgrade_partially_initialized(self, mock_parallax_run, mock_ask, mock_parallax_call: mock.MagicMock): - nodes = ['node-{}'.format(i) for i in range(1, 6)] - return_value = {'node-{}'.format(i): (0, b'', b'') for i in range(1, 4)} - return_value.update({'node-{}'.format(i): (1, b'', b'') for i in range(4, 6)}) - mock_parallax_run.return_value = return_value - mock_ask.return_value = True - healthcheck.feature_fix(healthcheck.PasswordlessHaclusterAuthenticationFeature(), nodes, mock_ask) - self.assertFalse(any(_Py37MockCallShim(call_args).args[1].startswith('crm cluster init ssh') for call_args in mock_parallax_call.call_args_list)) - self.assertEqual( - {'node-{}'.format(i) for i in range(4, 6)}, - set( - _Py37MockCallShim(call_args).args[0][0] for call_args in mock_parallax_call.call_args_list - if _Py37MockCallShim(call_args).args[1].startswith('crm cluster join ssh') - ), - ) - - @mock.patch('crmsh.parallax.parallax_call') - @mock.patch('crmsh.utils.ask') - @mock.patch('crmsh.parallax.parallax_run') - def test_upgrade_clean(self, mock_parallax_run, mock_ask, mock_parallax_call: mock.MagicMock): - nodes = ['node-{}'.format(i) for i in range(1, 6)] - mock_parallax_run.return_value = {node: (1, b'', b'') for node in nodes} - mock_ask.return_value = True - healthcheck.feature_fix(healthcheck.PasswordlessHaclusterAuthenticationFeature(), nodes, mock_ask) - self.assertEqual( - 1, len([ - True for call_args in mock_parallax_call.call_args_list - if _Py37MockCallShim(call_args).args[1].startswith('crm cluster init ssh') - ]) - ) - self.assertEqual( - len(nodes) - 1, - len([ - True for call_args in mock_parallax_call.call_args_list - if _Py37MockCallShim(call_args).args[1].startswith('crm cluster join ssh') - ]), - )