Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2021-08-11 11:47:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Wed Aug 11 11:47:42 2021 rev:219 rq:911376 version:4.3.1+20210811.4045e09d Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2021-07-26 17:38:26.854068799 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.1899/crmsh.changes 2021-08-11 11:49:35.313575358 +0200 @@ -1,0 +2,23 @@ +Wed Aug 11 07:15:15 UTC 2021 - xli...@suse.com + +- Update to version 4.3.1+20210811.4045e09d: + * Dev: unittest: Add unit test for previous changes + * Fix: bootstrap: Adjust corosync and sbd parameters according to the profile environment detected (bsc#1175896) + * Fix: sbd: adjust sbd systemd TimeoutStartSec together with SBD_DELAY_START + * Dev: Makefile: add etc/profiles.yml and move crm.conf.in into etc + +------------------------------------------------------------------- +Thu Aug 05 08:37:03 UTC 2021 - xli...@suse.com + +- Update to version 4.3.1+20210805.18f9a8c1: + * Fix: doc: Note that resource tracing is only supported by OCF RAs(bsc#1188966) + * Dev: testcases: adjust expected output for previous changes + * Dev: ui_resource: Enhancement trace output + +------------------------------------------------------------------- +Wed Jul 28 06:14:24 UTC 2021 - xli...@suse.com + +- Update to version 4.3.1+20210728.8029db25: + * Medium: ra: performance/usability improvement (avoid systemd) + +------------------------------------------------------------------- Old: ---- crmsh-4.3.1+20210726.3de6f304.tar.bz2 New: ---- crmsh-4.3.1+20210811.4045e09d.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.liSsWr/_old 2021-08-11 11:49:35.749574834 +0200 +++ /var/tmp/diff_new_pack.liSsWr/_new 2021-08-11 11:49:35.753574829 +0200 @@ -36,7 +36,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 4.3.1+20210726.3de6f304 +Version: 4.3.1+20210811.4045e09d Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.liSsWr/_old 2021-08-11 11:49:35.781574795 +0200 +++ /var/tmp/diff_new_pack.liSsWr/_new 2021-08-11 11:49:35.785574791 +0200 @@ -9,6 +9,6 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">27fd7dc2f00797a9593d6b672005c0590d13836e</param> + <param name="changesrevision">4045e09d6208e04923342b8ede1520aca88eda7d</param> </service> </servicedata> \ No newline at end of file ++++++ crmsh-4.3.1+20210726.3de6f304.tar.bz2 -> crmsh-4.3.1+20210811.4045e09d.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/Makefile.am new/crmsh-4.3.1+20210811.4045e09d/Makefile.am --- old/crmsh-4.3.1+20210726.3de6f304/Makefile.am 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/Makefile.am 2021-08-11 09:00:07.000000000 +0200 @@ -25,7 +25,7 @@ # Documentation doc_DATA = AUTHORS COPYING README.md ChangeLog $(generated_docs) crmconfdir=$(sysconfdir)/crm -crmconf_DATA = crm.conf +crmconf_DATA = etc/crm.conf etc/profiles.yml contribdir = $(docdir)/contrib contrib_DATA = contrib/pcmk.vim contrib/README.vimsyntax helpdir = $(datadir)/$(PACKAGE) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/configure.ac new/crmsh-4.3.1+20210811.4045e09d/configure.ac --- old/crmsh-4.3.1+20210726.3de6f304/configure.ac 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/configure.ac 2021-08-11 09:00:07.000000000 +0200 @@ -55,7 +55,7 @@ AC_CONFIG_FILES(Makefile \ hb_report/hb_report \ -crm.conf \ +etc/crm.conf \ version \ crmsh.spec \ ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crm.conf.in new/crmsh-4.3.1+20210811.4045e09d/crm.conf.in --- old/crmsh-4.3.1+20210726.3de6f304/crm.conf.in 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crm.conf.in 1970-01-01 01:00:00.000000000 +0100 @@ -1,117 +0,0 @@ -; crmsh configuration file -; To override per user, create a file ~/.config/crm/crm.conf -; -; [core] -; editor = $EDITOR -; pager = $PAGER -; user = -; skill_level = expert -; sort_elements = yes -; check_frequency = always -; check_mode = strict -; wait = no -; add_quotes = yes -; manage_children = ask -; force = no -; debug = no -; ptest = ptest, crm_simulate -; dotty = dotty -; dot = dot -; ignore_missing_metadata = no -; report_tool_options = -; lock_timeout = 120 - -; obscure_pattern option is the persisent configuration of CLI. -; Example, for the high security concern, obscure_pattern = passw* | ip -; which makes `crm configure show` is equal to -; -; node-1:~ # crm configure show obscure:passw* obscure:ip -; node 1084783297: node1 -; primitive fence_device stonith:fence_ilo5 \ -; params password="******" -; primitive ip IPaddr2 \ -; params ip="******" -; -; The default option is passw* -; If you don't want to obscure, change the value to blank. -; -; obscure_pattern = passw* - -[path] -; sharedir = <detected> -; cache = <detected> -; crm_config = <detected> -; crm_daemon_dir = <detected> -crm_daemon_user = @CRM_DAEMON_USER@ -ocf_root = @OCF_ROOT_DIR@ -; crm_dtd_dir = <detected> -; pe_state_dir = <detected> -; heartbeat_dir = <detected> -; hb_delnode = /usr/share/heartbeat/hb_delnode -; nagios_plugins = /usr/lib/nagios/plugins - -; [color] -; style = color -; error = red bold -; ok = green bold -; warn = yellow bold -; info = cyan -; help_keyword = blue bold underline -; help_header = normal bold -; help_topic = yellow bold -; help_block = cyan -; keyword = yellow -; identifier = normal -; attr_name = cyan -; attr_value = red -; resource_reference = green -; id_reference = green -; score = magenta -; ticket = magenta - -; [report] -; from_time = -12H -; compress = yes -; speed_up = no -; collect_extra_logs = /var/log/messages /var/log/pacemaker.log -; remove_exist_dest = no -; single_node = no -; -; sanitize_rule = sanitize_pattern[:options] ... -; -; This defines the way to hide sensitive data generated by hb_report. -; -; 'sanitize_pattern' is a RegEx string, which is used to matches 'name' -; field of CIB params. The sanitize process will hide 'value' of those -; matched 'name:value' pairs in CIB, PE, pacemaker.log. -; -; 'options' is the predefined, and 'raw' is the only one defined -; currently. With ':raw" option, the sanitize process will fetch -; 'value' results out of CIB 'name:value' pairs, and use them to -; hide all clear text occurence from all files hb_report collected. -; -; Example 1: -; sanitize_rule = passw.* -; -; This is the default. It will hide password nam:value pairs. -; The result of hb_report clould be like -; name="password", value=****** -; @name=password @value=****** -; passwd=****** -; -; -; Example 2: -; sanitize_rule = ip.*:raw -; -; This will only hide ip addresses. Example, the sanitize process will fetch -; ip=10.10.10.10 and replace all clear text occurrence of "10.10.10.10" -; -; -; Example 3: -; sanitize_rule = passw.*|ip.*:raw -; -; This is useful for the higher security concern. -; The sanitize process will hide all "name:value" pair for password like in -; example 1, and all clear text ip addresses like in example 2 above. -; -; sanitize_rule = passw.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crmsh/bootstrap.py new/crmsh-4.3.1+20210811.4045e09d/crmsh/bootstrap.py --- old/crmsh-4.3.1+20210726.3de6f304/crmsh/bootstrap.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crmsh/bootstrap.py 2021-08-11 09:00:07.000000000 +0200 @@ -19,6 +19,7 @@ import time import readline import shutil +import yaml from string import Template from lxml import etree from pathlib import Path @@ -75,6 +76,10 @@ Context object used to avoid having to pass these variables to every bootstrap method. """ + PROFILES_FILE = "/etc/crm/profiles.yml" + DEFAULT_PROFILE_NAME = "default" + S390_PROFILE_NAME = "s390" + def __init__(self): ''' Initialize attributes @@ -118,6 +123,10 @@ self.interfaces_inst = None self.with_other_user = True self.cluster_is_running = None + self.cloud_type = None + self.is_s390 = False + self.profiles_data = None + self.profiles_dict = {} self.default_nic_list = [] self.default_ip_list = [] self.local_ip_list = [] @@ -186,6 +195,59 @@ from .sbd import SBDManager self.sbd_manager = SBDManager(self) + def detect_platform(self): + """ + Detect platform + Return profile type for different platform + """ + profile_type = None + + self.is_s390 = "390" in os.uname().machine + if self.is_s390: + profile_type = self.S390_PROFILE_NAME + else: + self.cloud_type = utils.detect_cloud() + if self.cloud_type: + profile_type = self.cloud_type + + if profile_type: + status("Detected \"{}\" platform".format(profile_type)) + return profile_type + + def load_specific_profile(self, profile_type): + """ + Load specific profile + """ + profile_dict = {} + if not profile_type: + return profile_dict + + if profile_type in self.profiles_data: + status("Loading \"{}\" profile from {}".format(profile_type, self.PROFILES_FILE)) + profile_dict = self.profiles_data[profile_type] + else: + status("\"{}\" profile does not exist in {}".format(profile_type, self.PROFILES_FILE)) + return profile_dict + + def load_profiles(self): + """ + Load profiles data for different environment + """ + profile_type = self.detect_platform() + + if not os.path.exists(self.PROFILES_FILE): + return + with open(self.PROFILES_FILE) as f: + self.profiles_data = yaml.load(f, Loader=yaml.SafeLoader) + # empty file + if not self.profiles_data: + return + + default_profile_dict = self.load_specific_profile(self.DEFAULT_PROFILE_NAME) + specific_profile_dict = self.load_specific_profile(profile_type) + # merge two dictionaries + self.profiles_dict = {**default_profile_dict, **specific_profile_dict} + _context = None @@ -435,8 +497,9 @@ @contextmanager def status_long(msg): log("# {}...".format(msg)) - if not _context.quiet: - sys.stdout.write(" {}...".format(msg)) + if not _context or not _context.quiet: + space = " " if _context else "" + sys.stdout.write("{}{}...".format(space, msg)) sys.stdout.flush() try: yield @@ -447,14 +510,14 @@ def status_progress(): - if not _context.quiet: + if not _context or not _context.quiet: sys.stdout.write(".") sys.stdout.flush() def status_done(): log("# done") - if not _context.quiet: + if not _context or not _context.quiet: print("done") @@ -696,10 +759,24 @@ if pass_msg: warn("You should change the hacluster password to something more secure!") - utils.start_service("pacemaker.service", enable=True) + start_pacemaker() wait_for_cluster() +def start_pacemaker(): + """ + Start pacemaker service with wait time for sbd + """ + from .sbd import SBDManager + pacemaker_start_msg = "Starting pacemaker" + if utils.package_is_installed("sbd") and \ + utils.service_is_enabled("sbd.service") and \ + SBDManager.is_delay_start(): + pacemaker_start_msg += "(waiting for sbd {}s)".format(SBDManager.get_suitable_sbd_systemd_timeout()) + with status_long(pacemaker_start_msg): + utils.start_service("pacemaker.service", enable=True) + + def install_tmp(tmpfile, to): with open(tmpfile, "r") as src: with utils.open_atomic(to, "w") as dst: @@ -1164,26 +1241,32 @@ csync2_update(corosync.conf()) +def adjust_corosync_parameters_according_to_profiles(): + """ + Adjust corosync's parameters according profiles + """ + if not _context.profiles_dict: + return + for k, v in _context.profiles_dict.items(): + if k.startswith("corosync."): + corosync.set_value('.'.join(k.split('.')[1:]), v) + + def init_corosync(): """ Configure corosync (unicast or multicast, encrypted?) """ - def requires_unicast(): - host = utils.detect_cloud() - if host is not None: - status("Detected cloud platform: {}".format(host)) - return host is not None - init_corosync_auth() if os.path.exists(corosync.conf()): if not confirm("%s already exists - overwrite?" % (corosync.conf())): return - if _context.unicast or requires_unicast(): + if _context.unicast or _context.cloud_type: init_corosync_unicast() else: init_corosync_multicast() + adjust_corosync_parameters_according_to_profiles() def init_sbd(): @@ -2016,6 +2099,7 @@ _context.initialize_qdevice() _context.validate_option() + _context.load_profiles() _context.init_sbd_manager() # Need hostname resolution to work, want NTP (but don't block ssh_remote or csync2_remote) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crmsh/corosync.py new/crmsh-4.3.1+20210811.4045e09d/crmsh/corosync.py --- old/crmsh-4.3.1+20210726.3de6f304/crmsh/corosync.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crmsh/corosync.py 2021-08-11 09:00:07.000000000 +0200 @@ -608,17 +608,8 @@ totem { version: 2 - secauth: on - crypto_hash: sha1 - crypto_cipher: aes256 cluster_name: %(clustername)s clear_node_high_bit: yes - - token: 5000 - token_retransmits_before_loss_const: 10 - join: 60 - consensus: 6000 - max_messages: 20 """ _COROSYNC_CONF_TEMPLATE_TAIL = """ %(rrp_mode)s diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crmsh/ra.py new/crmsh-4.3.1+20210811.4045e09d/crmsh/ra.py --- old/crmsh-4.3.1+20210726.3de6f304/crmsh/ra.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crmsh/ra.py 2021-08-11 09:00:07.000000000 +0200 @@ -65,7 +65,7 @@ @utils.memoize def can_use_crm_resource(): - _rc, s = get_stdout("crm_resource --list-standards", stderr_on=False) + _rc, s = get_stdout("crm_resource --list-ocf-providers", stderr_on=False) return s != "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crmsh/sbd.py new/crmsh-4.3.1+20210811.4045e09d/crmsh/sbd.py --- old/crmsh-4.3.1+20210726.3de6f304/crmsh/sbd.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crmsh/sbd.py 2021-08-11 09:00:07.000000000 +0200 @@ -4,7 +4,6 @@ from . import utils from . import bootstrap from .bootstrap import SYSCONFIG_SBD -from .constants import SSH_OPTION class SBDManager(object): @@ -27,9 +26,10 @@ If you want to use diskless SBD for two-nodes cluster, should be combined with QDevice.""" PARSE_RE = "[; ]" DISKLESS_CRM_CMD = "crm configure property stonith-enabled=true stonith-watchdog-timeout={} stonith-timeout={}" + + SBD_WATCHDOG_TIMEOUT_DEFAULT = 5 + SBD_WATCHDOG_TIMEOUT_DEFAULT_S390 = 15 SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE = 35 - STONITH_WATCHDOG_TIMEOUT_DEFAULT = "10s" - STONITH_WATCHDOG_TIMEOUT_DEFAULT_S390 = "30s" def __init__(self, context): """ @@ -42,29 +42,37 @@ self.diskless_sbd = context.diskless_sbd self._sbd_devices = None self._watchdog_inst = None - self._stonith_watchdog_timeout = self.STONITH_WATCHDOG_TIMEOUT_DEFAULT self._stonith_timeout = 60 - self._sbd_watchdog_timeout = 0 - self._is_s390 = "390" in os.uname().machine + if context.is_s390: + self._sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT_S390 + else: + self._sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT + self._stonith_watchdog_timeout = -1 self._context = context + self._delay_start = False @staticmethod def _get_device_uuid(dev, node=None): """ Get UUID for specific device and node """ - cmd = "sbd -d {} dump".format(dev) - if node: - cmd = "ssh {} root@{} '{}'".format(SSH_OPTION, node, cmd) - - rc, out, err = utils.get_stdout_stderr(cmd) - if rc != 0 and err: - raise ValueError("Cannot dump sbd meta-data: {}".format(err)) - if rc == 0 and out: - res = re.search("UUID\s*:\s*(.*)\n", out) - if not res: - raise ValueError("Cannot find sbd device UUID for {}".format(dev)) - return res.group(1) + out = utils.get_stdout_or_raise_error("sbd -d {} dump".format(dev), remote=node) + res = re.search("UUID\s*:\s*(.*)\n", out) + if not res: + raise ValueError("Cannot find sbd device UUID for {}".format(dev)) + return res.group(1) + + @staticmethod + def _get_sbd_msgwait(dev): + """ + Get msgwait for sbd device + """ + out = utils.get_stdout_or_raise_error("sbd -d {} dump".format(dev)) + # Format like "Timeout (msgwait) : 30" + res = re.search("\(msgwait\)\s+:\s+(\d+)", out) + if not res: + raise ValueError("Cannot get sbd msgwait for {}".format(dev)) + return int(res.group(1)) def _compare_device_uuid(self, dev, node_list): """ @@ -144,27 +152,53 @@ dev_list = self._get_sbd_device_interactive() self._sbd_devices = dev_list - def _initialize_sbd(self): + def _adjust_sbd_watchdog_timeout_for_s390(self): """ - Initialize SBD device + Correct watchdog timeout if less than s390 default """ + if self._context.is_s390 and self._sbd_watchdog_timeout < self.SBD_WATCHDOG_TIMEOUT_DEFAULT_S390: + bootstrap.warn("sbd_watchdog_timeout is set to {} for s390, it was {}".format(self.SBD_WATCHDOG_TIMEOUT_DEFAULT_S390, self._sbd_watchdog_timeout)) + self._sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT_S390 + + def _initialize_sbd(self): + """ + Initialize SBD parameters according to profiles.yml, or the crmsh defined defaulst as the last resort. + This covers both disk-based-sbd, and diskless-sbd scenarios. + For diskless-sbd, set _sbd_watchdog_timeout then return; + For disk-based-sbd, also calculate the msgwait value, then initialize the SBD device. + """ + if "sbd.watchdog_timeout" in self._context.profiles_dict: + self._sbd_watchdog_timeout = self._context.profiles_dict["sbd.watchdog_timeout"] + self._adjust_sbd_watchdog_timeout_for_s390() if self.diskless_sbd: return + + sbd_msgwait_default = int(self._sbd_watchdog_timeout) * 2 + sbd_msgwait = sbd_msgwait_default + if "sbd.msgwait" in self._context.profiles_dict: + sbd_msgwait = self._context.profiles_dict["sbd.msgwait"] + if int(sbd_msgwait) < sbd_msgwait_default: + bootstrap.warn("sbd msgwait is set to {}, it was {}".format(sbd_msgwait_default, sbd_msgwait)) + sbd_msgwait = sbd_msgwait_default + opt = "-4 {} -1 {}".format(sbd_msgwait, self._sbd_watchdog_timeout) + for dev in self._sbd_devices: - rc, _, err = bootstrap.invoke("sbd -d {} create".format(dev)) + rc, _, err = bootstrap.invoke("sbd {} -d {} create".format(opt, dev)) if not rc: bootstrap.error("Failed to initialize SBD device {}: {}".format(dev, err)) - def _update_configuration(self): + def _update_sbd_configuration(self): """ Update /etc/sysconfig/sbd """ shutil.copyfile(self.SYSCONFIG_SBD_TEMPLATE, SYSCONFIG_SBD) - self._determine_sbd_watchdog_timeout() + self._adjust_sbd_watchdog_timeout_with_diskless_and_qdevice() + if utils.detect_virt(): + self._delay_start = True sbd_config_dict = { "SBD_PACEMAKER": "yes", "SBD_STARTMODE": "always", - "SBD_DELAY_START": "yes" if utils.detect_virt() and self._sbd_devices else "no", + "SBD_DELAY_START": "yes" if self._delay_start else "no", "SBD_WATCHDOG_DEV": self._watchdog_inst.watchdog_device_name } if self._sbd_watchdog_timeout > 0: @@ -174,34 +208,27 @@ utils.sysconfig_set(SYSCONFIG_SBD, **sbd_config_dict) bootstrap.csync2_update(SYSCONFIG_SBD) - def _determine_sbd_watchdog_timeout(self): + def _adjust_sbd_watchdog_timeout_with_diskless_and_qdevice(self): """ - When using diskless SBD, determine value of SBD_WATCHDOG_TIMEOUT + When using diskless SBD with Qdevice, adjust value of sbd_watchdog_timeout """ if not self.diskless_sbd: return # add sbd after qdevice started if utils.is_qdevice_configured() and utils.service_is_active("corosync-qdevice.service"): qdevice_sync_timeout = utils.get_qdevice_sync_timeout() - self._sbd_watchdog_timeout = qdevice_sync_timeout + 5 - if self._is_s390 and self._sbd_watchdog_timeout < 15: - self._sbd_watchdog_timeout = 15 + if self._sbd_watchdog_timeout <= qdevice_sync_timeout: + watchdog_timeout_with_qdevice = qdevice_sync_timeout + 5 + bootstrap.warn("sbd_watchdog_timeout is set to {} for qdevice, it was {}".format(watchdog_timeout_with_qdevice, self._sbd_watchdog_timeout)) + self._sbd_watchdog_timeout = watchdog_timeout_with_qdevice self._stonith_timeout = self.calculate_stonith_timeout(self._sbd_watchdog_timeout) # add sbd and qdevice together from beginning elif self._context.qdevice_inst: - self._sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE + if self._sbd_watchdog_timeout < self.SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE: + bootstrap.warn("sbd_watchdog_timeout is set to {} for qdevice, it was {}".format(self.SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE, self._sbd_watchdog_timeout)) + self._sbd_watchdog_timeout = self.SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE self._stonith_timeout = self.calculate_stonith_timeout(self._sbd_watchdog_timeout) - def _determine_stonith_watchdog_timeout(self): - """ - Determine value of stonith-watchdog-timeout - """ - res = SBDManager.get_sbd_value_from_config("SBD_WATCHDOG_TIMEOUT") - if res: - self._stonith_watchdog_timeout = -1 - elif self._is_s390: - self._stonith_watchdog_timeout = self.STONITH_WATCHDOG_TIMEOUT_DEFAULT_S390 - def _get_sbd_device_from_config(self): """ Gets currently configured SBD device, i.e. what's in /etc/sysconfig/sbd @@ -212,6 +239,44 @@ else: return None + @staticmethod + def is_delay_start(): + """ + Check if SBD_DELAY_START is yes + """ + res = SBDManager.get_sbd_value_from_config("SBD_DELAY_START") + return utils.is_boolean_true(res) + + @staticmethod + def get_sbd_watchdog_timeout(): + """ + Get SBD_WATCHDOG_TIMEOUT from /etc/sysconfig/sbd + """ + res = SBDManager.get_sbd_value_from_config("SBD_WATCHDOG_TIMEOUT") + if not res: + raise ValueError("Cannot get the value of SBD_WATCHDOG_TIMEOUT") + return int(res) + + @staticmethod + def get_sbd_start_timeout_threshold(): + """ + Get sbd start timeout threshold + TimeoutStartUSec of sbd shouldn't less than this value + """ + dev_list = SBDManager.get_sbd_device_from_config() + if not dev_list: + return int(SBDManager.get_sbd_watchdog_timeout() * 2) + else: + return int(SBDManager._get_sbd_msgwait(dev_list[0])) + + @staticmethod + def get_suitable_sbd_systemd_timeout(): + """ + Get suitable systemd start timeout for sbd.service + """ + timeout_value = SBDManager.get_sbd_start_timeout_threshold() + return int(timeout_value * 1.2) + def _restart_cluster_and_configure_sbd_ra(self): """ Try to configure sbd resource, restart cluster on needed @@ -241,6 +306,29 @@ # in init process bootstrap.invoke("systemctl enable sbd.service") + def _adjust_systemd(self): + """ + Adjust start timeout for sbd when set SBD_DELAY_START + """ + if not self.is_delay_start(): + return + + # TimeoutStartUSec default is 1min 30s, need to parse as seconds + cmd = "systemctl show -p TimeoutStartUSec sbd --value" + out = utils.get_stdout_or_raise_error(cmd) + res_seconds = re.search("(\d+)s", out) + default_start_timeout = int(res_seconds.group(1)) if res_seconds else 0 + res_min = re.search("(\d+)min", out) + default_start_timeout += 60 * int(res_min.group(1)) if res_min else 0 + if default_start_timeout >= self.get_sbd_start_timeout_threshold(): + return + + systemd_sbd_dir = "/etc/systemd/system/sbd.service.d" + utils.mkdirp(systemd_sbd_dir) + sbd_delay_start_file = "{}/sbd_delay_start.conf".format(systemd_sbd_dir) + utils.str2file("[Service]\nTimeoutSec={}".format(self.get_suitable_sbd_systemd_timeout()), sbd_delay_start_file) + utils.get_stdout_or_raise_error("systemctl daemon-reload") + def _warn_diskless_sbd(self, peer=None): """ Give warning when configuring diskless sbd @@ -275,9 +363,9 @@ self._warn_diskless_sbd() with bootstrap.status_long("Initializing {}SBD...".format("diskless " if self.diskless_sbd else "")): self._initialize_sbd() - self._update_configuration() - self._determine_stonith_watchdog_timeout() + self._update_sbd_configuration() self._enable_sbd_service() + self._adjust_systemd() def configure_sbd_resource(self): """ @@ -318,6 +406,7 @@ self._verify_sbd_device(dev_list, [peer_host]) else: self._warn_diskless_sbd(peer_host) + self._adjust_systemd() bootstrap.status("Got {}SBD configuration".format("" if dev_list else "diskless ")) bootstrap.invoke("systemctl enable sbd.service") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crmsh/ui_cluster.py new/crmsh-4.3.1+20210811.4045e09d/crmsh/ui_cluster.py --- old/crmsh-4.3.1+20210726.3de6f304/crmsh/ui_cluster.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crmsh/ui_cluster.py 2021-08-11 09:00:07.000000000 +0200 @@ -96,7 +96,7 @@ if utils.service_is_active("pacemaker.service"): err_buf.info("Cluster services already started") return - utils.start_service("pacemaker") + bootstrap.start_pacemaker() if utils.is_qdevice_configured(): utils.start_service("corosync-qdevice") err_buf.info("Cluster services started") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/crmsh/ui_resource.py new/crmsh-4.3.1+20210811.4045e09d/crmsh/ui_resource.py --- old/crmsh-4.3.1+20210726.3de6f304/crmsh/ui_resource.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/crmsh/ui_resource.py 2021-08-11 09:00:07.000000000 +0200 @@ -701,12 +701,8 @@ self._trace_op_interval(context, rsc_id, rsc, op, interval) if not cib_factory.commit(): return False - if op is not None: - common_info("Trace for %s:%s is written to %s/trace_ra/" % - (rsc_id, op, config.path.heartbeat_dir)) - else: - common_info("Trace for %s is written to %s/trace_ra/" % - (rsc_id, config.path.heartbeat_dir)) + rsc_type = rsc.node.get("type") + common_info("Trace for {}{} is written to {}/trace_ra/{}".format(rsc_id, ":"+op if op else "", config.path.heartbeat_dir, rsc_type)) if op is not None and op != "monitor": common_info("Trace set, restart %s to trace the %s operation" % (rsc_id, op)) else: @@ -760,4 +756,7 @@ self._untrace_op(context, rsc_id, rsc, op) else: self._untrace_op_interval(context, rsc_id, rsc, op, interval) - return cib_factory.commit() + if not cib_factory.commit(): + return False + common_info("Stop tracing {}{}".format(rsc_id, " for operation "+op if op else "")) + return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/doc/crm.8.adoc new/crmsh-4.3.1+20210811.4045e09d/doc/crm.8.adoc --- old/crmsh-4.3.1+20210726.3de6f304/doc/crm.8.adoc 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/doc/crm.8.adoc 2021-08-11 09:00:07.000000000 +0200 @@ -2006,6 +2006,10 @@ set a trace for `monitor` with interval `0`, or use `probe` as the operation name. +Note: RA tracing is only supported by OCF resource agents; +The pacemaker-execd daemon does not log recurring monitor operations +unless an error occurred. + Usage: ............... trace <rsc> [<op> [<interval>] ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/etc/crm.conf.in new/crmsh-4.3.1+20210811.4045e09d/etc/crm.conf.in --- old/crmsh-4.3.1+20210726.3de6f304/etc/crm.conf.in 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.3.1+20210811.4045e09d/etc/crm.conf.in 2021-08-11 09:00:07.000000000 +0200 @@ -0,0 +1,117 @@ +; crmsh configuration file +; To override per user, create a file ~/.config/crm/crm.conf +; +; [core] +; editor = $EDITOR +; pager = $PAGER +; user = +; skill_level = expert +; sort_elements = yes +; check_frequency = always +; check_mode = strict +; wait = no +; add_quotes = yes +; manage_children = ask +; force = no +; debug = no +; ptest = ptest, crm_simulate +; dotty = dotty +; dot = dot +; ignore_missing_metadata = no +; report_tool_options = +; lock_timeout = 120 + +; obscure_pattern option is the persisent configuration of CLI. +; Example, for the high security concern, obscure_pattern = passw* | ip +; which makes `crm configure show` is equal to +; +; node-1:~ # crm configure show obscure:passw* obscure:ip +; node 1084783297: node1 +; primitive fence_device stonith:fence_ilo5 \ +; params password="******" +; primitive ip IPaddr2 \ +; params ip="******" +; +; The default option is passw* +; If you don't want to obscure, change the value to blank. +; +; obscure_pattern = passw* + +[path] +; sharedir = <detected> +; cache = <detected> +; crm_config = <detected> +; crm_daemon_dir = <detected> +crm_daemon_user = @CRM_DAEMON_USER@ +ocf_root = @OCF_ROOT_DIR@ +; crm_dtd_dir = <detected> +; pe_state_dir = <detected> +; heartbeat_dir = <detected> +; hb_delnode = /usr/share/heartbeat/hb_delnode +; nagios_plugins = /usr/lib/nagios/plugins + +; [color] +; style = color +; error = red bold +; ok = green bold +; warn = yellow bold +; info = cyan +; help_keyword = blue bold underline +; help_header = normal bold +; help_topic = yellow bold +; help_block = cyan +; keyword = yellow +; identifier = normal +; attr_name = cyan +; attr_value = red +; resource_reference = green +; id_reference = green +; score = magenta +; ticket = magenta + +; [report] +; from_time = -12H +; compress = yes +; speed_up = no +; collect_extra_logs = /var/log/messages /var/log/pacemaker.log +; remove_exist_dest = no +; single_node = no +; +; sanitize_rule = sanitize_pattern[:options] ... +; +; This defines the way to hide sensitive data generated by hb_report. +; +; 'sanitize_pattern' is a RegEx string, which is used to matches 'name' +; field of CIB params. The sanitize process will hide 'value' of those +; matched 'name:value' pairs in CIB, PE, pacemaker.log. +; +; 'options' is the predefined, and 'raw' is the only one defined +; currently. With ':raw" option, the sanitize process will fetch +; 'value' results out of CIB 'name:value' pairs, and use them to +; hide all clear text occurence from all files hb_report collected. +; +; Example 1: +; sanitize_rule = passw.* +; +; This is the default. It will hide password nam:value pairs. +; The result of hb_report clould be like +; name="password", value=****** +; @name=password @value=****** +; passwd=****** +; +; +; Example 2: +; sanitize_rule = ip.*:raw +; +; This will only hide ip addresses. Example, the sanitize process will fetch +; ip=10.10.10.10 and replace all clear text occurrence of "10.10.10.10" +; +; +; Example 3: +; sanitize_rule = passw.*|ip.*:raw +; +; This is useful for the higher security concern. +; The sanitize process will hide all "name:value" pair for password like in +; example 1, and all clear text ip addresses like in example 2 above. +; +; sanitize_rule = passw.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/etc/profiles.yml new/crmsh-4.3.1+20210811.4045e09d/etc/profiles.yml --- old/crmsh-4.3.1+20210726.3de6f304/etc/profiles.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.3.1+20210811.4045e09d/etc/profiles.yml 2021-08-11 09:00:07.000000000 +0200 @@ -0,0 +1,29 @@ +# The valid profile names are: +# "microsoft-azure", "google-cloud-platform", "amazon-web-services", "s390", "default" +# +# "default" profile is loaded in the beginning. +# +# Those specific profile will override the corresponding values in "default" +# profile if the specific environment is detected. +# +# Users could customize the "default" profile for their needs, for example, +# those on-premise environments which is not defined yet. +# +# Profiles are only loaded on bootstrap init node. +# +# More details please see man corosync.conf, man sbd + +default: + corosync.totem.crypto_hash: sha1 + corosync.totem.crypto_cipher: aes256 + corosync.totem.token: 5000 + corosync.totem.join: 60 + corosync.totem.max_messages: 20 + corosync.totem.token_retransmits_before_loss_const: 10 + # sbd.msgwait is set to sbd.watchdog_timeout*2 by crmsh + # or, you can define your own value in profiles.yml + sbd.watchdog_timeout: 15 + +microsoft-azure: + corosync.totem.token: 30000 + sbd.watchdog_timeout: 60 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/test/testcases/common.excl new/crmsh-4.3.1+20210811.4045e09d/test/testcases/common.excl --- old/crmsh-4.3.1+20210726.3de6f304/test/testcases/common.excl 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/test/testcases/common.excl 2021-08-11 09:00:07.000000000 +0200 @@ -8,7 +8,7 @@ Error signing on to the CRMd service Error connecting to the controller Error performing operation: Transport endpoint is not connected -.EXT crm_resource --list-standards +.EXT crm_resource --list-ocf-providers .EXT crm_resource --list-ocf-alternatives Delay .EXT crm_resource --list-ocf-alternatives Dummy ^\.EXT crmd version diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/test/testcases/resource.exp new/crmsh-4.3.1+20210811.4045e09d/test/testcases/resource.exp --- old/crmsh-4.3.1+20210726.3de6f304/test/testcases/resource.exp 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/test/testcases/resource.exp 2021-08-11 09:00:07.000000000 +0200 @@ -327,7 +327,7 @@ </cib> .TRY resource trace p0 probe -INFO: Trace for p0:monitor is written to /var/lib/heartbeat/trace_ra/ +INFO: Trace for p0:monitor is written to /var/lib/heartbeat/trace_ra/Dummy INFO: Trace set, restart p0 to trace non-monitor operations .INP: configure .INP: _regtest on @@ -355,7 +355,7 @@ </cib> .TRY resource trace p0 start -INFO: Trace for p0:start is written to /var/lib/heartbeat/trace_ra/ +INFO: Trace for p0:start is written to /var/lib/heartbeat/trace_ra/Dummy INFO: Trace set, restart p0 to trace the start operation .INP: configure .INP: _regtest on @@ -388,7 +388,7 @@ </cib> .TRY resource trace p0 stop -INFO: Trace for p0:stop is written to /var/lib/heartbeat/trace_ra/ +INFO: Trace for p0:stop is written to /var/lib/heartbeat/trace_ra/Dummy INFO: Trace set, restart p0 to trace the stop operation .INP: configure .INP: _regtest on @@ -426,6 +426,7 @@ </cib> .TRY resource untrace p0 probe +INFO: Stop tracing p0 for operation monitor .INP: configure .INP: _regtest on .INP: show xml p0 @@ -457,6 +458,7 @@ </cib> .TRY resource untrace p0 start +INFO: Stop tracing p0 for operation start .INP: configure .INP: _regtest on .INP: show xml p0 @@ -483,6 +485,7 @@ </cib> .TRY resource untrace p0 stop +INFO: Stop tracing p0 for operation stop .INP: configure .INP: _regtest on .INP: show xml p0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/test/unittests/test_bootstrap.py new/crmsh-4.3.1+20210811.4045e09d/test/unittests/test_bootstrap.py --- old/crmsh-4.3.1+20210726.3de6f304/test/unittests/test_bootstrap.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/test/unittests/test_bootstrap.py 2021-08-11 09:00:07.000000000 +0200 @@ -12,6 +12,7 @@ import os import unittest +import yaml try: from unittest import mock @@ -139,6 +140,98 @@ ctx.qdevice_inst.valid_qdevice_options.assert_called_once_with() ctx._validate_sbd_option.assert_called_once_with() + @mock.patch('crmsh.bootstrap.status') + def test_load_specific_profile_return(self, mock_status): + res = self.ctx_inst.load_specific_profile(None) + assert res == {} + mock_status.assert_not_called() + + @mock.patch('crmsh.bootstrap.status') + def test_load_specific_profile_not_exist(self, mock_status): + self.ctx_inst.profiles_data = {"name": "test"} + res = self.ctx_inst.load_specific_profile("newname") + assert res == {} + mock_status.assert_called_once_with("\"newname\" profile does not exist in {}".format(bootstrap.Context.PROFILES_FILE)) + + @mock.patch('crmsh.bootstrap.status') + def test_load_specific_profile(self, mock_status): + self.ctx_inst.profiles_data = {"name": "test"} + res = self.ctx_inst.load_specific_profile("name") + assert res == "test" + mock_status.assert_called_once_with("Loading \"name\" profile from {}".format(bootstrap.Context.PROFILES_FILE)) + + @mock.patch('crmsh.bootstrap.status') + @mock.patch('crmsh.utils.detect_cloud') + @mock.patch('os.uname') + def test_detect_platform_s390(self, mock_uname, mock_cloud, mock_status): + mock_uname.return_value = mock.Mock(machine="s390") + res = self.ctx_inst.detect_platform() + self.assertEqual(res, bootstrap.Context.S390_PROFILE_NAME) + mock_uname.assert_called_once_with() + mock_cloud.assert_not_called() + mock_status.assert_called_once_with("Detected \"{}\" platform".format(res)) + + @mock.patch('crmsh.bootstrap.status') + @mock.patch('crmsh.utils.detect_cloud') + @mock.patch('os.uname') + def test_detect_platform(self, mock_uname, mock_cloud, mock_status): + mock_uname.return_value = mock.Mock(machine="xxx") + mock_cloud.return_value = "azure" + res = self.ctx_inst.detect_platform() + self.assertEqual(res, "azure") + mock_uname.assert_called_once_with() + mock_cloud.assert_called_once_with() + mock_status.assert_called_once_with("Detected \"{}\" platform".format(res)) + + @mock.patch('os.path.exists') + @mock.patch('crmsh.bootstrap.Context.detect_platform') + def test_load_profiles_file_not_exist(self, mock_platform, mock_exists): + mock_platform.return_value = "s390" + mock_exists.return_value = False + self.ctx_inst.load_profiles() + mock_platform.assert_called_once_with() + mock_exists.assert_called_once_with(bootstrap.Context.PROFILES_FILE) + + @mock.patch('yaml.load') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="") + @mock.patch('os.path.exists') + @mock.patch('crmsh.bootstrap.Context.detect_platform') + def test_load_profiles_file_empty(self, mock_platform, mock_exists, mock_open_file, mock_load): + mock_platform.return_value = "s390" + mock_exists.return_value = True + mock_load.return_value = "" + self.ctx_inst.load_profiles() + mock_platform.assert_called_once_with() + mock_exists.assert_called_once_with(bootstrap.Context.PROFILES_FILE) + mock_open_file.assert_called_once_with(bootstrap.Context.PROFILES_FILE) + mock_load.assert_called_once_with(mock_open_file.return_value, Loader=yaml.SafeLoader) + + @mock.patch('crmsh.bootstrap.Context.load_specific_profile') + @mock.patch('yaml.load') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="") + @mock.patch('os.path.exists') + @mock.patch('crmsh.bootstrap.Context.detect_platform') + def test_load_profiles_file(self, mock_platform, mock_exists, mock_open_file, mock_load, mock_load_specific): + mock_platform.return_value = "s390" + mock_exists.return_value = True + mock_load.return_value = "data" + mock_load_specific.side_effect = [ + {"name": "xin", "age": 18}, + {"name": "wang"} + ] + + self.ctx_inst.load_profiles() + assert self.ctx_inst.profiles_dict == {"name": "wang", "age": 18} + + mock_platform.assert_called_once_with() + mock_exists.assert_called_once_with(bootstrap.Context.PROFILES_FILE) + mock_open_file.assert_called_once_with(bootstrap.Context.PROFILES_FILE) + mock_load.assert_called_once_with(mock_open_file.return_value, Loader=yaml.SafeLoader) + mock_load_specific.assert_has_calls([ + mock.call(bootstrap.Context.DEFAULT_PROFILE_NAME), + mock.call("s390") + ]) + class TestBootstrap(unittest.TestCase): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/test/unittests/test_sbd.py new/crmsh-4.3.1+20210811.4045e09d/test/unittests/test_sbd.py --- old/crmsh-4.3.1+20210726.3de6f304/test/unittests/test_sbd.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/test/unittests/test_sbd.py 2021-08-11 09:00:07.000000000 +0200 @@ -189,11 +189,18 @@ self.sbd_inst_diskless._get_sbd_device() def test_initialize_sbd_return(self): + self.sbd_inst_diskless._context = mock.Mock(profiles_dict={}) self.sbd_inst_diskless._initialize_sbd() + @mock.patch('crmsh.bootstrap.warn') + @mock.patch('crmsh.sbd.SBDManager._adjust_sbd_watchdog_timeout_for_s390') @mock.patch('crmsh.bootstrap.error') @mock.patch('crmsh.bootstrap.invoke') - def test_initialize_sbd(self, mock_invoke, mock_error): + def test_initialize_sbd(self, mock_invoke, mock_error, mock_adjust_s390, mock_warn): + self.sbd_inst._context = mock.Mock(profiles_dict={ + "sbd.msgwait": 9, + "sbd.watchdog_timeout": 5 + }) self.sbd_inst._sbd_devices = ["/dev/sdb1", "/dev/sdc1"] mock_invoke.side_effect = [(True, None, None), (False, None, "error")] mock_error.side_effect = ValueError @@ -202,15 +209,16 @@ self.sbd_inst._initialize_sbd() mock_invoke.assert_has_calls([ - mock.call("sbd -d /dev/sdb1 create"), - mock.call("sbd -d /dev/sdc1 create") + mock.call("sbd -4 10 -1 5 -d /dev/sdb1 create"), + mock.call("sbd -4 10 -1 5 -d /dev/sdc1 create") ]) + mock_warn.assert_called_once_with("sbd msgwait is set to 10, it was 9") mock_error.assert_called_once_with("Failed to initialize SBD device /dev/sdc1: error") @mock.patch('crmsh.utils.detect_virt') @mock.patch('crmsh.bootstrap.csync2_update') @mock.patch('crmsh.utils.sysconfig_set') - @mock.patch('crmsh.sbd.SBDManager._determine_sbd_watchdog_timeout') + @mock.patch('crmsh.sbd.SBDManager._adjust_sbd_watchdog_timeout_with_diskless_and_qdevice') @mock.patch('shutil.copyfile') def test_update_configuration(self, mock_copy, mock_determine, mock_sysconfig, mock_update, mock_detect): self.sbd_inst._sbd_devices = ["/dev/sdb1", "/dev/sdc1"] @@ -218,7 +226,7 @@ mock_detect.return_value = True self.sbd_inst._sbd_watchdog_timeout = 30 - self.sbd_inst._update_configuration() + self.sbd_inst._update_sbd_configuration() mock_copy.assert_called_once_with("/usr/share/fillup-templates/sysconfig.sbd", "/etc/sysconfig/sbd") mock_sysconfig.assert_called_once_with("/etc/sysconfig/sbd", SBD_PACEMAKER='yes', SBD_STARTMODE='always', SBD_DELAY_START='yes', SBD_WATCHDOG_DEV='/dev/watchdog', SBD_DEVICE='/dev/sdb1;/dev/sdc1', SBD_WATCHDOG_TIMEOUT="30") @@ -274,7 +282,7 @@ mock_package.assert_called_once_with("sbd") @mock.patch('crmsh.bootstrap.invoke') - @mock.patch('crmsh.sbd.SBDManager._update_configuration') + @mock.patch('crmsh.sbd.SBDManager._update_sbd_configuration') @mock.patch('crmsh.sbd.SBDManager._initialize_sbd') @mock.patch('crmsh.bootstrap.status_long') @mock.patch('crmsh.sbd.SBDManager._get_sbd_device') @@ -300,16 +308,15 @@ mock_watchdog_inst.init_watchdog.assert_called_once_with() mock_invoke.assert_called_once_with("systemctl disable sbd.service") - @mock.patch('crmsh.sbd.SBDManager._determine_stonith_watchdog_timeout') @mock.patch('crmsh.sbd.SBDManager._enable_sbd_service') @mock.patch('crmsh.sbd.SBDManager._warn_diskless_sbd') - @mock.patch('crmsh.sbd.SBDManager._update_configuration') + @mock.patch('crmsh.sbd.SBDManager._update_sbd_configuration') @mock.patch('crmsh.sbd.SBDManager._initialize_sbd') @mock.patch('crmsh.bootstrap.status_long') @mock.patch('crmsh.sbd.SBDManager._get_sbd_device') @mock.patch('crmsh.watchdog.Watchdog') @mock.patch('crmsh.utils.package_is_installed') - def test_sbd_init(self, mock_package, mock_watchdog, mock_get_device, mock_status, mock_initialize, mock_update, mock_warn, mock_enable_sbd, mock_determine): + def test_sbd_init(self, mock_package, mock_watchdog, mock_get_device, mock_status, mock_initialize, mock_update, mock_warn, mock_enable_sbd): mock_package.return_value = True self.sbd_inst_diskless._context = mock.Mock(watchdog=None) mock_watchdog_inst = mock.Mock() @@ -326,7 +333,6 @@ mock_watchdog_inst.init_watchdog.assert_called_once_with() mock_warn.assert_called_once_with() mock_enable_sbd.assert_called_once_with() - mock_determine.assert_called_once_with() @mock.patch('crmsh.sbd.SBDManager.configure_sbd_resource') @mock.patch('crmsh.bootstrap.wait_for_cluster') @@ -348,7 +354,7 @@ self.sbd_inst_diskless._restart_cluster_and_configure_sbd_ra() mock_warn.assert_has_calls([ mock.call("To start sbd.service, need to restart cluster service manually on each node"), - mock.call("Then run \"crm configure property stonith-enabled=true stonith-watchdog-timeout=10s stonith-timeout=60s\" on any node") + mock.call("Then run \"crm configure property stonith-enabled=true stonith-watchdog-timeout=-1 stonith-timeout=60s\" on any node") ]) @mock.patch('crmsh.sbd.SBDManager.configure_sbd_resource') @@ -472,7 +478,7 @@ mock_package.assert_called_once_with("sbd") mock_enabled.assert_called_once_with("sbd.service") mock_get_device.assert_called_once_with() - mock_invoke.assert_called_once_with("crm configure property stonith-enabled=true stonith-watchdog-timeout=10s stonith-timeout=60s") + mock_invoke.assert_called_once_with("crm configure property stonith-enabled=true stonith-watchdog-timeout=-1 stonith-timeout=60s") mock_error.assert_called_once_with("Can't enable STONITH for diskless SBD") mock_ra_configured.assert_called_once_with("stonith:external/sbd") @@ -598,23 +604,15 @@ self.assertEqual("Device /dev/sdb1 doesn't have the same UUID with node1", str(err.exception)) mock_get_uuid.assert_has_calls([mock.call("/dev/sdb1"), mock.call("/dev/sdb1", "node1")]) - @mock.patch('crmsh.utils.get_stdout_stderr') - def test_get_device_uuid_error_dump(self, mock_run): - mock_run.return_value = (1, None, "error data") - with self.assertRaises(ValueError) as err: - self.sbd_inst._get_device_uuid("/dev/sdb1") - self.assertEqual("Cannot dump sbd meta-data: error data", str(err.exception)) - mock_run.assert_called_once_with("sbd -d /dev/sdb1 dump") - - @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('crmsh.utils.get_stdout_or_raise_error') def test_get_device_uuid_not_match(self, mock_run): - mock_run.return_value = (0, "output data", None) + mock_run.return_value = "data" with self.assertRaises(ValueError) as err: self.sbd_inst._get_device_uuid("/dev/sdb1") self.assertEqual("Cannot find sbd device UUID for /dev/sdb1", str(err.exception)) - mock_run.assert_called_once_with("sbd -d /dev/sdb1 dump") + mock_run.assert_called_once_with("sbd -d /dev/sdb1 dump", remote=None) - @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('crmsh.utils.get_stdout_or_raise_error') def test_get_device_uuid(self, mock_run): output = """ ==Dumping header on disk /dev/sda1 @@ -628,66 +626,50 @@ Timeout (msgwait) : 10 ==Header on disk /dev/sda1 is dumped """ - mock_run.return_value = (0, output, None) + mock_run.return_value = output res = self.sbd_inst._get_device_uuid("/dev/sda1", node="node1") self.assertEqual(res, "a2e9a92c-cc72-4ef9-ac55-ccc342f3546b") - mock_run.assert_called_once_with("ssh -o StrictHostKeyChecking=no root@node1 'sbd -d /dev/sda1 dump'") - - @mock.patch('crmsh.utils.parse_sysconfig') - def test_determine_watchdog_timeout(self, mock_parse): - mock_parse_inst = mock.Mock() - mock_parse.return_value = mock_parse_inst - mock_parse_inst.get.return_value = "5" - self.sbd_inst._determine_stonith_watchdog_timeout() - assert self.sbd_inst._stonith_watchdog_timeout == -1 - mock_parse.assert_called_once_with(bootstrap.SYSCONFIG_SBD) - mock_parse_inst.get.assert_called_once_with("SBD_WATCHDOG_TIMEOUT") - - @mock.patch('crmsh.utils.parse_sysconfig') - def test_determine_watchdog_timeout_s390(self, mock_parse): - mock_parse_inst = mock.Mock() - mock_parse.return_value = mock_parse_inst - mock_parse_inst.get.return_value = None - self.sbd_inst._is_s390 = True - self.sbd_inst._determine_stonith_watchdog_timeout() - assert self.sbd_inst._stonith_watchdog_timeout == "30s" - mock_parse.assert_called_once_with(bootstrap.SYSCONFIG_SBD) - mock_parse_inst.get.assert_called_once_with("SBD_WATCHDOG_TIMEOUT") + mock_run.assert_called_once_with("sbd -d /dev/sda1 dump", remote="node1") @mock.patch('crmsh.utils.is_qdevice_configured') - def test_determine_sbd_watchdog_timeout_return(self, mock_qdevice_configured): - self.sbd_inst._determine_sbd_watchdog_timeout() + def test_adjust_sbd_watchdog_timeout_with_diskless_and_qdevice_return(self, mock_qdevice_configured): + self.sbd_inst._adjust_sbd_watchdog_timeout_with_diskless_and_qdevice() mock_qdevice_configured.assert_not_called() + @mock.patch('crmsh.bootstrap.warn') @mock.patch('crmsh.sbd.SBDManager.calculate_stonith_timeout') @mock.patch('crmsh.utils.get_qdevice_sync_timeout') @mock.patch('crmsh.utils.service_is_active') @mock.patch('crmsh.utils.is_qdevice_configured') - def test_determine_sbd_watchdog_timeout_after_qdevice(self, mock_qdevice_configured, mock_active, mock_get_qsync_timeout, mock_cal_timeout): + def test_adjust_sbd_watchdog_timeout_with_diskless_and_qdevice_after_qdevice(self, mock_qdevice_configured, mock_active, mock_get_qsync_timeout, mock_cal_timeout, mock_warn): mock_qdevice_configured.return_value = True mock_active.return_value = True - mock_get_qsync_timeout.return_value = 5 - self.sbd_inst_diskless._is_s390 = True - mock_cal_timeout.return_value = 20 + mock_get_qsync_timeout.return_value = 30 + self.sbd_inst_diskless._sbd_watchdog_timeout = 5 + mock_cal_timeout.return_value = 70 - self.sbd_inst_diskless._determine_sbd_watchdog_timeout() + self.sbd_inst_diskless._adjust_sbd_watchdog_timeout_with_diskless_and_qdevice() mock_qdevice_configured.assert_called_once_with() mock_active.assert_called_once_with("corosync-qdevice.service") mock_get_qsync_timeout.assert_called_once_with() - mock_cal_timeout.assert_called_once_with(15) + mock_cal_timeout.assert_called_once_with(35) + mock_warn.assert_called_once_with("sbd_watchdog_timeout is set to 35 for qdevice, it was 5") + @mock.patch('crmsh.bootstrap.warn') @mock.patch('crmsh.sbd.SBDManager.calculate_stonith_timeout') @mock.patch('crmsh.utils.is_qdevice_configured') - def test_determine_sbd_watchdog_timeout(self, mock_qdevice_configured, mock_cal_timeout): + def test_adjust_sbd_watchdog_timeout_with_diskless_and_qdevice(self, mock_qdevice_configured, mock_cal_timeout, mock_warn): self.sbd_inst_diskless._context = mock.Mock(qdevice_inst=mock.Mock()) mock_qdevice_configured.return_value = False - mock_cal_timeout.return_value = 20 + mock_cal_timeout.return_value = 10 + self.sbd_inst_diskless._sbd_watchdog_timeout = 5 - self.sbd_inst_diskless._determine_sbd_watchdog_timeout() + self.sbd_inst_diskless._adjust_sbd_watchdog_timeout_with_diskless_and_qdevice() mock_qdevice_configured.assert_called_once_with() mock_cal_timeout.assert_called_once_with(sbd.SBDManager.SBD_WATCHDOG_TIMEOUT_DEFAULT_WITH_QDEVICE) + mock_warn.assert_called_once_with("sbd_watchdog_timeout is set to 35 for qdevice, it was 5") @mock.patch('crmsh.sbd.SBDManager._get_sbd_device_from_config') @mock.patch('crmsh.utils.service_is_active') @@ -737,3 +719,118 @@ def test_calculate_stonith_timeout(self): res = self.sbd_inst.calculate_stonith_timeout(5) assert res == 12 + + @mock.patch('crmsh.sbd.SBDManager.is_delay_start') + @mock.patch('crmsh.utils.get_stdout_or_raise_error') + def test_adjust_systemd_no_delay(self, mock_run, mock_delay_start): + mock_delay_start.return_value = False + self.sbd_inst._adjust_systemd() + mock_delay_start.assert_called_once_with() + mock_run.assert_not_called() + + @mock.patch('crmsh.sbd.SBDManager.get_sbd_start_timeout_threshold') + @mock.patch('crmsh.utils.mkdirp') + @mock.patch('crmsh.utils.get_stdout_or_raise_error') + @mock.patch('crmsh.sbd.SBDManager.is_delay_start') + def test_adjust_systemd_return(self, mock_delay_start, mock_run, mock_dirp, mock_threshold): + mock_threshold.return_value = 10 + mock_delay_start.return_value = True + mock_run.return_value = "1min 30s" + + self.sbd_inst._adjust_systemd() + + mock_run.assert_called_once_with("systemctl show -p TimeoutStartUSec sbd --value") + mock_threshold.assert_called_once_with() + mock_dirp.assert_not_called() + + @mock.patch('crmsh.utils.str2file') + @mock.patch('crmsh.utils.mkdirp') + @mock.patch('crmsh.utils.get_stdout_or_raise_error') + @mock.patch('crmsh.sbd.SBDManager.get_suitable_sbd_systemd_timeout') + @mock.patch('crmsh.sbd.SBDManager.get_sbd_start_timeout_threshold') + @mock.patch('crmsh.sbd.SBDManager.is_delay_start') + def test_adjust_systemd(self, mock_delay_start, mock_threshold, mock_systemd_timeout, mock_run, mock_dirp, mock_str2file): + mock_delay_start.return_value = True + mock_threshold.return_value = 120 + mock_systemd_timeout.return_value = 144 + mock_run.return_value = "1min 30s" + + self.sbd_inst._adjust_systemd() + + mock_run.assert_has_calls([ + mock.call("systemctl show -p TimeoutStartUSec sbd --value"), + mock.call("systemctl daemon-reload") + ]) + mock_dirp.assert_called_once_with("/etc/systemd/system/sbd.service.d") + mock_str2file.assert_called_once_with('[Service]\nTimeoutSec=144', '/etc/systemd/system/sbd.service.d/sbd_delay_start.conf') + mock_delay_start.assert_called_once_with() + mock_threshold.assert_called_once_with() + mock_systemd_timeout.assert_called_once_with() + + @mock.patch('crmsh.utils.get_stdout_or_raise_error') + def test_get_sbd_msgwait_exception(self, mock_run): + mock_run.return_value = "data" + with self.assertRaises(ValueError) as err: + sbd.SBDManager._get_sbd_msgwait("/dev/sda1") + self.assertEqual("Cannot get sbd msgwait for /dev/sda1", str(err.exception)) + + @mock.patch('crmsh.utils.get_stdout_or_raise_error') + def test_get_sbd_msgwait(self, mock_run): + mock_run.return_value = """ + Timeout (allocate) : 2 + Timeout (loop) : 1 + Timeout (msgwait) : 30 + ==Header on disk /dev/sda1 is dumped + """ + res = sbd.SBDManager._get_sbd_msgwait("/dev/sda1") + self.assertEqual(res, 30) + mock_run.assert_called_once_with("sbd -d /dev/sda1 dump") + + @mock.patch('crmsh.sbd.SBDManager.get_sbd_start_timeout_threshold') + def test_get_suitable_sbd_systemd_timeout(self, mock_threshold): + mock_threshold.return_value = 10 + res = sbd.SBDManager.get_suitable_sbd_systemd_timeout() + self.assertEqual(res, 12) + mock_threshold.assert_called_once_with() + + @mock.patch('crmsh.bootstrap.warn') + def test_adjust_sbd_watchdog_timeout_for_s390(self, mock_warn): + self.sbd_inst._context = mock.Mock(is_s390=True) + self.sbd_inst._sbd_watchdog_timeout = 10 + self.sbd_inst._adjust_sbd_watchdog_timeout_for_s390() + mock_warn.assert_called_once_with("sbd_watchdog_timeout is set to 15 for s390, it was 10") + + @mock.patch('crmsh.sbd.SBDManager.get_sbd_value_from_config') + def test_get_sbd_watchdog_timeout_exception(self, mock_get_value): + mock_get_value.return_value = None + with self.assertRaises(ValueError) as err: + sbd.SBDManager.get_sbd_watchdog_timeout() + self.assertEqual("Cannot get the value of SBD_WATCHDOG_TIMEOUT", str(err.exception)) + mock_get_value.assert_called_once_with("SBD_WATCHDOG_TIMEOUT") + + @mock.patch('crmsh.sbd.SBDManager.get_sbd_value_from_config') + def test_get_sbd_watchdog_timeout(self, mock_get_value): + mock_get_value.return_value = 10 + res = sbd.SBDManager.get_sbd_watchdog_timeout() + self.assertEqual(res, 10) + mock_get_value.assert_called_once_with("SBD_WATCHDOG_TIMEOUT") + + @mock.patch('crmsh.sbd.SBDManager.get_sbd_watchdog_timeout') + @mock.patch('crmsh.sbd.SBDManager.get_sbd_device_from_config') + def test_get_sbd_start_timeout_threshold_diskless(self, mock_get_device, mock_get_timeout): + mock_get_device.return_value = None + mock_get_timeout.return_value = 10 + res = sbd.SBDManager.get_sbd_start_timeout_threshold() + self.assertEqual(res, 20) + mock_get_device.assert_called_once_with() + mock_get_timeout.assert_called_once_with() + + @mock.patch('crmsh.sbd.SBDManager._get_sbd_msgwait') + @mock.patch('crmsh.sbd.SBDManager.get_sbd_device_from_config') + def test_get_sbd_start_timeout_threshold(self, mock_get_device, mock_get_msgwait): + mock_get_device.return_value = ["/dev/sdb1"] + mock_get_msgwait.return_value = 10 + res = sbd.SBDManager.get_sbd_start_timeout_threshold() + self.assertEqual(res, 10) + mock_get_device.assert_called_once_with() + mock_get_msgwait.assert_called_once_with("/dev/sdb1") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.3.1+20210726.3de6f304/test/unittests/test_ui_cluster.py new/crmsh-4.3.1+20210811.4045e09d/test/unittests/test_ui_cluster.py --- old/crmsh-4.3.1+20210726.3de6f304/test/unittests/test_ui_cluster.py 2021-07-26 05:56:03.000000000 +0200 +++ new/crmsh-4.3.1+20210811.4045e09d/test/unittests/test_ui_cluster.py 2021-08-11 09:00:07.000000000 +0200 @@ -43,11 +43,12 @@ mock_active.assert_called_once_with("pacemaker.service") mock_info.assert_called_once_with("Cluster services already started") + @mock.patch('crmsh.bootstrap.start_pacemaker') @mock.patch('crmsh.ui_cluster.err_buf.info') @mock.patch('crmsh.utils.is_qdevice_configured') @mock.patch('crmsh.utils.start_service') @mock.patch('crmsh.utils.service_is_active') - def test_do_start(self, mock_active, mock_start, mock_qdevice_configured, mock_info): + def test_do_start(self, mock_active, mock_start, mock_qdevice_configured, mock_info, mock_start_pacemaker): context_inst = mock.Mock() mock_active.return_value = False mock_qdevice_configured.return_value = True @@ -55,7 +56,7 @@ self.ui_cluster_inst.do_start(context_inst) mock_active.assert_called_once_with("pacemaker.service") - mock_start.assert_has_calls([mock.call("pacemaker"), mock.call("corosync-qdevice")]) + mock_start.assert_has_calls([mock.call("corosync-qdevice")]) mock_qdevice_configured.assert_called_once_with() mock_info.assert_called_once_with("Cluster services started")