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_*

Reply via email to