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-02-19 23:43:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.28504 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Fri Feb 19 23:43:25 2021 rev:202 rq:873670 version:4.3.0+git.20210219.811c32f0 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2021-01-25 18:24:23.876496459 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.28504/crmsh.changes 2021-02-19 23:44:59.919347706 +0100 @@ -1,0 +2,41 @@ +Fri Feb 19 08:30:58 UTC 2021 - xli...@suse.com + +- Update to version 4.3.0+git.20210219.811c32f0: + * Dev: unittest: adjust unit test for analyze sublevel + * Dev: doc: add analyze and preflight_check help messages in doc + * Dev: analyze: Add analyze sublevel and put preflight_check in it + +------------------------------------------------------------------- +Tue Feb 09 08:54:36 UTC 2021 - xli...@suse.com + +- Update to version 4.2.0+git.1612860179.982502ba: + * Dev: utils: change default file mod as 644 for str2file function + +------------------------------------------------------------------- +Fri Jan 29 01:59:06 UTC 2021 - xli...@suse.com + +- Update to version 4.2.0+git.1611885080.8dea33ff: + * Dev: unittest: adjust unittest for error message when raise ClaimLockError + * Dev: hb_report: Detect if any ocfs2 partitions exist + * Dev: lock: give more specific error message when raise ClaimLockError + +------------------------------------------------------------------- +Tue Jan 26 06:31:07 UTC 2021 - xli...@suse.com + +- Update to version 4.2.0+git.1611641933.1ccbf10a: + * Dev: unittest: unit test for lsof_ocfs2_device function + * Fix: hb_report: run lsof with specific ocfs2 device(bsc#1180688) + +------------------------------------------------------------------- +Mon Jan 25 03:06:19 UTC 2021 - xli...@suse.com + +- Update to version 4.2.0+git.1611543254.8c7eb168: + * Fix: Replace mktemp() to mkstemp() for security + * Dev: unit test cases for preflight check ASR SBD feature utils.py + * Fix: Remove the duplicate --cov-report html in tox. + * Dev: unit test cases for preflight check ASR SBD feature check.py and task.py + * Fix: fix some lint issues. + * Fix: Replace utils.msg_info to task.info + * Fix: Solve a circular import error of utils.py + +------------------------------------------------------------------- Old: ---- crmsh-4.2.0+git.1611201540.a1006e39.tar.bz2 New: ---- crmsh-4.3.0+git.20210219.811c32f0.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.nwZPSV/_old 2021-02-19 23:45:00.611348385 +0100 +++ /var/tmp/diff_new_pack.nwZPSV/_new 2021-02-19 23:45:00.615348388 +0100 @@ -1,7 +1,7 @@ # # spec file for package crmsh # -# Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -36,9 +36,9 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 4.2.0+git.1611201540.a1006e39 +Version: 4.3.0+git.20210219.811c32f0 Release: 0 -Url: http://crmsh.github.io +URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 BuildRoot: %{_tmppath}/%{name}-%{version}-build ++++++ _service ++++++ --- /var/tmp/diff_new_pack.nwZPSV/_old 2021-02-19 23:45:00.643348416 +0100 +++ /var/tmp/diff_new_pack.nwZPSV/_new 2021-02-19 23:45:00.643348416 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/ClusterLabs/crmsh.git</param> <param name="scm">git</param> <param name="filename">crmsh</param> - <param name="versionformat">4.2.0+git.%ct.%h</param> + <param name="versionformat">4.3.0+git.%cd.%h</param> <param name="revision">master</param> <param name="changesgenerate">enable</param> </service> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.nwZPSV/_old 2021-02-19 23:45:00.659348432 +0100 +++ /var/tmp/diff_new_pack.nwZPSV/_new 2021-02-19 23:45:00.659348432 +0100 @@ -9,6 +9,6 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">ddfecadde6263ce6092aba52248b07a56273ea4b</param> + <param name="changesrevision">811c32f0044997e5be46cf2f705ded005dfff590</param> </service> </servicedata> \ No newline at end of file ++++++ crmsh-4.2.0+git.1611201540.a1006e39.tar.bz2 -> crmsh-4.3.0+git.20210219.811c32f0.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/Makefile.am new/crmsh-4.3.0+git.20210219.811c32f0/Makefile.am --- old/crmsh-4.2.0+git.1611201540.a1006e39/Makefile.am 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/Makefile.am 2021-02-19 02:48:25.000000000 +0100 @@ -61,7 +61,6 @@ preflightnoarchdir = $(datadir)/@PACKAGE@/preflight_check preflightnoarch_DATA = preflight_check/check.py preflight_check/explain.py preflight_check/__init__.py \ preflight_check/config.py preflight_check/main.py preflight_check/utils.py preflight_check/task.py -preflightnoarch_SCRIPTS = preflight_check/ha-cluster-preflight-check EXTRA_DIST = $(hanoarch_DATA) $(preflightnoarch_DATA) # Python module installation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/config.py new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/config.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/config.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/config.py 2021-02-19 02:48:25.000000000 +0100 @@ -7,9 +7,21 @@ import os import re import configparser +from contextlib import contextmanager from . import userdir +@contextmanager +def _disable_exception_traceback(): + """ + All traceback information is suppressed and only the exception type and value are printed + """ + default_value = getattr(sys, "tracebacklimit", 1000) # `1000` is a Python's default value + sys.tracebacklimit = 0 + yield + sys.tracebacklimit = default_value # revert changes + + def configure_libdir(): ''' sysconfig is only available in 2.7 and above @@ -310,11 +322,10 @@ """ Try to handle configparser.MissingSectionHeaderError while reading """ - from . import utils try: config_parser_inst.read(file_list) except configparser.MissingSectionHeaderError: - with utils.disable_exception_traceback(): + with _disable_exception_traceback(): raise def load(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/corosync.py new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/corosync.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/corosync.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/corosync.py 2021-02-19 02:48:25.000000000 +0100 @@ -1294,4 +1294,3 @@ _COROSYNC_CONF_TEMPLATE_RING_ALL + \ _COROSYNC_CONF_TEMPLATE_TAIL utils.str2file(_COROSYNC_CONF_TEMPLATE % config_common, conf()) - os.chmod(conf(), 0o644) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/lock.py new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/lock.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/lock.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/lock.py 2021-02-19 02:48:25.000000000 +0100 @@ -60,7 +60,7 @@ Raise ClaimLockError if claiming lock failed """ if not self._create_lock_dir(): - raise ClaimLockError("Failed to claim lock") + raise ClaimLockError("Failed to claim lock (the lock directory exists at {})".format(self.LOCK_DIR)) def _unlock(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/ui_analyze.py new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/ui_analyze.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/ui_analyze.py 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/ui_analyze.py 2021-02-19 02:48:25.000000000 +0100 @@ -0,0 +1,28 @@ +# Copyright (C) 2021 Xin Liang <xli...@suse.com> +# See COPYING for license information. + + +import sys + +from . import command +from . import utils +sys.path.append("/usr/share/crmsh") +from preflight_check import main + + +class Analyze(command.UI): + """ + """ + name = "analyze" + + def __init__(self): + command.UI.__init__(self) + + def do_preflight_check(self, context, *args): + sys.argv[1:] = args + main.ctx.process_name = context.command_name + try: + main.run(main.ctx) + except utils.TerminateSubCommand: + return False + return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/ui_root.py new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/ui_root.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/ui_root.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/ui_root.py 2021-02-19 02:48:25.000000000 +0100 @@ -29,6 +29,7 @@ from . import ui_maintenance from . import ui_node from . import ui_options +from . import ui_analyze from . import ui_ra from . import ui_report from . import ui_resource @@ -122,6 +123,11 @@ def do_options(self): pass + @command.level(ui_analyze.Analyze) + @command.help('''Analyze help''') + def do_analyze(self): + pass + @command.level(ui_ra.RA) @command.help('''resource agents information center This level contains commands which show various information about diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/utils.py new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/utils.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/crmsh/utils.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/crmsh/utils.py 2021-02-19 02:48:25.000000000 +0100 @@ -26,6 +26,12 @@ from .msg import common_warn, common_info, common_debug, common_err, err_buf +class TerminateSubCommand(Exception): + """ + This is an exception to jump out of subcommand when meeting errors while staying interactive shell + """ + + def to_ascii(input_str): """Convert the bytes string to a ASCII string Usefull to remove accent (diacritics)""" @@ -508,13 +514,14 @@ os.rename(tmppath, filepath) -def str2file(s, fname): +def str2file(s, fname, mod=0o644): ''' Write a string to a file. ''' try: with open_atomic(fname, 'w', encoding='utf-8') as dst: dst.write(to_ascii(s)) + os.chmod(fname, mod) except IOError as msg: common_err(msg) return False @@ -2625,17 +2632,6 @@ return rc == 0 -@contextmanager -def disable_exception_traceback(): - """ - All traceback information is suppressed and only the exception type and value are printed - """ - default_value = getattr(sys, "tracebacklimit", 1000) # `1000` is a Python's default value - sys.tracebacklimit = 0 - yield - sys.tracebacklimit = default_value # revert changes - - def ping_node(node): """ Check if the remote node is reachable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/doc/crm.8.adoc new/crmsh-4.3.0+git.20210219.811c32f0/doc/crm.8.adoc --- old/crmsh-4.2.0+git.1611201540.a1006e39/doc/crm.8.adoc 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/doc/crm.8.adoc 2021-02-19 02:48:25.000000000 +0100 @@ -932,6 +932,44 @@ verify [scores] ............... +[[cmdhelp_analyze,Cluster analyze]] +=== `analyze` - Cluster analyze + +The subcommands of this level try to analyze the cluster, report any +error or less optimal configuration, provide the suggestions to the +users, and even fix it with the option, if applicable. + +[[cmdhelp_analyze_preflight_check,Cluster preflight check tool set]] +==== `preflight_check` - Cluster preflight check tool set + +It standardizes the steps to simulate cluster failures and to +verify some key configuration before you move your cluster into +production. It is carefully designed with the proper steps and does not +change any configuration to harm the cluster without the confirmation +from users. + +usage: preflight_check [-c] + [--kill-sbd | --kill-corosync | --kill-pacemakerd | --fence-node NODE | --split-brain-iptables] + [-l] [-y] [-h] + +Cluster preflight check tool set + +optional arguments: + -c, --check-conf Validate the configurations + --kill-sbd Kill sbd daemon + --kill-corosync Kill corosync daemon + --kill-pacemakerd Kill pacemakerd daemon + --fence-node NODE Fence specific node + --split-brain-iptables Make split brain by blocking corosync ports + -l, --kill-loop Kill process in loop + +other options: + -y, --yes Answer "yes" if asked to run the test + -h, --help Show this help message and exit + +Log: /var/log/crmsh/preflight_check.log +Json results: /var/lib/crmsh/preflight_check/preflight_check.json +For each --kill-* testcase, report directory: /var/lib/crmsh/preflight_check [[cmdhelp_cluster,Cluster setup and management]] === `cluster` - Cluster setup and management diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/hb_report/utillib.py new/crmsh-4.3.0+git.20210219.811c32f0/hb_report/utillib.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/hb_report/utillib.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/hb_report/utillib.py 2021-02-19 02:48:25.000000000 +0100 @@ -1643,12 +1643,39 @@ return out_string +def lsof_ocfs2_device(): + """ + List open files for OCFS2 device + """ + out_string = "" + _, out, _ = crmutils.get_stdout_stderr("mount") + dev_list = re.findall("\n(.*) on .* type ocfs2 ", out) + for dev in dev_list: + cmd = "lsof {}".format(dev) + out_string += "\n\n#=====[ Command ] ==========================#\n" + out_string += "# {}\n".format(cmd) + _, cmd_out, _ = crmutils.get_stdout_stderr(cmd) + if cmd_out: + out_string += cmd_out + return out_string + + def dump_ocfs2(): ocfs2_f = os.path.join(constants.WORKDIR, constants.OCFS2_F) with open(ocfs2_f, "w") as f: + rc, out, err = crmutils.get_stdout_stderr("mounted.ocfs2 -d") + if rc != 0: + f.write("Failed to run \"mounted.ocfs2 -d\": {}".format(err)) + return + # No ocfs2 device, just header line printed + elif len(out.split('\n')) == 1: + f.write("No ocfs2 partitions found") + return + f.write(dump_D_process()) + f.write(lsof_ocfs2_device()) - cmds = [ "dmesg", "ps -efL", "lsof", + cmds = [ "dmesg", "ps -efL", "lsblk -o 'NAME,KNAME,MAJ:MIN,FSTYPE,LABEL,RO,RM,MODEL,SIZE,OWNER,GROUP,MODE,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,ROTA,SCHED,MOUNTPOINT'", "mounted.ocfs2 -f", "findmnt", "mount", "cat /sys/fs/ocfs2/cluster_stack" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/check.py new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/check.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/check.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/check.py 2021-02-19 02:48:25.000000000 +0100 @@ -34,14 +34,14 @@ with task_inst.run(): if not os.path.exists(config.SBD_CONF): - utils.msg_info("SBD configuration file {} not found.". + task_inst.info("SBD configuration file {} not found.". format(config.SBD_CONF)) return "" sbd_options = crmshutils.parse_sysconfig(config.SBD_CONF) if not "SBD_DEVICE" in sbd_options: - utils.msg_info("SBD DEVICE not used.") + task_inst.info("SBD DEVICE not used.") return "" dev = sbd_options["SBD_DEVICE"] @@ -83,7 +83,7 @@ task_inst.verify() except task.TaskError as err: task_inst.error(str(err)) - sys.exit(1) + raise crmshutils.TerminateSubCommand def check(context): @@ -312,3 +312,6 @@ task_inst.info("Stopped resources: {}".format(','.join(stopped_list))) if failed_list: task_inst.warn("Failed resources: {}".format(','.join(failed_list))) + + if not (started_list or stopped_list or failed_list): + task_inst.info("No resources configured") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/ha-cluster-preflight-check new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/ha-cluster-preflight-check --- old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/ha-cluster-preflight-check 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/ha-cluster-preflight-check 1970-01-01 01:00:00.000000000 +0100 @@ -1,11 +0,0 @@ -#!/usr/bin/python3 -import os -import sys -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from preflight_check import main - - -if __name__ == "__main__": - main.ctx.process_name = os.path.basename(sys.argv[0]) - main.run(main.ctx) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/main.py new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/main.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/main.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/main.py 2021-02-19 02:48:25.000000000 +0100 @@ -8,6 +8,7 @@ from . import check from . import utils from . import task +from crmsh import utils as crmshutils logger = logging.getLogger('cpc') @@ -113,7 +114,7 @@ task_inst.wait() except task.TaskError as err: task_inst.error(str(err)) - sys.exit(1) + raise crmshutils.TerminateSubCommand def split_brain(context): @@ -132,7 +133,7 @@ task_inst.wait() except task.TaskError as err: task_inst.error(str(err)) - sys.exit(1) + raise crmshutils.TerminateSubCommand def fence_node(context): @@ -150,7 +151,7 @@ task_inst.wait() except task.TaskError as err: task_inst.error(str(err)) - sys.exit(1) + raise crmshutils.TerminateSubCommand class MyArgParseFormatter(RawTextHelpFormatter): @@ -174,7 +175,7 @@ context.report_path)) parser.add_argument('-c', '--check-conf', dest='check_conf', action='store_true', - help='Valid the configurations') + help='Validate the configurations') group_mutual = parser.add_mutually_exclusive_group() group_mutual.add_argument('--kill-sbd', dest='sbd', action='store_true', @@ -199,13 +200,15 @@ args = parser.parse_args() if args.help: parser.print_help() - sys.exit(0) + raise crmshutils.TerminateSubCommand for arg in vars(args): setattr(context, arg, getattr(args, arg)) if len(sys.argv) == 1: setattr(context, 'cluster_check', True) + else: + setattr(context, 'cluster_check', False) def setup_logging(context): @@ -220,11 +223,11 @@ """ Setup basic context """ - var_dir = "/var/lib/{}".format(context.process_name) + var_dir = "/var/lib/crmsh/{}".format(context.process_name) context.var_dir = var_dir context.report_path = var_dir context.jsonfile = "{}/{}.json".format(var_dir, context.process_name) - context.logfile = "/var/log/{}.log".format(context.process_name) + context.logfile = "/var/log/crmsh/{}.log".format(context.process_name) def run(context): @@ -235,7 +238,7 @@ parse_argument(context) if not utils.is_root(): logging.fatal("{} can only be executed as user root!".format(context.process_name)) - sys.exit(1) + raise crmshutils.TerminateSubCommand if not os.path.exists(context.var_dir): os.makedirs(context.var_dir, exist_ok=True) setup_logging(context) @@ -249,5 +252,4 @@ except KeyboardInterrupt: utils.json_dumps() - print("\nCtrl-C, leaving") - sys.exit(1) + raise diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/task.py new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/task.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/preflight_check/task.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/preflight_check/task.py 2021-02-19 02:48:25.000000000 +0100 @@ -91,7 +91,7 @@ print(self.header()) if not self.yes and not crmshutils.ask("Run?"): self.info("Testcase cancelled") - sys.exit() + raise crmshutils.TerminateSubCommand def task_pre_check(self, need_fence=True): """ @@ -576,8 +576,8 @@ self.description = "Replace SBD_DEVICE with candidate {}".format(self.new) self.conf = config.SBD_CONF super(self.__class__, self).__init__(self.description, flush=True) - self.bak = tempfile.mktemp() - self.edit = tempfile.mktemp() + self.bak = tempfile.mkstemp()[1] + self.edit = tempfile.mkstemp()[1] self.yes = yes sbd_options = crmshutils.parse_sysconfig(self.conf) @@ -594,7 +594,7 @@ '''.format(self.description, self.old, self.new) return h - def to_json(self): # pragma: no cover + def to_json(self): """ Generate json output """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_lock.py new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_lock.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_lock.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_lock.py 2021-02-19 02:48:25.000000000 +0100 @@ -73,7 +73,7 @@ mock_create.return_value = False with self.assertRaises(lock.ClaimLockError) as err: self.local_inst._lock_or_fail() - self.assertEqual("Failed to claim lock", str(err.exception)) + self.assertEqual("Failed to claim lock (the lock directory exists at /tmp/.crmsh_lock_directory)", str(err.exception)) mock_create.assert_called_once_with() @mock.patch('crmsh.lock.Lock._run') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_check.py new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_check.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_check.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_check.py 2021-02-19 02:48:25.000000000 +0100 @@ -1,17 +1,17 @@ import os import sys -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -import unittest try: - from unittest import mock + from unittest import mock, TestCase except ImportError: import mock -from preflight_check import check +from crmsh import utils as crmshutils +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) +from preflight_check import check, config -class TestCheck(unittest.TestCase): +class TestCheck(TestCase): @mock.patch('preflight_check.check.check_cluster') def test_check(self, mock_cluster_check): @@ -134,7 +134,7 @@ mock_corosync_port.assert_called_once_with() task_inst.error.assert_called_once_with("Can not get corosync's port") - + @mock.patch('preflight_check.check.crmshutils.get_stdout_stderr') @mock.patch('preflight_check.utils.corosync_port_list') def test_check_port_open(self, mock_corosync_port, mock_run): @@ -188,7 +188,7 @@ mock_installed.assert_called_once_with("firewalld") mock_task_inst.info.assert_called_once_with("firewalld.service is available") mock_task_inst.warn.assert_called_once_with("firewalld.service is not active") - + @mock.patch('preflight_check.check.check_port_open') @mock.patch('preflight_check.check.crmshutils.service_is_active') @mock.patch('preflight_check.check.crmshutils.package_is_installed') @@ -482,3 +482,310 @@ mock.call("Started resources: r1,r2"), mock.call("Stopped resources: r3,r4") ]) + + # Test fix() + @classmethod + @mock.patch('preflight_check.check.correct_sbd') + @mock.patch('preflight_check.check.check_sbd') + def test_fix_no_candidate(cls, mock_check_sbd, mock_correct_sbd): + """ + Test fix() has no valid candidate + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + ctx = mock.Mock(fix_conf=True) + mock_check_sbd.return_value = dev + check.fix(ctx) + mock_correct_sbd.assert_called_once_with(ctx, dev) + + @classmethod + @mock.patch('preflight_check.check.correct_sbd') + @mock.patch('preflight_check.check.check_sbd') + def test_fix_has_candidate(cls, mock_check_sbd, mock_correct_sbd): + """ + Test fix() has valid candidate + """ + ctx = mock.Mock(fix_conf=True) + mock_check_sbd.return_value = "" + mock_correct_sbd.return_value = "" + check.fix(ctx) + mock_correct_sbd.assert_not_called() + + # Test check_sbd() + @classmethod + @mock.patch('preflight_check.task.TaskCheck.print_result') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('os.path.exists') + def test_check_sbd_no_conf(cls, mock_os_path_exists, + mock_utils_msg_info, mock_run): + """ + Test no configuration file + """ + mock_os_path_exists.return_value = False + check.check_sbd() + mock_utils_msg_info.assert_called_with("SBD configuration file {} not found.". + format(config.SBD_CONF), to_stdout=False) + mock_run.assert_called_once_with() + + @classmethod + @mock.patch('preflight_check.task.TaskCheck.print_result') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + def test_check_sbd_not_configured(cls, mock_os_path_exists, mock_utils_parse_sysconf, + mock_utils_msg_info, mock_run): + """ + Test SBD device not configured + """ + mock_os_path_exists.return_value = True + mock_utils_parse_sysconf.return_value = {} + check.check_sbd() + mock_utils_msg_info.assert_called_with("SBD DEVICE not used.", to_stdout=False) + mock_run.assert_called_once_with() + + @classmethod + @mock.patch('preflight_check.task.TaskCheck.print_result') + @mock.patch('preflight_check.utils.is_valid_sbd') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + def test_check_sbd_exist_and_valid(cls, mock_os_path_exists, + mock_utils_parse_sysconf, mock_find_hexdump, + mock_msg_info, mock_is_valid_sbd, mock_run): + """ + Test configured SBD device exist and valid + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + mock_os_path_exists.side_effect = [True, True, True] + mock_utils_parse_sysconf.return_value = {"SBD_DEVICE": dev} + mock_find_hexdump.return_value = (0, "/usr/bin/hexdump", None) + mock_is_valid_sbd.return_value = True + + check.check_sbd() + mock_msg_info.assert_called_with("'{}' is a valid SBD device.".format(dev), + to_stdout=False) + mock_run.assert_called_once_with() + + @classmethod + @mock.patch('preflight_check.task.TaskCheck.print_result') + @mock.patch('preflight_check.utils.find_candidate_sbd') + @mock.patch('preflight_check.utils.is_valid_sbd') + @mock.patch('preflight_check.utils.msg_warn') + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + def test_check_sbd_exist_and_not_valid_but_no_can(cls, mock_os_path_exists, + mock_utils_parse_sysconf, mock_find_hexdump, + mock_msg_warn, mock_is_valid_sbd, + mock_find_can_sbd, mock_run): + """ + Test configured SBD device not valid and no candidate + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + mock_os_path_exists.side_effect = [True, True, True] + mock_utils_parse_sysconf.return_value = {"SBD_DEVICE": dev} + mock_find_hexdump.return_value = (0, "/usr/bin/hexdump", None) + mock_is_valid_sbd.return_value = False + mock_find_can_sbd.return_value = "" + + check.check_sbd() + mock_msg_warn.assert_has_calls( + [mock.call("Device '{}' is not valid for SBD, may need initialize.". + format(dev), to_stdout=False), + mock.call("Fail to find a valid candidate SBD device.", + to_stdout=False)]) + mock_run.assert_called_once_with() + + @classmethod + @mock.patch('preflight_check.task.TaskCheck.print_result') + @mock.patch('preflight_check.utils.find_candidate_sbd') + @mock.patch('preflight_check.utils.is_valid_sbd') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('preflight_check.utils.msg_warn') + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + def test_check_sbd_exist_and_not_exist_has_can(cls, mock_os_path_exists, + mock_utils_parse_sysconf, mock_find_hexdump, + mock_msg_warn, mock_msg_info, mock_is_valid_sbd, + mock_find_can_sbd, mock_run): + """ + Test configured SBD device not valid but has candidate + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + candev = "/dev/disk/by-id/scsi-SATA_ST2037LM010-2R82_WDZ5J36B" + mock_os_path_exists.side_effect = [True, False] + mock_utils_parse_sysconf.return_value = {"SBD_DEVICE": dev} + mock_find_hexdump.return_value = (0, "/usr/bin/hexdump", None) + mock_is_valid_sbd.return_value = False + mock_find_can_sbd.return_value = candev + + check.check_sbd() + mock_msg_warn.assert_called_once_with( + "SBD device '{}' is not exist.".format(dev), + to_stdout=False) + mock_msg_info.assert_called_with("Found '{}' with SBD header exist.".format(candev), + to_stdout=False) + mock_run.assert_called_once_with() + + @classmethod + @mock.patch('preflight_check.task.TaskCheck.print_result') + @mock.patch('preflight_check.utils.find_candidate_sbd') + @mock.patch('preflight_check.utils.is_valid_sbd') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('preflight_check.utils.msg_warn') + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + def test_check_sbd_exist_and_not_valid_has_can(cls, mock_os_path_exists, + mock_utils_parse_sysconf, mock_find_hexdump, + mock_msg_warn, mock_msg_info, mock_is_valid_sbd, + mock_find_can_sbd, mock_run): + """ + Test configured SBD device not valid but has candidate + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + candev = "/dev/disk/by-id/scsi-SATA_ST2037LM010-2R82_WDZ5J36B" + mock_os_path_exists.side_effect = [True, True, True] + mock_utils_parse_sysconf.return_value = {"SBD_DEVICE": dev} + mock_find_hexdump.return_value = (0, "/usr/bin/hexdump", None) + mock_is_valid_sbd.return_value = False + mock_find_can_sbd.return_value = candev + + check.check_sbd() + mock_msg_warn.assert_called_once_with( + "Device '{}' is not valid for SBD, may need initialize.".format(dev), + to_stdout=False) + mock_msg_info.assert_called_with("Found '{}' with SBD header exist.".format(candev), + to_stdout=False) + mock_run.assert_called_once_with() + + # Test correct_sbd() + @mock.patch('preflight_check.task.Task.error') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + @mock.patch('preflight_check.main.Context') + def test_correct_sbd_exception_no_conf(self, mock_context, mock_os_path_exists, + mock_utils_parse_sysconf, mock_msg_info, + mock_error): + """ + Test correct_sbd with exception + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + mock_context = mock.Mock(yes=True) + mock_os_path_exists.side_effect = [False, True] + mock_utils_parse_sysconf.retrun_value = {"SBD_DEVICE": dev} + + with self.assertRaises(crmshutils.TerminateSubCommand): + check.correct_sbd(mock_context, dev) + + mock_msg_info.assert_called_once_with('Replace SBD_DEVICE with candidate {}'. + format(dev), to_stdout=False) + mock_error.assert_called_once_with('Configure file {} not exist!'. + format(config.SBD_CONF)) + + @mock.patch('preflight_check.task.Task.error') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + @mock.patch('preflight_check.main.Context') + def test_correct_sbd_exception_no_dev(self, mock_context, mock_os_path_exists, + mock_utils_parse_sysconf, mock_msg_info, + mock_error): + """ + Test correct_sbd with exception + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + mock_context = mock.Mock(yes=True) + mock_os_path_exists.side_effect = [True, False] + mock_utils_parse_sysconf.retrun_value = {"SBD_DEVICE": dev} + + with self.assertRaises(crmshutils.TerminateSubCommand): + check.correct_sbd(mock_context, dev) + + mock_msg_info.assert_called_once_with('Replace SBD_DEVICE with candidate {}'. + format(dev), to_stdout=False) + mock_error.assert_called_once_with('Device {} not exist!'.format(dev)) + + @classmethod + @mock.patch('builtins.open') + @mock.patch('preflight_check.task.TaskFixSBD.verify') + @mock.patch('tempfile.mkstemp') + @mock.patch('os.remove') + @mock.patch('shutil.move') + @mock.patch('shutil.copymode') + @mock.patch('shutil.copyfile') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + @mock.patch('preflight_check.main.Context') + def test_correct_sbd(cls, mock_context, mock_os_path_exists, + mock_utils_parse_sysconf, mock_msg_info, mock_copyfile, + mock_copymode, mock_move, mock_remove, + mock_mkstemp, mock_sbd_verify, mock_open): + """ + Test correct_sbd + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + bak = "/tmp/tmpmby3ty9g" + edit = "/tmp/tmpnic4t30s" + mock_context.return_value = mock.Mock(yes=True) + mock_os_path_exists.side_effect = [True, True] + mock_utils_parse_sysconf.retrun_value = {"SBD_DEVICE": dev} + mock_open.side_effect = [ + mock.mock_open(read_data="data1").return_value, + mock.mock_open(read_data="SBD_DEVICE={}".format(dev)).return_value + ] + mock_mkstemp.side_effect = [(1, bak), (2, edit)] + + check.correct_sbd(mock_context, dev) + + mock_msg_info.assert_called_once_with('Replace SBD_DEVICE with candidate {}'. + format(dev), to_stdout=False) + mock_copyfile.assert_called_once_with(config.SBD_CONF, bak) + mock_copymode.assert_called_once_with(config.SBD_CONF, edit) + mock_move.assert_called_once_with(edit, config.SBD_CONF) + mock_remove.assert_called() + mock_sbd_verify.assert_called_once_with() + + @classmethod + @mock.patch('builtins.open') + @mock.patch('preflight_check.task.Task.error') + @mock.patch('tempfile.mkstemp') + @mock.patch('shutil.copymode') + @mock.patch('shutil.copyfile') + @mock.patch('preflight_check.utils.msg_info') + @mock.patch('crmsh.utils.parse_sysconfig') + @mock.patch('os.path.exists') + @mock.patch('preflight_check.main.Context') + def test_correct_sbd_run_exception(cls, mock_context, mock_os_path_exists, + mock_utils_parse_sysconf, mock_msg_info, mock_copyfile, + mock_copymode, mock_mkstemp, mock_msg_error, + mock_open): + """ + Test correct_sbd + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + bak = "/tmp/tmpmby3ty9g" + edit = "/tmp/tmpnic4t30s" + mock_context.return_value = mock.Mock(yes=True) + mock_os_path_exists.side_effect = [True, True] + mock_utils_parse_sysconf.retrun_value = {"SBD_DEVICE": dev} + mock_open.side_effect = [ + mock.mock_open(read_data="data1").return_value, + mock.mock_open(read_data="data2").return_value + ] + mock_mkstemp.side_effect = [(1, bak), (2, edit)] + mock_copymode.side_effect = Exception('Copy file error!') + + with cls.assertRaises(cls, crmshutils.TerminateSubCommand): + check.correct_sbd(mock_context, dev) + + mock_msg_info.assert_called_once_with('Replace SBD_DEVICE with candidate {}'. + format(dev), to_stdout=False) + mock_copyfile.assert_has_calls([mock.call(config.SBD_CONF, bak), + mock.call(bak, config.SBD_CONF)]) + mock_copymode.assert_called_once_with(config.SBD_CONF, edit) + mock_msg_error.assert_called_once_with('Fail to modify file {}'. + format(config.SBD_CONF)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_main.py new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_main.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_main.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_main.py 2021-02-19 02:48:25.000000000 +0100 @@ -1,50 +1,47 @@ import os import sys -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -import unittest try: - from unittest import mock + from unittest import mock, TestCase except ImportError: import mock +from crmsh import utils as crmshutils +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from preflight_check import utils, main, config, task -class TestContext(unittest.TestCase): +class TestContext(TestCase): def test_context(self): main.ctx.name = "xin" self.assertEqual(main.ctx.name, "xin") -class TestMain(unittest.TestCase): +class TestMain(TestCase): - @mock.patch('sys.exit') @mock.patch('preflight_check.main.MyArgParseFormatter') @mock.patch('argparse.ArgumentParser') - def test_parse_argument_help(self, mock_parser, mock_myformatter, mock_exit): + def test_parse_argument_help(self, mock_parser, mock_myformatter): mock_parser_inst = mock.Mock() mock_parser.return_value = mock_parser_inst - ctx = mock.Mock(process_name="preflight_check", logfile="logfile1", jsonfile="jsonfile1", - report_path="/var/log/report") + ctx = mock.Mock(process_name="preflight_check", logfile="logfile1", + jsonfile="jsonfile1", report_path="/var/log/report") mock_parse_args_inst = mock.Mock(help=True) mock_parser_inst.parse_args.return_value = mock_parse_args_inst - mock_exit.side_effect = SystemExit - with self.assertRaises(SystemExit): + with self.assertRaises(crmshutils.TerminateSubCommand): main.parse_argument(ctx) mock_parser_inst.print_help.assert_called_once_with() - mock_exit.assert_called_once_with(0) @mock.patch('preflight_check.main.MyArgParseFormatter') @mock.patch('argparse.ArgumentParser') def test_parse_argument(self, mock_parser, mock_myformatter): mock_parser_inst = mock.Mock() mock_parser.return_value = mock_parser_inst - ctx = mock.Mock(process_name="preflight_check", logfile="logfile1", jsonfile="jsonfile1", - report_path="/var/log/report") + ctx = mock.Mock(process_name="preflight_check", logfile="logfile1", + jsonfile="jsonfile1", report_path="/var/log/report") mock_parse_args_inst = mock.Mock(help=False, env_check=True, sbd=True) mock_parser_inst.parse_args.return_value = mock_parse_args_inst @@ -63,29 +60,26 @@ def test_setup_basic_context(self): ctx = mock.Mock(process_name="preflight_check") main.setup_basic_context(ctx) - self.assertEqual(ctx.var_dir, "/var/lib/preflight_check") - self.assertEqual(ctx.report_path, "/var/lib/preflight_check") - self.assertEqual(ctx.jsonfile, "/var/lib/preflight_check/preflight_check.json") - self.assertEqual(ctx.logfile, "/var/log/preflight_check.log") + self.assertEqual(ctx.var_dir, "/var/lib/crmsh/preflight_check") + self.assertEqual(ctx.report_path, "/var/lib/crmsh/preflight_check") + self.assertEqual(ctx.jsonfile, "/var/lib/crmsh/preflight_check/preflight_check.json") + self.assertEqual(ctx.logfile, "/var/log/crmsh/preflight_check.log") - @mock.patch('sys.exit') @mock.patch('logging.fatal') @mock.patch('preflight_check.utils.is_root') @mock.patch('preflight_check.main.parse_argument') @mock.patch('preflight_check.main.setup_basic_context') - def test_run_non_root(self, mock_setup, mock_parse, mock_is_root, mock_log_fatal, mock_exit): + def test_run_non_root(self, mock_setup, mock_parse, mock_is_root, mock_log_fatal): mock_is_root.return_value = False ctx = mock.Mock(process_name="preflight_check") - mock_exit.side_effect = SystemExit - with self.assertRaises(SystemExit): + with self.assertRaises(crmshutils.TerminateSubCommand): main.run(ctx) mock_setup.assert_called_once_with(ctx) mock_parse.assert_called_once_with(ctx) mock_is_root.assert_called_once_with() mock_log_fatal.assert_called_once_with("{} can only be executed as user root!".format(ctx.process_name)) - mock_exit.assert_called_once_with(1) @mock.patch('preflight_check.main.split_brain') @mock.patch('preflight_check.main.fence_node') @@ -99,7 +93,7 @@ @mock.patch('preflight_check.main.parse_argument') @mock.patch('preflight_check.main.setup_basic_context') def test_run(self, mock_setup, mock_parse, mock_is_root, mock_exists, mock_mkdir, - mock_setup_logging, mock_fix, mock_check, mock_kill, mock_fence, mock_sb): + mock_setup_logging, mock_fix, mock_check, mock_kill, mock_fence, mock_sb): mock_is_root.return_value = True ctx = mock.Mock(var_dir="/var/lib/preflight_check") mock_exists.return_value = False @@ -118,7 +112,6 @@ mock_fence.assert_called_once_with(ctx) mock_sb.assert_called_once_with(ctx) - @mock.patch('sys.exit') @mock.patch('preflight_check.utils.json_dumps') @mock.patch('preflight_check.main.check.check') @mock.patch('preflight_check.main.check.fix') @@ -128,14 +121,13 @@ @mock.patch('preflight_check.main.parse_argument') @mock.patch('preflight_check.main.setup_basic_context') def test_run_except(self, mock_setup, mock_parse, mock_is_root, mock_exists, - mock_setup_logging, mock_fix, mock_check, mock_dumps, mock_exit): + mock_setup_logging, mock_fix, mock_check, mock_dumps): mock_is_root.return_value = True ctx = mock.Mock(var_dir="/var/lib/preflight_check") mock_exists.return_value = True mock_check.side_effect = KeyboardInterrupt - mock_exit.side_effect = SystemExit - with self.assertRaises(SystemExit): + with self.assertRaises(KeyboardInterrupt): main.run(ctx) mock_setup.assert_called_once_with(ctx) @@ -146,7 +138,6 @@ mock_check.assert_called_once_with(ctx) mock_fix.assert_called_once_with(ctx) mock_dumps.assert_called_once_with() - mock_exit.assert_called_once_with(1) @mock.patch('preflight_check.task.TaskKill') def test_kill_porcess_return_pacemaker_loop(self, mock_task_kill): @@ -160,15 +151,15 @@ main.kill_process(ctx) mock_task_kill.assert_not_called() - @mock.patch('sys.exit') @mock.patch('preflight_check.task.TaskKill') - def test_kill_process(self, mock_task_kill, mock_exit): + def test_kill_process(self, mock_task_kill): mock_task_kill_inst = mock.Mock() mock_task_kill.return_value = mock_task_kill_inst mock_task_kill_inst.wait.side_effect = task.TaskError("error data") ctx = mock.Mock(sbd=True) - main.kill_process(ctx) + with self.assertRaises(crmshutils.TerminateSubCommand): + main.kill_process(ctx) mock_task_kill_inst.pre_check.assert_called_once_with() mock_task_kill_inst.print_header.assert_called_once_with() @@ -176,7 +167,6 @@ mock_task_kill_inst.run.assert_called_once_with() mock_task_kill_inst.wait.assert_called_once_with() mock_task_kill_inst.error.assert_called_once_with("error data") - mock_exit.assert_called_once_with(1) def test_split_brain_return(self): ctx = mock.Mock(sp_iptables=None) @@ -199,39 +189,38 @@ mock_sp_inst.run.assert_called_once_with() mock_sp_inst.wait.assert_called_once_with() - @mock.patch('sys.exit') @mock.patch('preflight_check.task.TaskSplitBrain') - def test_split_brain_exception(self, mock_sp, mock_exit): + def test_split_brain_exception(self, mock_sp): ctx = mock.Mock(sp_iptables=True) mock_sp_inst = mock.Mock() mock_sp.return_value = mock_sp_inst mock_sp_inst.pre_check.side_effect = task.TaskError("error data") - main.split_brain(ctx) + with self.assertRaises(crmshutils.TerminateSubCommand): + main.split_brain(ctx) mock_sp_inst.error.assert_called_once_with("error data") - mock_exit.assert_called_once_with(1) def test_fence_node_return(self): ctx = mock.Mock(fence_node=None) main.fence_node(ctx) - @mock.patch('sys.exit') @mock.patch('preflight_check.task.TaskFence') - def test_fence_node(self, mock_task_fence, mock_exit): + def test_fence_node(self, mock_task_fence): mock_task_fence_inst = mock.Mock() mock_task_fence.return_value = mock_task_fence_inst mock_task_fence_inst.wait.side_effect = task.TaskError("error data") ctx = mock.Mock(fence_node=True) - main.fence_node(ctx) + with self.assertRaises(crmshutils.TerminateSubCommand): + main.fence_node(ctx) mock_task_fence_inst.pre_check.assert_called_once_with() mock_task_fence_inst.print_header.assert_called_once_with() mock_task_fence_inst.run.assert_called_once_with() mock_task_fence_inst.wait.assert_called_once_with() mock_task_fence_inst.error.assert_called_once_with("error data") - mock_exit.assert_called_once_with(1) - def test_MyArgParseFormatter(self): - inst = main.MyArgParseFormatter("test") + @classmethod + def test_MyArgParseFormatter(cls): + main.MyArgParseFormatter("test") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_task.py new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_task.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_task.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_task.py 2021-02-19 02:48:25.000000000 +0100 @@ -1,19 +1,18 @@ import os import sys -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -import unittest try: - from unittest import mock + from unittest import mock, TestCase except ImportError: import mock -import logging from datetime import datetime +from crmsh import utils as crmshutils +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from preflight_check import utils, main, config, task -class TestTaskKill(unittest.TestCase): +class TestTaskKill(TestCase): @classmethod def setUpClass(cls): @@ -47,7 +46,7 @@ mock_isdir.return_value = False main.ctx = mock.Mock(report_path="/path") with self.assertRaises(task.TaskError) as error: - self.task_kill_inst.enable_report() + self.task_kill_inst.enable_report() self.assertEqual("/path is not a directory", str(error.exception)) mock_isdir.assert_called_once_with("/path") @@ -104,7 +103,7 @@ file_handle = mock_open_file.return_value.__enter__.return_value self.task_kill_inst.to_report() - + file_handle.write.assert_has_calls([ mock.call("#### header"), mock.call("\nLog:\n"), @@ -196,7 +195,7 @@ mock_sleep.assert_called_once_with(1) -class TestTaskCheck(unittest.TestCase): +class TestTaskCheck(TestCase): @classmethod def setUpClass(cls): @@ -299,7 +298,7 @@ self.task_check_inst.print_result.assert_called_once_with() -class TestTaskSplitBrain(unittest.TestCase): +class TestTaskSplitBrain(TestCase): @classmethod def setUpClass(cls): @@ -494,7 +493,7 @@ self.task_sp_inst.thread_stop_event.set.assert_called_once_with() -class TestFence(unittest.TestCase): +class TestFence(TestCase): @classmethod def setUpClass(cls): @@ -507,7 +506,7 @@ """ Test setUp. """ - ctx = mock.Mock(fence_node="node1", yes=False) + ctx = mock.Mock(fence_node="node1", yes=False) self.task_fence_inst = task.TaskFence(ctx) self.task_fence_inst.fence_action = "reboot" self.task_fence_inst.fence_timeout = 60 @@ -610,7 +609,7 @@ self.task_fence_inst.fence_finish_event.wait.assert_called_once_with(60) -class TestTask(unittest.TestCase): +class TestTask(TestCase): @classmethod def setUpClass(cls): @@ -707,26 +706,23 @@ def test_build_base_result(self): self.task_inst.build_base_result() expected_result = { - "Timestamp": self.task_inst.timestamp, - "Description": self.task_inst.description, - "Messages": [] - } + "Timestamp": self.task_inst.timestamp, + "Description": self.task_inst.description, + "Messages": [] + } self.assertDictEqual(expected_result, self.task_inst.result) - @mock.patch('sys.exit') @mock.patch('preflight_check.task.crmshutils.ask') - def test_print_header(self, mock_ask, mock_exit): + def test_print_header(self, mock_ask): self.task_inst.header = mock.Mock() self.task_inst.info = mock.Mock() mock_ask.return_value = False - mock_exit.side_effect = SystemExit - with self.assertRaises(SystemExit): + with self.assertRaises(crmshutils.TerminateSubCommand): self.task_inst.print_header() self.task_inst.header.assert_called_once_with() mock_ask.assert_called_once_with("Run?") - mock_exit.assert_called_once_with() self.task_inst.info.assert_called_once_with("Testcase cancelled") @mock.patch('preflight_check.utils.str_to_datetime') @@ -743,9 +739,9 @@ mock_run.side_effect = [(1, None, None), (0, output, None), (1, None, None), (0, output2, None)] self.task_inst.timestamp = "2021/01/19 16:08:24" mock_datetime.side_effect = [ - datetime.strptime(self.task_inst.timestamp, '%Y/%m/%d %H:%M:%S'), - datetime.strptime("Tue Jan 19 16:08:37 2021", '%a %b %d %H:%M:%S %Y') - ] + datetime.strptime(self.task_inst.timestamp, '%Y/%m/%d %H:%M:%S'), + datetime.strptime("Tue Jan 19 16:08:37 2021", '%a %b %d %H:%M:%S %Y') + ] self.task_inst.fence_action_monitor() @@ -767,3 +763,74 @@ ]) self.task_inst.fence_start_event.set.assert_called_once_with() self.task_inst.fence_finish_event.set.assert_called_once_with() + +class TestFixSBD(TestCase): + """ + Class to test TaskFixSBD of task.py + All tested in test_preflight_check.py except verify() + """ + + @mock.patch('builtins.open') + @mock.patch('os.path.isfile') + @mock.patch('tempfile.mkstemp') + @mock.patch('preflight_check.utils.msg_info') + def setUp(self, mock_msg_info, mock_mkstemp, mock_isfile, mock_open): + """ + Test setUp. + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + bak = "/tmp/tmpmby3ty9g" + edit = "/tmp/tmpnic4t30s" + mock_isfile.return_value = True + mock_open.return_value = mock.mock_open(read_data="SBD_DEVICE={}". + format(dev)).return_value + mock_mkstemp.side_effect = [(1, bak), (2, edit)] + + self.task_fixsbd = task.TaskFixSBD(dev, yes=False) + mock_msg_info.assert_called_once_with('Replace SBD_DEVICE with candidate {}'. + format(dev), to_stdout=False) + + def tearDown(self): + """ + Test tearDown. + """ + pass + + @mock.patch('os.fsync') + @mock.patch('builtins.open') + @mock.patch('os.path.isfile') + @mock.patch('preflight_check.utils.msg_info') + def test_verify_succeed(self, mock_msg_info, mock_isfile, mock_open, mock_fsync): + """ + Test verify successful. + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + mock_isfile.return_value = True + mock_open.return_value = mock.mock_open(read_data="SBD_DEVICE={}". + format(dev)).return_value + self.task_fixsbd.prev_task_list = [] + + self.task_fixsbd.verify() + mock_isfile.assert_called_once_with(config.SBD_CONF) + mock_msg_info.assert_called_once_with('SBD DEVICE change succeed', + to_stdout=True) + mock_fsync.assert_called() + + @mock.patch('builtins.open') + @mock.patch('os.path.isfile') + def test_verify_fail(self, mock_isfile, mock_open): + """ + Test verify failed. + """ + dev = "/dev/disk/by-id/scsi-SATA_ST2000LM007-1R81_WDZ5J42A" + dev_cur = "/dev/disk/by-id/scsi-SATA_ST2000LM007-no_change" + mock_isfile.return_value = True + mock_open.return_value = mock.mock_open(read_data="SBD_DEVICE={}". + format(dev_cur)).return_value + self.task_fixsbd.prev_task_list = [] + + with self.assertRaises(task.TaskError) as err: + self.task_fixsbd.verify() + mock_isfile.assert_called_once_with(config.SBD_CONF) + self.assertEqual("Fail to replace SBD device {} in {}!". + format(dev, config.SBD_CONF), str(err.exception)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_utils.py new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_utils.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_preflight_utils.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_preflight_utils.py 2021-02-19 02:48:25.000000000 +0100 @@ -2,17 +2,17 @@ import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -import unittest try: - from unittest import mock + from unittest import mock, TestCase except ImportError: import mock import logging +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from preflight_check import utils, main, config -class TestMyLoggingFormatter(unittest.TestCase): +class TestMyLoggingFormatter(TestCase): @classmethod def setUpClass(cls): @@ -38,7 +38,7 @@ """ -class TestFenceInfo(unittest.TestCase): +class TestFenceInfo(TestCase): @classmethod def setUpClass(cls): @@ -108,7 +108,7 @@ mock_get_property.assert_called_once_with("stonith-timeout") -class TestUtils(unittest.TestCase): +class TestUtils(TestCase): ''' Unitary tests for preflight_check/utils.py ''' @@ -270,13 +270,13 @@ mock_open_read_1 = mock.mock_open(read_data=b'/usr/sbin/cmd1\x00--user\x00') mock_open_read_2 = mock.mock_open(read_data=b'/usr/sbin/cmd2\x00') mock_open_file.side_effect = [ - mock_open_read_1.return_value, - mock_open_read_2.return_value - ] + mock_open_read_1.return_value, + mock_open_read_2.return_value + ] mock_to_ascii.side_effect = [ - "/usr/sbin/cmd1\x00--user\x00", - "/usr/sbin/cmd2\x00" - ] + "/usr/sbin/cmd1\x00--user\x00", + "/usr/sbin/cmd2\x00" + ] mock_basename.side_effect = ["cmd1", "cmd2"] rc, pid = utils.get_process_status("sbd") @@ -308,13 +308,13 @@ mock_open_read_1 = mock.mock_open(read_data=b'/usr/sbin/cmd1\x00--user\x00') mock_open_read_2 = mock.mock_open(read_data=b'/usr/sbin/sbd\x00') mock_open_file.side_effect = [ - mock_open_read_1.return_value, - mock_open_read_2.return_value - ] + mock_open_read_1.return_value, + mock_open_read_2.return_value + ] mock_to_ascii.side_effect = [ - "/usr/sbin/cmd1\x00--user\x00", - "/usr/sbin/sbd\x00" - ] + "/usr/sbin/cmd1\x00--user\x00", + "/usr/sbin/sbd\x00" + ] mock_basename.side_effect = ["cmd1", "sbd"] rc, pid = utils.get_process_status("sbd") @@ -411,3 +411,145 @@ res = utils.peer_node_list() self.assertEqual(res, ["node2"]) mock_online.assert_called_once_with() + + # Test is_valid_sbd(): + @classmethod + @mock.patch('os.path.exists') + def test_is_valid_sbd_not_exist(cls, mock_os_path_exists): + """ + Test device not exist + """ + dev = "/dev/disk/by-id/scsi-device1" + mock_os_path_exists.return_value = False + + res = utils.is_valid_sbd(dev) + assert res is False + + @classmethod + @mock.patch('preflight_check.utils.msg_error') + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('os.path.exists') + def test_is_valid_sbd_cmd_error(cls, mock_os_path_exists, + mock_sbd_check_header, mock_msg_err): + """ + Test device is not valid sbd + """ + dev = "/dev/disk/by-id/scsi-device1" + mock_os_path_exists.return_value = True + mock_sbd_check_header.return_value = (-1, None, "Unknown error!") + mock_msg_err.return_value = "" + + res = utils.is_valid_sbd(dev) + mock_msg_err.assert_called_once_with("Unknown error!") + assert res is False + + @classmethod + @mock.patch('preflight_check.utils.msg_error') + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('os.path.exists') + def test_is_valid_sbd_not_sbd(cls, mock_os_path_exists, + mock_sbd_check_header, mock_msg_err): + """ + Test device is not SBD device + """ + dev = "/dev/disk/by-id/scsi-device1" + err_output = """ +==Dumping header on disk {} +==Header on disk {} NOT dumped +sbd failed; please check the logs. +""".format(dev, dev) + mock_os_path_exists.return_value = True + mock_sbd_check_header.return_value = (1, "==Dumping header on disk {}".format(dev), + err_output) + + res = utils.is_valid_sbd(dev) + assert res is False + mock_msg_err.assert_called_once_with(err_output) + + @classmethod + @mock.patch('crmsh.utils.get_stdout_stderr') + @mock.patch('os.path.exists') + def test_is_valid_sbd_is_sbd(cls, mock_os_path_exists, + mock_sbd_check_header): + """ + Test device is not SBD device + """ + dev = "/dev/disk/by-id/scsi-device1" + std_output = """ +==Dumping header on disk {} +Header version : 2.1 +UUID : f4c99362-6522-46fc-8ce4-7db60aff19bb +Number of slots : 255 +Sector size : 512 +Timeout (watchdog) : 5 +Timeout (allocate) : 2 +Timeout (loop) : 1 +Timeout (msgwait) : 10 +==Header on disk {} is dumped +""".format(dev, dev) + mock_os_path_exists.return_value = True + mock_sbd_check_header.return_value = (0, std_output, None) + + res = utils.is_valid_sbd(dev) + assert res is True + + # Test find_candidate_sbd() and _find_match_count() + @classmethod + @mock.patch('glob.glob') + @mock.patch('os.path.basename') + @mock.patch('os.path.dirname') + def test_find_candidate_no_dev(cls, mock_os_path_dname, mock_os_path_bname, + mock_glob): + """ + Test no suitable device + """ + mock_os_path_dname.return_value = "/dev/disk/by-id" + mock_os_path_bname.return_value = "scsi-label_CN_devA" + mock_glob.return_value = [] + + res = utils.find_candidate_sbd("/not-exist-folder/not-exist-dev") + assert res == "" + + @classmethod + @mock.patch('preflight_check.utils.is_valid_sbd') + @mock.patch('glob.glob') + @mock.patch('os.path.basename') + @mock.patch('os.path.dirname') + def test_find_candidate_no_can(cls, mock_os_path_dname, mock_os_path_bname, + mock_glob, mock_is_valid_sbd): + """ + Test no valid candidate device + """ + mock_os_path_dname.return_value = "/dev/disk/by-id" + mock_os_path_bname.return_value = "scsi-label_CN_devA" + mock_glob.return_value = ["/dev/disk/by-id/scsi-label_DE_devA", + "/dev/disk/by-id/scsi-label_DE_devB", + "/dev/disk/by-id/scsi-label_DE_devC", + "/dev/disk/by-id/scsi-label_DE_devD"] + mock_is_valid_sbd.side_effect = [False, False, False, False] + + res = utils.find_candidate_sbd("/dev/disk/by-id/scsi-label_CN_devA") + assert res == "" + + @classmethod + @mock.patch('preflight_check.utils.is_valid_sbd') + @mock.patch('glob.glob') + @mock.patch('os.path.basename') + @mock.patch('os.path.dirname') + def test_find_candidate_has_multi(cls, mock_os_path_dname, mock_os_path_bname, + mock_glob, mock_is_valid_sbd): + """ + Test has multiple valid candidate devices + """ + mock_os_path_dname.return_value = "/dev/disk/by-id" + mock_os_path_bname.return_value = "scsi-label_CN_devA" + mock_glob.return_value = ["/dev/disk/by-id/scsi-label_DE_devA", + "/dev/disk/by-id/scsi-label_DE_devB", + "/dev/disk/by-id/scsi-label_CN_devC", + "/dev/disk/by-id/scsi-label_CN_devD", + "/dev/disk/by-id/scsi-mp_China_devE", + "/dev/disk/by-id/scsi-mp_China_devF"] + mock_is_valid_sbd.side_effect = [True, False, False, True, True, False] + + res = utils.find_candidate_sbd("/dev/disk/by-id/scsi-label_CN_devA") + assert res == "/dev/disk/by-id/scsi-label_CN_devD" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_report.py new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_report.py --- old/crmsh-4.2.0+git.1611201540.a1006e39/test/unittests/test_report.py 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/test/unittests/test_report.py 2021-02-19 02:48:25.000000000 +0100 @@ -357,6 +357,20 @@ mock.call("cat /proc/10002/stack") ]) + +@mock.patch('crmsh.utils.get_stdout_stderr') +def test_lsof_ocfs2_device(mock_run): + output = "\n/dev/sdb1 on /data type ocfs2 (rw,relatime,heartbeat=none)" + output_lsof = "lsof data" + mock_run.side_effect = [(0, output, None), (0, output_lsof, None)] + expected = "\n\n#=====[ Command ] ==========================#\n# lsof /dev/sdb1\nlsof data" + assert hb_report.utillib.lsof_ocfs2_device() == expected + mock_run.assert_has_calls([ + mock.call("mount"), + mock.call("lsof /dev/sdb1") + ]) + + @mock.patch('hb_report.utillib.constants') def test_sub_sensitive_string(mock_const): data = """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.2.0+git.1611201540.a1006e39/tox.ini new/crmsh-4.3.0+git.20210219.811c32f0/tox.ini --- old/crmsh-4.2.0+git.1611201540.a1006e39/tox.ini 2021-01-21 04:59:00.000000000 +0100 +++ new/crmsh-4.3.0+git.20210219.811c32f0/tox.ini 2021-02-19 02:48:25.000000000 +0100 @@ -14,7 +14,7 @@ {[base]deps} commands = - py.test -vv --cov=crmsh --cov=preflight_check --cov-config .coveragerc --cov-report term --cov-report html --cov-report html {posargs} + py.test -vv --cov=crmsh --cov=preflight_check --cov-config .coveragerc --cov-report term --cov-report html {posargs} [testenv:py38-codeclimate] passenv = TRAVIS TRAVIS_*