Blame SOURCES/bz1327739-01-add-pcs-quorum-expected-votes-command.patch

15f218
From bd852905ad905b83daa1a7240e7a79c3357db5b8 Mon Sep 17 00:00:00 2001
15f218
From: Tomas Jelinek <tojeline@redhat.com>
15f218
Date: Thu, 30 Jun 2016 15:09:31 +0200
15f218
Subject: [PATCH] bz1158805-01 add "pcs quorum expected-votes" command
15f218
15f218
---
15f218
 pcs/cli/common/lib_wrapper.py        |  1 +
15f218
 pcs/common/report_codes.py           |  1 +
15f218
 pcs/lib/commands/quorum.py           | 21 ++++++++++++++++++
15f218
 pcs/lib/corosync/live.py             | 15 +++++++++++++
15f218
 pcs/lib/reports.py                   | 13 +++++++++++
15f218
 pcs/pcs.8                            |  3 +++
15f218
 pcs/quorum.py                        |  7 ++++++
15f218
 pcs/test/suite.py                    |  6 +++--
15f218
 pcs/test/test_lib_commands_quorum.py | 43 ++++++++++++++++++++++++++++++++++++
15f218
 pcs/test/test_lib_corosync_live.py   | 42 +++++++++++++++++++++++++++++++++++
15f218
 pcs/usage.py                         |  4 ++++
15f218
 11 files changed, 154 insertions(+), 2 deletions(-)
15f218
15f218
diff --git a/pcs/cli/common/lib_wrapper.py b/pcs/cli/common/lib_wrapper.py
15f218
index 2dd5810..c4b8342 100644
15f218
--- a/pcs/cli/common/lib_wrapper.py
15f218
+++ b/pcs/cli/common/lib_wrapper.py
15f218
@@ -116,6 +116,7 @@ def load_module(env, middleware_factory, name):
15f218
                 "add_device": quorum.add_device,
15f218
                 "get_config": quorum.get_config,
15f218
                 "remove_device": quorum.remove_device,
15f218
+                "set_expected_votes_live": quorum.set_expected_votes_live,
15f218
                 "set_options": quorum.set_options,
15f218
                 "status": quorum.status_text,
15f218
                 "status_device": quorum.status_device_text,
15f218
diff --git a/pcs/common/report_codes.py b/pcs/common/report_codes.py
15f218
index afe0554..2b39938 100644
15f218
--- a/pcs/common/report_codes.py
15f218
+++ b/pcs/common/report_codes.py
15f218
@@ -47,6 +47,7 @@ COROSYNC_NOT_RUNNING_CHECK_NODE_ERROR = "COROSYNC_NOT_RUNNING_CHECK_NODE_ERROR"
15f218
 COROSYNC_NOT_RUNNING_ON_NODE = "COROSYNC_NOT_RUNNING_ON_NODE"
15f218
 COROSYNC_OPTIONS_INCOMPATIBLE_WITH_QDEVICE = "COROSYNC_OPTIONS_INCOMPATIBLE_WITH_QDEVICE"
15f218
 COROSYNC_QUORUM_GET_STATUS_ERROR = "COROSYNC_QUORUM_GET_STATUS_ERROR"
15f218
+COROSYNC_QUORUM_SET_EXPECTED_VOTES_ERROR = "COROSYNC_QUORUM_SET_EXPECTED_VOTES_ERROR"
15f218
 COROSYNC_RUNNING_ON_NODE = "COROSYNC_RUNNING_ON_NODE"
15f218
 CRM_MON_ERROR = "CRM_MON_ERROR"
15f218
 DUPLICATE_CONSTRAINTS_EXIST = "DUPLICATE_CONSTRAINTS_EXIST"
15f218
diff --git a/pcs/lib/commands/quorum.py b/pcs/lib/commands/quorum.py
15f218
index aa00bbd..7425e78 100644
15f218
--- a/pcs/lib/commands/quorum.py
15f218
+++ b/pcs/lib/commands/quorum.py
15f218
@@ -314,6 +314,27 @@ def _remove_device_model_net(lib_env, cluster_nodes, skip_offline_nodes):
15f218
         skip_offline_nodes
15f218
     )
15f218
 
15f218
+def set_expected_votes_live(lib_env, expected_votes):
15f218
+    """
15f218
+    set expected votes in live cluster to specified value
15f218
+    numeric expected_votes desired value of expected votes
15f218
+    """
15f218
+    if lib_env.is_cman_cluster:
15f218
+        raise LibraryError(reports.cman_unsupported_command())
15f218
+
15f218
+    try:
15f218
+        votes_int = int(expected_votes)
15f218
+        if votes_int < 1:
15f218
+            raise ValueError()
15f218
+    except ValueError:
15f218
+        raise LibraryError(reports.invalid_option_value(
15f218
+            "expected votes",
15f218
+            expected_votes,
15f218
+            "positive integer"
15f218
+        ))
15f218
+
15f218
+    corosync_live.set_expected_votes(lib_env.cmd_runner(), votes_int)
15f218
+
15f218
 def __ensure_not_cman(lib_env):
15f218
     if lib_env.is_corosync_conf_live and lib_env.is_cman_cluster:
15f218
         raise LibraryError(reports.cman_unsupported_command())
15f218
diff --git a/pcs/lib/corosync/live.py b/pcs/lib/corosync/live.py
15f218
index 4129aeb..b49b9f6 100644
15f218
--- a/pcs/lib/corosync/live.py
15f218
+++ b/pcs/lib/corosync/live.py
15f218
@@ -62,3 +62,18 @@ def get_quorum_status_text(runner):
15f218
             reports.corosync_quorum_get_status_error(output)
15f218
         )
15f218
     return output
15f218
+
15f218
+def set_expected_votes(runner, votes):
15f218
+    """
15f218
+    set expected votes in live cluster to specified value
15f218
+    """
15f218
+    output, retval = runner.run([
15f218
+        os.path.join(settings.corosync_binaries, "corosync-quorumtool"),
15f218
+        # format votes to handle the case where they are int
15f218
+        "-e", "{0}".format(votes)
15f218
+    ])
15f218
+    if retval != 0:
15f218
+        raise LibraryError(
15f218
+            reports.corosync_quorum_set_expected_votes_error(output)
15f218
+        )
15f218
+    return output
15f218
diff --git a/pcs/lib/reports.py b/pcs/lib/reports.py
15f218
index d8f88cd..9ececf9 100644
15f218
--- a/pcs/lib/reports.py
15f218
+++ b/pcs/lib/reports.py
15f218
@@ -565,6 +565,19 @@ def corosync_quorum_get_status_error(reason):
15f218
         }
15f218
     )
15f218
 
15f218
+def corosync_quorum_set_expected_votes_error(reason):
15f218
+    """
15f218
+    unable to set expcted votes in a live cluster
15f218
+    string reason an error message
15f218
+    """
15f218
+    return ReportItem.error(
15f218
+        report_codes.COROSYNC_QUORUM_SET_EXPECTED_VOTES_ERROR,
15f218
+        "Unable to set expected votes: {reason}",
15f218
+        info={
15f218
+            "reason": reason,
15f218
+        }
15f218
+    )
15f218
+
15f218
 def corosync_config_reloaded():
15f218
     """
15f218
     corosync configuration has been reloaded
15f218
diff --git a/pcs/pcs.8 b/pcs/pcs.8
15f218
index 949d918..a436b4c 100644
15f218
--- a/pcs/pcs.8
15f218
+++ b/pcs/pcs.8
15f218
@@ -563,6 +563,9 @@ Add/Change quorum device options.  Generic options and model options are all doc
15f218
 
15f218
 WARNING: If you want to change "host" option of qdevice model net, use "pcs quorum device remove" and "pcs quorum device add" commands to set up configuration properly unless old and new host is the same machine.
15f218
 .TP
15f218
+expected\-votes <votes>
15f218
+Set expected votes in the live cluster to specified value.  This only affects the live cluster, not changes any configuration files.
15f218
+.TP
15f218
 unblock [\fB\-\-force\fR]
15f218
 Cancel waiting for all nodes when establishing quorum.  Useful in situations where you know the cluster is inquorate, but you are confident that the cluster should proceed with resource management regardless.  This command should ONLY be used when nodes which the cluster is waiting for have been confirmed to be powered off and to have no access to shared resources.
15f218
 
15f218
diff --git a/pcs/quorum.py b/pcs/quorum.py
15f218
index 27085ac..2d54ed7 100644
15f218
--- a/pcs/quorum.py
15f218
+++ b/pcs/quorum.py
15f218
@@ -28,6 +28,8 @@ def quorum_cmd(lib, argv, modificators):
15f218
             usage.quorum(argv)
15f218
         elif sub_cmd == "config":
15f218
             quorum_config_cmd(lib, argv_next, modificators)
15f218
+        elif sub_cmd == "expected-votes":
15f218
+            quorum_expected_votes_cmd(lib, argv_next, modificators)
15f218
         elif sub_cmd == "status":
15f218
             quorum_status_cmd(lib, argv_next, modificators)
15f218
         elif sub_cmd == "device":
15f218
@@ -101,6 +103,11 @@ def quorum_config_to_str(config):
15f218
 
15f218
     return lines
15f218
 
15f218
+def quorum_expected_votes_cmd(lib, argv, modificators):
15f218
+    if len(argv) != 1:
15f218
+        raise CmdLineInputError()
15f218
+    lib.quorum.set_expected_votes_live(argv[0])
15f218
+
15f218
 def quorum_status_cmd(lib, argv, modificators):
15f218
     if argv:
15f218
         raise CmdLineInputError()
15f218
diff --git a/pcs/test/suite.py b/pcs/test/suite.py
15f218
index 85dd20c..5b29918 100755
15f218
--- a/pcs/test/suite.py
15f218
+++ b/pcs/test/suite.py
15f218
@@ -74,7 +74,7 @@ def run_tests(tests, verbose=False, color=False):
15f218
         verbosity=2 if verbose else 1,
15f218
         resultclass=resultclass
15f218
     )
15f218
-    testRunner.run(tests)
15f218
+    return testRunner.run(tests)
15f218
 
15f218
 put_package_to_path()
15f218
 explicitly_enumerated_tests = [
15f218
@@ -85,7 +85,7 @@ explicitly_enumerated_tests = [
15f218
         "--all-but",
15f218
     )
15f218
 ]
15f218
-run_tests(
15f218
+test_result = run_tests(
15f218
     discover_tests(explicitly_enumerated_tests, "--all-but" in sys.argv),
15f218
     verbose="-v" in sys.argv,
15f218
     color=(
15f218
@@ -99,6 +99,8 @@ run_tests(
15f218
         )
15f218
     ),
15f218
 )
15f218
+if not test_result.wasSuccessful():
15f218
+    sys.exit(1)
15f218
 
15f218
 # assume that we are in pcs root dir
15f218
 #
15f218
diff --git a/pcs/test/test_lib_commands_quorum.py b/pcs/test/test_lib_commands_quorum.py
15f218
index e824f37..c12ab66 100644
15f218
--- a/pcs/test/test_lib_commands_quorum.py
15f218
+++ b/pcs/test/test_lib_commands_quorum.py
15f218
@@ -1750,3 +1750,46 @@ class UpdateDeviceTest(TestCase, CmanMixin):
15f218
                 "model: net\n        bad_option: bad_value"
15f218
             )
15f218
         )
15f218
+
15f218
+
15f218
+@mock.patch("pcs.lib.commands.quorum.corosync_live.set_expected_votes")
15f218
+@mock.patch.object(
15f218
+    LibraryEnvironment,
15f218
+    "cmd_runner",
15f218
+    lambda self: "mock_runner"
15f218
+)
15f218
+class SetExpectedVotesLiveTest(TestCase, CmanMixin):
15f218
+    def setUp(self):
15f218
+        self.mock_logger = mock.MagicMock(logging.Logger)
15f218
+        self.mock_reporter = MockLibraryReportProcessor()
15f218
+
15f218
+    @mock.patch("pcs.lib.env.is_cman_cluster", lambda self: True)
15f218
+    def test_disabled_on_cman(self, mock_set_votes):
15f218
+        lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
15f218
+        self.assert_disabled_on_cman(
15f218
+            lambda: lib.set_expected_votes_live(lib_env, "5")
15f218
+        )
15f218
+        mock_set_votes.assert_not_called()
15f218
+
15f218
+    @mock.patch("pcs.lib.env.is_cman_cluster", lambda self: False)
15f218
+    def test_success(self, mock_set_votes):
15f218
+        lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
15f218
+        lib.set_expected_votes_live(lib_env, "5")
15f218
+        mock_set_votes.assert_called_once_with("mock_runner", 5)
15f218
+
15f218
+    @mock.patch("pcs.lib.env.is_cman_cluster", lambda self: False)
15f218
+    def test_invalid_votes(self, mock_set_votes):
15f218
+        lib_env = LibraryEnvironment(self.mock_logger, self.mock_reporter)
15f218
+        assert_raise_library_error(
15f218
+            lambda: lib.set_expected_votes_live(lib_env, "-5"),
15f218
+            (
15f218
+                severity.ERROR,
15f218
+                report_codes.INVALID_OPTION_VALUE,
15f218
+                {
15f218
+                    "option_name": "expected votes",
15f218
+                    "option_value": "-5",
15f218
+                    "allowed_values": "positive integer",
15f218
+                }
15f218
+            )
15f218
+        )
15f218
+        mock_set_votes.assert_not_called()
15f218
diff --git a/pcs/test/test_lib_corosync_live.py b/pcs/test/test_lib_corosync_live.py
15f218
index 96fe235..0fc5eb2 100644
15f218
--- a/pcs/test/test_lib_corosync_live.py
15f218
+++ b/pcs/test/test_lib_corosync_live.py
15f218
@@ -141,3 +141,45 @@ class GetQuorumStatusTextTest(TestCase):
15f218
         self.mock_runner.run.assert_called_once_with([
15f218
             self.quorum_tool, "-p"
15f218
         ])
15f218
+
15f218
+
15f218
+class SetExpectedVotesTest(TestCase):
15f218
+    def setUp(self):
15f218
+        self.mock_runner = mock.MagicMock(spec_set=CommandRunner)
15f218
+
15f218
+    def path(self, name):
15f218
+        return os.path.join(settings.corosync_binaries, name)
15f218
+
15f218
+    def test_success(self):
15f218
+        cmd_retval = 0
15f218
+        cmd_output = "cmd output"
15f218
+        mock_runner = mock.MagicMock(spec_set=CommandRunner)
15f218
+        mock_runner.run.return_value = (cmd_output, cmd_retval)
15f218
+
15f218
+        lib.set_expected_votes(mock_runner, 3)
15f218
+
15f218
+        mock_runner.run.assert_called_once_with([
15f218
+            self.path("corosync-quorumtool"), "-e", "3"
15f218
+        ])
15f218
+
15f218
+    def test_error(self):
15f218
+        cmd_retval = 1
15f218
+        cmd_output = "cmd output"
15f218
+        mock_runner = mock.MagicMock(spec_set=CommandRunner)
15f218
+        mock_runner.run.return_value = (cmd_output, cmd_retval)
15f218
+
15f218
+        assert_raise_library_error(
15f218
+            lambda: lib.set_expected_votes(mock_runner, 3),
15f218
+            (
15f218
+                severity.ERROR,
15f218
+                report_codes.COROSYNC_QUORUM_SET_EXPECTED_VOTES_ERROR,
15f218
+                {
15f218
+                    "reason": cmd_output,
15f218
+                }
15f218
+            )
15f218
+        )
15f218
+
15f218
+        mock_runner.run.assert_called_once_with([
15f218
+            self.path("corosync-quorumtool"), "-e", "3"
15f218
+        ])
15f218
+
15f218
diff --git a/pcs/usage.py b/pcs/usage.py
15f218
index 542f806..ee53a2f 100644
15f218
--- a/pcs/usage.py
15f218
+++ b/pcs/usage.py
15f218
@@ -1352,6 +1352,10 @@ Commands:
15f218
         to set up configuration properly unless old and new host is the same
15f218
         machine.
15f218
 
15f218
+    expected-votes <votes>
15f218
+        Set expected votes in the live cluster to specified value.  This only
15f218
+        affects the live cluster, not changes any configuration files.
15f218
+
15f218
     unblock [--force]
15f218
         Cancel waiting for all nodes when establishing quorum.  Useful in
15f218
         situations where you know the cluster is inquorate, but you are
15f218
-- 
15f218
1.8.3.1
15f218