Blame SOURCES/bz1793574-01-fix-detecting-fence-history-support.patch

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