Blob Blame History Raw
From ac0305a8b6bb040ef06dcbfff309c91321400d44 Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
-    <start>
-        <ref name="element-crm_mon"/>
-    </start>
-    <define name="element-crm_mon">
-        <element name="crm_mon">
-            <optional>
-                <element name="fence_history"/>
-            </optional>
-        </element>
-    </define>
-</grammar>
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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
-    <start>
-        <ref name="element-crm_mon"/>
-    </start>
-    <define name="element-crm_mon">
-        <element name="crm_mon"/>
-    </define>
-</grammar>
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("<cib/>")
-            .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