diff --git a/src/frostfs_testlib/cli/frostfs_cli/control.py b/src/frostfs_testlib/cli/frostfs_cli/control.py index bfcd6eca..2cddfdfa 100644 --- a/src/frostfs_testlib/cli/frostfs_cli/control.py +++ b/src/frostfs_testlib/cli/frostfs_cli/control.py @@ -39,14 +39,12 @@ class FrostfsCliControl(CliCommand): address: Optional[str] = None, timeout: Optional[str] = None, ) -> CommandResult: - """Set status of the storage node in FrostFS network map + """Health check for FrostFS storage nodes Args: wallet: Path to the wallet or binary key address: Address of wallet account endpoint: Remote node control address (as 'multiaddr' or ':') - force: Force turning to local maintenance - status: New netmap status keyword ('online', 'offline', 'maintenance') timeout: Timeout for an operation (default 15s) Returns: @@ -56,3 +54,28 @@ class FrostfsCliControl(CliCommand): "control healthcheck", **{param: value for param, value in locals().items() if param not in ["self"]}, ) + + def drop_objects( + self, + endpoint: str, + objects: str, + wallet: Optional[str] = None, + address: Optional[str] = None, + timeout: Optional[str] = None, + ) -> CommandResult: + """Drop objects from the node's local storage + + Args: + wallet: Path to the wallet or binary key + address: Address of wallet account + endpoint: Remote node control address (as 'multiaddr' or ':') + objects: List of object addresses to be removed in string format + timeout: Timeout for an operation (default 15s) + + Returns: + Command`s result. + """ + return self._execute( + "control drop-objects", + **{param: value for param, value in locals().items() if param not in ["self"]}, + ) \ No newline at end of file diff --git a/src/frostfs_testlib/cli/frostfs_cli/object.py b/src/frostfs_testlib/cli/frostfs_cli/object.py index 0e4654bf..38a69e40 100644 --- a/src/frostfs_testlib/cli/frostfs_cli/object.py +++ b/src/frostfs_testlib/cli/frostfs_cli/object.py @@ -357,7 +357,7 @@ class FrostfsCliObject(CliCommand): wallet: Optional[str] = None, address: Optional[str] = None, bearer: Optional[str] = None, - generate_key: Optional = None, + generate_key: Optional[bool] = None, oid: Optional[str] = None, trace: bool = False, root: bool = False, diff --git a/src/frostfs_testlib/cli/frostfs_cli/shards.py b/src/frostfs_testlib/cli/frostfs_cli/shards.py index 17272490..4399b139 100644 --- a/src/frostfs_testlib/cli/frostfs_cli/shards.py +++ b/src/frostfs_testlib/cli/frostfs_cli/shards.py @@ -39,10 +39,10 @@ class FrostfsCliShards(CliCommand): def set_mode( self, endpoint: str, - wallet: str, - wallet_password: str, mode: str, id: Optional[list[str]], + wallet: Optional[str] = None, + wallet_password: Optional[str] = None, address: Optional[str] = None, all: bool = False, clear_errors: bool = False, @@ -65,6 +65,11 @@ class FrostfsCliShards(CliCommand): Returns: Command's result. """ + if not wallet_password: + return self._execute( + "control shards set-mode", + **{param: value for param, value in locals().items() if param not in ["self"]}, + ) return self._execute_with_password( "control shards set-mode", wallet_password, @@ -137,3 +142,4 @@ class FrostfsCliShards(CliCommand): wallet_password, **{param: value for param, value in locals().items() if param not in ["self", "wallet_password"]}, ) + diff --git a/src/frostfs_testlib/steps/node_management.py b/src/frostfs_testlib/steps/node_management.py index 28e38201..dd38279d 100644 --- a/src/frostfs_testlib/steps/node_management.py +++ b/src/frostfs_testlib/steps/node_management.py @@ -13,7 +13,6 @@ from frostfs_testlib.resources.common import MORPH_BLOCK_TIME from frostfs_testlib.shell import Shell from frostfs_testlib.steps.epoch import tick_epoch, wait_for_epochs_align from frostfs_testlib.storage.cluster import Cluster, StorageNode -from frostfs_testlib.storage.dataclasses.frostfs_services import S3Gate from frostfs_testlib.utils import datetime_utils logger = logging.getLogger("NeoLogger") @@ -52,9 +51,24 @@ def storage_node_healthcheck(node: StorageNode) -> HealthStatus: Returns: health status as HealthStatus object. """ - command = "control healthcheck" - output = _run_control_command_with_retries(node, command) - return HealthStatus.from_stdout(output) + + host = node.host + service_config = host.get_service_config(node.name) + wallet_path = service_config.attributes["wallet_path"] + wallet_password = service_config.attributes["wallet_password"] + control_endpoint = service_config.attributes["control_endpoint"] + + shell = host.get_shell() + wallet_config_path = f"/tmp/{node.name}-config.yaml" + wallet_config = f'wallet: {wallet_path}\npassword: "{wallet_password}"' + shell.exec(f"echo '{wallet_config}' > {wallet_config_path}") + + cli_config = host.get_cli_config("frostfs-cli") + + cli = FrostfsCli(shell, cli_config.exec_path, wallet_config_path) + result = cli.control.healthcheck(control_endpoint) + + return HealthStatus.from_stdout(result.stdout) @reporter.step("Set status for {node}") @@ -66,8 +80,21 @@ def storage_node_set_status(node: StorageNode, status: str, retries: int = 0) -> status: online or offline. retries (optional, int): number of retry attempts if it didn't work from the first time """ - command = f"control set-status --status {status}" - _run_control_command_with_retries(node, command, retries) + host = node.host + service_config = host.get_service_config(node.name) + wallet_path = service_config.attributes["wallet_path"] + wallet_password = service_config.attributes["wallet_password"] + control_endpoint = service_config.attributes["control_endpoint"] + + shell = host.get_shell() + wallet_config_path = f"/tmp/{node.name}-config.yaml" + wallet_config = f'wallet: {wallet_path}\npassword: "{wallet_password}"' + shell.exec(f"echo '{wallet_config}' > {wallet_config_path}") + + cli_config = host.get_cli_config("frostfs-cli") + + cli = FrostfsCli(shell, cli_config.exec_path, wallet_config_path) + cli.control.set_status(control_endpoint, status) @reporter.step("Get netmap snapshot") @@ -91,7 +118,7 @@ def get_netmap_snapshot(node: StorageNode, shell: Shell) -> str: @reporter.step("Get shard list for {node}") -def node_shard_list(node: StorageNode) -> list[str]: +def node_shard_list(node: StorageNode, json: Optional[bool] = None) -> list[str]: """ The function returns list of shards for specified storage node. Args: @@ -99,31 +126,72 @@ def node_shard_list(node: StorageNode) -> list[str]: Returns: list of shards. """ - command = "control shards list" - output = _run_control_command_with_retries(node, command) - return re.findall(r"Shard (.*):", output) + host = node.host + service_config = host.get_service_config(node.name) + wallet_path = service_config.attributes["wallet_path"] + wallet_password = service_config.attributes["wallet_password"] + control_endpoint = service_config.attributes["control_endpoint"] + + shell = host.get_shell() + wallet_config_path = f"/tmp/{node.name}-config.yaml" + wallet_config = f'wallet: {wallet_path}\npassword: "{wallet_password}"' + shell.exec(f"echo '{wallet_config}' > {wallet_config_path}") + + cli_config = host.get_cli_config("frostfs-cli") + + cli = FrostfsCli(shell, cli_config.exec_path, wallet_config_path) + result = cli.shards.list(endpoint=control_endpoint, json_mode=json) + + return re.findall(r"Shard (.*):", result.stdout) @reporter.step("Shard set for {node}") -def node_shard_set_mode(node: StorageNode, shard: str, mode: str) -> str: +def node_shard_set_mode(node: StorageNode, shard: list[str], mode: str) -> None: """ The function sets mode for specified shard. Args: node: node on which shard mode should be set. """ - command = f"control shards set-mode --id {shard} --mode {mode}" - return _run_control_command_with_retries(node, command) + host = node.host + service_config = host.get_service_config(node.name) + wallet_path = service_config.attributes["wallet_path"] + wallet_password = service_config.attributes["wallet_password"] + control_endpoint = service_config.attributes["control_endpoint"] + + shell = host.get_shell() + wallet_config_path = f"/tmp/{node.name}-config.yaml" + wallet_config = f'wallet: {wallet_path}\npassword: "{wallet_password}"' + shell.exec(f"echo '{wallet_config}' > {wallet_config_path}") + + cli_config = host.get_cli_config("frostfs-cli") + + cli = FrostfsCli(shell, cli_config.exec_path, wallet_config_path) + cli.shards.set_mode(endpoint=control_endpoint, mode=mode, id=shard) @reporter.step("Drop object from {node}") -def drop_object(node: StorageNode, cid: str, oid: str) -> str: +def drop_object(node: StorageNode, cid: str, oid: str) -> None: """ The function drops object from specified node. Args: - node_id str: node from which object should be dropped. + node: node from which object should be dropped. """ - command = f"control drop-objects -o {cid}/{oid}" - return _run_control_command_with_retries(node, command) + host = node.host + service_config = host.get_service_config(node.name) + wallet_path = service_config.attributes["wallet_path"] + wallet_password = service_config.attributes["wallet_password"] + control_endpoint = service_config.attributes["control_endpoint"] + + shell = host.get_shell() + wallet_config_path = f"/tmp/{node.name}-config.yaml" + wallet_config = f'wallet: {wallet_path}\npassword: "{wallet_password}"' + shell.exec(f"echo '{wallet_config}' > {wallet_config_path}") + + cli_config = host.get_cli_config("frostfs-cli") + + cli = FrostfsCli(shell, cli_config.exec_path, wallet_config_path) + objects = f"{cid}/{oid}" + cli.control.drop_objects(control_endpoint, objects) @reporter.step("Delete data from host for node {node}") @@ -238,38 +306,3 @@ def remove_nodes_from_map_morph( config_file=FROSTFS_ADM_CONFIG_PATH, ) frostfsadm.morph.remove_nodes(node_netmap_keys) - - -def _run_control_command_with_retries(node: StorageNode, command: str, retries: int = 0) -> str: - for attempt in range(1 + retries): # original attempt + specified retries - try: - return _run_control_command(node, command) - except AssertionError as err: - if attempt < retries: - logger.warning(f"Command {command} failed with error {err} and will be retried") - continue - raise AssertionError(f"Command {command} failed with error {err}") from err - - -def _run_control_command(node: StorageNode, command: str) -> None: - host = node.host - - service_config = host.get_service_config(node.name) - wallet_path = service_config.attributes["wallet_path"] - wallet_password = service_config.attributes["wallet_password"] - control_endpoint = service_config.attributes["control_endpoint"] - - shell = host.get_shell() - wallet_config_path = f"/tmp/{node.name}-config.yaml" - wallet_config = f'password: "{wallet_password}"' - shell.exec(f"echo '{wallet_config}' > {wallet_config_path}") - - cli_config = host.get_cli_config("frostfs-cli") - - # TODO: implement cli.control - # cli = FrostfsCli(shell, cli_config.exec_path, wallet_config_path) - result = shell.exec( - f"{cli_config.exec_path} {command} --endpoint {control_endpoint} " - f"--wallet {wallet_path} --config {wallet_config_path}" - ) - return result.stdout diff --git a/src/frostfs_testlib/storage/controllers/shards_watcher.py b/src/frostfs_testlib/storage/controllers/shards_watcher.py index 95a419eb..ad07ff4f 100644 --- a/src/frostfs_testlib/storage/controllers/shards_watcher.py +++ b/src/frostfs_testlib/storage/controllers/shards_watcher.py @@ -97,8 +97,6 @@ class ShardsWatcher: response = shards_cli.list( endpoint=self.storage_node.get_control_endpoint(), - wallet=self.storage_node.get_remote_wallet_path(), - wallet_password=self.storage_node.get_wallet_password(), json_mode=True, ) @@ -110,9 +108,7 @@ class ShardsWatcher: self.storage_node.host.get_cli_config("frostfs-cli").exec_path, ) return shards_cli.set_mode( - self.storage_node.get_control_endpoint(), - self.storage_node.get_remote_wallet_path(), - self.storage_node.get_wallet_password(), + endpoint=self.storage_node.get_control_endpoint(), mode=mode, id=[shard_id], clear_errors=clear_errors,