From ac0305a8b6bb040ef06dcbfff309c91321400d44 Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Mon, 27 Jan 2020 17:05:42 +0100 Subject: [PATCH 3/7] fix detecting fence history support --- pcs/lib/commands/stonith.py | 38 ++++++++------ pcs/lib/pacemaker/live.py | 45 +++++++++------- .../crm_mon.rng.with_fence_history.xml | 13 ----- .../crm_mon.rng.without_fence_history.xml | 9 ---- pcs_test/tier0/lib/commands/test_status.py | 35 +++---------- .../lib/commands/test_stonith_history.py | 52 ++++++------------- pcs_test/tier0/lib/pacemaker/test_live.py | 31 ++++++++++- .../tools/command_env/config_runner_pcmk.py | 41 +++++++++++++++ pcs_test/tools/command_env/mock_runner.py | 1 + 9 files changed, 141 insertions(+), 124 deletions(-) delete mode 100644 pcs_test/resources/crm_mon.rng.with_fence_history.xml delete mode 100644 pcs_test/resources/crm_mon.rng.without_fence_history.xml diff --git a/pcs/lib/commands/stonith.py b/pcs/lib/commands/stonith.py index c0849a54..ff87c852 100644 --- a/pcs/lib/commands/stonith.py +++ b/pcs/lib/commands/stonith.py @@ -1,3 +1,7 @@ +from typing import ( + Optional, +) + from pcs.lib import reports from pcs.lib.cib import resource from pcs.lib.cib.resource.common import are_meta_disabled @@ -6,13 +10,14 @@ from pcs.lib.commands.resource import ( _ensure_disabled_after_wait, resource_environment ) +from pcs.lib.env import LibraryEnvironment from pcs.lib.errors import LibraryError from pcs.lib.pacemaker.live import ( FenceHistoryCommandErrorException, fence_history_cleanup, fence_history_text, fence_history_update, - is_fence_history_supported, + is_fence_history_supported_management, ) from pcs.lib.pacemaker.values import validate_id from pcs.lib.resource_agent import find_valid_stonith_agent_by_name as get_agent @@ -162,51 +167,54 @@ def create_in_group( put_after_adjacent, ) -def history_get_text(env, node=None): +def history_get_text(env: LibraryEnvironment, node: Optional[str] = None): """ Get full fencing history in plain text - LibraryEnvironment env - string node -- get history for the specified node or all nodes if None + env + node -- get history for the specified node or all nodes if None """ - if not is_fence_history_supported(): + runner = env.cmd_runner() + if not is_fence_history_supported_management(runner): raise LibraryError(reports.fence_history_not_supported()) try: - return fence_history_text(env.cmd_runner(), node) + return fence_history_text(runner, node) except FenceHistoryCommandErrorException as e: raise LibraryError( reports.fence_history_command_error(str(e), "show") ) -def history_cleanup(env, node=None): +def history_cleanup(env: LibraryEnvironment, node: Optional[str] = None): """ Clear fencing history - LibraryEnvironment env - string node -- clear history for the specified node or all nodes if None + env + node -- clear history for the specified node or all nodes if None """ - if not is_fence_history_supported(): + runner = env.cmd_runner() + if not is_fence_history_supported_management(runner): raise LibraryError(reports.fence_history_not_supported()) try: - return fence_history_cleanup(env.cmd_runner(), node) + return fence_history_cleanup(runner, node) except FenceHistoryCommandErrorException as e: raise LibraryError( reports.fence_history_command_error(str(e), "cleanup") ) -def history_update(env): +def history_update(env: LibraryEnvironment): """ Update fencing history in a cluster (sync with other nodes) - LibraryEnvironment env + env """ - if not is_fence_history_supported(): + runner = env.cmd_runner() + if not is_fence_history_supported_management(runner): raise LibraryError(reports.fence_history_not_supported()) try: - return fence_history_update(env.cmd_runner()) + return fence_history_update(runner) except FenceHistoryCommandErrorException as e: raise LibraryError( reports.fence_history_command_error(str(e), "update") diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py index 233f2e2d..d6741441 100644 --- a/pcs/lib/pacemaker/live.py +++ b/pcs/lib/pacemaker/live.py @@ -1,6 +1,7 @@ import os.path import re from typing import ( + Iterable, List, Tuple, ) @@ -56,7 +57,7 @@ def get_cluster_status_text( cmd.extend(["--show-detail", "--show-node-attributes", "--failcounts"]) # by default, pending and failed actions are displayed # with verbose==True, we display the whole history - if is_fence_history_supported(): + if is_fence_history_supported_status(runner): cmd.append("--fence-history=3") stdout, stderr, retval = runner.run(cmd) @@ -523,25 +524,15 @@ def _resource_move_ban_clear( ### fence history -def is_fence_history_supported(): - try: - crm_mon_rng = xml_fromstring(open(settings.crm_mon_schema, "r").read()) - # Namespaces must be provided otherwise xpath won't match anything. - # 'None' namespace is not supported, so we rename it. - namespaces_map = { - "ns": crm_mon_rng.nsmap.pop(None) - } - history_elements = crm_mon_rng.xpath( - ".//ns:element[@name='fence_history']", - namespaces=namespaces_map - ) - if history_elements: - return True - except (EnvironmentError, etree.XMLSyntaxError): - # if we cannot tell for sure fence_history is supported, we will - # continue as if it was not supported - pass - return False +def is_fence_history_supported_status(runner: CommandRunner) -> bool: + return _is_in_pcmk_tool_help( + runner, "crm_mon", ["--fence-history"] + ) + +def is_fence_history_supported_management(runner: CommandRunner) -> bool: + return _is_in_pcmk_tool_help( + runner, "stonith_admin", ["--history", "--broadcast", "--cleanup"] + ) def fence_history_cleanup(runner, node=None): return _run_fence_history_command(runner, "--cleanup", node) @@ -583,3 +574,17 @@ def __is_in_crm_resource_help(runner, text): ) # help goes to stderr but we check stdout as well if that gets changed return text in stderr or text in stdout + +def _is_in_pcmk_tool_help( + runner: CommandRunner, tool: str, text_list: Iterable[str] +) -> bool: + stdout, stderr, dummy_retval = runner.run( + [__exec(tool), "--help-all"] + ) + # Help goes to stderr but we check stdout as well if that gets changed. Use + # generators in all to return early. + return ( + all(text in stderr for text in text_list) + or + all(text in stdout for text in text_list) + ) diff --git a/pcs_test/resources/crm_mon.rng.with_fence_history.xml b/pcs_test/resources/crm_mon.rng.with_fence_history.xml deleted file mode 100644 index 45b380bd..00000000 --- a/pcs_test/resources/crm_mon.rng.with_fence_history.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/pcs_test/resources/crm_mon.rng.without_fence_history.xml b/pcs_test/resources/crm_mon.rng.without_fence_history.xml deleted file mode 100644 index f7efe52c..00000000 --- a/pcs_test/resources/crm_mon.rng.without_fence_history.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/pcs_test/tier0/lib/commands/test_status.py b/pcs_test/tier0/lib/commands/test_status.py index 517aa908..06878668 100644 --- a/pcs_test/tier0/lib/commands/test_status.py +++ b/pcs_test/tier0/lib/commands/test_status.py @@ -1,15 +1,12 @@ from textwrap import dedent -from unittest import mock, TestCase +from unittest import TestCase -from pcs import settings from pcs.common import file_type_codes, report_codes from pcs.lib.commands import status from pcs_test.tools import fixture from pcs_test.tools.command_env import get_env_tools from pcs_test.tools.misc import read_test_resource as rc_read -crm_mon_rng_with_history = rc_read("crm_mon.rng.with_fence_history.xml") -crm_mon_rng_without_history = rc_read("crm_mon.rng.without_fence_history.xml") class FullClusterStatusPlaintext(TestCase): def setUp(self): @@ -212,11 +209,7 @@ class FullClusterStatusPlaintext(TestCase): def test_success_live_verbose(self): (self.config .env.set_known_nodes(self.node_name_list) - .fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng" - ) + .runner.pcmk.can_fence_history_status(stderr="not supported") .runner.pcmk.load_state_plaintext( verbose=True, stdout="crm_mon cluster status", @@ -288,11 +281,7 @@ class FullClusterStatusPlaintext(TestCase): (self.config .env.set_corosync_conf_data(rc_read("corosync.conf")) .env.set_cib_data("") - .fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng" - ) + .runner.pcmk.can_fence_history_status(stderr="not supported") .runner.pcmk.load_state_plaintext( verbose=True, stdout="crm_mon cluster status", ) @@ -320,11 +309,7 @@ class FullClusterStatusPlaintext(TestCase): def test_success_verbose_inactive_and_fence_history(self): (self.config .env.set_known_nodes(self.node_name_list) - .fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_with_history)(), - name="fs.open.crm_mon_rng" - ) + .runner.pcmk.can_fence_history_status() .runner.pcmk.load_state_plaintext( verbose=True, inactive=False, @@ -375,11 +360,7 @@ class FullClusterStatusPlaintext(TestCase): def _assert_success_with_ticket_status_failure(self, stderr="", msg=""): (self.config .env.set_known_nodes(self.node_name_list) - .fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng" - ) + .runner.pcmk.can_fence_history_status(stderr="not supported") .runner.pcmk.load_state_plaintext( verbose=True, stdout="crm_mon cluster status", @@ -553,11 +534,7 @@ class FullClusterStatusPlaintext(TestCase): (self.config .env.set_known_nodes(self.node_name_list[1:]) - .fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng" - ) + .runner.pcmk.can_fence_history_status(stderr="not supported") .runner.pcmk.load_state_plaintext( verbose=True, stdout="crm_mon cluster status", diff --git a/pcs_test/tier0/lib/commands/test_stonith_history.py b/pcs_test/tier0/lib/commands/test_stonith_history.py index e1bd35cb..cfdef13c 100644 --- a/pcs_test/tier0/lib/commands/test_stonith_history.py +++ b/pcs_test/tier0/lib/commands/test_stonith_history.py @@ -1,25 +1,16 @@ -from unittest import mock, TestCase +from unittest import TestCase from pcs_test.tools import fixture from pcs_test.tools.command_env import get_env_tools -from pcs_test.tools.misc import read_test_resource as rc_read -from pcs import settings from pcs.common import report_codes from pcs.lib.commands import stonith -crm_mon_rng_with_history = rc_read("crm_mon.rng.with_fence_history.xml") -crm_mon_rng_without_history = rc_read("crm_mon.rng.without_fence_history.xml") - class HistoryGetText(TestCase): def setUp(self): self.env_assist, self.config = get_env_tools(test_case=self) - self.config.fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_with_history)(), - name="fs.open.crm_mon_rng" - ) + self.config.runner.pcmk.can_fence_history_manage() def test_success_all_nodes(self): history = ( @@ -68,11 +59,10 @@ class HistoryGetText(TestCase): ) def test_history_not_supported(self): - self.config.fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng", - instead="fs.open.crm_mon_rng" + self.config.runner.pcmk.can_fence_history_manage( + stderr="not supported", + name="runner.pcmk.can_fence_history_manage", + instead="runner.pcmk.can_fence_history_manage", ) self.env_assist.assert_raise_library_error( lambda: stonith.history_get_text(self.env_assist.get_env()), @@ -88,11 +78,7 @@ class HistoryGetText(TestCase): class HistoryCleanup(TestCase): def setUp(self): self.env_assist, self.config = get_env_tools(test_case=self) - self.config.fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_with_history)(), - name="fs.open.crm_mon_rng" - ) + self.config.runner.pcmk.can_fence_history_manage() def test_success_all_nodes(self): msg = "cleaning up fencing-history for node *\n" @@ -129,11 +115,10 @@ class HistoryCleanup(TestCase): ) def test_history_not_supported(self): - self.config.fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng", - instead="fs.open.crm_mon_rng" + self.config.runner.pcmk.can_fence_history_manage( + stderr="not supported", + name="runner.pcmk.can_fence_history_manage", + instead="runner.pcmk.can_fence_history_manage", ) self.env_assist.assert_raise_library_error( lambda: stonith.history_cleanup(self.env_assist.get_env()), @@ -149,11 +134,7 @@ class HistoryCleanup(TestCase): class HistoryUpdate(TestCase): def setUp(self): self.env_assist, self.config = get_env_tools(test_case=self) - self.config.fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_with_history)(), - name="fs.open.crm_mon_rng" - ) + self.config.runner.pcmk.can_fence_history_manage() def test_success_all_nodes(self): msg = "gather fencing-history from all nodes\n" @@ -182,11 +163,10 @@ class HistoryUpdate(TestCase): ) def test_history_not_supported(self): - self.config.fs.open( - settings.crm_mon_schema, - mock.mock_open(read_data=crm_mon_rng_without_history)(), - name="fs.open.crm_mon_rng", - instead="fs.open.crm_mon_rng" + self.config.runner.pcmk.can_fence_history_manage( + stderr="not supported", + name="runner.pcmk.can_fence_history_manage", + instead="runner.pcmk.can_fence_history_manage", ) self.env_assist.assert_raise_library_error( lambda: stonith.history_update(self.env_assist.get_env()), diff --git a/pcs_test/tier0/lib/pacemaker/test_live.py b/pcs_test/tier0/lib/pacemaker/test_live.py index 1ea5454e..d69d8b34 100644 --- a/pcs_test/tier0/lib/pacemaker/test_live.py +++ b/pcs_test/tier0/lib/pacemaker/test_live.py @@ -79,7 +79,7 @@ class GetClusterStatusXmlTest(LibraryPacemakerTest): class GetClusterStatusText(TestCase): def setUp(self): self.mock_fencehistory_supported = mock.patch( - "pcs.lib.pacemaker.live.is_fence_history_supported", + "pcs.lib.pacemaker.live.is_fence_history_supported_status", return_value=True ) self.mock_fencehistory_supported.start() @@ -125,7 +125,7 @@ class GetClusterStatusText(TestCase): def test_success_no_fence_history(self): self.mock_fencehistory_supported.stop() self.mock_fencehistory_supported = mock.patch( - "pcs.lib.pacemaker.live.is_fence_history_supported", + "pcs.lib.pacemaker.live.is_fence_history_supported_status", return_value=False ) self.mock_fencehistory_supported.start() @@ -1399,3 +1399,30 @@ class ResourcesWaitingTest(LibraryPacemakerTest): mock_runner.run.assert_called_once_with( [self.path("crm_resource"), "--wait"] ) + + +class IsInPcmkToolHelp(TestCase): + # pylint: disable=protected-access + def test_all_in_stderr(self): + mock_runner = get_runner("", "ABCDE", 0) + self.assertTrue( + lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"]) + ) + + def test_all_in_stdout(self): + mock_runner = get_runner("ABCDE", "", 0) + self.assertTrue( + lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"]) + ) + + def test_some_in_stderr_all_in_stdout(self): + mock_runner = get_runner("ABCDE", "ABC", 0) + self.assertTrue( + lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"]) + ) + + def test_some_in_stderr_some_in_stdout(self): + mock_runner = get_runner("CDE", "ABC", 0) + self.assertFalse( + lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"]) + ) diff --git a/pcs_test/tools/command_env/config_runner_pcmk.py b/pcs_test/tools/command_env/config_runner_pcmk.py index 5bb9755b..0580e8d6 100644 --- a/pcs_test/tools/command_env/config_runner_pcmk.py +++ b/pcs_test/tools/command_env/config_runner_pcmk.py @@ -70,11 +70,52 @@ def _fixture_state_node_xml( class PcmkShortcuts(): + #pylint: disable=too-many-public-methods def __init__(self, calls): self.__calls = calls self.default_wait_timeout = DEFAULT_WAIT_TIMEOUT self.default_wait_error_returncode = WAIT_TIMEOUT_EXPIRED_RETURNCODE + def can_fence_history_manage( + self, + name="runner.pcmk.can_fence_history_manage", + stderr="--history --cleanup --broadcast", + instead=None, + ): + """ + Create a call to check if fence_history is supported by stonith_admin + + string name -- key of the call + string stderr -- stonith_admin help text + string instead -- key of call instead of which this new call is to be + placed + """ + self.__calls.place( + name, + RunnerCall("stonith_admin --help-all", stderr=stderr), + instead=instead, + ) + + def can_fence_history_status( + self, + name="runner.pcmk.can_fence_history_status", + stderr="--fence-history", + instead=None, + ): + """ + Create a call to check if fence_history is supported by crm_mon + + string name -- key of the call + string stderr -- crm_mon help text + string instead -- key of call instead of which this new call is to be + placed + """ + self.__calls.place( + name, + RunnerCall("crm_mon --help-all", stderr=stderr), + instead=instead, + ) + def fence_history_get( self, name="runner.pcmk.fence_history_get", node=None, stdout="", stderr="", returncode=0 diff --git a/pcs_test/tools/command_env/mock_runner.py b/pcs_test/tools/command_env/mock_runner.py index 2fe43137..8b9cb771 100644 --- a/pcs_test/tools/command_env/mock_runner.py +++ b/pcs_test/tools/command_env/mock_runner.py @@ -61,6 +61,7 @@ COMMAND_COMPLETIONS = { "crm_ticket": path.join(settings.pacemaker_binaries, "crm_ticket"), "crm_verify": path.join(settings.pacemaker_binaries, "crm_verify"), "sbd": settings.sbd_binary, + "stonith_admin": path.join(settings.pacemaker_binaries, "stonith_admin"), } def complete_command(command): -- 2.21.1