Hello community,

here is the log from the commit of package crmsh for openSUSE:Leap:15.2 checked 
in at 2020-03-15 07:11:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/crmsh (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.crmsh.new.3160 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Sun Mar 15 07:11:49 2020 rev:70 rq:784743 version:4.2.0+git.1584013187.b45cfcb6

Changes:
--------
--- /work/SRC/openSUSE:Leap:15.2/crmsh/crmsh.changes    2020-02-11 
23:27:28.125333734 +0100
+++ /work/SRC/openSUSE:Leap:15.2/.crmsh.new.3160/crmsh.changes  2020-03-15 
07:11:54.304982884 +0100
@@ -1,0 +2,10 @@
+Fri Mar 13 12:27:32 UTC 2020 - [email protected]
+
+- Update to version 4.2.0+git.1584013187.b45cfcb6:
+  * Fix: crmsh.spec.in: enable completion of crm command(bsc#1166329)
+  * Low: crmsh.spec.in: sync contents from NHF's crmsh.spec file
+  * Low: corosync: check whether local ip has already configured
+  * Low: bootstrap: check whether init node is online while joining
+  * Low: bootstrap: for udpu, don't check join node's ip was in the same 
network
+
+-------------------------------------------------------------------

Old:
----
  crmsh-4.2.0+git.1580544897.c42c9530.tar.bz2

New:
----
  crmsh-4.2.0+git.1584013187.b45cfcb6.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.Q8VDgZ/_old  2020-03-15 07:11:54.704983123 +0100
+++ /var/tmp/diff_new_pack.Q8VDgZ/_new  2020-03-15 07:11:54.708983126 +0100
@@ -36,7 +36,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0-or-later
 Group:          %{pkg_group}
-Version:        4.2.0+git.1580544897.c42c9530
+Version:        4.2.0+git.1584013187.b45cfcb6
 Release:        0
 Url:            http://crmsh.github.io
 Source0:        %{name}-%{version}.tar.bz2
@@ -106,7 +106,7 @@
 Requires(post):  mailx
 Requires(post):  procps
 Requires(post):  python3-python-dateutil
-Requires(post):  python3-nose
+Requires(post):  python3-tox
 Requires(post):  python3-parallax
 Requires(post):  pacemaker
 %if 0%{?suse_version} > 1110
@@ -160,7 +160,7 @@
 make %{_smp_mflags} VERSION="%{version}" sysconfdir=%{_sysconfdir} 
localstatedir=%{_var}
 
 %if %{with regression_tests}
-./test/run --quiet
+tox
 if [ ! $? ]; then
     echo "Unit tests failed."
     exit 1
@@ -169,7 +169,7 @@
 
 %install
 make DESTDIR=%{buildroot} docdir=%{crmsh_docdir} install
-install -Dm0644 contrib/bash_completion.sh 
%{buildroot}%{_datadir}/bash-completion/completions/crm.sh
+install -Dm0644 contrib/bash_completion.sh 
%{buildroot}%{_datadir}/bash-completion/completions/crm
 if [ -f %{buildroot}%{_bindir}/crm ]; then
        install -Dm0755 %{buildroot}%{_bindir}/crm %{buildroot}%{_sbindir}/crm
        rm %{buildroot}%{_bindir}/crm
@@ -182,7 +182,7 @@
 # Run regression tests after installing the package
 # NB: this is called twice by OBS, that's why we touch the file
 %post test
-testfile=/tmp/.crmsh_regression_tests_ran
+testfile=`mktemp -t .crmsh_regression_tests_ran_XXXXXX`
 # check if time in file is less than 2 minutes ago
 if [ -e $testfile ] && [ "$(( $(date +%s) - $(cat $testfile) ))" -lt 120 ]; 
then
        echo "Skipping regression tests..."
@@ -226,7 +226,7 @@
 %dir %{crmsh_docdir}
 %dir %{crmsh_docdir}/contrib
 %dir %attr (770, %{uname}, %{gname}) %{_var}/cache/crm
-%{_datadir}/bash-completion/completions/crm.sh
+%{_datadir}/bash-completion/completions/crm
 
 %files scripts
 %defattr(-,root,root)

++++++ _service ++++++
--- /var/tmp/diff_new_pack.Q8VDgZ/_old  2020-03-15 07:11:54.740983145 +0100
+++ /var/tmp/diff_new_pack.Q8VDgZ/_new  2020-03-15 07:11:54.740983145 +0100
@@ -4,7 +4,7 @@
     <param name="scm">git</param>
     <param name="filename">crmsh</param>
     <param name="versionformat">4.2.0+git.%ct.%h</param>
-    <param name="revision">master</param>
+    <param name="revision">b45cfcb6</param>
     <param name="changesgenerate">enable</param>
   </service>
 

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.Q8VDgZ/_old  2020-03-15 07:11:54.756983154 +0100
+++ /var/tmp/diff_new_pack.Q8VDgZ/_new  2020-03-15 07:11:54.756983154 +0100
@@ -5,4 +5,4 @@
                 <param 
name="url">https://github.com/liangxin1300/crmsh.git</param>
               <param 
name="changesrevision">d8dc51b4cb34964aa72e918999ebc7f03b48f3c9</param></service><service
 name="tar_scm">
                 <param 
name="url">https://github.com/ClusterLabs/crmsh.git</param>
-              <param 
name="changesrevision">a8fc76646a14dfcf7b34e62303d2195ef96c3daa</param></service></servicedata>
\ No newline at end of file
+              <param 
name="changesrevision">b256772e34cde162c8245581097d13070ae51254</param></service></servicedata>
\ No newline at end of file

++++++ crmsh-4.2.0+git.1580544897.c42c9530.tar.bz2 -> 
crmsh-4.2.0+git.1584013187.b45cfcb6.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.2.0+git.1580544897.c42c9530/configure.ac 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/configure.ac
--- old/crmsh-4.2.0+git.1580544897.c42c9530/configure.ac        2020-02-01 
09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/configure.ac        2020-03-12 
12:39:47.000000000 +0100
@@ -8,7 +8,7 @@
 
 AC_PREREQ([2.53])
 
-AC_INIT([crmsh],[4.0.0],[[email protected]])
+AC_INIT([crmsh],[4.2.0],[[email protected]])
 
 AC_ARG_WITH(version,
     [  --with-version=version   Override package version (if you're a packager 
needing to pretend) ],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh/bootstrap.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh/bootstrap.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh/bootstrap.py  2020-02-01 
09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh/bootstrap.py  2020-03-12 
12:39:47.000000000 +0100
@@ -18,6 +18,7 @@
 import re
 import time
 import readline
+import shutil
 from string import Template
 from lxml import etree
 from . import config
@@ -38,6 +39,7 @@
 SYSCONFIG_FW = "/etc/sysconfig/SuSEfirewall2"
 SYSCONFIG_FW_CLUSTER = "/etc/sysconfig/SuSEfirewall2.d/services/cluster"
 PCMK_REMOTE_AUTH = "/etc/pacemaker/authkey"
+COROSYNC_CONF_ORIG = tmpfiles.create()[1]
 
 
 INIT_STAGES = ("ssh", "ssh_remote", "csync2", "csync2_remote", "corosync", 
"storage", "sbd", "cluster", "vgfs", "admin", "qdevice")
@@ -228,13 +230,53 @@
     status_long("Waiting for cluster")
     while True:
         _rc, out, _err = utils.get_stdout_stderr("crm_mon -1")
-        if "online" in out.lower():
+        if is_online(out):
             break
         status_progress()
-        sleep(5)
+        sleep(2)
     status_done()
 
 
+def get_cluster_node_hostname():
+    """
+    Get the hostname of the cluster node used during the join process if an IP 
address is used.
+    """
+    peer_node = None
+    if _context.cluster_node:
+        if utils.valid_ip_addr(_context.cluster_node):
+            rc, out, err = utils.get_stdout_stderr("ssh {} crm_node 
--name".format(_context.cluster_node))
+            if rc != 0:
+                error(err)
+            peer_node = out
+        else:
+            peer_node = _context.cluster_node
+    return peer_node
+
+
+def is_online(crm_mon_txt):
+    """
+    Check whether local node is online
+    Besides that, in join process, check whether init node is online
+    """
+    if not re.search("Online: .* {} ".format(utils.this_node()), crm_mon_txt):
+        return False
+
+    # if peer_node is None, this is in the init process
+    peer_node = get_cluster_node_hostname()
+    if peer_node is None:
+        return True
+    # In join process
+    # If the joining node is already online but can't find the init node
+    # The communication IP maybe mis-configured
+    if not re.search("Online: .* {} ".format(peer_node), crm_mon_txt):
+        shutil.copy(COROSYNC_CONF_ORIG, corosync.conf())
+        csync2_update(corosync.conf())
+        stop_service("corosync")
+        print()
+        error("Cannot see peer node \"{}\", please check the communication 
IP".format(peer_node))
+    return True
+
+
 def pick_default_value(default_list, prev_list):
     """
     Provide default value for function 'prompt_for_string'.
@@ -1846,6 +1888,8 @@
         else:
             corosync.set_value("totem.nodeid", nodeid)
 
+    shutil.copy(corosync.conf(), COROSYNC_CONF_ORIG)
+
     # check if use IPv6
     ipv6_flag = False
     ipv6 = corosync.get_value("totem.ip_version")
@@ -1909,21 +1953,6 @@
                                               ringXaddr_res)
                 if not ringXaddr:
                     error("No value for ring{}".format(i))
-
-                # this check does not work on GCP (bsc#1106946)
-                if utils.detect_cloud() != "google-cloud-platform":
-                    _, outp = utils.get_stdout("ip addr show")
-                    tmp = re.findall(r' {}/[0-9]+ '.format(ringXaddr), outp, 
re.M)[0].strip()
-                    peer_ip = 
corosync.get_value("nodelist.node.ring{}_addr".format(i))
-                    # peer ring0_addr and local ring0_addr must be configured 
in the same network
-                    if not utils.ip_in_network(peer_ip, tmp):
-                        errmsg = "    Peer IP {} is not in the same network: 
{}".format(peer_ip, tmp)
-                        if _context.yes_to_all:
-                            error(errmsg)
-                        else:
-                            print(term.render(clidisplay.error(errmsg)))
-                            continue
-
                 ringXaddr_res.append(ringXaddr)
                 break
             if not rrp_flag:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh/corosync.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh/corosync.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh/corosync.py   2020-02-01 
09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh/corosync.py   2020-03-12 
12:39:47.000000000 +0100
@@ -811,24 +811,47 @@
     pass
 
 
-def add_node_ucast(IParray, node_id=None):
+def find_configured_ip(ip_list):
+    """
+    find if the same IP already configured
+    If so, raise IPAlreadyConfiguredError
+    """
+    with open(conf()) as f:
+        p = Parser(f.read())
+
+    # get exist ip list from corosync.conf
+    corosync_iplist = []
+    for path in set(p.all_paths()):
+        if re.search('nodelist.node.ring[0-9]*_addr', path):
+            corosync_iplist.extend(p.get_all(path))
+
+    # all_possible_ip is a ip set to check whether one of them already 
configured
+    all_possible_ip = set(ip_list)
+    # get local ip list
+    local_ip_list = utils.ip_in_local(utils.is_ipv6(ip_list[0]))
+    # extend all_possible_ip if ip_list contain local ip
+    # to avoid this scenarios in join node:
+    #   eth0's ip already configured in corosync.conf
+    #   eth1's ip also want to add in nodelist
+    # if this scenarios happened, raise IPAlreadyConfiguredError
+    if bool(set(ip_list) & set(local_ip_list)):
+        all_possible_ip |= set(local_ip_list)
+    configured_ip = list(all_possible_ip & set(corosync_iplist))
+    if configured_ip:
+        raise IPAlreadyConfiguredError("IP {} was already 
configured".format(','.join(configured_ip)))
+
+
+def add_node_ucast(ip_list, node_id=None):
+
+    find_configured_ip(ip_list)
 
     f = open(conf()).read()
     p = Parser(f)
 
-    # to check if the same IP already configured
-    exist_iplist = []
-    for path in p.all_paths():
-        if re.search('nodelist.node.ring[0-9]*_addr', path):
-            exist_iplist.extend(p.get_all(path))
-    for ip in IParray:
-        if ip in exist_iplist:
-            raise IPAlreadyConfiguredError("IP {} was already 
configured".format(ip))
-
     if node_id is None:
         node_id = get_free_nodeid(p)
     node_value = []
-    for i, addr in enumerate(IParray):
+    for i, addr in enumerate(ip_list):
         node_value += make_value('nodelist.node.ring{}_addr'.format(i), addr)
     node_value += make_value('nodelist.node.nodeid', str(node_id))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh/utils.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh/utils.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh/utils.py      2020-02-01 
09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh/utils.py      2020-03-12 
12:39:47.000000000 +0100
@@ -2083,6 +2083,11 @@
     def version(self):
         return self.addr.version
 
+
+def is_ipv6(addr):
+    return IP(addr).version() == 6
+
+
 # Set by detect_cloud() or iplist_for_cloud()
 # to avoid multiple requests
 _ip_for_cloud = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh.spec.in 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh.spec.in
--- old/crmsh-4.2.0+git.1580544897.c42c9530/crmsh.spec.in       2020-02-01 
09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/crmsh.spec.in       2020-03-12 
12:39:47.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package crmsh
 #
-# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,7 +12,7 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
@@ -34,7 +34,7 @@
 
 Name:           crmsh
 Summary:        High Availability cluster command-line interface
-License:        GPL-2.0+
+License:        GPL-2.0-or-later
 Group:          %{pkg_group}
 Version:        @VERSION@
 Release:        0
@@ -49,14 +49,10 @@
 %endif
 Requires:       %{name}-scripts >= %{version}-%{release}
 Requires:       /usr/bin/which
-Requires:       python3 >= 3.6
+Requires:       python3 >= 3.4
 Requires:       python3-lxml
-Requires:       python3-python-dateutil
-%if 0%{?suse_version} > 1320
 Requires:       python3-parallax
-%else
-Requires:       python-parallax
-%endif
+Requires:       python3-python-dateutil
 BuildRequires:  python3-lxml
 BuildRequires:  python3-setuptools
 
@@ -111,11 +107,7 @@
 Requires(post):  procps
 Requires(post):  python3-python-dateutil
 Requires(post):  python3-tox
-%if 0%{?suse_version} > 1320
 Requires(post):  python3-parallax
-%else
-Requires(post):  python-parallax
-%endif
 Requires(post):  pacemaker
 %if 0%{?suse_version} > 1110
 BuildArch:      noarch
@@ -150,23 +142,12 @@
 
 %prep
 %setup -q
-touch -r doc/crm.8.adoc{,.timestamp}
 
 # replace the shebang in all the scripts
 # with ${_bindir}/python3
 find . -type f -exec perl -pi -e 'BEGIN{undef 
$/};s[^#\!/usr/bin/python[3]?][#\!%{_bindir}/python3]' {} \;
 find . -type f -exec perl -pi -e 'BEGIN{undef $/};s[^#\!/usr/bin/env 
python[3]?][#\!%{_bindir}/python3]' {} \;
 
-# Force the local time
-#
-# 'hg archive' sets the file date to the date of the last commit.
-# This can result in files having been created in the future
-# when building on machines in timezones 'behind' the one the
-# commit occurred in - which seriously confuses 'make'
-find . -mtime -0 -exec touch \{\} \;
-# keep the .adoc mtime because it gets embedded in crm.8.html
-touch -r doc/crm.8.adoc{.timestamp,}
-
 %build
 ./autogen.sh
 
@@ -179,7 +160,7 @@
 make %{_smp_mflags} VERSION="%{version}" sysconfdir=%{_sysconfdir} 
localstatedir=%{_var}
 
 %if %{with regression_tests}
-tox -q
+tox
 if [ ! $? ]; then
     echo "Unit tests failed."
     exit 1
@@ -188,7 +169,7 @@
 
 %install
 make DESTDIR=%{buildroot} docdir=%{crmsh_docdir} install
-install -Dm0644 contrib/bash_completion.sh 
%{buildroot}%{_sysconfdir}/bash_completion.d/crm.sh
+install -Dm0644 contrib/bash_completion.sh 
%{buildroot}%{_datadir}/bash-completion/completions/crm
 if [ -f %{buildroot}%{_bindir}/crm ]; then
        install -Dm0755 %{buildroot}%{_bindir}/crm %{buildroot}%{_sbindir}/crm
        rm %{buildroot}%{_bindir}/crm
@@ -245,7 +226,7 @@
 %dir %{crmsh_docdir}
 %dir %{crmsh_docdir}/contrib
 %dir %attr (770, %{uname}, %{gname}) %{_var}/cache/crm
-%config %{_sysconfdir}/bash_completion.d/crm.sh
+%{_datadir}/bash-completion/completions/crm
 
 %files scripts
 %defattr(-,root,root)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.2.0+git.1580544897.c42c9530/setup.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/setup.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/setup.py    2020-02-01 
09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/setup.py    2020-03-12 
12:39:47.000000000 +0100
@@ -4,7 +4,7 @@
 from setuptools import setup
 
 setup(name='crmsh',
-      version='4.0.0',
+      version='4.2.0',
       description='Command-line interface for High-Availability cluster 
management',
       author='Kristoffer Gronlund',
       author_email='[email protected]',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/features/bootstrap_bugs.feature 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/features/bootstrap_bugs.feature
--- 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/features/bootstrap_bugs.feature    
    2020-02-01 09:14:57.000000000 +0100
+++ 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/features/bootstrap_bugs.feature    
    2020-03-12 12:39:47.000000000 +0100
@@ -33,3 +33,15 @@
     Then    Except "ERROR: cluster.geo_join: Space value not allowed for dest 
"node""
     When    Try "crm cluster geo-init-arbitrator -c ' '"
     Then    Except "ERROR: cluster.geo_init_arbitrator: Space value not 
allowed for dest "other""
+
+  @clean
+  Scenario: Setup cluster with crossed network(udpu only)
+    Given   Cluster service is "stopped" on "hanode1"
+    Given   Cluster service is "stopped" on "hanode2"
+    When    Run "crm cluster init -u -i eth0 -y --no-overwrite-sshkey" on 
"hanode1"
+    Then    Cluster service is "started" on "hanode1"
+    When    Try "crm cluster join -c hanode1 -i eth1 -y" on "hanode2"
+    Then    Cluster service is "stopped" on "hanode2"
+    And     Except "Cannot see peer node "hanode1", please check the 
communication IP" in stderr
+    When    Run "crm cluster join -c hanode1 -i eth0 -y" on "hanode2"
+    Then    Cluster service is "started" on "hanode2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/features/steps/step_implenment.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/features/steps/step_implenment.py
--- 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/features/steps/step_implenment.py  
    2020-02-01 09:14:57.000000000 +0100
+++ 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/features/steps/step_implenment.py  
    2020-03-12 12:39:47.000000000 +0100
@@ -35,6 +35,11 @@
     context.stdout = out
 
 
+@when('Try "{cmd}" on "{addr}"')
+def step_impl(context, cmd, addr):
+    run_command_local_or_remote(context, cmd, addr, err_record=True)
+
+
 @when('Try "{cmd}"')
 def step_impl(context, cmd):
     run_command(context, cmd, err_record=True)
@@ -69,6 +74,12 @@
     context.command_error_output = None
 
 
+@then('Except "{msg}" in stderr')
+def step_impl(context, msg):
+    assert msg in context.command_error_output
+    context.command_error_output = None
+
+
 @then('Cluster service is "{state}" on "{addr}"')
 def step_impl(context, state, addr):
     assert check_cluster_state(context, state, addr) is True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/features/steps/utils.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/features/steps/utils.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/test/features/steps/utils.py        
2020-02-01 09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/features/steps/utils.py        
2020-03-12 12:39:47.000000000 +0100
@@ -19,14 +19,17 @@
     return out
 
 
-def run_command_local_or_remote(context, cmd, addr):
+def run_command_local_or_remote(context, cmd, addr, err_record=False):
     if addr == me():
-        out = run_command(context, cmd)
+        out = run_command(context, cmd, err_record)
         return out
     else:
         try:
             results = parallax.parallax_call([addr], cmd)
         except ValueError as err:
+            if err_record:
+                context.command_error_output = str(err)
+                return
             context.logger.error("\n{}\n".format(err))
             context.failed = True
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/testcases/ra.exp 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/testcases/ra.exp
--- old/crmsh-4.2.0+git.1580544897.c42c9530/test/testcases/ra.exp       
2020-02-01 09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/testcases/ra.exp       
2020-03-12 12:39:47.000000000 +0100
@@ -9,16 +9,10 @@
 .EXT crm_resource --show-metadata ocf:pacemaker:Dummy
 Example stateless resource agent (ocf:pacemaker:Dummy)
 
-This is a Dummy Resource Agent. It does absolutely nothing except 
-keep track of whether its running or not.
-Its purpose in life is for testing and to serve as a template for RA writers.
-
-NB: Please pay attention to the timeouts specified in the actions
-section below. They should be meaningful for the kind of resource
-the agent manages. They should be the minimum advised timeouts,
-but they shouldn't/cannot cover _all_ possible resource
-instances. So, try to be neither overly generous nor too stingy,
-but moderate. The minimum timeouts should never be below 10 seconds.
+This is a dummy OCF resource agent. It does absolutely nothing except keep 
track
+of whether it is running or not, and can be configured so that actions fail or
+take a long time. Its purpose is primarily for testing, and to serve as a
+template for resource agent writers.
 
 Parameters (*: required, []: default):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/testcases/resource.exp 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/testcases/resource.exp
--- old/crmsh-4.2.0+git.1580544897.c42c9530/test/testcases/resource.exp 
2020-02-01 09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/testcases/resource.exp 
2020-03-12 12:39:47.000000000 +0100
@@ -914,7 +914,7 @@
 .TRY configure ms msg g
 .TRY resource scores
 .EXT crm_simulate -sUL
-3 of 6 resources DISABLED and 0 BLOCKED from being started due to failures
+2 of 6 resource instances DISABLED and 0 BLOCKED from further action due to 
failure
 
 Current cluster status:
 Node node1: UNCLEAN (offline)
@@ -930,24 +930,24 @@
 
 Allocation scores and utilization information:
 Original: node1 capacity:
-native_color: st allocation score on node1: 0
-clone_color: c1 allocation score on node1: 0
-clone_color: p1:0 allocation score on node1: 0
-native_color: p1:0 allocation score on node1: -INFINITY
-clone_color: m1 allocation score on node1: 0
-clone_color: p2:0 allocation score on node1: 0
-native_color: p2:0 allocation score on node1: -INFINITY
+pcmk__native_allocate: st allocation score on node1: 0
+pcmk__clone_allocate: c1 allocation score on node1: 0
+pcmk__clone_allocate: p1:0 allocation score on node1: 0
+pcmk__native_allocate: p1:0 allocation score on node1: -INFINITY
+pcmk__clone_allocate: m1 allocation score on node1: 0
+pcmk__clone_allocate: p2:0 allocation score on node1: 0
+pcmk__native_allocate: p2:0 allocation score on node1: -INFINITY
 p2:0 promotion score on none: 0
-native_color: p3 allocation score on node1: -INFINITY
-clone_color: msg allocation score on node1: 0
-clone_color: g:0 allocation score on node1: 0
-clone_color: p0:0 allocation score on node1: 0
-clone_color: p4:0 allocation score on node1: 0
-group_color: g:0 allocation score on node1: -INFINITY
-group_color: p0:0 allocation score on node1: -INFINITY
-group_color: p4:0 allocation score on node1: -INFINITY
-native_color: p0:0 allocation score on node1: -INFINITY
-native_color: p4:0 allocation score on node1: -INFINITY
+pcmk__native_allocate: p3 allocation score on node1: -INFINITY
+pcmk__clone_allocate: msg allocation score on node1: 0
+pcmk__clone_allocate: g:0 allocation score on node1: 0
+pcmk__clone_allocate: p0:0 allocation score on node1: 0
+pcmk__clone_allocate: p4:0 allocation score on node1: 0
+pcmk__group_allocate: g:0 allocation score on node1: -INFINITY
+pcmk__group_allocate: p0:0 allocation score on node1: -INFINITY
+pcmk__group_allocate: p4:0 allocation score on node1: -INFINITY
+pcmk__native_allocate: p0:0 allocation score on node1: -INFINITY
+pcmk__native_allocate: p4:0 allocation score on node1: -INFINITY
 g:0 promotion score on none: 0
 Remaining: node1 capacity:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/testcases/scripts.exp 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/testcases/scripts.exp
--- old/crmsh-4.2.0+git.1580544897.c42c9530/test/testcases/scripts.exp  
2020-02-01 09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/testcases/scripts.exp  
2020-03-12 12:39:47.000000000 +0100
@@ -287,7 +287,7 @@
 ** localhost - crm --wait --no configure load update <<temporary file>>
 OK: 10: Configure cluster resources
 .INP: json '["show", "mailto"]'
-{"category": "basic", "longdesc": "Notifies recipient by e-mail in the event 
of a resource takeover.", "name": "mailto", "shortdesc": "E-Mail", "steps": 
[{"longdesc": " This is a resource agent for MailTo. It sends email to a 
sysadmin\nwhenever  a takeover occurs.", "parameters": [{"advanced": false, 
"longdesc": "", "name": "id", "required": true, "shortdesc": "Identifier for 
the cluster resource", "type": "resource", "unique": true}, {"advanced": false, 
"example": "", "longdesc": " The email address of sysadmin.", "name": "email", 
"required": true, "shortdesc": "Email address", "type": "email", "unique": 
false}, {"advanced": false, "example": "", "longdesc": " The subject of the 
email.", "name": "subject", "required": false, "shortdesc": "Subject", "type": 
"string", "unique": false}], "required": true, "shortdesc": "Notifies 
recipients by email in the event of resource takeover"}]}
+{"category": "basic", "longdesc": "Notifies recipient by e-mail in the event 
of a resource takeover.", "name": "mailto", "shortdesc": "E-Mail", "steps": 
[{"longdesc": " This is a resource agent for MailTo. It sends email to a 
sysadmin\nwhenever  a takeover occurs.", "parameters": [{"advanced": false, 
"longdesc": "", "name": "id", "required": true, "shortdesc": "Identifier for 
the cluster resource", "type": "resource", "unique": true}, {"advanced": false, 
"example": "0", "longdesc": " The email address of sysadmin.", "name": "email", 
"required": true, "shortdesc": "Email address", "type": "email", "unique": 
false}, {"advanced": false, "example": "Resource Group", "longdesc": " The 
subject of the email.", "name": "subject", "required": false, "shortdesc": 
"Subject", "type": "string", "unique": false}], "required": true, "shortdesc": 
"Notifies recipients by email in the event of resource takeover"}]}
 .INP: json '["verify", "mailto", {"id":"foo", "email":"[email protected]", 
"subject":"hello"}]'
 {"longdesc": "", "name": "install", "nodes": "", "shortdesc": "Ensure mail 
package is installed", "text": "mailx"}
 {"longdesc": "", "name": "cib", "nodes": "", "shortdesc": "Configure cluster 
resources", "text": "primitive foo 
ocf:heartbeat:MailTo\n\temail=\"[email protected]\"\n\tsubject=\"hello\"\n\top 
start timeout=\"10\"\n\top stop timeout=\"10\"\n\top monitor interval=\"10\" 
timeout=\"10\"\n\nclone c-foo foo"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/unittests/test_bootstrap.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/unittests/test_bootstrap.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/test/unittests/test_bootstrap.py    
2020-02-01 09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/unittests/test_bootstrap.py    
2020-03-12 12:39:47.000000000 +0100
@@ -32,7 +32,6 @@
         Global setUp.
         """
 
-    #@mock.patch('crmsh.bootstrap.Context')
     def setUp(self):
         """
         Test setUp.
@@ -158,3 +157,128 @@
             mock.call(['10.10.10.1', '20.20.20.1'], '1'),
             mock.call(['10.10.10.2', '20.20.20.2'], '2')
         ])
+
+    @mock.patch('crmsh.utils.valid_ip_addr')
+    @mock.patch('crmsh.utils.get_stdout_stderr')
+    def test_get_cluster_node_hostname_None(self, mock_stdout_stderr, 
mock_valid_ip):
+        bootstrap._context = mock.Mock(cluster_node=None)
+
+        peer_node = bootstrap.get_cluster_node_hostname()
+        assert peer_node is None
+
+        mock_valid_ip.assert_not_called()
+        mock_stdout_stderr.assert_not_called()
+
+    @mock.patch('crmsh.utils.valid_ip_addr')
+    @mock.patch('crmsh.utils.get_stdout_stderr')
+    def test_get_cluster_node_hostname_IP(self, mock_stdout_stderr, 
mock_valid_ip):
+        bootstrap._context = mock.Mock(cluster_node="1.1.1.1")
+        mock_valid_ip.return_value = True
+        mock_stdout_stderr.return_value = (0, "node1", None)
+
+        peer_node = bootstrap.get_cluster_node_hostname()
+        assert peer_node == "node1"
+
+        mock_valid_ip.assert_called_once_with("1.1.1.1")
+        mock_stdout_stderr.assert_called_once_with("ssh 1.1.1.1 crm_node 
--name")
+
+    @mock.patch('crmsh.utils.valid_ip_addr')
+    @mock.patch('crmsh.utils.get_stdout_stderr')
+    def test_get_cluster_node_hostname_HOST(self, mock_stdout_stderr, 
mock_valid_ip):
+        bootstrap._context = mock.Mock(cluster_node="node2")
+        mock_valid_ip.return_value = False
+
+        peer_node = bootstrap.get_cluster_node_hostname()
+        assert peer_node == "node2"
+
+        mock_valid_ip.assert_called_once_with("node2")
+        mock_stdout_stderr.assert_not_called()
+
+    @mock.patch('crmsh.utils.this_node')
+    @mock.patch('re.search')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_hostname')
+    def test_is_online_local_offline(self, mock_get_peer, mock_search, 
mock_this_node):
+        mock_this_node.return_value = "node1"
+        mock_search.return_value = None
+
+        assert bootstrap.is_online("text") is False
+
+        mock_this_node.assert_called_once_with()
+        mock_get_peer.assert_not_called()
+        mock_search.assert_called_once_with("Online: .* node1 ", "text")
+
+    @mock.patch('crmsh.utils.this_node')
+    @mock.patch('re.search')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_hostname')
+    def test_is_online_on_init_node(self, mock_get_peer, mock_search, 
mock_this_node):
+        mock_search.return_value = mock.Mock()
+        mock_this_node.return_value = "node1"
+        mock_get_peer.return_value = None
+
+        assert bootstrap.is_online("text") is True
+
+        mock_this_node.assert_called_once_with()
+        mock_get_peer.assert_called_once_with()
+        mock_search.assert_called_once_with("Online: .* node1 ", "text")
+
+    @mock.patch('crmsh.bootstrap.error')
+    @mock.patch('crmsh.bootstrap.stop_service')
+    @mock.patch('crmsh.bootstrap.csync2_update')
+    @mock.patch('crmsh.corosync.conf')
+    @mock.patch('shutil.copy')
+    @mock.patch('crmsh.utils.this_node')
+    @mock.patch('re.search')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_hostname')
+    def test_is_online_peer_offline(self, mock_get_peer, mock_search, 
mock_this_node,
+            mock_copy, mock_corosync_conf, mock_csync2, mock_stop_service, 
mock_error):
+        bootstrap.COROSYNC_CONF_ORIG = "/tmp/crmsh_tmpfile"
+        mock_search.side_effect = [ mock.Mock(), None ]
+        mock_this_node.return_value = "node2"
+        mock_get_peer.return_value = "node1"
+        mock_corosync_conf.side_effect = [ "/etc/corosync/corosync.conf", 
+                "/etc/corosync/corosync.conf"]
+
+        bootstrap.is_online("text")
+
+        mock_this_node.assert_called_once_with()
+        mock_get_peer.assert_called_once_with()
+        mock_search.assert_has_calls([
+            mock.call("Online: .* node2 ", "text"),
+            mock.call("Online: .* node1 ", "text")
+            ])
+        mock_corosync_conf.assert_has_calls([
+            mock.call(),
+            mock.call()
+            ])
+        mock_copy.assert_called_once_with(bootstrap.COROSYNC_CONF_ORIG, 
"/etc/corosync/corosync.conf")
+        mock_csync2.assert_called_once_with("/etc/corosync/corosync.conf")
+        mock_stop_service.assert_called_once_with("corosync")
+        mock_error.assert_called_once_with("Cannot see peer node \"node1\", 
please check the communication IP")
+
+    @mock.patch('crmsh.bootstrap.error')
+    @mock.patch('crmsh.bootstrap.stop_service')
+    @mock.patch('crmsh.bootstrap.csync2_update')
+    @mock.patch('crmsh.corosync.conf')
+    @mock.patch('shutil.copy')
+    @mock.patch('crmsh.utils.this_node')
+    @mock.patch('re.search')
+    @mock.patch('crmsh.bootstrap.get_cluster_node_hostname')
+    def test_is_online_both_online(self, mock_get_peer, mock_search, 
mock_this_node,
+            mock_copy, mock_corosync_conf, mock_csync2, mock_stop_service, 
mock_error):
+        mock_search.side_effect = [ mock.Mock(), mock.Mock() ]
+        mock_this_node.return_value = "node2"
+        mock_get_peer.return_value = "node1"
+
+        assert bootstrap.is_online("text") is True
+
+        mock_this_node.assert_called_once_with()
+        mock_get_peer.assert_called_once_with()
+        mock_search.assert_has_calls([
+            mock.call("Online: .* node2 ", "text"),
+            mock.call("Online: .* node1 ", "text")
+            ])
+        mock_corosync_conf.assert_not_called()
+        mock_copy.assert_not_called()
+        mock_csync2.assert_not_called()
+        mock_stop_service.assert_not_called()
+        mock_error.assert_not_called()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.2.0+git.1580544897.c42c9530/test/unittests/test_corosync.py 
new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/unittests/test_corosync.py
--- old/crmsh-4.2.0+git.1580544897.c42c9530/test/unittests/test_corosync.py     
2020-02-01 09:14:57.000000000 +0100
+++ new/crmsh-4.2.0+git.1584013187.b45cfcb6/test/unittests/test_corosync.py     
2020-03-12 12:39:47.000000000 +0100
@@ -93,6 +93,60 @@
                            make_value('nodelist.node.nodeid', str(nid))))
         _valid(p)
         self.assertEqual(p.count('nodelist.node'), nid - 1)
+ 
+    @mock.patch("crmsh.utils.is_ipv6")
+    @mock.patch("crmsh.utils.ip_in_local")
+    @mock.patch("re.search")
+    @mock.patch("crmsh.corosync.Parser")
+    @mock.patch("crmsh.corosync.conf")
+    @mock.patch("builtins.open", new_callable=mock.mock_open, 
read_data="corosync conf data")
+    def test_find_configured_ip_no_exception(self, mock_open_file, mock_conf, 
mock_parser, mock_search, mock_ip_local, mock_isv6):
+        mock_conf.return_value = "/etc/corosync/corosync.conf"
+        mock_parser_inst = mock.Mock()
+        mock_parser.return_value = mock_parser_inst
+        mock_parser_inst.all_paths.return_value = ["nodelist.node.ring0_addr"]
+        mock_search.return_value = mock.Mock()
+        mock_parser_inst.get_all.return_value = ["10.10.10.1"]
+        mock_isv6.return_value = False
+        mock_ip_local.return_value = ["192.168.1.1", "10.10.10.2", 
"20.20.20.2"]
+
+        corosync.find_configured_ip(["10.10.10.2"])
+
+        mock_conf.assert_called_once_with()
+        mock_parser_inst.all_paths.assert_called_once_with()
+        
mock_parser_inst.get_all.assert_called_once_with("nodelist.node.ring0_addr")
+        mock_open_file.assert_called_once_with(mock_conf.return_value)
+        mock_isv6.assert_called_once_with("10.10.10.2")
+        mock_ip_local.assert_called_once_with(False)
+        mock_search.assert_called_once_with("nodelist.node.ring[0-9]*_addr", 
"nodelist.node.ring0_addr")
+
+    @mock.patch("crmsh.utils.is_ipv6")
+    @mock.patch("crmsh.utils.ip_in_local")
+    @mock.patch("re.search")
+    @mock.patch("crmsh.corosync.Parser")
+    @mock.patch("crmsh.corosync.conf")
+    @mock.patch("builtins.open", new_callable=mock.mock_open, 
read_data="corosync conf data")
+    def test_find_configured_ip_exception(self, mock_open_file, mock_conf, 
mock_parser, mock_search, mock_ip_local, mock_isv6):
+        mock_conf.return_value = "/etc/corosync/corosync.conf"
+        mock_parser_inst = mock.Mock()
+        mock_parser.return_value = mock_parser_inst
+        mock_parser_inst.all_paths.return_value = ["nodelist.node.ring0_addr"]
+        mock_search.return_value = mock.Mock()
+        mock_parser_inst.get_all.return_value = ["10.10.10.1", "10.10.10.2"]
+        mock_isv6.return_value = False
+        mock_ip_local.return_value = ["192.168.1.1", "10.10.10.2", 
"20.20.20.2"]
+
+        with self.assertRaises(corosync.IPAlreadyConfiguredError) as err:
+            corosync.find_configured_ip(["10.10.10.2"])
+        self.assertEqual("IP 10.10.10.2 was already configured", 
str(err.exception))
+
+        mock_conf.assert_called_once_with()
+        mock_parser_inst.all_paths.assert_called_once_with()
+        
mock_parser_inst.get_all.assert_called_once_with("nodelist.node.ring0_addr")
+        mock_open_file.assert_called_once_with(mock_conf.return_value)
+        mock_isv6.assert_called_once_with("10.10.10.2")
+        mock_ip_local.assert_called_once_with(False)
+        mock_search.assert_called_once_with("nodelist.node.ring[0-9]*_addr", 
"nodelist.node.ring0_addr")
 
     def test_add_node_ucast(self):
         from crmsh.corosync import add_node_ucast, get_values


Reply via email to