forked from TrueCloudLab/frostfs-testlib
[#191] Credentials work overhaul
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
This commit is contained in:
parent
09a7f66d1e
commit
25925c637b
31 changed files with 370 additions and 485 deletions
|
@ -1,6 +1,5 @@
|
|||
import copy
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import frostfs_testlib.resources.optionals as optionals
|
||||
from frostfs_testlib import reporter
|
||||
|
@ -10,7 +9,6 @@ from frostfs_testlib.load.load_report import LoadReport
|
|||
from frostfs_testlib.load.load_verifiers import LoadVerifier
|
||||
from frostfs_testlib.storage.cluster import ClusterNode
|
||||
from frostfs_testlib.storage.dataclasses.frostfs_services import S3Gate, StorageNode
|
||||
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
|
||||
from frostfs_testlib.testing.parallel import parallel
|
||||
from frostfs_testlib.testing.test_control import run_optionally
|
||||
|
||||
|
@ -23,7 +21,6 @@ class BackgroundLoadController:
|
|||
cluster_nodes: list[ClusterNode]
|
||||
nodes_under_load: list[ClusterNode]
|
||||
load_counter: int
|
||||
loaders_wallet: WalletInfo
|
||||
load_summaries: dict
|
||||
endpoints: list[str]
|
||||
runner: ScenarioRunner
|
||||
|
@ -34,7 +31,6 @@ class BackgroundLoadController:
|
|||
self,
|
||||
k6_dir: str,
|
||||
load_params: LoadParams,
|
||||
loaders_wallet: WalletInfo,
|
||||
cluster_nodes: list[ClusterNode],
|
||||
nodes_under_load: list[ClusterNode],
|
||||
runner: ScenarioRunner,
|
||||
|
@ -45,7 +41,6 @@ class BackgroundLoadController:
|
|||
self.cluster_nodes = cluster_nodes
|
||||
self.nodes_under_load = nodes_under_load
|
||||
self.load_counter = 1
|
||||
self.loaders_wallet = loaders_wallet
|
||||
self.runner = runner
|
||||
self.started = False
|
||||
self.load_reporters = []
|
||||
|
@ -64,10 +59,7 @@ class BackgroundLoadController:
|
|||
)
|
||||
),
|
||||
EndpointSelectionStrategy.FIRST: list(
|
||||
set(
|
||||
node_under_load.service(StorageNode).get_rpc_endpoint()
|
||||
for node_under_load in self.nodes_under_load
|
||||
)
|
||||
set(node_under_load.service(StorageNode).get_rpc_endpoint() for node_under_load in self.nodes_under_load)
|
||||
),
|
||||
},
|
||||
# for some reason xk6 appends http protocol on its own
|
||||
|
|
|
@ -11,12 +11,13 @@ from frostfs_testlib.healthcheck.interfaces import Healthcheck
|
|||
from frostfs_testlib.hosting.interfaces import HostStatus
|
||||
from frostfs_testlib.plugins import load_all
|
||||
from frostfs_testlib.resources.cli import FROSTFS_ADM_CONFIG_PATH, FROSTFS_ADM_EXEC, FROSTFS_CLI_EXEC
|
||||
from frostfs_testlib.resources.common import DEFAULT_WALLET_CONFIG, MORPH_BLOCK_TIME
|
||||
from frostfs_testlib.resources.common import MORPH_BLOCK_TIME
|
||||
from frostfs_testlib.shell import CommandOptions, Shell, SshConnectionProvider
|
||||
from frostfs_testlib.steps.network import IpHelper
|
||||
from frostfs_testlib.storage.cluster import Cluster, ClusterNode, S3Gate, StorageNode
|
||||
from frostfs_testlib.storage.controllers.disk_controller import DiskController
|
||||
from frostfs_testlib.storage.dataclasses.node_base import NodeBase, ServiceClass
|
||||
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
|
||||
from frostfs_testlib.testing import parallel
|
||||
from frostfs_testlib.testing.test_control import retry, run_optionally, wait_for_success
|
||||
from frostfs_testlib.utils.datetime_utils import parse_time
|
||||
|
@ -413,12 +414,12 @@ class ClusterStateController:
|
|||
frostfs_adm.morph.set_config(set_key_value=f"MaintenanceModeAllowed={status}")
|
||||
|
||||
@reporter.step("Set mode node to {status}")
|
||||
def set_mode_node(self, cluster_node: ClusterNode, wallet: str, status: str, await_tick: bool = True) -> None:
|
||||
def set_mode_node(self, cluster_node: ClusterNode, wallet: WalletInfo, status: str, await_tick: bool = True) -> None:
|
||||
rpc_endpoint = cluster_node.storage_node.get_rpc_endpoint()
|
||||
control_endpoint = cluster_node.service(StorageNode).get_control_endpoint()
|
||||
|
||||
frostfs_adm, frostfs_cli, frostfs_cli_remote = self._get_cli(local_shell=self.shell, cluster_node=cluster_node)
|
||||
node_netinfo = NetmapParser.netinfo(frostfs_cli.netmap.netinfo(rpc_endpoint=rpc_endpoint, wallet=wallet).stdout)
|
||||
frostfs_adm, frostfs_cli, frostfs_cli_remote = self._get_cli(local_shell=self.shell, local_wallet=wallet, cluster_node=cluster_node)
|
||||
node_netinfo = NetmapParser.netinfo(frostfs_cli.netmap.netinfo(rpc_endpoint=rpc_endpoint).stdout)
|
||||
|
||||
with reporter.step("If status maintenance, then check that the option is enabled"):
|
||||
if node_netinfo.maintenance_mode_allowed == "false":
|
||||
|
@ -437,12 +438,10 @@ class ClusterStateController:
|
|||
self.check_node_status(status=status, wallet=wallet, cluster_node=cluster_node)
|
||||
|
||||
@wait_for_success(80, 8, title="Wait for storage status become {status}")
|
||||
def check_node_status(self, status: str, wallet: str, cluster_node: ClusterNode):
|
||||
frostfs_cli = FrostfsCli(
|
||||
shell=self.shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=DEFAULT_WALLET_CONFIG
|
||||
)
|
||||
def check_node_status(self, status: str, wallet: WalletInfo, cluster_node: ClusterNode):
|
||||
frostfs_cli = FrostfsCli(self.shell, FROSTFS_CLI_EXEC, wallet.config_path)
|
||||
netmap = NetmapParser.snapshot_all_nodes(
|
||||
frostfs_cli.netmap.snapshot(rpc_endpoint=cluster_node.storage_node.get_rpc_endpoint(), wallet=wallet).stdout
|
||||
frostfs_cli.netmap.snapshot(rpc_endpoint=cluster_node.storage_node.get_rpc_endpoint()).stdout
|
||||
)
|
||||
netmap = [node for node in netmap if cluster_node.host_ip == node.node]
|
||||
if status == "offline":
|
||||
|
@ -450,7 +449,9 @@ class ClusterStateController:
|
|||
else:
|
||||
assert netmap[0].node_status == status.upper(), f"Node state - {netmap[0].node_status} != {status} expect"
|
||||
|
||||
def _get_cli(self, local_shell: Shell, cluster_node: ClusterNode) -> tuple[FrostfsAdm, FrostfsCli, FrostfsCli]:
|
||||
def _get_cli(
|
||||
self, local_shell: Shell, local_wallet: WalletInfo, cluster_node: ClusterNode
|
||||
) -> tuple[FrostfsAdm, FrostfsCli, FrostfsCli]:
|
||||
# TODO Move to service config
|
||||
host = cluster_node.host
|
||||
service_config = host.get_service_config(cluster_node.storage_node.name)
|
||||
|
@ -462,12 +463,8 @@ class ClusterStateController:
|
|||
wallet_config = f'wallet: {wallet_path}\npassword: "{wallet_password}"'
|
||||
shell.exec(f"echo '{wallet_config}' > {wallet_config_path}")
|
||||
|
||||
frostfs_adm = FrostfsAdm(
|
||||
shell=shell, frostfs_adm_exec_path=FROSTFS_ADM_EXEC, config_file=FROSTFS_ADM_CONFIG_PATH
|
||||
)
|
||||
frostfs_cli = FrostfsCli(
|
||||
shell=local_shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=DEFAULT_WALLET_CONFIG
|
||||
)
|
||||
frostfs_adm = FrostfsAdm(shell=shell, frostfs_adm_exec_path=FROSTFS_ADM_EXEC, config_file=FROSTFS_ADM_CONFIG_PATH)
|
||||
frostfs_cli = FrostfsCli(local_shell, FROSTFS_CLI_EXEC, local_wallet.config_path)
|
||||
frostfs_cli_remote = FrostfsCli(
|
||||
shell=shell,
|
||||
frostfs_cli_exec_path=FROSTFS_CLI_EXEC,
|
||||
|
@ -511,9 +508,7 @@ class ClusterStateController:
|
|||
options = CommandOptions(check=False)
|
||||
return self.shell.exec(f"ping {node.host.config.address} -c 1", options).return_code
|
||||
|
||||
@retry(
|
||||
max_attempts=60, sleep_interval=10, expected_result=HostStatus.ONLINE, title="Waiting for {node} to go online"
|
||||
)
|
||||
@retry(max_attempts=60, sleep_interval=10, expected_result=HostStatus.ONLINE, title="Waiting for {node} to go online")
|
||||
def _wait_for_host_online(self, node: ClusterNode):
|
||||
try:
|
||||
ping_result = self._ping_host(node)
|
||||
|
@ -524,9 +519,7 @@ class ClusterStateController:
|
|||
logger.warning(f"Host ping fails with error {err}")
|
||||
return HostStatus.OFFLINE
|
||||
|
||||
@retry(
|
||||
max_attempts=60, sleep_interval=10, expected_result=HostStatus.OFFLINE, title="Waiting for {node} to go offline"
|
||||
)
|
||||
@retry(max_attempts=60, sleep_interval=10, expected_result=HostStatus.OFFLINE, title="Waiting for {node} to go offline")
|
||||
def _wait_for_host_offline(self, node: ClusterNode):
|
||||
try:
|
||||
ping_result = self._ping_host(node)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
|
||||
from frostfs_testlib.testing.readable import HumanReadableEnum
|
||||
from frostfs_testlib.utils import wallet_utils
|
||||
|
||||
|
@ -65,11 +65,7 @@ class EACLFilters:
|
|||
|
||||
def __str__(self):
|
||||
return ",".join(
|
||||
[
|
||||
f"{filter.header_type.value}:"
|
||||
f"{filter.key}{filter.match_type.value}{filter.value}"
|
||||
for filter in self.filters
|
||||
]
|
||||
[f"{filter.header_type.value}:" f"{filter.key}{filter.match_type.value}{filter.value}" for filter in self.filters]
|
||||
if self.filters
|
||||
else []
|
||||
)
|
||||
|
@ -84,7 +80,7 @@ class EACLPubKey:
|
|||
class EACLRule:
|
||||
operation: Optional[EACLOperation] = None
|
||||
access: Optional[EACLAccess] = None
|
||||
role: Optional[Union[EACLRole, str]] = None
|
||||
role: Optional[Union[EACLRole, WalletInfo]] = None
|
||||
filters: Optional[EACLFilters] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
|
@ -96,9 +92,9 @@ class EACLRule:
|
|||
}
|
||||
|
||||
def __str__(self):
|
||||
role = (
|
||||
self.role.value
|
||||
if isinstance(self.role, EACLRole)
|
||||
else f'pubkey:{wallet_utils.get_wallet_public_key(self.role, "")}'
|
||||
)
|
||||
role = ""
|
||||
if isinstance(self.role, EACLRole):
|
||||
role = self.role.value
|
||||
if isinstance(self.role, WalletInfo):
|
||||
role = f"pubkey:{wallet_utils.get_wallet_public_key(self.role.path, self.role.password)}"
|
||||
return f'{self.access.value} {self.operation.value} {self.filters or ""} {role}'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
|
||||
from frostfs_testlib.testing.readable import HumanReadableEnum
|
||||
|
||||
|
||||
|
@ -19,7 +20,7 @@ class LockObjectInfo(ObjectRef):
|
|||
@dataclass
|
||||
class StorageObjectInfo(ObjectRef):
|
||||
size: Optional[int] = None
|
||||
wallet_file_path: Optional[str] = None
|
||||
wallet: Optional[WalletInfo] = None
|
||||
file_path: Optional[str] = None
|
||||
file_hash: Optional[str] = None
|
||||
attributes: Optional[list[dict[str, str]]] = None
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from frostfs_testlib.resources.common import DEFAULT_WALLET_CONFIG, DEFAULT_WALLET_PASS
|
||||
import yaml
|
||||
|
||||
from frostfs_testlib import reporter
|
||||
from frostfs_testlib.resources.common import ASSETS_DIR, DEFAULT_WALLET_CONFIG, DEFAULT_WALLET_PASS
|
||||
from frostfs_testlib.shell import Shell
|
||||
from frostfs_testlib.storage.cluster import Cluster, NodeBase
|
||||
from frostfs_testlib.storage.cluster import NodeBase
|
||||
from frostfs_testlib.utils.wallet_utils import get_last_address_from_wallet, init_wallet
|
||||
|
||||
logger = logging.getLogger("frostfs.testlib.utils")
|
||||
|
@ -21,9 +23,13 @@ class WalletInfo:
|
|||
|
||||
@staticmethod
|
||||
def from_node(node: NodeBase):
|
||||
return WalletInfo(
|
||||
node.get_wallet_path(), node.get_wallet_password(), node.get_wallet_config_path()
|
||||
)
|
||||
wallet_path = node.get_wallet_path()
|
||||
wallet_password = node.get_wallet_password()
|
||||
wallet_config_file = os.path.join(ASSETS_DIR, os.path.basename(node.get_wallet_config_path()))
|
||||
with open(wallet_config_file, "w") as file:
|
||||
file.write(yaml.dump({"wallet": wallet_path, "password": wallet_password}))
|
||||
|
||||
return WalletInfo(wallet_path, wallet_password, wallet_config_file)
|
||||
|
||||
def get_address(self) -> str:
|
||||
"""
|
||||
|
@ -47,22 +53,17 @@ class WalletInfo:
|
|||
"""
|
||||
with open(self.path, "r") as wallet:
|
||||
wallet_json = json.load(wallet)
|
||||
assert abs(account_id) + 1 <= len(
|
||||
wallet_json["accounts"]
|
||||
), f"There is no index '{account_id}' in wallet: {wallet_json}"
|
||||
assert abs(account_id) + 1 <= len(wallet_json["accounts"]), f"There is no index '{account_id}' in wallet: {wallet_json}"
|
||||
|
||||
return wallet_json["accounts"][account_id]["address"]
|
||||
|
||||
|
||||
class WalletFactory:
|
||||
def __init__(self, wallets_dir: str, shell: Shell, cluster: Cluster) -> None:
|
||||
def __init__(self, wallets_dir: str, shell: Shell) -> None:
|
||||
self.shell = shell
|
||||
self.wallets_dir = wallets_dir
|
||||
self.cluster = cluster
|
||||
|
||||
def create_wallet(
|
||||
self, file_name: Optional[str] = None, password: Optional[str] = None
|
||||
) -> WalletInfo:
|
||||
def create_wallet(self, file_name: str, password: Optional[str] = None) -> WalletInfo:
|
||||
"""
|
||||
Creates new default wallet.
|
||||
|
||||
|
@ -74,8 +75,6 @@ class WalletFactory:
|
|||
WalletInfo object of new wallet.
|
||||
"""
|
||||
|
||||
if file_name is None:
|
||||
file_name = str(uuid.uuid4())
|
||||
if password is None:
|
||||
password = ""
|
||||
|
||||
|
@ -85,6 +84,8 @@ class WalletFactory:
|
|||
init_wallet(wallet_path, password)
|
||||
|
||||
with open(wallet_config_path, "w") as config_file:
|
||||
config_file.write(f'password: "{password}"')
|
||||
config_file.write(f'wallet: {wallet_path}\npassword: "{password}"')
|
||||
|
||||
reporter.attach(wallet_path, os.path.basename(wallet_path))
|
||||
|
||||
return WalletInfo(wallet_path, password, wallet_config_path)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue