From b1a3d740e99e7d7e9ec7fdcb939c3b0572ce989a Mon Sep 17 00:00:00 2001 From: Andrey Berezin Date: Wed, 25 Oct 2023 15:57:38 +0300 Subject: [PATCH] [#102] Updates for failover Signed-off-by: Andrey Berezin --- pyproject.toml | 2 +- src/frostfs_testlib/storage/cluster.py | 36 +++++++++++++++++++ .../controllers/cluster_state_controller.py | 4 +-- .../storage/dataclasses/frostfs_services.py | 3 ++ .../storage/dataclasses/node_base.py | 14 +++++--- 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bf65d15..3178bbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "neo-mamba==1.0.0", "paramiko>=2.10.3", "pexpect>=4.8.0", - "requests>=2.28.0", + "requests==2.28.1", "docstring_parser>=0.15", "testrail-api>=1.12.0", "pytest==7.1.2", diff --git a/src/frostfs_testlib/storage/cluster.py b/src/frostfs_testlib/storage/cluster.py index 7a48a1d..fa4ee0a 100644 --- a/src/frostfs_testlib/storage/cluster.py +++ b/src/frostfs_testlib/storage/cluster.py @@ -208,6 +208,42 @@ class Cluster: def morph_chain(self) -> list[MorphChain]: return self.services(MorphChain) + def nodes(self, services: list[ServiceClass]) -> list[ClusterNode]: + """ + Resolve which cluster nodes hosting the specified services. + + Args: + services: list of services to resolve hosting cluster nodes. + + Returns: + list of cluster nodes which host specified services. + """ + + cluster_nodes = set() + for service in services: + cluster_nodes.update( + [node for node in self.cluster_nodes if node.service(type(service)) == service] + ) + + return list(cluster_nodes) + + def node(self, service: ServiceClass) -> ClusterNode: + """ + Resolve single cluster node hosting the specified service. + + Args: + services: list of services to resolve hosting cluster nodes. + + Returns: + list of cluster nodes which host specified services. + """ + + nodes = [node for node in self.cluster_nodes if node.service(type(service)) == service] + if not len(nodes): + raise RuntimeError(f"Cannot find service {service} on any node") + + return nodes[0] + def services(self, service_type: type[ServiceClass]) -> list[ServiceClass]: """ Get all services in a cluster of specified type. diff --git a/src/frostfs_testlib/storage/controllers/cluster_state_controller.py b/src/frostfs_testlib/storage/controllers/cluster_state_controller.py index c6391f5..7304f5d 100644 --- a/src/frostfs_testlib/storage/controllers/cluster_state_controller.py +++ b/src/frostfs_testlib/storage/controllers/cluster_state_controller.py @@ -41,10 +41,10 @@ class ClusterStateController: provider = SshConnectionProvider() provider.drop(node.host_ip) + self.stopped_nodes.append(node) with reporter.step(f"Stop host {node.host.config.address}"): node.host.stop_host(mode=mode) wait_for_host_offline(self.shell, node.storage_node) - self.stopped_nodes.append(node) @run_optionally(optionals.OPTIONAL_FAILOVER_ENABLED) @reporter.step_deco("Shutdown whole cluster") @@ -136,8 +136,8 @@ class ClusterStateController: @run_optionally(optionals.OPTIONAL_FAILOVER_ENABLED) @reporter.step_deco("Stop storage service on {node}") def stop_storage_service(self, node: ClusterNode): - node.storage_node.stop_service() self.stopped_storage_nodes.append(node) + node.storage_node.stop_service() @run_optionally(optionals.OPTIONAL_FAILOVER_ENABLED) @reporter.step_deco("Stop all {service_type} services") diff --git a/src/frostfs_testlib/storage/dataclasses/frostfs_services.py b/src/frostfs_testlib/storage/dataclasses/frostfs_services.py index 9e6783c..6413ded 100644 --- a/src/frostfs_testlib/storage/dataclasses/frostfs_services.py +++ b/src/frostfs_testlib/storage/dataclasses/frostfs_services.py @@ -145,6 +145,9 @@ class StorageNode(NodeBase): def get_shard_config_path(self) -> str: return self._get_attribute(ConfigAttributes.SHARD_CONFIG_PATH) + def get_shards_config(self) -> tuple[str, dict]: + return self.get_config(self.get_shard_config_path()) + def get_control_endpoint(self) -> str: return self._get_attribute(ConfigAttributes.CONTROL_ENDPOINT) diff --git a/src/frostfs_testlib/storage/dataclasses/node_base.py b/src/frostfs_testlib/storage/dataclasses/node_base.py index 3b1964c..5352080 100644 --- a/src/frostfs_testlib/storage/dataclasses/node_base.py +++ b/src/frostfs_testlib/storage/dataclasses/node_base.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Optional, Tuple, TypedDict, TypeVar +from typing import Optional, TypedDict, TypeVar import yaml @@ -103,8 +103,10 @@ class NodeBase(HumanReadableABC): ConfigAttributes.WALLET_CONFIG, ) - def get_config(self) -> Tuple[str, dict]: - config_file_path = self._get_attribute(ConfigAttributes.CONFIG_PATH) + def get_config(self, config_file_path: Optional[str] = None) -> tuple[str, dict]: + if config_file_path is None: + config_file_path = self._get_attribute(ConfigAttributes.CONFIG_PATH) + shell = self.host.get_shell() result = shell.exec(f"cat {config_file_path}") @@ -113,8 +115,10 @@ class NodeBase(HumanReadableABC): config = yaml.safe_load(config_text) return config_file_path, config - def save_config(self, new_config: dict) -> None: - config_file_path = self._get_attribute(ConfigAttributes.CONFIG_PATH) + def save_config(self, new_config: dict, config_file_path: Optional[str] = None) -> None: + if config_file_path is None: + config_file_path = self._get_attribute(ConfigAttributes.CONFIG_PATH) + shell = self.host.get_shell() config_str = yaml.dump(new_config)