Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2019-11-11 13:01:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.2990 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Mon Nov 11 13:01:06 2019 rev:166 rq:747268 version:4.1.0+git.1573020742.a0b88227 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2019-11-06 13:56:49.444203017 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.2990/crmsh.changes 2019-11-11 13:01:07.965737490 +0100 @@ -1,0 +2,7 @@ +Wed Nov 06 06:17:48 UTC 2019 - [email protected] + +- Update to version 4.1.0+git.1573020742.a0b88227: + * Test: unittest: test Parallax class + * Dev: parallax: create class Parallax to simplify using parallax + +------------------------------------------------------------------- Old: ---- crmsh-4.1.0+git.1572504697.472361c5.tar.bz2 New: ---- crmsh-4.1.0+git.1573020742.a0b88227.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.IAcYTa/_old 2019-11-11 13:01:09.213738812 +0100 +++ /var/tmp/diff_new_pack.IAcYTa/_new 2019-11-11 13:01:09.229738829 +0100 @@ -36,7 +36,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 4.1.0+git.1572504697.472361c5 +Version: 4.1.0+git.1573020742.a0b88227 Release: 0 Url: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.IAcYTa/_old 2019-11-11 13:01:09.309738913 +0100 +++ /var/tmp/diff_new_pack.IAcYTa/_new 2019-11-11 13:01:09.309738913 +0100 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">git://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">c8d41bd637dd03b4d60a9f35ca099e41ac32eec4</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">b8bb14dbbc9d6a0b1e79d696e64246bec0a98357</param></service></servicedata> \ No newline at end of file ++++++ crmsh-4.1.0+git.1572504697.472361c5.tar.bz2 -> crmsh-4.1.0+git.1573020742.a0b88227.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.1.0+git.1572504697.472361c5/crmsh/parallax.py new/crmsh-4.1.0+git.1573020742.a0b88227/crmsh/parallax.py --- old/crmsh-4.1.0+git.1572504697.472361c5/crmsh/parallax.py 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.1.0+git.1573020742.a0b88227/crmsh/parallax.py 2019-11-06 07:12:22.000000000 +0100 @@ -0,0 +1,104 @@ +# Copyright (C) 2019 Xin Liang <[email protected]> +# See COPYING for license information. + + +import os +import parallax + + +class Parallax(object): + """ + # Parallax SSH API + # call: Executes the given command on a set of hosts, collecting the output + # copy: Copies files from the local machine to a set of remote hosts + # slurp: Copies files from a set of remote hosts to local folders + """ + def __init__(self, nodes, cmd=None, localdir=None, filename=None, + src=None, dst=None, askpass=False, ssh_options=None): + self.nodes = nodes + self.askpass = askpass + self.ssh_options = ssh_options + + # used for call + self.cmd = cmd + # used for slurp + self.localdir = localdir + self.filename = filename + # used for copy + self.src = src + self.dst = dst + + self.opts = self.prepare() + + def prepare(self): + opts = parallax.Options() + if self.ssh_options is None: + self.ssh_options = ['StrictHostKeyChecking=no', 'ConnectTimeout=10'] + opts.ssh_options = self.ssh_options + opts.askpass = self.askpass + # warn_message will available from parallax-1.0.5 + if hasattr(opts, 'warn_message'): + opts.warn_message = False + opts.localdir = self.localdir + return opts + + def handle(self, results): + for host, result in results: + if isinstance(result, parallax.Error): + raise ValueError("Failed on {}: {}".format(host, result)) + return results + + def call(self): + results = parallax.call(self.nodes, self.cmd, self.opts) + return self.handle(list(results.items())) + + def slurp(self): + dst = os.path.basename(self.filename) + results = parallax.slurp(self.nodes, self.filename, dst, self.opts) + return self.handle(list(results.items())) + + def copy(self): + results = parallax.copy(self.nodes, self.src, self.dst, self.opts) + return self.handle(list(results.items())) + + +def parallax_call(nodes, cmd, askpass=False, ssh_options=None): + """ + Executes the given command on a set of hosts, collecting the output + nodes: a set of hosts + cmd: command + askpass: Ask for a password if passwordless not configured + ssh_options: Extra options to pass to SSH + Returns [(host, (rc, stdout, stdin)), ...] or ValueError exception + """ + p = Parallax(nodes, cmd=cmd, askpass=askpass, ssh_options=ssh_options) + return p.call() + + +def parallax_slurp(nodes, localdir, filename, askpass=False, ssh_options=None): + """ + Copies from the remote node to the local node + nodes: a set of hosts + localdir: localpath + filename: remote filename want to slurp + askpass: Ask for a password if passwordless not configured + ssh_options: Extra options to pass to SSH + Returns [(host, (rc, stdout, stdin, localpath)), ...] or ValueError exception + """ + p = Parallax(nodes, localdir=localdir, filename=filename, + askpass=askpass, ssh_options=ssh_options) + return p.slurp() + + +def parallax_copy(nodes, src, dst, askpass=False, ssh_options=None): + """ + Copies from the local node to a set of remote hosts + nodes: a set of hosts + src: local path + dst: remote path + askpass: Ask for a password if passwordless not configured + ssh_options: Extra options to pass to SSH + Returns [(host, (rc, stdout, stdin)), ...] or ValueError exception + """ + p = Parallax(nodes, src=src, dst=dst, askpass=askpass, ssh_options=ssh_options) + return p.copy() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.1.0+git.1572504697.472361c5/data-manifest new/crmsh-4.1.0+git.1573020742.a0b88227/data-manifest --- old/crmsh-4.1.0+git.1572504697.472361c5/data-manifest 2019-10-31 07:51:37.000000000 +0100 +++ new/crmsh-4.1.0+git.1573020742.a0b88227/data-manifest 2019-11-06 07:12:22.000000000 +0100 @@ -155,6 +155,7 @@ test/unittests/scripts/vipinc/main.yml test/unittests/scripts/vip/main.yml test/unittests/scripts/workflows/10-webserver.xml +test/unittests/test_bootstrap.py test/unittests/test_bugs.py test/unittests/test_cib.py test/unittests/test_cliformat.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.1.0+git.1572504697.472361c5/test/unittests/test_parallax.py new/crmsh-4.1.0+git.1573020742.a0b88227/test/unittests/test_parallax.py --- old/crmsh-4.1.0+git.1572504697.472361c5/test/unittests/test_parallax.py 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-4.1.0+git.1573020742.a0b88227/test/unittests/test_parallax.py 2019-11-06 07:12:22.000000000 +0100 @@ -0,0 +1,127 @@ +from __future__ import unicode_literals +# Copyright (C) 2019 Xin Liang <[email protected]> +# See COPYING for license information. +# +# unit tests for parallax.py + + +import os +import unittest +from unittest import mock +import parallax +from crmsh import parallax as cparallax + + +class TestParallax(unittest.TestCase): + @classmethod + def setUpClass(cls): + """ + Global setUp. + """ + + def setUp(self): + """ + Test setUp. + """ + # Use the setup to create a fresh instance for each test + self.parallax_call_instance = cparallax.Parallax(["node1"], cmd="ls") + self.parallax_slurp_instance = cparallax.Parallax(["node1"], localdir="/opt", filename="/opt/file.c") + self.parallax_copy_instance = cparallax.Parallax(["node1", "node2"], src="/opt/file.c", dst="/tmp") + + def tearDown(self): + """ + Test tearDown. + """ + + @classmethod + def tearDownClass(cls): + """ + Global tearDown. + """ + + @mock.patch("parallax.call") + @mock.patch("crmsh.parallax.Parallax.handle") + def test_call(self, mock_handle, mock_call): + mock_call.return_value = {"node1": (0, None, None)} + mock_handle.return_value = [("node1", (0, None, None))] + + result = self.parallax_call_instance.call() + self.assertEqual(result, mock_handle.return_value) + + mock_call.assert_called_once_with(["node1"], "ls", self.parallax_call_instance.opts) + mock_handle.assert_called_once_with(list(mock_call.return_value.items())) + + @mock.patch("parallax.Error") + @mock.patch("parallax.call") + @mock.patch("crmsh.parallax.Parallax.handle") + def test_call_exception(self, mock_handle, mock_call, mock_error): + mock_error = mock.Mock() + mock_call.return_value = {"node1": mock_error} + mock_handle.side_effect = ValueError("error happen") + + with self.assertRaises(ValueError) as err: + self.parallax_call_instance.call() + self.assertEqual("error happen", str(err.exception)) + + mock_call.assert_called_once_with(["node1"], "ls", self.parallax_call_instance.opts) + mock_handle.assert_called_once_with(list(mock_call.return_value.items())) + + @mock.patch("crmsh.parallax.Parallax.handle") + @mock.patch("parallax.slurp") + @mock.patch("os.path.basename") + def test_slurp(self, mock_basename, mock_slurp, mock_handle): + mock_basename.return_value = "file.c" + mock_slurp.return_value = {"node1": (0, None, None, "/opt")} + mock_handle.return_value = [("node1", (0, None, None, "/opt"))] + + result = self.parallax_slurp_instance.slurp() + self.assertEqual(result, mock_handle.return_value) + + mock_basename.assert_called_once_with("/opt/file.c") + mock_slurp.assert_called_once_with(["node1"], "/opt/file.c", "file.c", self.parallax_slurp_instance.opts) + mock_handle.assert_called_once_with(list(mock_slurp.return_value.items())) + + @mock.patch("parallax.Error") + @mock.patch("crmsh.parallax.Parallax.handle") + @mock.patch("parallax.slurp") + @mock.patch("os.path.basename") + def test_slurp_exception(self, mock_basename, mock_slurp, mock_handle, mock_error): + mock_basename.return_value = "file.c" + mock_error = mock.Mock() + mock_slurp.return_value = {"node1": mock_error} + mock_handle.side_effect = ValueError("error happen") + + with self.assertRaises(ValueError) as err: + self.parallax_slurp_instance.slurp() + self.assertEqual("error happen", str(err.exception)) + + mock_basename.assert_called_once_with("/opt/file.c") + mock_slurp.assert_called_once_with(["node1"], "/opt/file.c", "file.c", self.parallax_slurp_instance.opts) + mock_handle.assert_called_once_with(list(mock_slurp.return_value.items())) + + @mock.patch("parallax.copy") + @mock.patch("crmsh.parallax.Parallax.handle") + def test_copy(self, mock_handle, mock_copy): + mock_copy.return_value = {"node1": (0, None, None), "node2": (0, None, None)} + mock_handle.return_value = [("node1", (0, None, None)), ("node2", (0, None, None))] + + result = self.parallax_copy_instance.copy() + self.assertEqual(result, mock_handle.return_value) + + mock_copy.assert_called_once_with(["node1", "node2"], "/opt/file.c", "/tmp", self.parallax_copy_instance.opts) + mock_handle.assert_called_once_with(list(mock_copy.return_value.items())) + + @mock.patch("parallax.Error") + @mock.patch("parallax.copy") + @mock.patch("crmsh.parallax.Parallax.handle") + def test_copy_exception(self, mock_handle, mock_copy, mock_error): + mock_error = mock.Mock() + mock_copy.return_value = {"node1": mock_error, "node2": (0, None, None)} + mock_handle.side_effect = ValueError("error happen") + + with self.assertRaises(ValueError) as err: + self.parallax_copy_instance.copy() + self.assertEqual("error happen", str(err.exception)) + + mock_copy.assert_called_once_with(["node1", "node2"], "/opt/file.c", "/tmp", self.parallax_copy_instance.opts) + mock_handle.assert_called_once_with(list(mock_copy.return_value.items()))
