diff --git a/.gitignore b/.gitignore index 06c771f..9ed2ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,7 @@ /pcs-0.11.5.tar.gz /pcs-web-ui-0.1.16.1.tar.gz /pcs-web-ui-node-modules-0.1.16.1.tar.xz +/pcs-0.11.6.tar.gz +/pcs-web-ui-node-modules-0.1.17.tar.xz +/pcs-web-ui-0.1.17.tar.gz +/dacite-1.8.1.tar.gz diff --git a/fix-broken-typeahead-component.patch b/fix-broken-typeahead-component.patch deleted file mode 100644 index d75e81f..0000000 --- a/fix-broken-typeahead-component.patch +++ /dev/null @@ -1,67 +0,0 @@ -From b53feb9febd8be85ab8eb90fda02478e4d7e9fea Mon Sep 17 00:00:00 2001 -From: Ivan Devat -Date: Tue, 13 Dec 2022 12:58:00 +0100 -Subject: [PATCH 1/2] fix agents filter in resource/fence device create - ---- - .../cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx | 4 ++-- - .../view/cluster/resources/task/create/NameTypeTypeSelect.tsx | 4 ++-- - src/app/view/share/form/Select.tsx | 2 +- - 3 files changed, 5 insertions(+), 5 deletions(-) - -diff --git a/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx b/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx -index 80327801..8d623e2b 100644 ---- a/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx -+++ b/src/app/view/cluster/fenceDevices/task/create/NameTypeTypeSelect.tsx -@@ -38,13 +38,13 @@ export const NameTypeTypeSelect = ({ - return ( - 0 ? agentName : undefined} - data-test="resource-agent" - > -diff --git a/src/app/view/share/form/Select.tsx b/src/app/view/share/form/Select.tsx -index d73f126c..e2b81ce2 100644 ---- a/src/app/view/share/form/Select.tsx -+++ b/src/app/view/share/form/Select.tsx -@@ -31,7 +31,7 @@ export const Select = ( - const filter = onFilter - ? (_event: React.ChangeEvent | null, value: string) => { - onFilter(value); -- return null as unknown as React.ReactElement[]; -+ return undefined; - } - : null; - --- -2.39.2 - diff --git a/fix-cluster-status-fence-levels.patch b/fix-cluster-status-fence-levels.patch deleted file mode 100644 index e21c253..0000000 --- a/fix-cluster-status-fence-levels.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 9e960df30366a93c3b383b34612451817cc617d8 Mon Sep 17 00:00:00 2001 -From: Ivan Devat -Date: Thu, 30 Mar 2023 17:03:06 +0200 -Subject: [PATCH 2/2] fix cluster-status/fence_levels shape expectation - ---- - jest.config.js | 1 + - .../endpoints/clusterStatus/shape/cluster.ts | 10 +++-- - .../cluster/displayAdvancedStatus.test.ts | 37 +++++++++++++++++++ - 3 files changed, 44 insertions(+), 4 deletions(-) - create mode 100644 src/test/scenes/cluster/displayAdvancedStatus.test.ts - -diff --git a/jest.config.js b/jest.config.js -index 08660443..c5c39dc5 100644 ---- a/jest.config.js -+++ b/jest.config.js -@@ -1,4 +1,5 @@ - module.exports = { - globalSetup: "./src/test/jest-preset.ts", - moduleDirectories: ["node_modules", "src"], -+ testTimeout: 10000, - }; -diff --git a/src/app/backend/endpoints/clusterStatus/shape/cluster.ts b/src/app/backend/endpoints/clusterStatus/shape/cluster.ts -index 97ec4f17..ea29470e 100644 ---- a/src/app/backend/endpoints/clusterStatus/shape/cluster.ts -+++ b/src/app/backend/endpoints/clusterStatus/shape/cluster.ts -@@ -13,10 +13,12 @@ The key of record is a target. - */ - const ApiFencingLevels = t.record( - t.string, -- t.type({ -- level: t.string, -- devices: t.array(t.string), -- }), -+ t.array( -+ t.type({ -+ level: t.string, -+ devices: t.string, -+ }), -+ ), - ); - - export const ApiClusterStatusFlag = t.keyof({ -diff --git a/src/test/scenes/cluster/displayAdvancedStatus.test.ts b/src/test/scenes/cluster/displayAdvancedStatus.test.ts -new file mode 100644 -index 00000000..78eb7dbe ---- /dev/null -+++ b/src/test/scenes/cluster/displayAdvancedStatus.test.ts -@@ -0,0 +1,37 @@ -+// Cluster status is pretty complex. Sometimes a discrepancy between frontend -+// and backend appears. This modules collect tests for discovered cases. -+ -+import * as t from "dev/responses/clusterStatus/tools"; -+ -+import {dt} from "test/tools/selectors"; -+import {location, shortcuts} from "test/tools"; -+ -+const clusterName = "test-cluster"; -+ -+// We want to see browser behavior with (for now) invalid status before fix. But -+// the typecheck tell us that it is wrong and dev build fails. So, we decive it. -+const deceiveTypeCheck = (maybeInvalidPart: ReturnType) => -+ JSON.parse(JSON.stringify(maybeInvalidPart)); -+ -+describe("Cluster with advanced status", () => { -+ it("accept fence levels", async () => { -+ shortcuts.interceptWithCluster({ -+ clusterStatus: t.cluster(clusterName, "ok", { -+ fence_levels: deceiveTypeCheck({ -+ "node-1": [ -+ { -+ level: "1", -+ devices: "fence-1", -+ }, -+ { -+ level: "2", -+ devices: "fence-2", -+ }, -+ ], -+ }), -+ }), -+ }); -+ await page.goto(location.cluster({clusterName})); -+ await page.waitForSelector(dt("cluster-overview")); -+ }); -+}); --- -2.39.2 - diff --git a/fix-pcs-config-checkpoint-diff.patch b/fix-pcs-config-checkpoint-diff.patch deleted file mode 100644 index 3046951..0000000 --- a/fix-pcs-config-checkpoint-diff.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 22b275bd8571b7b949b23c5c39d05fbc9e812e12 Mon Sep 17 00:00:00 2001 -From: Miroslav Lisik -Date: Mon, 6 Mar 2023 15:42:35 +0100 -Subject: [PATCH 1/2] fix `pcs config checkpoint diff` command - ---- - pcs/cli/common/lib_wrapper.py | 15 +-------------- - pcs/config.py | 3 +++ - 2 files changed, 4 insertions(+), 14 deletions(-) - -diff --git a/pcs/cli/common/lib_wrapper.py b/pcs/cli/common/lib_wrapper.py -index dabc5fd2..1c9e2f4d 100644 ---- a/pcs/cli/common/lib_wrapper.py -+++ b/pcs/cli/common/lib_wrapper.py -@@ -1,9 +1,5 @@ - import logging - from collections import namedtuple --from typing import ( -- Any, -- Dict, --) - - from pcs import settings - from pcs.cli.common import middleware -@@ -36,9 +32,6 @@ from pcs.lib.commands.constraint import order as constraint_order - from pcs.lib.commands.constraint import ticket as constraint_ticket - from pcs.lib.env import LibraryEnvironment - --# Note: not properly typed --_CACHE: Dict[Any, Any] = {} -- - - def wrapper(dictionary): - return namedtuple("wrapper", dictionary.keys())(**dictionary) -@@ -106,12 +99,6 @@ def bind_all(env, run_with_middleware, dictionary): - ) - - --def get_module(env, middleware_factory, name): -- if name not in _CACHE: -- _CACHE[name] = load_module(env, middleware_factory, name) -- return _CACHE[name] -- -- - def load_module(env, middleware_factory, name): - # pylint: disable=too-many-return-statements, too-many-branches - if name == "acl": -@@ -547,4 +534,4 @@ class Library: - self.middleware_factory = middleware_factory - - def __getattr__(self, name): -- return get_module(self.env, self.middleware_factory, name) -+ return load_module(self.env, self.middleware_factory, name) -diff --git a/pcs/config.py b/pcs/config.py -index e0d179f0..6da1151b 100644 ---- a/pcs/config.py -+++ b/pcs/config.py -@@ -691,6 +691,7 @@ def _checkpoint_to_lines(lib, checkpoint_number): - orig_usefile = utils.usefile - orig_filename = utils.filename - orig_middleware = lib.middleware_factory -+ orig_env = lib.env - # configure old code to read the CIB from a file - utils.usefile = True - utils.filename = os.path.join( -@@ -700,6 +701,7 @@ def _checkpoint_to_lines(lib, checkpoint_number): - lib.middleware_factory = orig_middleware._replace( - cib=middleware.cib(utils.filename, utils.touch_cib_file) - ) -+ lib.env = utils.get_cli_env() - # export the CIB to text - result = False, [] - if os.path.isfile(utils.filename): -@@ -708,6 +710,7 @@ def _checkpoint_to_lines(lib, checkpoint_number): - utils.usefile = orig_usefile - utils.filename = orig_filename - lib.middleware_factory = orig_middleware -+ lib.env = orig_env - return result - - --- -2.39.2 - diff --git a/fix-pcs-stonith-update-scsi-devices.patch b/fix-pcs-stonith-update-scsi-devices.patch deleted file mode 100644 index cb04eb7..0000000 --- a/fix-pcs-stonith-update-scsi-devices.patch +++ /dev/null @@ -1,975 +0,0 @@ -From 16cfc184b4544156df36fa66f7cb5fbf015b18f1 Mon Sep 17 00:00:00 2001 -From: Miroslav Lisik -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 b730fbbf..6eec6611 100644 ---- a/pcs/lib/cib/resource/stonith.py -+++ b/pcs/lib/cib/resource/stonith.py -@@ -173,12 +173,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( -@@ -282,21 +334,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: -+ ::,... -+ -+ 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( -@@ -315,6 +435,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: -@@ -345,17 +467,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( -@@ -368,7 +487,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( -@@ -384,7 +503,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", -@@ -413,3 +532,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""" -- -- -- -- -- -- {lrm_ops} -- -- -- -- -- -+ -+ -+ -+ {lrm_ops} -+ -+ -+ -+ """ -+ -+ -+def _fixture_digest_nvpair(node_id, digest_name, digest_value): -+ return ( -+ f'' -+ ) -+ -+ -+def _fixture_transient_attributes(node_id, digests_nvpairs): -+ return f""" -+ -+ -+ -+ -+ {digests_nvpairs} -+ -+ -+ """ -+ -+ -+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""" -+ -+ {lrm_ops} -+ {transient_attrs} -+ - """ - - --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""" -+ -+ {node_states} -+ -+ """ -+ - -+def fixture_digests_xml(resource_id, node_name, devices="", nonprivate=True): -+ nonprivate_xml = ( -+ f""" -+ -+ -+ -+ """ -+ if nonprivate -+ else "" -+ ) - --def fixture_digests_xml(resource_id, node_name, devices=""): - return f""" - - - - - -- -- -- -+ {nonprivate_xml} - - - -@@ -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 - diff --git a/pcs.spec b/pcs.spec index a3ec696..18a7eea 100644 --- a/pcs.spec +++ b/pcs.spec @@ -1,6 +1,6 @@ Name: pcs -Version: 0.11.5 -Release: 2%{?dist} +Version: 0.11.6 +Release: 1%{?dist} # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses # GPL-2.0-only: pcs @@ -17,14 +17,14 @@ BuildArch: noarch %global pcs_source_name %{name}-%{version_or_commit} # ui_commit can be determined by hash, tag or branch -%global ui_commit 0.1.16.1 -%global ui_modules_version 0.1.16.1 +%global ui_commit 0.1.17 +%global ui_modules_version 0.1.17 %global ui_src_name pcs-web-ui-%{ui_commit} %global pcs_snmp_pkg_name pcs-snmp %global pyagentx_version 0.4.pcs.2 -%global dacite_version 1.8.0 +%global dacite_version 1.8.1 %global required_pacemaker_version 2.1.0 @@ -47,12 +47,9 @@ Source101: https://github.com/ClusterLabs/pcs-web-ui/releases/download/%{ui_comm # pcs patches: <= 200 # Patch0: name.patch -Patch0: fix-pcs-config-checkpoint-diff.patch -Patch1: fix-pcs-stonith-update-scsi-devices.patch # ui patches: >200 -Patch201: fix-broken-typeahead-component.patch -Patch202: fix-cluster-status-fence-levels.patch +# Patch201: name-web-ui.patch # git for patches BuildRequires: git-core @@ -105,8 +102,6 @@ BuildRequires: pam # for working with qdevice certificates (certutil) - used in configure.ac BuildRequires: nss-tools -# for creating the web ui favicon symlink to the Fedora logo -BuildRequires: fedora-logos # for building web ui %if 0%{?fedora} < 37 BuildRequires: npm @@ -169,8 +164,6 @@ Requires: pam Requires: logrotate # for working with qdevice certificates (certutil) Requires: nss-tools -# for web ui favicon - symlink to the Fedora logo -Requires: fedora-logos Provides: bundled(dacite) = %{dacite_version} @@ -250,15 +243,11 @@ update_times_patch(){ %autosetup -D -T -b 100 -a 101 -S git -n %{ui_src_name} -N %autopatch -p1 -m 201 # update_times_patch %%{PATCH201} -update_times_patch %{PATCH201} -update_times_patch %{PATCH202} # patch pcs sources %autosetup -S git -n %{pcs_source_name} -N %autopatch -p1 -M 200 # update_times_patch %%{PATCH0} -update_times_patch %{PATCH0} -update_times_patch %{PATCH1} # generate .tarball-version if building from an untagged commit, not a released version # autogen uses git-version-gen which uses .tarball-version for generating version number @@ -289,7 +278,7 @@ cp -f %SOURCE42 rpm/ make all # build pcs-web-ui -make -C %{_builddir}/%{ui_src_name} build BUILD_USE_EXISTING_NODE_MODULES=true +BUILD_USE_CURRENT_NODE_MODULES=true make -C %{_builddir}/%{ui_src_name} build %install rm -rf $RPM_BUILD_ROOT @@ -298,13 +287,8 @@ pwd %make_install # install pcs-web-ui -# cp -r %%{_builddir}/%%{ui_src_name}/build ${RPM_BUILD_ROOT}%%{_prefix}/lib/%%{pcsd_public_dir}/ui make -C %{_builddir}/%{ui_src_name} _install PCSD_DIR=${RPM_BUILD_ROOT}%{_prefix}/lib/pcsd -# symlink favicon into pcsd directories -mkdir -p ${RPM_BUILD_ROOT}%{_prefix}/lib/%{pcsd_public_dir}/images/ -ln -fs /etc/favicon.png ${RPM_BUILD_ROOT}%{_prefix}/lib/%{pcsd_public_dir}/images/favicon.png - # prepare license files cp %{pcs_bundled_dir}/src/pyagentx-*/LICENSE.txt pyagentx_LICENSE.txt cp %{pcs_bundled_dir}/src/pyagentx-*/CONTRIBUTORS.txt pyagentx_CONTRIBUTORS.txt @@ -425,6 +409,12 @@ run_all_tests %license pyagentx_LICENSE.txt %changelog +* Wed Jun 21 2023 Michal Pospisil - 0.11.6-1 +- Rebased to the latest upstream sources (see CHANGELOG.md) +- Updated pcs-web-ui +- Removed dependency fedora-logos - favicon is now correctly provided by pcs-web-ui +- Resolves: rhbz#2109852 rhbz#2170648 + * Wed Apr 12 2023 Michal Pospisil - 0.11.5-2 - Fix displaying differences between configuration checkpoints in “pcs config checkpoint diff” command - Fix “pcs stonith update-scsi-devices” command which was broken since Pacemaker-2.1.5-rc1 diff --git a/sources b/sources index c0b4a02..02e35b4 100644 --- a/sources +++ b/sources @@ -1,5 +1,5 @@ SHA512 (pyagentx-0.4.pcs.2.tar.gz) = d4194fec9a3e5fefe3793d49b7fec1feafef294c7e613a06046c2993daeefc5cb39d7c5b2b402ff83e49b2d976953f862264288c758c0be09d997b5323cc558a -SHA512 (dacite-1.8.0.tar.gz) = 97816021b8285197bcd00d9323dd61b33b3a2e51424287ff8e63545082ae9c6b45a416cd2ff2daff41b271a0312192d6a0ec967f6f929402d3c6b8c12b809e08 -SHA512 (pcs-0.11.5.tar.gz) = 2c344537d41416c9af22a18057523b22a1b40b6354ad4b7c7061f1a1690828553ba1d7d10cff0c8adda3c7ef0bee5ce76ece8f2a05d80865f49b71c676fed6aa -SHA512 (pcs-web-ui-0.1.16.1.tar.gz) = 01427f35276cd5ee2926d6541ec9ccce7e86ec592d294dfb08b086ca701e6b937563ac09ba2b5e82b342b58234f41f6cec38bb22599a3b5181db96e0a0382004 -SHA512 (pcs-web-ui-node-modules-0.1.16.1.tar.xz) = 6263f14ba017ed98a17985ee2899f25eb97288f62ef2ded90b0217702bc30d0aa80238f1787e34f6ae6b276df2543451eda44422c1df7cade96617209de5c62d +SHA512 (pcs-0.11.6.tar.gz) = ea439808070b171d02e1d6e071bd083fb4c4e30d92c2e637810c89cbb5feec2c56aabfbcda4c25eccb7ab46df2cd56f192dbd6a29f4c7c1fe7aca5a82f4a42ef +SHA512 (pcs-web-ui-node-modules-0.1.17.tar.xz) = 51f47be3b28a378542ebe862a333e8883bbcf4e40a2aea685986b9d17db91dab01cb3b58c9ffba56f335bedfe0a9477ebb1ff93824228ed5348f864afad5b98d +SHA512 (pcs-web-ui-0.1.17.tar.gz) = a5dd551c47040d9c9a2f714a83b835aaf5cca8d5dd05c83f641ddecdb7d99ac82a3b265df508c0ec1bc51ea572210d6255d79631f4680205c6302cb89460d14c +SHA512 (dacite-1.8.1.tar.gz) = 4b40c0bdcf5490bcc77de9e7f04b7267642bcfd41e4168607a5457f38abe3ad4b3041d8a23cb43af76de14eabee45f900ad5ddf7af8f70a2be4850bccc2d3af1