From 6841064bf1d06e16c9c5bf9a6ab42b3185d55afb Mon Sep 17 00:00:00 2001
From: Miroslav Lisik <mlisik@redhat.com>
Date: Mon, 20 Mar 2023 10:35:34 +0100
Subject: [PATCH 2/2] fix `pcs stonith update-scsi-devices` command
---
pcs/lib/cib/resource/stonith.py | 168 +++++-
.../test_stonith_update_scsi_devices.py | 571 ++++++++++++++----
2 files changed, 601 insertions(+), 138 deletions(-)
diff --git a/pcs/lib/cib/resource/stonith.py b/pcs/lib/cib/resource/stonith.py
index 1f5bddff..07cffba6 100644
--- a/pcs/lib/cib/resource/stonith.py
+++ b/pcs/lib/cib/resource/stonith.py
@@ -169,12 +169,64 @@ def get_node_key_map_for_mpath(
return node_key_map
-DIGEST_ATTRS = ["op-digest", "op-secure-digest", "op-restart-digest"]
-DIGEST_ATTR_TO_TYPE_MAP = {
+DIGEST_ATTR_TO_DIGEST_TYPE_MAP = {
"op-digest": "all",
"op-secure-digest": "nonprivate",
"op-restart-digest": "nonreloadable",
}
+TRANSIENT_DIGEST_ATTR_TO_DIGEST_TYPE_MAP = {
+ "#digests-all": "all",
+ "#digests-secure": "nonprivate",
+}
+DIGEST_ATTRS = frozenset(DIGEST_ATTR_TO_DIGEST_TYPE_MAP.keys())
+TRANSIENT_DIGEST_ATTRS = frozenset(
+ TRANSIENT_DIGEST_ATTR_TO_DIGEST_TYPE_MAP.keys()
+)
+
+
+def _get_digest(
+ attr: str,
+ attr_to_type_map: Dict[str, str],
+ calculated_digests: Dict[str, Optional[str]],
+) -> str:
+ """
+ Return digest of right type for the specified attribute. If missing, raise
+ an error.
+
+ attr -- name of digest attribute
+ atttr_to_type_map -- map for attribute name to digest type conversion
+ calculated_digests -- digests calculated by pacemaker
+ """
+ if attr not in attr_to_type_map:
+ raise AssertionError(
+ f"Key '{attr}' is missing in the attribute name to digest type map"
+ )
+ digest = calculated_digests.get(attr_to_type_map[attr])
+ if digest is None:
+ # this should not happen and when it does it is pacemaker fault
+ raise LibraryError(
+ ReportItem.error(
+ reports.messages.StonithRestartlessUpdateUnableToPerform(
+ f"necessary digest for '{attr}' attribute is missing"
+ )
+ )
+ )
+ return digest
+
+
+def _get_transient_instance_attributes(cib: _Element) -> List[_Element]:
+ """
+ Return list of instance_attributes elements which could contain digest
+ attributes.
+
+ cib -- CIB root element
+ """
+ return cast(
+ List[_Element],
+ cib.xpath(
+ "./status/node_state/transient_attributes/instance_attributes"
+ ),
+ )
def _get_lrm_rsc_op_elements(
@@ -278,21 +330,89 @@ def _update_digest_attrs_in_lrm_rsc_op(
)
)
for attr in common_digests_attrs:
- new_digest = calculated_digests[DIGEST_ATTR_TO_TYPE_MAP[attr]]
- if new_digest is None:
- # this should not happen and when it does it is pacemaker fault
+ # update digest in cib
+ lrm_rsc_op.attrib[attr] = _get_digest(
+ attr, DIGEST_ATTR_TO_DIGEST_TYPE_MAP, calculated_digests
+ )
+
+
+def _get_transient_digest_value(
+ old_value: str, stonith_id: str, stonith_type: str, digest: str
+) -> str:
+ """
+ Return transient digest value with replaced digest.
+
+ Value has comma separated format:
+ <stonith_id>:<stonith_type>:<digest>,...
+
+ and we need to replace only digest for our currently updated stonith device.
+
+ old_value -- value to be replaced
+ stonith_id -- id of stonith resource
+ stonith_type -- stonith resource type
+ digest -- digest for new value
+ """
+ new_comma_values_list = []
+ for comma_value in old_value.split(","):
+ if comma_value:
+ try:
+ _id, _type, _ = comma_value.split(":")
+ except ValueError as e:
+ raise LibraryError(
+ ReportItem.error(
+ reports.messages.StonithRestartlessUpdateUnableToPerform(
+ f"invalid digest attribute value: '{old_value}'"
+ )
+ )
+ ) from e
+ if _id == stonith_id and _type == stonith_type:
+ comma_value = ":".join([stonith_id, stonith_type, digest])
+ new_comma_values_list.append(comma_value)
+ return ",".join(new_comma_values_list)
+
+
+def _update_digest_attrs_in_transient_instance_attributes(
+ nvset_el: _Element,
+ stonith_id: str,
+ stonith_type: str,
+ calculated_digests: Dict[str, Optional[str]],
+) -> None:
+ """
+ Update digests attributes in transient instance attributes element.
+
+ nvset_el -- instance_attributes element containing nvpairs with digests
+ attributes
+ stonith_id -- id of stonith resource being updated
+ stonith_type -- type of stonith resource being updated
+ calculated_digests -- digests calculated by pacemaker
+ """
+ for attr in TRANSIENT_DIGEST_ATTRS:
+ nvpair_list = cast(
+ List[_Element],
+ nvset_el.xpath("./nvpair[@name=$name]", name=attr),
+ )
+ if not nvpair_list:
+ continue
+ if len(nvpair_list) > 1:
raise LibraryError(
ReportItem.error(
reports.messages.StonithRestartlessUpdateUnableToPerform(
- (
- f"necessary digest for '{attr}' attribute is "
- "missing"
- )
+ f"multiple digests attributes: '{attr}'"
)
)
)
- # update digest in cib
- lrm_rsc_op.attrib[attr] = new_digest
+ old_value = nvpair_list[0].attrib["value"]
+ if old_value:
+ nvpair_list[0].attrib["value"] = _get_transient_digest_value(
+ str(old_value),
+ stonith_id,
+ stonith_type,
+ _get_digest(
+ attr,
+ TRANSIENT_DIGEST_ATTR_TO_DIGEST_TYPE_MAP,
+ calculated_digests,
+ ),
+ )
def update_scsi_devices_without_restart(
@@ -311,6 +431,8 @@ def update_scsi_devices_without_restart(
id_provider -- elements' ids generator
device_list -- list of updated scsi devices
"""
+ # pylint: disable=too-many-locals
+ cib = get_root(resource_el)
resource_id = resource_el.get("id", "")
roles_with_nodes = get_resource_state(cluster_state, resource_id)
if "Started" not in roles_with_nodes:
@@ -341,17 +463,14 @@ def update_scsi_devices_without_restart(
)
lrm_rsc_op_start_list = _get_lrm_rsc_op_elements(
- get_root(resource_el), resource_id, node_name, "start"
+ cib, resource_id, node_name, "start"
+ )
+ new_instance_attrs_digests = get_resource_digests(
+ runner, resource_id, node_name, new_instance_attrs
)
if len(lrm_rsc_op_start_list) == 1:
_update_digest_attrs_in_lrm_rsc_op(
- lrm_rsc_op_start_list[0],
- get_resource_digests(
- runner,
- resource_id,
- node_name,
- new_instance_attrs,
- ),
+ lrm_rsc_op_start_list[0], new_instance_attrs_digests
)
else:
raise LibraryError(
@@ -364,7 +483,7 @@ def update_scsi_devices_without_restart(
monitor_attrs_list = _get_monitor_attrs(resource_el)
lrm_rsc_op_monitor_list = _get_lrm_rsc_op_elements(
- get_root(resource_el), resource_id, node_name, "monitor"
+ cib, resource_id, node_name, "monitor"
)
if len(lrm_rsc_op_monitor_list) != len(monitor_attrs_list):
raise LibraryError(
@@ -380,7 +499,7 @@ def update_scsi_devices_without_restart(
for monitor_attrs in monitor_attrs_list:
lrm_rsc_op_list = _get_lrm_rsc_op_elements(
- get_root(resource_el),
+ cib,
resource_id,
node_name,
"monitor",
@@ -409,3 +528,10 @@ def update_scsi_devices_without_restart(
)
)
)
+ for nvset_el in _get_transient_instance_attributes(cib):
+ _update_digest_attrs_in_transient_instance_attributes(
+ nvset_el,
+ resource_id,
+ resource_el.get("type", ""),
+ new_instance_attrs_digests,
+ )
diff --git a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py
index 69ea097c..72c7dbcf 100644
--- a/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py
+++ b/pcs_test/tier0/lib/commands/test_stonith_update_scsi_devices.py
@@ -38,6 +38,7 @@ DEFAULT_DIGEST = _DIGEST + "0"
ALL_DIGEST = _DIGEST + "1"
NONPRIVATE_DIGEST = _DIGEST + "2"
NONRELOADABLE_DIGEST = _DIGEST + "3"
+DIGEST_ATTR_VALUE_GOOD_FORMAT = f"stonith_id:stonith_type:{DEFAULT_DIGEST},"
DEV_1 = "/dev/sda"
DEV_2 = "/dev/sdb"
DEV_3 = "/dev/sdc"
@@ -151,33 +152,58 @@ def _fixture_lrm_rsc_start_ops(resource_id, lrm_start_ops):
return _fixture_lrm_rsc_ops("start", resource_id, lrm_start_ops)
-def _fixture_status_lrm_ops_base(
- resource_id,
- resource_type,
- lrm_ops,
-):
+def _fixture_status_lrm_ops(resource_id, resource_type, lrm_ops):
return f"""
- <status>
- <node_state id="1" uname="node1">
- <lrm id="1">
- <lrm_resources>
- <lrm_resource id="{resource_id}" type="{resource_type}" class="stonith">
- {lrm_ops}
- </lrm_resource>
- </lrm_resources>
- </lrm>
- </node_state>
- </status>
+ <lrm id="1">
+ <lrm_resources>
+ <lrm_resource id="{resource_id}" type="{resource_type}" class="stonith">
+ {lrm_ops}
+ </lrm_resource>
+ </lrm_resources>
+ </lrm>
+ """
+
+
+def _fixture_digest_nvpair(node_id, digest_name, digest_value):
+ return (
+ f'<nvpair id="status-{node_id}-.{digest_name}" name="#{digest_name}" '
+ f'value="{digest_value}"/>'
+ )
+
+
+def _fixture_transient_attributes(node_id, digests_nvpairs):
+ return f"""
+ <transient_attributes id="{node_id}">
+ <instance_attributes id="status-{node_id}">
+ <nvpair id="status-{node_id}-.feature-set" name="#feature-set" value="3.16.2"/>
+ <nvpair id="status-{node_id}-.node-unfenced" name="#node-unfenced" value="1679319764"/>
+ {digests_nvpairs}
+ </instance_attributes>
+ </transient_attributes>
+ """
+
+
+def _fixture_node_state(node_id, lrm_ops=None, transient_attrs=None):
+ if transient_attrs is None:
+ transient_attrs = ""
+ if lrm_ops is None:
+ lrm_ops = ""
+ return f"""
+ <node_state id="{node_id}" uname="node{node_id}">
+ {lrm_ops}
+ {transient_attrs}
+ </node_state>
"""
-def _fixture_status_lrm_ops(
+def _fixture_status(
resource_id,
resource_type,
lrm_start_ops=DEFAULT_LRM_START_OPS,
lrm_monitor_ops=DEFAULT_LRM_MONITOR_OPS,
+ digests_attrs_list=None,
):
- return _fixture_status_lrm_ops_base(
+ lrm_ops = _fixture_status_lrm_ops(
resource_id,
resource_type,
"\n".join(
@@ -185,18 +211,52 @@ def _fixture_status_lrm_ops(
+ _fixture_lrm_rsc_monitor_ops(resource_id, lrm_monitor_ops)
),
)
+ node_states_list = []
+ if not digests_attrs_list:
+ node_states_list.append(
+ _fixture_node_state("1", lrm_ops, transient_attrs=None)
+ )
+ else:
+ for node_id, digests_attrs in enumerate(digests_attrs_list, start=1):
+ transient_attrs = _fixture_transient_attributes(
+ node_id,
+ "\n".join(
+ _fixture_digest_nvpair(node_id, name, value)
+ for name, value in digests_attrs
+ ),
+ )
+ node_state = _fixture_node_state(
+ node_id,
+ lrm_ops=lrm_ops if node_id == 1 else None,
+ transient_attrs=transient_attrs,
+ )
+ node_states_list.append(node_state)
+ node_states = "\n".join(node_states_list)
+ return f"""
+ <status>
+ {node_states}
+ </status>
+ """
+
+def fixture_digests_xml(resource_id, node_name, devices="", nonprivate=True):
+ nonprivate_xml = (
+ f"""
+ <digest type="nonprivate" hash="{NONPRIVATE_DIGEST}">
+ <parameters devices="{devices}"/>
+ </digest>
+ """
+ if nonprivate
+ else ""
+ )
-def fixture_digests_xml(resource_id, node_name, devices=""):
return f"""
<pacemaker-result api-version="2.9" request="crm_resource --digests --resource {resource_id} --node {node_name} --output-as xml devices={devices}">
<digests resource="{resource_id}" node="{node_name}" task="stop" interval="0ms">
<digest type="all" hash="{ALL_DIGEST}">
<parameters devices="{devices}" pcmk_host_check="static-list" pcmk_host_list="node1 node2 node3" pcmk_reboot_action="off"/>
</digest>
- <digest type="nonprivate" hash="{NONPRIVATE_DIGEST}">
- <parameters devices="{devices}"/>
- </digest>
+ {nonprivate_xml}
</digests>
<status code="0" message="OK"/>
</pacemaker-result>
@@ -334,6 +394,8 @@ class UpdateScsiDevicesMixin:
nodes_running_on=1,
start_digests=True,
monitor_digests=True,
+ digests_attrs_list=None,
+ crm_digests_xml=None,
):
# pylint: disable=too-many-arguments
# pylint: disable=too-many-locals
@@ -346,11 +408,12 @@ class UpdateScsiDevicesMixin:
resource_ops=resource_ops,
host_map=host_map,
),
- status=_fixture_status_lrm_ops(
+ status=_fixture_status(
self.stonith_id,
self.stonith_type,
lrm_start_ops=lrm_start_ops,
lrm_monitor_ops=lrm_monitor_ops,
+ digests_attrs_list=digests_attrs_list,
),
)
self.config.runner.pcmk.is_resource_digests_supported()
@@ -363,14 +426,17 @@ class UpdateScsiDevicesMixin:
nodes=FIXTURE_CRM_MON_NODES,
)
devices_opt = "devices={}".format(devices_value)
+
+ if crm_digests_xml is None:
+ crm_digests_xml = fixture_digests_xml(
+ self.stonith_id, SCSI_NODE, devices=devices_value
+ )
if start_digests:
self.config.runner.pcmk.resource_digests(
self.stonith_id,
SCSI_NODE,
name="start.op.digests",
- stdout=fixture_digests_xml(
- self.stonith_id, SCSI_NODE, devices=devices_value
- ),
+ stdout=crm_digests_xml,
args=[devices_opt],
)
if monitor_digests:
@@ -394,11 +460,7 @@ class UpdateScsiDevicesMixin:
self.stonith_id,
SCSI_NODE,
name=f"{name}-{num}.op.digests",
- stdout=fixture_digests_xml(
- self.stonith_id,
- SCSI_NODE,
- devices=devices_value,
- ),
+ stdout=crm_digests_xml,
args=args,
)
@@ -406,14 +468,16 @@ class UpdateScsiDevicesMixin:
self,
devices_before=DEVICES_1,
devices_updated=DEVICES_2,
- devices_add=(),
- devices_remove=(),
+ devices_add=None,
+ devices_remove=None,
unfence=None,
resource_ops=DEFAULT_OPS,
lrm_monitor_ops=DEFAULT_LRM_MONITOR_OPS,
lrm_start_ops=DEFAULT_LRM_START_OPS,
lrm_monitor_ops_updated=DEFAULT_LRM_MONITOR_OPS_UPDATED,
lrm_start_ops_updated=DEFAULT_LRM_START_OPS_UPDATED,
+ digests_attrs_list=None,
+ digests_attrs_list_updated=None,
):
# pylint: disable=too-many-arguments
self.config_cib(
@@ -422,6 +486,7 @@ class UpdateScsiDevicesMixin:
resource_ops=resource_ops,
lrm_monitor_ops=lrm_monitor_ops,
lrm_start_ops=lrm_start_ops,
+ digests_attrs_list=digests_attrs_list,
)
if unfence:
self.config.corosync_conf.load_content(
@@ -445,20 +510,34 @@ class UpdateScsiDevicesMixin:
devices=devices_updated,
resource_ops=resource_ops,
),
- status=_fixture_status_lrm_ops(
+ status=_fixture_status(
self.stonith_id,
self.stonith_type,
lrm_start_ops=lrm_start_ops_updated,
lrm_monitor_ops=lrm_monitor_ops_updated,
+ digests_attrs_list=digests_attrs_list_updated,
),
)
- self.command(
- devices_updated=devices_updated,
- devices_add=devices_add,
- devices_remove=devices_remove,
- )()
+ kwargs = dict(devices_updated=devices_updated)
+ if devices_add is not None:
+ kwargs["devices_add"] = devices_add
+ if devices_remove is not None:
+ kwargs["devices_remove"] = devices_remove
+ self.command(**kwargs)()
self.env_assist.assert_reports([])
+ def digest_attr_value_single(self, digest, last_comma=True):
+ comma = "," if last_comma else ""
+ return f"{self.stonith_id}:{self.stonith_type}:{digest}{comma}"
+
+ def digest_attr_value_multiple(self, digest, last_comma=True):
+ if self.stonith_type == STONITH_TYPE_SCSI:
+ value = f"{STONITH_ID_MPATH}:{STONITH_TYPE_MPATH}:{DEFAULT_DIGEST},"
+ else:
+ value = f"{STONITH_ID_SCSI}:{STONITH_TYPE_SCSI}:{DEFAULT_DIGEST},"
+
+ return f"{value}{self.digest_attr_value_single(digest, last_comma=last_comma)}"
+
class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
def test_pcmk_doesnt_support_digests(self):
@@ -567,9 +646,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
)
def test_no_lrm_start_op(self):
- self.config_cib(
- lrm_start_ops=(), start_digests=False, monitor_digests=False
- )
+ self.config_cib(lrm_start_ops=(), monitor_digests=False)
self.env_assist.assert_raise_library_error(
self.command(),
[
@@ -622,6 +699,59 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
expected_in_processor=False,
)
+ def test_crm_resource_digests_missing_for_transient_digests_attrs(self):
+ self.config_cib(
+ digests_attrs_list=[
+ [
+ (
+ "digests-secure",
+ self.digest_attr_value_single(ALL_DIGEST),
+ ),
+ ],
+ ],
+ crm_digests_xml=fixture_digests_xml(
+ self.stonith_id, SCSI_NODE, devices="", nonprivate=False
+ ),
+ )
+ self.env_assist.assert_raise_library_error(
+ self.command(),
+ [
+ fixture.error(
+ reports.codes.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM,
+ reason=(
+ "necessary digest for '#digests-secure' attribute is "
+ "missing"
+ ),
+ reason_type=reports.const.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM_REASON_OTHER,
+ )
+ ],
+ expected_in_processor=False,
+ )
+
+ def test_multiple_digests_attributes(self):
+ self.config_cib(
+ digests_attrs_list=[
+ 2
+ * [
+ (
+ "digests-all",
+ self.digest_attr_value_single(DEFAULT_DIGEST),
+ ),
+ ],
+ ],
+ )
+ self.env_assist.assert_raise_library_error(
+ self.command(),
+ [
+ fixture.error(
+ reports.codes.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM,
+ reason=("multiple digests attributes: '#digests-all'"),
+ reason_type=reports.const.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM_REASON_OTHER,
+ )
+ ],
+ expected_in_processor=False,
+ )
+
def test_monitor_ops_and_lrm_monitor_ops_do_not_match(self):
self.config_cib(
resource_ops=(
@@ -812,7 +942,7 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
stonith_type=self.stonith_type,
devices=DEVICES_2,
),
- status=_fixture_status_lrm_ops(
+ status=_fixture_status(
self.stonith_id,
self.stonith_type,
lrm_start_ops=DEFAULT_LRM_START_OPS_UPDATED,
@@ -959,6 +1089,28 @@ class UpdateScsiDevicesFailuresMixin(UpdateScsiDevicesMixin):
]
)
+ def test_transient_digests_attrs_bad_value_format(self):
+ bad_format = f"{DIGEST_ATTR_VALUE_GOOD_FORMAT}id:type,"
+ self.config_cib(
+ digests_attrs_list=[
+ [
+ ("digests-all", DIGEST_ATTR_VALUE_GOOD_FORMAT),
+ ("digests-secure", bad_format),
+ ]
+ ]
+ )
+ self.env_assist.assert_raise_library_error(
+ self.command(),
+ [
+ fixture.error(
+ reports.codes.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM,
+ reason=f"invalid digest attribute value: '{bad_format}'",
+ reason_type=reports.const.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM_REASON_OTHER,
+ )
+ ],
+ expected_in_processor=False,
+ )
+
class UpdateScsiDevicesSetBase(UpdateScsiDevicesMixin, CommandSetMixin):
def test_update_1_to_1_devices(self):
@@ -1002,80 +1154,6 @@ class UpdateScsiDevicesSetBase(UpdateScsiDevicesMixin, CommandSetMixin):
unfence=[DEV_3, DEV_4],
)
- def test_default_monitor(self):
- self.assert_command_success(unfence=[DEV_2])
-
- def test_no_monitor_ops(self):
- self.assert_command_success(
- unfence=[DEV_2],
- resource_ops=(),
- lrm_monitor_ops=(),
- lrm_monitor_ops_updated=(),
- )
-
- def test_1_monitor_with_timeout(self):
- self.assert_command_success(
- unfence=[DEV_2],
- resource_ops=(("monitor", "30s", "10s", None),),
- lrm_monitor_ops=(("30000", DEFAULT_DIGEST, None, None),),
- lrm_monitor_ops_updated=(("30000", ALL_DIGEST, None, None),),
- )
-
- def test_2_monitor_ops_with_timeouts(self):
- self.assert_command_success(
- unfence=[DEV_2],
- resource_ops=(
- ("monitor", "30s", "10s", None),
- ("monitor", "40s", "20s", None),
- ),
- lrm_monitor_ops=(
- ("30000", DEFAULT_DIGEST, None, None),
- ("40000", DEFAULT_DIGEST, None, None),
- ),
- lrm_monitor_ops_updated=(
- ("30000", ALL_DIGEST, None, None),
- ("40000", ALL_DIGEST, None, None),
- ),
- )
-
- def test_2_monitor_ops_with_one_timeout(self):
- self.assert_command_success(
- unfence=[DEV_2],
- resource_ops=(
- ("monitor", "30s", "10s", None),
- ("monitor", "60s", None, None),
- ),
- lrm_monitor_ops=(
- ("30000", DEFAULT_DIGEST, None, None),
- ("60000", DEFAULT_DIGEST, None, None),
- ),
- lrm_monitor_ops_updated=(
- ("30000", ALL_DIGEST, None, None),
- ("60000", ALL_DIGEST, None, None),
- ),
- )
-
- def test_various_start_ops_one_lrm_start_op(self):
- self.assert_command_success(
- unfence=[DEV_2],
- resource_ops=(
- ("monitor", "60s", None, None),
- ("start", "0s", "40s", None),
- ("start", "0s", "30s", "1"),
- ("start", "10s", "5s", None),
- ("start", "20s", None, None),
- ),
- )
-
- def test_1_nonrecurring_start_op_with_timeout(self):
- self.assert_command_success(
- unfence=[DEV_2],
- resource_ops=(
- ("monitor", "60s", None, None),
- ("start", "0s", "40s", None),
- ),
- )
-
class UpdateScsiDevicesAddRemoveBase(
UpdateScsiDevicesMixin, CommandAddRemoveMixin
@@ -1245,6 +1323,221 @@ class MpathFailuresMixin:
self.assert_failure("node1:1;node2=", ["node2", "node3"])
+class UpdateScsiDevicesDigestsBase(UpdateScsiDevicesMixin):
+ def test_default_monitor(self):
+ self.assert_command_success(unfence=[DEV_2])
+
+ def test_no_monitor_ops(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ resource_ops=(),
+ lrm_monitor_ops=(),
+ lrm_monitor_ops_updated=(),
+ )
+
+ def test_1_monitor_with_timeout(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ resource_ops=(("monitor", "30s", "10s", None),),
+ lrm_monitor_ops=(("30000", DEFAULT_DIGEST, None, None),),
+ lrm_monitor_ops_updated=(("30000", ALL_DIGEST, None, None),),
+ )
+
+ def test_2_monitor_ops_with_timeouts(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ resource_ops=(
+ ("monitor", "30s", "10s", None),
+ ("monitor", "40s", "20s", None),
+ ),
+ lrm_monitor_ops=(
+ ("30000", DEFAULT_DIGEST, None, None),
+ ("40000", DEFAULT_DIGEST, None, None),
+ ),
+ lrm_monitor_ops_updated=(
+ ("30000", ALL_DIGEST, None, None),
+ ("40000", ALL_DIGEST, None, None),
+ ),
+ )
+
+ def test_2_monitor_ops_with_one_timeout(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ resource_ops=(
+ ("monitor", "30s", "10s", None),
+ ("monitor", "60s", None, None),
+ ),
+ lrm_monitor_ops=(
+ ("30000", DEFAULT_DIGEST, None, None),
+ ("60000", DEFAULT_DIGEST, None, None),
+ ),
+ lrm_monitor_ops_updated=(
+ ("30000", ALL_DIGEST, None, None),
+ ("60000", ALL_DIGEST, None, None),
+ ),
+ )
+
+ def test_various_start_ops_one_lrm_start_op(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ resource_ops=(
+ ("monitor", "60s", None, None),
+ ("start", "0s", "40s", None),
+ ("start", "0s", "30s", "1"),
+ ("start", "10s", "5s", None),
+ ("start", "20s", None, None),
+ ),
+ )
+
+ def test_1_nonrecurring_start_op_with_timeout(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ resource_ops=(
+ ("monitor", "60s", None, None),
+ ("start", "0s", "40s", None),
+ ),
+ )
+
+ def _digests_attrs_before(self, last_comma=True):
+ return [
+ (
+ "digests-all",
+ self.digest_attr_value_single(DEFAULT_DIGEST, last_comma),
+ ),
+ (
+ "digests-secure",
+ self.digest_attr_value_single(DEFAULT_DIGEST, last_comma),
+ ),
+ ]
+
+ def _digests_attrs_after(self, last_comma=True):
+ return [
+ (
+ "digests-all",
+ self.digest_attr_value_single(ALL_DIGEST, last_comma),
+ ),
+ (
+ "digests-secure",
+ self.digest_attr_value_single(NONPRIVATE_DIGEST, last_comma),
+ ),
+ ]
+
+ def _digests_attrs_before_multi(self, last_comma=True):
+ return [
+ (
+ "digests-all",
+ self.digest_attr_value_multiple(DEFAULT_DIGEST, last_comma),
+ ),
+ (
+ "digests-secure",
+ self.digest_attr_value_multiple(DEFAULT_DIGEST, last_comma),
+ ),
+ ]
+
+ def _digests_attrs_after_multi(self, last_comma=True):
+ return [
+ (
+ "digests-all",
+ self.digest_attr_value_multiple(ALL_DIGEST, last_comma),
+ ),
+ (
+ "digests-secure",
+ self.digest_attr_value_multiple(NONPRIVATE_DIGEST, last_comma),
+ ),
+ ]
+
+ def test_transient_digests_attrs_all_nodes(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=len(self.existing_nodes)
+ * [self._digests_attrs_before()],
+ digests_attrs_list_updated=len(self.existing_nodes)
+ * [self._digests_attrs_after()],
+ )
+
+ def test_transient_digests_attrs_not_on_all_nodes(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=[self._digests_attrs_before()],
+ digests_attrs_list_updated=[self._digests_attrs_after()],
+ )
+
+ def test_transient_digests_attrs_all_nodes_multi_value(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=len(self.existing_nodes)
+ * [self._digests_attrs_before_multi()],
+ digests_attrs_list_updated=len(self.existing_nodes)
+ * [self._digests_attrs_after_multi()],
+ )
+
+ def test_transient_digests_attrs_not_on_all_nodes_multi_value(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=[self._digests_attrs_before()],
+ digests_attrs_list_updated=[self._digests_attrs_after()],
+ )
+
+ def test_transient_digests_attrs_not_all_digest_types(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=len(self.existing_nodes)
+ * [self._digests_attrs_before()[0:1]],
+ digests_attrs_list_updated=len(self.existing_nodes)
+ * [self._digests_attrs_after()[0:1]],
+ )
+
+ def test_transient_digests_attrs_without_digests_attrs(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=len(self.existing_nodes) * [[]],
+ digests_attrs_list_updated=len(self.existing_nodes) * [[]],
+ )
+
+ def test_transient_digests_attrs_without_last_comma(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=[self._digests_attrs_before(last_comma=False)],
+ digests_attrs_list_updated=[
+ self._digests_attrs_after(last_comma=False)
+ ],
+ )
+
+ def test_transient_digests_attrs_without_last_comma_multi_value(self):
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=[
+ self._digests_attrs_before_multi(last_comma=False)
+ ],
+ digests_attrs_list_updated=[
+ self._digests_attrs_after_multi(last_comma=False)
+ ],
+ )
+
+ def test_transient_digests_attrs_no_digest_for_our_stonith_id(self):
+ digests_attrs_list = len(self.existing_nodes) * [
+ [
+ ("digests-all", DIGEST_ATTR_VALUE_GOOD_FORMAT),
+ ("digests-secure", DIGEST_ATTR_VALUE_GOOD_FORMAT),
+ ]
+ ]
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=digests_attrs_list,
+ digests_attrs_list_updated=digests_attrs_list,
+ )
+
+ def test_transient_digests_attrs_digests_with_empty_value(self):
+ digests_attrs_list = len(self.existing_nodes) * [
+ [("digests-all", ""), ("digests-secure", "")]
+ ]
+ self.assert_command_success(
+ unfence=[DEV_2],
+ digests_attrs_list=digests_attrs_list,
+ digests_attrs_list_updated=digests_attrs_list,
+ )
+
+
@mock.patch.object(
settings,
"pacemaker_api_result_schema",
@@ -1337,3 +1630,47 @@ class TestUpdateScsiDevicesAddRemoveFailuresScsi(
UpdateScsiDevicesAddRemoveFailuresBaseMixin, ScsiMixin, TestCase
):
pass
+
+
+@mock.patch.object(
+ settings,
+ "pacemaker_api_result_schema",
+ rc("pcmk_api_rng/api-result.rng"),
+)
+class TestUpdateScsiDevicesDigestsSetScsi(
+ UpdateScsiDevicesDigestsBase, ScsiMixin, CommandSetMixin, TestCase
+):
+ pass
+
+
+@mock.patch.object(
+ settings,
+ "pacemaker_api_result_schema",
+ rc("pcmk_api_rng/api-result.rng"),
+)
+class TestUpdateScsiDevicesDigestsAddRemoveScsi(
+ UpdateScsiDevicesDigestsBase, ScsiMixin, CommandAddRemoveMixin, TestCase
+):
+ pass
+
+
+@mock.patch.object(
+ settings,
+ "pacemaker_api_result_schema",
+ rc("pcmk_api_rng/api-result.rng"),
+)
+class TestUpdateScsiDevicesDigestsSetMpath(
+ UpdateScsiDevicesDigestsBase, MpathMixin, CommandSetMixin, TestCase
+):
+ pass
+
+
+@mock.patch.object(
+ settings,
+ "pacemaker_api_result_schema",
+ rc("pcmk_api_rng/api-result.rng"),
+)
+class TestUpdateScsiDevicesDigestsAddRemoveMpath(
+ UpdateScsiDevicesDigestsBase, MpathMixin, CommandAddRemoveMixin, TestCase
+):
+ pass
--
2.39.2