Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2022-12-09 13:18:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.1835 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Fri Dec 9 13:18:11 2022 rev:270 rq:1041757 version:4.4.1+20221207.84e6ea16 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2022-12-05 18:01:38.652726341 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.1835/crmsh.changes 2022-12-09 13:19:37.663590267 +0100 @@ -1,0 +2,16 @@ +Wed Dec 07 02:27:46 UTC 2022 - xli...@suse.com + +- Update to version 4.4.1+20221207.84e6ea16: + * Dev: parse: Don't set timeout value when is not set by user meanwhile no value is advised in the metadata + * Dev: parse: complete advised operation values for other actions beside monitor + * Dev: unittest: Add unit test for utils.compatible_role + * Dev: parse: Consider compatibility for role when complete operation actions with advised values + +------------------------------------------------------------------- +Tue Dec 06 09:17:16 UTC 2022 - xli...@suse.com + +- Update to version 4.4.1+20221206.b25bc04c: + * Dev: unittest: Adjust unit test based on previous changes + * Dev: qdevice: Refactor qdevice validation code + +------------------------------------------------------------------- Old: ---- crmsh-4.4.1+20221203.9bb5442e.tar.bz2 New: ---- crmsh-4.4.1+20221207.84e6ea16.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.BOUFht/_old 2022-12-09 13:19:38.183593030 +0100 +++ /var/tmp/diff_new_pack.BOUFht/_new 2022-12-09 13:19:38.187593051 +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+20221203.9bb5442e +Version: 4.4.1+20221207.84e6ea16 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.BOUFht/_old 2022-12-09 13:19:38.231593285 +0100 +++ /var/tmp/diff_new_pack.BOUFht/_new 2022-12-09 13:19:38.235593306 +0100 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">d450e2c49093db2e6ba718b6e92800bd3f843128</param> + <param name="changesrevision">84e6ea16faf7d379e21af00ba934a6b644723309</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-4.4.1+20221203.9bb5442e.tar.bz2 -> crmsh-4.4.1+20221207.84e6ea16.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/bootstrap.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/bootstrap.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/bootstrap.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/bootstrap.py 2022-12-07 03:10:51.000000000 +0100 @@ -293,7 +293,7 @@ readline.remove_history_item(hlen - 1) -def prompt_for_string(msg, match=None, default='', valid_func=None, prev_value=[]): +def prompt_for_string(msg, match=None, default='', valid_func=None, prev_value=[], allow_empty=False): if _context.yes_to_all: return default @@ -303,8 +303,10 @@ enable_completion() if val: drop_last_history() - else: + elif allow_empty: return None + else: + continue if not match and not valid_func: return val if match and not re.match(match, val): @@ -312,7 +314,10 @@ continue if valid_func: try: - valid_func(val, prev_value) + if prev_value: + valid_func(val, prev_value) + else: + valid_func(val) except ValueError as err: logger.error(err) continue @@ -1336,8 +1341,6 @@ return adminaddr = prompt_for_string('Virtual IP', valid_func=Validation.valid_admin_ip) - if not adminaddr: - utils.fatal("Expected an IP address") crm_configure_load("update", 'primitive admin-ip IPaddr2 ip=%s op monitor interval=10 timeout=20' % (utils.doublequote(adminaddr))) wait_for_resource("Configuring virtual IP ({})".format(adminaddr), "admin-ip") @@ -1352,15 +1355,33 @@ logger.info("Configure Qdevice/Qnetd:\n" + QDEVICE_HELP_INFO + "\n") if not confirm("Do you want to configure QDevice?"): return - qnetd_addr = prompt_for_string("HOST or IP of the QNetd server to be used") - if not qnetd_addr: - utils.fatal("Address of QNetd is required") - qdevice_port = prompt_for_string("TCP PORT of QNetd server", default=5403) - qdevice_algo = prompt_for_string("QNetd decision ALGORITHM (ffsplit/lms)", default="ffsplit") - qdevice_tie_breaker = prompt_for_string("QNetd TIE_BREAKER (lowest/highest/valid node id)", default="lowest") - qdevice_tls = prompt_for_string("Whether using TLS on QDevice/QNetd (on/off/required)", default="on") - qdevice_heuristics = prompt_for_string("Heuristics COMMAND to run with absolute path; For multiple commands, use \";\" to separate") - qdevice_heuristics_mode = prompt_for_string("MODE of operation of heuristics (on/sync/off)", default="sync") if qdevice_heuristics else None + while True: + try: + qdevice.QDevice.check_package_installed("corosync-qdevice") + break + except ValueError as err: + logger.error(err) + if confirm("Please install the package manually and press 'y' to continue"): + continue + else: + return + + qnetd_addr = prompt_for_string("HOST or IP of the QNetd server to be used", + valid_func=qdevice.QDevice.check_qnetd_addr) + qdevice_port = prompt_for_string("TCP PORT of QNetd server", default=5403, + valid_func=qdevice.QDevice.check_qdevice_port) + qdevice_algo = prompt_for_string("QNetd decision ALGORITHM (ffsplit/lms)", default="ffsplit", + valid_func=qdevice.QDevice.check_qdevice_algo) + qdevice_tie_breaker = prompt_for_string("QNetd TIE_BREAKER (lowest/highest/valid node id)", default="lowest", + valid_func=qdevice.QDevice.check_qdevice_tie_breaker) + qdevice_tls = prompt_for_string("Whether using TLS on QDevice/QNetd (on/off/required)", default="on", + valid_func=qdevice.QDevice.check_qdevice_tls) + qdevice_heuristics = prompt_for_string("Heuristics COMMAND to run with absolute path; For multiple commands, use \";\" to separate", + valid_func=qdevice.QDevice.check_qdevice_heuristics, + allow_empty=True) + 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 + _context.qdevice_inst = qdevice.QDevice( qnetd_addr, port=qdevice_port, @@ -1370,7 +1391,6 @@ cmds=qdevice_heuristics, mode=qdevice_heuristics_mode, is_stage=_context.stage == "qdevice") - _context.qdevice_inst.valid_qdevice_options() def init_qdevice(): @@ -1787,16 +1807,16 @@ if is_unicast: ringXaddr_res = [] for i in 0, 1: - while True: - ringXaddr = prompt_for_string( - 'Address for ring{}'.format(i), - default=pick_default_value(_context.default_ip_list, ringXaddr_res), - valid_func=Validation.valid_ucast_ip, - prev_value=ringXaddr_res) - if not ringXaddr: - utils.fatal("No value for ring{}".format(i)) - ringXaddr_res.append(ringXaddr) - break + ringXaddr = prompt_for_string( + 'Address for ring{}'.format(i), + default=pick_default_value(_context.default_ip_list, ringXaddr_res), + valid_func=Validation.valid_ucast_ip, + prev_value=ringXaddr_res) + # The ringXaddr here still might be empty on non-interactive mode + # when don't have default ip addresses(_context.default_ip_list is empty or just one) + if not ringXaddr: + utils.fatal("No value for ring{}".format(i)) + ringXaddr_res.append(ringXaddr) if not rrp_flag: break invoke("rm -f /var/lib/heartbeat/crm/* /var/lib/pacemaker/cib/*") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/constants.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/constants.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/constants.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/constants.py 2022-12-07 03:10:51.000000000 +0100 @@ -241,8 +241,6 @@ date_spec_names = '''hours monthdays weekdays yearsdays months \ weeks years weekyears moon'''.split() in_range_attrs = ('start', 'end') -roles_names = ('Stopped', 'Started', 'Master', 'Slave') -actions_names = ('start', 'promote', 'demote', 'stop') node_default_type = "normal" node_attributes_keyw = ("attributes", "utilization") shadow_envvar = "CIB_shadow" @@ -527,7 +525,11 @@ ADVISED_ACTION_LIST = ['monitor', 'start', 'stop', 'promote', 'demote'] ADVISED_KEY_LIST = ['timeout', 'interval', 'role'] DEFAULT_INTERVAL_IN_ACTION = "20s" -DEFAULT_TIMEOUT_IN_ACTION = "60s" WAIT_TIMEOUT_MS_DEFAULT = 120000 + +RSC_ROLE_PROMOTED = "Promoted" +RSC_ROLE_UNPROMOTED = "Unpromoted" +RSC_ROLE_PROMOTED_LEGACY = "Master" +RSC_ROLE_UNPROMOTED_LEGACY = "Slave" # vim:ts=4:sw=4:et: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/parse.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/parse.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/parse.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/parse.py 2022-12-07 03:10:51.000000000 +0100 @@ -12,7 +12,7 @@ from . import schema from .utils import keyword_cmp, verify_boolean, lines2cli from .utils import get_boolean, olist, canonical_boolean -from .utils import handle_role_for_ocf_1_1 +from .utils import handle_role_for_ocf_1_1, compatible_role from . import xmlutil from . import log @@ -668,15 +668,18 @@ if not ra_actions_dict: return - def extract_advised_monitor_value(advised_dict, attr, role=None): + def extract_advised_value(advised_dict, action, attr, role=None): adv_attr_value = None try: - if role: - for monitor_item in advised_dict['monitor']: - if role == monitor_item['role']: - adv_attr_value = monitor_item[attr] + if action == "monitor": + if role: + for monitor_item in advised_dict[action]: + if compatible_role(role, monitor_item['role']): + adv_attr_value = monitor_item[attr] + else: + adv_attr_value = advised_dict[action][0][attr] else: - adv_attr_value = advised_dict['monitor'][0][attr] + adv_attr_value = advised_dict[action][attr] except KeyError: pass return adv_attr_value @@ -693,15 +696,13 @@ for op_node in op_nodes_list: action = op_node.get('name') # complete advised value if interval or timeout not configured - if action == "monitor": - adv_interval = extract_advised_monitor_value(action_advised_attr_dict, 'interval', op_node.get('role')) or \ - constants.DEFAULT_INTERVAL_IN_ACTION - adv_timeout = extract_advised_monitor_value(action_advised_attr_dict, 'timeout', op_node.get('role')) or \ - constants.DEFAULT_TIMEOUT_IN_ACTION - if op_node.get('interval') is None: - op_node.set('interval', adv_interval) - if op_node.get('timeout') is None: - op_node.set('timeout', adv_timeout) + adv_interval = extract_advised_value(action_advised_attr_dict, action, 'interval', op_node.get('role')) or \ + constants.DEFAULT_INTERVAL_IN_ACTION + adv_timeout = extract_advised_value(action_advised_attr_dict, action, 'timeout', op_node.get('role')) + if op_node.get('interval') is None: + op_node.set('interval', adv_interval) + if op_node.get('timeout') is None and adv_timeout: + op_node.set('timeout', adv_timeout) configured_action_list.append(action) for action in action_advised_attr_dict: @@ -716,7 +717,7 @@ for k, v in v_dict.items(): # set normal attributes if k in constants.ADVISED_KEY_LIST: - op_node.set(k, v) + op_node.set(k, handle_role_for_ocf_1_1(v)) operations_node.append(op_node) else: op_node = xmlutil.new('op', name=action, **value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/qdevice.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/qdevice.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/qdevice.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/qdevice.py 2022-12-07 03:10:51.000000000 +0100 @@ -201,52 +201,82 @@ """ return "{}/{}/{}".format(self.qdevice_path, self.cluster_node, self.qdevice_p12_filename) - def valid_qdevice_options(self): - """ - Validate qdevice related options - """ + @staticmethod + def check_qnetd_addr(qnetd_addr): qnetd_ip = None - - if not utils.package_is_installed("corosync-qdevice"): - raise ValueError("Package \"corosync-qdevice\" not installed on this node") - try: # socket.getaddrinfo works for both ipv4 and ipv6 address # The function returns a list of 5-tuples with the following structure: # (family, type, proto, canonname, sockaddr) # sockaddr is a tuple describing a socket address, whose format depends on the returned family # (a (address, port) 2-tuple for AF_INET, a (address, port, flow info, scope id) 4-tuple for AF_INET6) - res = socket.getaddrinfo(self.qnetd_addr, None) + res = socket.getaddrinfo(qnetd_addr, None) qnetd_ip = res[0][-1][0] except socket.error: - raise ValueError("host \"{}\" is unreachable".format(self.qnetd_addr)) + raise ValueError("host \"{}\" is unreachable".format(qnetd_addr)) - utils.ping_node(self.qnetd_addr) + utils.ping_node(qnetd_addr) if utils.InterfacesInfo.ip_in_local(qnetd_ip): raise ValueError("host for qnetd must be a remote one") if not utils.check_port_open(qnetd_ip, 22): - raise ValueError("ssh service on \"{}\" not available".format(self.qnetd_addr)) + raise ValueError("ssh service on \"{}\" not available".format(qnetd_addr)) - if not utils.valid_port(self.port): + @staticmethod + def check_qdevice_port(qdevice_port): + if not utils.valid_port(qdevice_port): raise ValueError("invalid qdevice port range(1024 - 65535)") - if self.algo not in ("ffsplit", "lms"): - raise ValueError("invalid ALGORITHM choice: '{}' (choose from 'ffsplit', 'lms')".format(self.algo)) - - if self.tie_breaker not in ("lowest", "highest") and not utils.valid_nodeid(self.tie_breaker): + @staticmethod + def check_qdevice_algo(qdevice_algo): + if qdevice_algo not in ("ffsplit", "lms"): + raise ValueError("invalid ALGORITHM choice: '{}' (choose from 'ffsplit', 'lms')".format(qdevice_algo)) + + @staticmethod + def check_qdevice_tie_breaker(qdevice_tie_breaker): + if qdevice_tie_breaker not in ("lowest", "highest") and not utils.valid_nodeid(qdevice_tie_breaker): raise ValueError("invalid qdevice tie_breaker(lowest/highest/valid_node_id)") - if self.tls not in ("on", "off", "required"): - raise ValueError("invalid TLS choice: '{}' (choose from 'on', 'off', 'required')".format(self.tls)) + @staticmethod + def check_qdevice_tls(qdevice_tls): + if qdevice_tls not in ("on", "off", "required"): + raise ValueError("invalid TLS choice: '{}' (choose from 'on', 'off', 'required')".format(qdevice_tls)) + + @staticmethod + def check_qdevice_heuristics_mode(mode): + if not mode: + return + if mode not in ("on", "sync", "off"): + raise ValueError("invalid MODE choice: '{}' (choose from 'on', 'sync', 'off')".format(mode)) + + @staticmethod + def check_qdevice_heuristics(cmds): + if not cmds: + return + for cmd in cmds.strip(';').split(';'): + if not cmd.startswith('/'): + raise ValueError("commands for heuristics should be absolute path") + if not os.path.exists(cmd.split()[0]): + raise ValueError("command {} not exist".format(cmd.split()[0])) + + @staticmethod + def check_package_installed(pkg, remote=None): + if not utils.package_is_installed(pkg, remote_addr=remote): + raise ValueError("Package \"{}\" not installed on {}".format(pkg, remote if remote else "this node")) - if self.cmds: - for cmd in self.cmds.strip(';').split(';'): - if not cmd.startswith('/'): - raise ValueError("commands for heuristics should be absolute path") - if not os.path.exists(cmd.split()[0]): - raise ValueError("command {} not exist".format(cmd.split()[0])) + def valid_qdevice_options(self): + """ + Validate qdevice related options + """ + self.check_package_installed("corosync-qdevice") + self.check_qnetd_addr(self.qnetd_addr) + self.check_qdevice_port(self.port) + self.check_qdevice_algo(self.algo) + self.check_qdevice_tie_breaker(self.tie_breaker) + self.check_qdevice_tls(self.tls) + self.check_qdevice_heuristics(self.cmds) + self.check_qdevice_heuristics_mode(self.mode) def valid_qnetd(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/sbd.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/sbd.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/sbd.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/sbd.py 2022-12-07 03:10:51.000000000 +0100 @@ -352,8 +352,6 @@ dev_looks_sane = False while not dev_looks_sane: dev = bootstrap.prompt_for_string('Path to storage device (e.g. /dev/disk/by-id/...), or "none" for diskless sbd, use ";" as separator for multi path', r'none|\/.*') - if not dev: - continue if dev == "none": self.diskless_sbd = True return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/ui_resource.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/ui_resource.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/ui_resource.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/ui_resource.py 2022-12-07 03:10:51.000000000 +0100 @@ -338,7 +338,7 @@ if not xmlutil.RscState().is_ms_or_promotable_clone(rsc): logger.error("%s is not a promotable resource", rsc) return False - role = "Promoted" if config.core.OCF_1_1_SUPPORT else "Master" + role = utils.handle_role_for_ocf_1_1(constants.RSC_ROLE_PROMOTED_LEGACY) return utils.ext_cmd(self.rsc_setrole % (rsc, role)) == 0 def do_scores(self, context): @@ -367,7 +367,7 @@ if not xmlutil.RscState().is_ms_or_promotable_clone(rsc): logger.error("%s is not a promotable resource", rsc) return False - role = "Unpromoted" if config.core.OCF_1_1_SUPPORT else "Slave" + role = utils.handle_role_for_ocf_1_1(constants.RSC_ROLE_UNPROMOTED_LEGACY) return utils.ext_cmd(self.rsc_setrole % (rsc, role)) == 0 @command.completers(compl.resources) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/crmsh/utils.py new/crmsh-4.4.1+20221207.84e6ea16/crmsh/utils.py --- old/crmsh-4.4.1+20221203.9bb5442e/crmsh/utils.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/crmsh/utils.py 2022-12-07 03:10:51.000000000 +0100 @@ -3059,16 +3059,28 @@ Only turn on ocf_1_1 feature the cib schema version is pacemaker-3.7 or above """ from .cibconfig import cib_factory + cib_factory.get_cib() return is_larger_than_min_version(cib_factory.get_schema(), constants.SCHEMA_MIN_VER_SUPPORT_OCF_1_1) +def compatible_role(role1, role2): + master_or_promoted = (constants.RSC_ROLE_PROMOTED_LEGACY, constants.RSC_ROLE_PROMOTED) + slave_or_unpromoted = (constants.RSC_ROLE_UNPROMOTED_LEGACY, constants.RSC_ROLE_UNPROMOTED) + res1 = role1 in master_or_promoted and role2 in master_or_promoted + res2 = role1 in slave_or_unpromoted and role2 in slave_or_unpromoted + return res1 or res2 + + def handle_role_for_ocf_1_1(value, name='role'): """ * Convert role from Promoted/Unpromoted to Master/Slave if schema doesn't support OCF 1.1 * Convert role from Master/Slave to Promoted/Unpromoted if ocf1.1 cib schema detected and OCF_1_1_SUPPORT is yes """ role_names = ["role", "target-role"] - downgrade_dict = {"Promoted": "Master", "Unpromoted": "Slave"} + downgrade_dict = { + constants.RSC_ROLE_PROMOTED: constants.RSC_ROLE_PROMOTED_LEGACY, + constants.RSC_ROLE_UNPROMOTED: constants.RSC_ROLE_UNPROMOTED_LEGACY + } upgrade_dict = {v: k for k, v in downgrade_dict.items()} if name not in role_names: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_bootstrap.py new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_bootstrap.py --- old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_bootstrap.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_bootstrap.py 2022-12-07 03:10:51.000000000 +0100 @@ -870,26 +870,34 @@ bootstrap.configure_qdevice_interactive() mock_prompt.assert_not_called() - @mock.patch('crmsh.utils.fatal') - @mock.patch('crmsh.bootstrap.prompt_for_string') + @mock.patch('logging.Logger.info') @mock.patch('crmsh.bootstrap.confirm') - def test_configure_qdevice_interactive_error(self, mock_confirm, mock_prompt, mock_error): + def test_configure_qdevice_interactive_not_confirm(self, mock_confirm, mock_info): bootstrap._context = mock.Mock(yes_to_all=False) - mock_confirm.return_value = True - mock_prompt.return_value = None - mock_error.side_effect = SystemExit - - with self.assertRaises(SystemExit): - bootstrap.configure_qdevice_interactive() - - mock_error.assert_called_once_with("Address of QNetd is required") + mock_confirm.return_value = False + bootstrap.configure_qdevice_interactive() mock_confirm.assert_called_once_with("Do you want to configure QDevice?") - mock_prompt.assert_called_once_with("HOST or IP of the QNetd server to be used") + + @mock.patch('logging.Logger.error') + @mock.patch('crmsh.qdevice.QDevice.check_package_installed') + @mock.patch('logging.Logger.info') + @mock.patch('crmsh.bootstrap.confirm') + def test_configure_qdevice_interactive_not_installed(self, mock_confirm, mock_info, mock_installed, mock_error): + bootstrap._context = mock.Mock(yes_to_all=False) + mock_confirm.side_effect = [True, False] + mock_installed.side_effect = ValueError("corosync-qdevice not installed") + bootstrap.configure_qdevice_interactive() + mock_confirm.assert_has_calls([ + mock.call("Do you want to configure QDevice?"), + mock.call("Please install the package manually and press 'y' to continue") + ]) @mock.patch('crmsh.qdevice.QDevice') @mock.patch('crmsh.bootstrap.prompt_for_string') + @mock.patch('crmsh.qdevice.QDevice.check_package_installed') + @mock.patch('logging.Logger.info') @mock.patch('crmsh.bootstrap.confirm') - def test_configure_qdevice_interactive(self, mock_confirm, mock_prompt, mock_qdevice): + 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] @@ -899,15 +907,21 @@ bootstrap.configure_qdevice_interactive() mock_confirm.assert_called_once_with("Do you want to configure QDevice?") mock_prompt.assert_has_calls([ - mock.call("HOST or IP of the QNetd server to be used"), - mock.call("TCP PORT of QNetd server", default=5403), - mock.call("QNetd decision ALGORITHM (ffsplit/lms)", default="ffsplit"), - mock.call("QNetd TIE_BREAKER (lowest/highest/valid node id)", default="lowest"), - mock.call("Whether using TLS on QDevice/QNetd (on/off/required)", default="on"), - mock.call("Heuristics COMMAND to run with absolute path; For multiple commands, use \";\" to separate") + mock.call("HOST or IP of the QNetd server to be used", + valid_func=qdevice.QDevice.check_qnetd_addr), + mock.call("TCP PORT of QNetd server", default=5403, + valid_func=qdevice.QDevice.check_qdevice_port), + mock.call("QNetd decision ALGORITHM (ffsplit/lms)", default="ffsplit", + valid_func=qdevice.QDevice.check_qdevice_algo), + mock.call("QNetd TIE_BREAKER (lowest/highest/valid node id)", default="lowest", + valid_func=qdevice.QDevice.check_qdevice_tie_breaker), + mock.call("Whether using TLS on QDevice/QNetd (on/off/required)", default="on", + valid_func=qdevice.QDevice.check_qdevice_tls), + mock.call("Heuristics COMMAND to run with absolute path; For multiple commands, use \";\" to separate", + 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_inst.valid_qdevice_options.assert_called_once_with() @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+20221203.9bb5442e/test/unittests/test_qdevice.py new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_qdevice.py --- old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_qdevice.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_qdevice.py 2022-12-07 03:10:51.000000000 +0100 @@ -198,175 +198,105 @@ res = self.qdevice_with_ip_cluster_node.qdevice_p12_on_cluster self.assertEqual(res, "/etc/corosync/qdevice/net/node1.com/qdevice-net-node.p12") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_not_installed(self, mock_installed): - mock_installed.return_value = False - with self.assertRaises(ValueError) as err: - self.qdevice_with_ip.valid_qdevice_options() - self.assertEqual("Package \"corosync-qdevice\" not installed on this node", str(err.exception)) - mock_installed.assert_called_once_with("corosync-qdevice") - - @mock.patch("crmsh.utils.InterfacesInfo.ip_in_local") - @mock.patch("crmsh.utils.ping_node") - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_remote_exception(self, mock_installed, mock_getaddrinfo, mock_ping, mock_ip_local): - mock_installed.return_value = True - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_ip_local.return_value = True + @mock.patch('crmsh.utils.check_port_open') + @mock.patch('crmsh.utils.InterfacesInfo.ip_in_local') + @mock.patch('crmsh.utils.ping_node') + @mock.patch('socket.getaddrinfo') + def test_check_qnetd_addr_port_error(self, mock_getaddrinfo, mock_ping, mock_in_local, mock_check): + mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] + mock_in_local.return_value = False + mock_check.return_value = False + with self.assertRaises(ValueError) as err: + qdevice.QDevice.check_qnetd_addr("qnetd-node") + excepted_err_string = "ssh service on \"qnetd-node\" not available" + self.assertEqual(excepted_err_string, str(err.exception)) + + @mock.patch('crmsh.utils.InterfacesInfo.ip_in_local') + @mock.patch('crmsh.utils.ping_node') + @mock.patch('socket.getaddrinfo') + def test_check_qnetd_addr_local(self, mock_getaddrinfo, mock_ping, mock_in_local): + mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] + mock_in_local.return_value = True + with self.assertRaises(ValueError) as err: + qdevice.QDevice.check_qnetd_addr("qnetd-node") + excepted_err_string = "host for qnetd must be a remote one" + self.assertEqual(excepted_err_string, str(err.exception)) - with self.assertRaises(ValueError) as err: - self.qdevice_with_ip.valid_qdevice_options() - self.assertEqual("host for qnetd must be a remote one", str(err.exception)) - - mock_installed.assert_called_once_with("corosync-qdevice") - mock_getaddrinfo.assert_called_once_with("10.10.10.123", None) - mock_ping.assert_called_once_with("10.10.10.123") - mock_ip_local.assert_called_once_with("10.10.10.123") - - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_getaddrinfo_exception(self, mock_installed, mock_getaddrinfo): - mock_installed.return_value = True + @mock.patch('socket.getaddrinfo') + def test_check_qnetd_addr(self, mock_getaddrinfo): mock_getaddrinfo.side_effect = socket.error - with self.assertRaises(ValueError) as err: - self.qdevice_with_hostname.valid_qdevice_options() - self.assertEqual("host \"node.qnetd\" is unreachable", str(err.exception)) - - mock_installed.assert_called_once_with("corosync-qdevice") - mock_getaddrinfo.assert_called_once_with("node.qnetd", None) - - @mock.patch("crmsh.utils.check_port_open") - @mock.patch("crmsh.utils.InterfacesInfo.ip_in_local") - @mock.patch("crmsh.utils.ping_node") - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_ssh_service_exception(self, mock_installed, mock_getaddrinfo, - mock_ping, mock_ip_local, mock_port_open): - mock_installed.return_value = True - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_ip_local.return_value = False - mock_port_open.return_value = False + qdevice.QDevice.check_qnetd_addr("qnetd-node") + excepted_err_string = "host \"qnetd-node\" is unreachable" + self.assertEqual(excepted_err_string, str(err.exception)) + @mock.patch('crmsh.utils.valid_port') + def test_check_qdevice_port(self, mock_port): + mock_port.return_value = False with self.assertRaises(ValueError) as err: - self.qdevice_with_ip.valid_qdevice_options() - self.assertEqual("ssh service on \"10.10.10.123\" not available", str(err.exception)) - - mock_installed.assert_called_once_with("corosync-qdevice") - mock_getaddrinfo.assert_called_once_with("10.10.10.123", None) - mock_ping.assert_called_once_with("10.10.10.123") - mock_port_open.assert_called_once_with("10.10.10.123", 22) - mock_ip_local.assert_called_once_with("10.10.10.123") - - @mock.patch("crmsh.utils.valid_port") - @mock.patch("crmsh.utils.check_port_open") - @mock.patch("crmsh.utils.InterfacesInfo.ip_in_local") - @mock.patch("crmsh.utils.ping_node") - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_invalid_port_exception(self, mock_installed, mock_getaddrinfo, - mock_ping, mock_ip_local, mock_port_open, mock_valid_port): - mock_installed.return_value = True - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_ip_local.return_value = False - mock_port_open.return_value = True - mock_valid_port.return_value = False + qdevice.QDevice.check_qdevice_port("1") + excepted_err_string = "invalid qdevice port range(1024 - 65535)" + self.assertEqual(excepted_err_string, str(err.exception)) + def test_check_qdevice_algo(self): with self.assertRaises(ValueError) as err: - self.qdevice_with_invalid_port.valid_qdevice_options() - self.assertEqual("invalid qdevice port range(1024 - 65535)", str(err.exception)) - - mock_installed.assert_called_once_with("corosync-qdevice") - mock_getaddrinfo.assert_called_once_with("10.10.10.123", None) - mock_ping.assert_called_once_with("10.10.10.123") - mock_ip_local.assert_called_once_with("10.10.10.123") - mock_port_open.assert_called_once_with("10.10.10.123", 22) - mock_valid_port.assert_called_once_with(100) - - @mock.patch("crmsh.utils.valid_nodeid") - @mock.patch("crmsh.utils.valid_port") - @mock.patch("crmsh.utils.check_port_open") - @mock.patch("crmsh.utils.InterfacesInfo.ip_in_local") - @mock.patch("crmsh.utils.ping_node") - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_invalid_nodeid_exception(self, mock_installed, mock_getaddrinfo, - mock_ping, mock_ip_local, mock_port_open, mock_valid_port, mock_valid_nodeid): - mock_installed.return_value = True - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_ip_local.return_value = False - mock_port_open.return_value = True - mock_valid_port.return_value = True - mock_valid_nodeid.return_value = False + qdevice.QDevice.check_qdevice_algo("1") + excepted_err_string = "invalid ALGORITHM choice: '1' (choose from 'ffsplit', 'lms')" + self.assertEqual(excepted_err_string, str(err.exception)) + def test_check_qdevice_tie_breaker(self): with self.assertRaises(ValueError) as err: - self.qdevice_with_invalid_tie_breaker.valid_qdevice_options() - self.assertEqual("invalid qdevice tie_breaker(lowest/highest/valid_node_id)", str(err.exception)) - - mock_installed.assert_called_once_with("corosync-qdevice") - mock_ip_local.assert_called_once_with("10.10.10.123") - mock_getaddrinfo.assert_called_once_with("10.10.10.123", None) - mock_ping.assert_called_once_with("10.10.10.123") - mock_port_open.assert_called_once_with("10.10.10.123", 22) - mock_valid_port.assert_called_once_with(5403) - mock_valid_nodeid.assert_called_once_with("wrong") - - @mock.patch("crmsh.utils.valid_nodeid") - @mock.patch("crmsh.utils.valid_port") - @mock.patch("crmsh.utils.check_port_open") - @mock.patch("crmsh.utils.InterfacesInfo.ip_in_local") - @mock.patch("crmsh.utils.ping_node") - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_invalid_cmds_relative_path(self, mock_installed, mock_getaddrinfo, - mock_ping, mock_ip_local, mock_port_open, mock_valid_port, mock_valid_nodeid): - mock_installed.return_value = True - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_ip_local.return_value = False - mock_port_open.return_value = True - mock_valid_port.return_value = True - mock_valid_nodeid.return_value = True + qdevice.QDevice.check_qdevice_tie_breaker("1") + excepted_err_string = "invalid qdevice tie_breaker(lowest/highest/valid_node_id)" + self.assertEqual(excepted_err_string, str(err.exception)) + def test_check_qdevice_tls(self): with self.assertRaises(ValueError) as err: - self.qdevice_with_invalid_cmds_relative_path.valid_qdevice_options() - self.assertEqual("commands for heuristics should be absolute path", str(err.exception)) + qdevice.QDevice.check_qdevice_tls("1") + excepted_err_string = "invalid TLS choice: '1' (choose from 'on', 'off', 'required')" + self.assertEqual(excepted_err_string, str(err.exception)) - mock_installed.assert_called_once_with("corosync-qdevice") - mock_getaddrinfo.assert_called_once_with("10.10.10.123", None) - mock_ping.assert_called_once_with("10.10.10.123") - mock_ip_local.assert_called_once_with("10.10.10.123") - mock_port_open.assert_called_once_with("10.10.10.123", 22) - mock_valid_port.assert_called_once_with(5403) - mock_valid_nodeid.assert_not_called() - - @mock.patch("crmsh.utils.valid_nodeid") - @mock.patch("crmsh.utils.valid_port") - @mock.patch("crmsh.utils.check_port_open") - @mock.patch("crmsh.utils.InterfacesInfo.ip_in_local") - @mock.patch("crmsh.utils.ping_node") - @mock.patch("socket.getaddrinfo") - @mock.patch("crmsh.utils.package_is_installed") - def test_valid_qdevice_options_invalid_cmds_not_exist(self, mock_installed, mock_getaddrinfo, - mock_ping, mock_ip_local, mock_port_open, mock_valid_port, mock_valid_nodeid): - mock_installed.return_value = True - mock_getaddrinfo.return_value = [(None, ("10.10.10.123",)),] - mock_ip_local.return_value = False - mock_port_open.return_value = True - mock_valid_port.return_value = True - mock_valid_nodeid.return_value = True + def test_check_qdevice_hm(self): + with self.assertRaises(ValueError) as err: + qdevice.QDevice.check_qdevice_heuristics_mode("1") + excepted_err_string = "invalid MODE choice: '1' (choose from 'on', 'sync', 'off')" + self.assertEqual(excepted_err_string, str(err.exception)) + def test_check_qdevice_he_path_error(self): with self.assertRaises(ValueError) as err: - self.qdevice_with_invalid_cmds_not_exist.valid_qdevice_options() - self.assertEqual("command /not_exist not exist", str(err.exception)) + qdevice.QDevice.check_qdevice_heuristics("command1") + excepted_err_string = "commands for heuristics should be absolute path" + self.assertEqual(excepted_err_string, str(err.exception)) + @mock.patch('os.path.exists') + def test_check_qdevice_he_not_exist_erro(self, mock_exists): + mock_exists.return_value = False + with self.assertRaises(ValueError) as err: + qdevice.QDevice.check_qdevice_heuristics("/usr/bin/testst") + excepted_err_string = "command /usr/bin/testst not exist" + self.assertEqual(excepted_err_string, str(err.exception)) + + @mock.patch('crmsh.utils.package_is_installed') + def test_check_package_installed(self, mock_installed): + mock_installed.return_value = False + with self.assertRaises(ValueError) as err: + qdevice.QDevice.check_package_installed("corosync-qdevice") + excepted_err_string = "Package \"corosync-qdevice\" not installed on this node" + self.assertEqual(excepted_err_string, str(err.exception)) + + @mock.patch('crmsh.qdevice.QDevice.check_qdevice_heuristics_mode') + @mock.patch('crmsh.qdevice.QDevice.check_qdevice_heuristics') + @mock.patch('crmsh.qdevice.QDevice.check_qdevice_tls') + @mock.patch('crmsh.qdevice.QDevice.check_qdevice_tie_breaker') + @mock.patch('crmsh.qdevice.QDevice.check_qdevice_algo') + @mock.patch('crmsh.qdevice.QDevice.check_qdevice_port') + @mock.patch('crmsh.qdevice.QDevice.check_qnetd_addr') + @mock.patch('crmsh.qdevice.QDevice.check_package_installed') + def test_valid_qdevice_options(self, mock_installed, mock_check_qnetd, mock_check_port, + mock_check_algo, mock_check_tie, mock_check_tls, mock_check_h, mock_check_hm): + self.qdevice_with_ip.valid_qdevice_options() mock_installed.assert_called_once_with("corosync-qdevice") - mock_getaddrinfo.assert_called_once_with("10.10.10.123", None) - mock_ping.assert_called_once_with("10.10.10.123") - mock_ip_local.assert_called_once_with("10.10.10.123") - mock_port_open.assert_called_once_with("10.10.10.123", 22) - mock_valid_port.assert_called_once_with(5403) - mock_valid_nodeid.assert_not_called() + mock_check_qnetd.assert_called_once_with("10.10.10.123") @mock.patch("crmsh.utils.package_is_installed") def test_valid_qnetd_not_installed(self, mock_installed): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_sbd.py new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_sbd.py --- old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_sbd.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_sbd.py 2022-12-07 03:10:51.000000000 +0100 @@ -383,7 +383,7 @@ mock_confirm.return_value = True mock_no_overwrite.return_value = False mock_from_config.return_value = [] - mock_prompt.side_effect = [None, "none"] + mock_prompt.return_value = "none" self.sbd_inst._get_sbd_device_interactive() @@ -391,7 +391,7 @@ mock_confirm.assert_called_once_with("Do you wish to use SBD?") mock_from_config.assert_called_once_with() mock_prompt.assert_has_calls([ - mock.call('Path to storage device (e.g. /dev/disk/by-id/...), or "none" for diskless sbd, use ";" as separator for multi path', 'none|\\/.*') for x in range(2) + mock.call('Path to storage device (e.g. /dev/disk/by-id/...), or "none" for diskless sbd, use ";" as separator for multi path', 'none|\\/.*') ]) @mock.patch('crmsh.utils.re_split_string') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_utils.py new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_utils.py --- old/crmsh-4.4.1+20221203.9bb5442e/test/unittests/test_utils.py 2022-12-03 15:47:59.000000000 +0100 +++ new/crmsh-4.4.1+20221207.84e6ea16/test/unittests/test_utils.py 2022-12-07 03:10:51.000000000 +0100 @@ -1729,3 +1729,7 @@ def test_handle_role_for_ocf_1_1_return_not_role(): assert utils.handle_role_for_ocf_1_1("test", name='other') == "test" + + +def test_compatible_role(): + assert utils.compatible_role("Slave", "Unpromoted") is True