forked from TrueCloudLab/frostfs-testcases
Integrate with hosting from testlib
Replace service_helper with hosting class from the testlib. Instead of invoking commands on remote via ssh_helper, we now use shell from the hosting. Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
This commit is contained in:
parent
88da942b03
commit
bfd02531ef
15 changed files with 324 additions and 749 deletions
84
.devenv.hosting.yaml
Normal file
84
.devenv.hosting.yaml
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
hosts:
|
||||||
|
- address: localhost
|
||||||
|
plugin_name: docker
|
||||||
|
services:
|
||||||
|
- name: s01
|
||||||
|
attributes:
|
||||||
|
container_name: s01
|
||||||
|
config_path: ../neofs-dev-env/services/storage/.storage.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/storage/wallet01.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s01
|
||||||
|
rpc_endpoint: s01.neofs.devenv:8080
|
||||||
|
control_endpoint: s01.neofs.devenv:8081
|
||||||
|
un_locode: "RU MOW"
|
||||||
|
- name: s02
|
||||||
|
attributes:
|
||||||
|
container_name: s02
|
||||||
|
config_path: ../neofs-dev-env/services/storage/.storage.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/storage/wallet02.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s02
|
||||||
|
rpc_endpoint: s02.neofs.devenv:8080
|
||||||
|
control_endpoint: s02.neofs.devenv:8081
|
||||||
|
un_locode: "RU LED"
|
||||||
|
- name: s03
|
||||||
|
attributes:
|
||||||
|
container_name: s03
|
||||||
|
config_path: ../neofs-dev-env/services/storage/.storage.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/storage/wallet03.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s03
|
||||||
|
rpc_endpoint: s03.neofs.devenv:8080
|
||||||
|
control_endpoint: s03.neofs.devenv:8081
|
||||||
|
un_locode: "SE STO"
|
||||||
|
- name: s04
|
||||||
|
attributes:
|
||||||
|
container_name: s04
|
||||||
|
config_path: ../neofs-dev-env/services/storage/.storage.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/storage/wallet04.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s04
|
||||||
|
rpc_endpoint: s04.neofs.devenv:8080
|
||||||
|
control_endpoint: s04.neofs.devenv:8081
|
||||||
|
un_locode: "FI HEL"
|
||||||
|
- name: s3-gate01
|
||||||
|
attributes:
|
||||||
|
container_name: s3_gate
|
||||||
|
config_path: ../neofs-dev-env/services/s3_gate/.s3.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/s3_gate/wallet.json
|
||||||
|
wallet_password: "s3"
|
||||||
|
endpoint: https://s3.neofs.devenv:8080
|
||||||
|
- name: http-gate01
|
||||||
|
attributes:
|
||||||
|
container_name: http_gate
|
||||||
|
config_path: ../neofs-dev-env/services/http_gate/.http.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/http_gate/wallet.json
|
||||||
|
wallet_password: "one"
|
||||||
|
endpoint: http://http.neofs.devenv
|
||||||
|
- name: ir01
|
||||||
|
attributes:
|
||||||
|
container_name: ir01
|
||||||
|
config_path: ../neofs-dev-env/services/ir/.ir.env
|
||||||
|
wallet_path: ../neofs-dev-env/services/ir/az.json
|
||||||
|
wallet_password: "one"
|
||||||
|
- name: morph-chain01
|
||||||
|
attributes:
|
||||||
|
container_name: morph_chain
|
||||||
|
config_path: ../neofs-dev-env/services/morph_chain/protocol.privnet.yml
|
||||||
|
wallet_path: ../neofs-dev-env/services/morph_chain/node-wallet.json
|
||||||
|
wallet_password: "one"
|
||||||
|
endpoint: http://morph-chain.neofs.devenv:30333
|
||||||
|
- name: main-chain01
|
||||||
|
attributes:
|
||||||
|
container_name: main_chain
|
||||||
|
config_path: ../neofs-dev-env/services/chain/protocol.privnet.yml
|
||||||
|
wallet_path: ../neofs-dev-env/services/chain/node-wallet.json
|
||||||
|
wallet_password: "one"
|
||||||
|
endpoint: http://main-chain.neofs.devenv:30333
|
||||||
|
- name: coredns01
|
||||||
|
attributes:
|
||||||
|
container_name: coredns
|
||||||
|
clis:
|
||||||
|
- name: neofs-cli
|
||||||
|
exec_path: neofs-cli
|
72
pytest_tests/helpers/binary_version_helper.py
Normal file
72
pytest_tests/helpers/binary_version_helper.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from common import NEOFS_ADM_EXEC, NEOFS_CLI_EXEC, WALLET_CONFIG
|
||||||
|
from neofs_testlib.cli import NeofsAdm, NeofsCli
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
|
from neofs_testlib.shell import Shell
|
||||||
|
|
||||||
|
logger = logging.getLogger("NeoLogger")
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_binaries_versions(shell: Shell) -> dict[str, str]:
|
||||||
|
versions = {}
|
||||||
|
|
||||||
|
for binary in ["neo-go", "neofs-authmate"]:
|
||||||
|
out = shell.exec(f"{binary} --version").stdout
|
||||||
|
versions[binary] = _parse_version(out)
|
||||||
|
|
||||||
|
neofs_cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||||
|
versions["neofs-cli"] = _parse_version(neofs_cli.version.get())
|
||||||
|
|
||||||
|
try:
|
||||||
|
neofs_adm = NeofsAdm(shell, NEOFS_ADM_EXEC)
|
||||||
|
versions["neofs-adm"] = _parse_version(neofs_adm.version.get())
|
||||||
|
except RuntimeError:
|
||||||
|
logger.info(f"neofs-adm not installed")
|
||||||
|
|
||||||
|
out = shell.exec("aws --version").stdout
|
||||||
|
out_lines = out.split("\n")
|
||||||
|
versions["AWS"] = out_lines[0] if out_lines else "Unknown"
|
||||||
|
|
||||||
|
return versions
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_binaries_versions(hosting: Hosting) -> dict[str, str]:
|
||||||
|
versions_by_host = {}
|
||||||
|
for host in hosting.hosts:
|
||||||
|
binaries = []
|
||||||
|
for service_config in host.config.services:
|
||||||
|
exec_path = service_config.attributes.get("exec_path")
|
||||||
|
if exec_path:
|
||||||
|
binaries.append(exec_path)
|
||||||
|
for cli_config in host.config.clis:
|
||||||
|
binaries.append(cli_config.exec_path)
|
||||||
|
|
||||||
|
shell = host.get_shell()
|
||||||
|
versions_at_host = {}
|
||||||
|
for binary in binaries:
|
||||||
|
try:
|
||||||
|
result = shell.exec(f"{binary} --version")
|
||||||
|
versions_at_host[service_config.name] = _parse_version(result.stdout)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Cannot get version for {exec_path} because of\n{exc}")
|
||||||
|
versions_at_host[service_config.name] = "Unknown"
|
||||||
|
continue
|
||||||
|
versions_by_host[host.config.address] = versions_at_host
|
||||||
|
|
||||||
|
# Consolidate versions across all hosts
|
||||||
|
versions = {}
|
||||||
|
for host, binary_versions in versions_by_host.items():
|
||||||
|
for name, version in binary_versions.items():
|
||||||
|
captured_version = versions.get(name)
|
||||||
|
if captured_version:
|
||||||
|
assert captured_version == version, f"Binary {name} has inconsistent version on host {host}"
|
||||||
|
else:
|
||||||
|
versions[name] = version
|
||||||
|
return versions
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_version(version_output: str) -> str:
|
||||||
|
version = re.search(r"version[:\s]*v?(.+)", version_output, re.IGNORECASE)
|
||||||
|
return version.group(1).strip() if version else "Unknown"
|
|
@ -1,6 +1,7 @@
|
||||||
from ssh_helper import HostClient
|
from ssh_helper import HostClient
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: convert to shell from hosting
|
||||||
class IpTablesHelper:
|
class IpTablesHelper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def drop_input_traffic_to_port(client: HostClient, ports: list[str]):
|
def drop_input_traffic_to_port(client: HostClient, ports: list[str]):
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
import binascii
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
from urllib.parse import quote, unquote
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SberCloudConfig:
|
|
||||||
access_key_id: Optional[str] = None
|
|
||||||
secret_key: Optional[str] = None
|
|
||||||
ecs_endpoint: Optional[str] = None
|
|
||||||
project_id: Optional[str] = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_dict(config_dict: dict) -> "SberCloudConfig":
|
|
||||||
return SberCloudConfig(**config_dict)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_yaml(config_path: str) -> "SberCloudConfig":
|
|
||||||
with open(config_path) as file:
|
|
||||||
config_dict = yaml.load(file, Loader=yaml.FullLoader)
|
|
||||||
return SberCloudConfig.from_dict(config_dict["sbercloud"])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_env() -> "SberCloudConfig":
|
|
||||||
config_dict = {
|
|
||||||
"access_key_id": os.getenv("SBERCLOUD_ACCESS_KEY_ID"),
|
|
||||||
"secret_key": os.getenv("SBERCLOUD_SECRET_KEY"),
|
|
||||||
"ecs_endpoint": os.getenv("SBERCLOUD_ECS_ENDPOINT"),
|
|
||||||
"project_id": os.getenv("SBERCLOUD_PROJECT_ID"),
|
|
||||||
}
|
|
||||||
return SberCloudConfig.from_dict(config_dict)
|
|
||||||
|
|
||||||
|
|
||||||
class SberCloudAuthRequests:
|
|
||||||
"""
|
|
||||||
Implements authentication mechanism with access key+secret key in accordance with:
|
|
||||||
https://support.hc.sbercloud.ru/devg/apisign/api-sign-algorithm.html
|
|
||||||
|
|
||||||
endpoint - represents endpoint of a specific service (listed at https://support.hc.sbercloud.ru/en-us/endpoint/index.html)
|
|
||||||
base_path - is prefix for all request path's that will be sent via this instance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ENCODING = "utf-8"
|
|
||||||
ALGORITHM = "SDK-HMAC-SHA256"
|
|
||||||
TIMESTAMP_FORMAT = "%Y%m%dT%H%M%SZ"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, endpoint: str, access_key_id: str, secret_key: str, base_path: str = ""
|
|
||||||
) -> None:
|
|
||||||
self.endpoint = endpoint
|
|
||||||
self.base_path = base_path
|
|
||||||
self.access_key_id = access_key_id
|
|
||||||
self.secret_key = secret_key
|
|
||||||
|
|
||||||
def get(self, path: str, query: Optional[dict] = None) -> requests.Response:
|
|
||||||
return self._send_request("GET", path, query, data=None)
|
|
||||||
|
|
||||||
def post(
|
|
||||||
self, path: str, query: Optional[dict] = None, data: Optional[dict] = None
|
|
||||||
) -> requests.Response:
|
|
||||||
return self._send_request("POST", path, query, data)
|
|
||||||
|
|
||||||
def _send_request(
|
|
||||||
self, method: str, path: str, query: Optional[dict], data: Optional[dict]
|
|
||||||
) -> requests.Response:
|
|
||||||
if self.base_path:
|
|
||||||
path = self.base_path + path
|
|
||||||
|
|
||||||
timestamp = datetime.strftime(datetime.utcnow(), self.TIMESTAMP_FORMAT)
|
|
||||||
headers = self._build_original_headers(timestamp)
|
|
||||||
|
|
||||||
content = ""
|
|
||||||
if data:
|
|
||||||
# At the moment we support json content only
|
|
||||||
content = json.dumps(data)
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
body = content.encode(self.ENCODING)
|
|
||||||
|
|
||||||
signed_headers = self._build_signed_headers(headers)
|
|
||||||
canonical_request = self._build_canonical_request(
|
|
||||||
method, path, query, body, headers, signed_headers
|
|
||||||
)
|
|
||||||
signature = self._build_signature(timestamp, canonical_request)
|
|
||||||
headers["Authorization"] = self._build_authorization_header(signature, signed_headers)
|
|
||||||
|
|
||||||
query_string = "?" + self._build_canonical_query_string(query) if query else ""
|
|
||||||
url = f"https://{self.endpoint}{path}{query_string}"
|
|
||||||
|
|
||||||
response = requests.request(method, url, headers=headers, data=body)
|
|
||||||
if response.status_code < 200 or response.status_code >= 300:
|
|
||||||
raise AssertionError(
|
|
||||||
f"Request to url={url} failed: status={response.status_code} "
|
|
||||||
f"response={response.text})"
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _build_original_headers(self, timestamp: str) -> dict[str, str]:
|
|
||||||
return {
|
|
||||||
"X-Sdk-Date": timestamp,
|
|
||||||
"host": self.endpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _build_signed_headers(self, headers: dict[str, str]) -> list[str]:
|
|
||||||
return sorted(header_name.lower() for header_name in headers)
|
|
||||||
|
|
||||||
def _build_canonical_request(
|
|
||||||
self,
|
|
||||||
method: str,
|
|
||||||
path: str,
|
|
||||||
query: Optional[dict],
|
|
||||||
body: bytes,
|
|
||||||
headers: dict[str, str],
|
|
||||||
signed_headers: list[str],
|
|
||||||
) -> str:
|
|
||||||
canonical_headers = self._build_canonical_headers(headers, signed_headers)
|
|
||||||
body_hash = self._calc_sha256_hash(body)
|
|
||||||
canonical_url = self._build_canonical_url(path)
|
|
||||||
canonical_query_string = self._build_canonical_query_string(query)
|
|
||||||
|
|
||||||
return "\n".join(
|
|
||||||
[
|
|
||||||
method.upper(),
|
|
||||||
canonical_url,
|
|
||||||
canonical_query_string,
|
|
||||||
canonical_headers,
|
|
||||||
";".join(signed_headers),
|
|
||||||
body_hash,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def _build_canonical_headers(self, headers: dict[str, str], signed_headers: list[str]) -> str:
|
|
||||||
normalized_headers = {}
|
|
||||||
for key, value in headers.items():
|
|
||||||
normalized_key = key.lower()
|
|
||||||
normalized_value = value.strip()
|
|
||||||
normalized_headers[normalized_key] = normalized_value
|
|
||||||
# Re-encode header in request itself (iso-8859-1 comes from HTTP 1.1 standard)
|
|
||||||
headers[key] = normalized_value.encode(self.ENCODING).decode("iso-8859-1")
|
|
||||||
|
|
||||||
# Join headers in the same order as they are sorted in signed_headers list
|
|
||||||
joined_headers = "\n".join(f"{key}:{normalized_headers[key]}" for key in signed_headers)
|
|
||||||
return joined_headers + "\n"
|
|
||||||
|
|
||||||
def _calc_sha256_hash(self, value: bytes) -> str:
|
|
||||||
sha256 = hashlib.sha256()
|
|
||||||
sha256.update(value)
|
|
||||||
return sha256.hexdigest()
|
|
||||||
|
|
||||||
def _build_canonical_url(self, path: str) -> str:
|
|
||||||
path_parts = unquote(path).split("/")
|
|
||||||
canonical_url = "/".join(quote(path_part) for path_part in path_parts)
|
|
||||||
|
|
||||||
if not canonical_url.endswith("/"):
|
|
||||||
canonical_url += "/"
|
|
||||||
return canonical_url
|
|
||||||
|
|
||||||
def _build_canonical_query_string(self, query: Optional[dict]) -> str:
|
|
||||||
if not query:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
key_value_pairs = []
|
|
||||||
for key in sorted(query.keys()):
|
|
||||||
# NOTE: we do not support list values, as they are not used in API at the moment
|
|
||||||
encoded_key = quote(key)
|
|
||||||
encoded_value = quote(str(query[key]))
|
|
||||||
key_value_pairs.append(f"{encoded_key}={encoded_value}")
|
|
||||||
return "&".join(key_value_pairs)
|
|
||||||
|
|
||||||
def _build_signature(self, timestamp: str, canonical_request: str) -> str:
|
|
||||||
canonical_request_hash = self._calc_sha256_hash(canonical_request.encode(self.ENCODING))
|
|
||||||
string_to_sign = f"{self.ALGORITHM}\n{timestamp}\n{canonical_request_hash}"
|
|
||||||
|
|
||||||
hmac_digest = hmac.new(
|
|
||||||
key=self.secret_key.encode(self.ENCODING),
|
|
||||||
msg=string_to_sign.encode(self.ENCODING),
|
|
||||||
digestmod=hashlib.sha256,
|
|
||||||
).digest()
|
|
||||||
signature = binascii.hexlify(hmac_digest).decode()
|
|
||||||
|
|
||||||
return signature
|
|
||||||
|
|
||||||
def _build_authorization_header(self, signature: str, signed_headers: list[str]) -> str:
|
|
||||||
joined_signed_headers = ";".join(signed_headers)
|
|
||||||
return f"{self.ALGORITHM} Access={self.access_key_id}, SignedHeaders={joined_signed_headers}, Signature={signature}"
|
|
||||||
|
|
||||||
|
|
||||||
class SberCloud:
|
|
||||||
"""
|
|
||||||
Manages resources in Sbercloud via API.
|
|
||||||
|
|
||||||
API reference:
|
|
||||||
https://docs.sbercloud.ru/terraform/ug/topics/quickstart.html
|
|
||||||
https://support.hc.sbercloud.ru/en-us/api/ecs/en-us_topic_0020212668.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, config: SberCloudConfig) -> None:
|
|
||||||
self.ecs_requests = SberCloudAuthRequests(
|
|
||||||
endpoint=config.ecs_endpoint,
|
|
||||||
base_path=f"/v1/{config.project_id}/cloudservers",
|
|
||||||
access_key_id=config.access_key_id,
|
|
||||||
secret_key=config.secret_key,
|
|
||||||
)
|
|
||||||
self.ecs_node_by_ip = {} # Cached list of ecs servers
|
|
||||||
|
|
||||||
def find_ecs_node_by_ip(self, ip: str) -> str:
|
|
||||||
if ip not in self.ecs_node_by_ip:
|
|
||||||
self.ecs_node_by_ip[ip] = self.get_ecs_node_id(ip)
|
|
||||||
assert ip in self.ecs_node_by_ip
|
|
||||||
return self.ecs_node_by_ip[ip]
|
|
||||||
|
|
||||||
def get_ecs_node_id(self, ip: str) -> str:
|
|
||||||
response = self.ecs_requests.get("/detail", {"ip": ip}).json()
|
|
||||||
return response["servers"][0]["id"]
|
|
||||||
|
|
||||||
def start_node(self, node_id: Optional[str] = None, node_ip: Optional[str] = None) -> None:
|
|
||||||
data = {"os-start": {"servers": [{"id": node_id or self.find_ecs_node_by_ip(node_ip)}]}}
|
|
||||||
self.ecs_requests.post("/action", data=data)
|
|
||||||
|
|
||||||
def stop_node(
|
|
||||||
self, node_id: Optional[str] = None, node_ip: Optional[str] = None, hard: bool = False
|
|
||||||
) -> None:
|
|
||||||
data = {
|
|
||||||
"os-stop": {
|
|
||||||
"type": "HARD" if hard else "SOFT",
|
|
||||||
"servers": [{"id": node_id or self.find_ecs_node_by_ip(node_ip)}],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.ecs_requests.post("/action", data=data)
|
|
|
@ -1,342 +0,0 @@
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import docker
|
|
||||||
from cli_helpers import _cmd_run
|
|
||||||
from common import (
|
|
||||||
INFRASTRUCTURE_TYPE,
|
|
||||||
NEOFS_CLI_EXEC,
|
|
||||||
NEOFS_NETMAP_DICT,
|
|
||||||
STORAGE_NODE_BIN_PATH,
|
|
||||||
STORAGE_NODE_SSH_PASSWORD,
|
|
||||||
STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
|
|
||||||
STORAGE_NODE_SSH_USER,
|
|
||||||
WALLET_CONFIG,
|
|
||||||
)
|
|
||||||
from requests import HTTPError
|
|
||||||
from ssh_helper import HostClient
|
|
||||||
|
|
||||||
logger = logging.getLogger("NeoLogger")
|
|
||||||
|
|
||||||
|
|
||||||
class LocalDevEnvStorageServiceHelper:
|
|
||||||
"""
|
|
||||||
Manages storage services running on local devenv.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Names of all containers that are running neofs code
|
|
||||||
ALL_CONTAINERS = [
|
|
||||||
"s3_gate",
|
|
||||||
"http_gate",
|
|
||||||
"s03",
|
|
||||||
"s01",
|
|
||||||
"s02",
|
|
||||||
"s04",
|
|
||||||
"ir01",
|
|
||||||
"morph_chain",
|
|
||||||
"main_chain",
|
|
||||||
]
|
|
||||||
|
|
||||||
def stop_node(self, node_name: str, wait: bool = True) -> None:
|
|
||||||
container_name = _get_storage_container_name(node_name)
|
|
||||||
client = self._get_docker_client(node_name)
|
|
||||||
client.stop(container_name)
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
self._wait_for_container_to_be_in_state(node_name, container_name, "exited")
|
|
||||||
|
|
||||||
def start_node(self, node_name: str, wait: bool = True) -> None:
|
|
||||||
container_name = _get_storage_container_name(node_name)
|
|
||||||
client = self._get_docker_client(node_name)
|
|
||||||
client.start(container_name)
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
self._wait_for_container_to_be_in_state(node_name, container_name, "running")
|
|
||||||
|
|
||||||
def run_control_command(self, node_name: str, command: str) -> str:
|
|
||||||
control_endpoint = NEOFS_NETMAP_DICT[node_name]["control"]
|
|
||||||
wallet_path = NEOFS_NETMAP_DICT[node_name]["wallet_path"]
|
|
||||||
|
|
||||||
cmd = (
|
|
||||||
f"{NEOFS_CLI_EXEC} {command} --endpoint {control_endpoint} "
|
|
||||||
f"--wallet {wallet_path} --config {WALLET_CONFIG}"
|
|
||||||
)
|
|
||||||
output = _cmd_run(cmd)
|
|
||||||
return output
|
|
||||||
|
|
||||||
def delete_node_data(self, node_name: str) -> None:
|
|
||||||
volume_name = _get_storage_volume_name(node_name)
|
|
||||||
|
|
||||||
client = self._get_docker_client(node_name)
|
|
||||||
volume_info = client.inspect_volume(volume_name)
|
|
||||||
volume_path = volume_info["Mountpoint"]
|
|
||||||
|
|
||||||
_cmd_run(f"rm -rf {volume_path}/*")
|
|
||||||
|
|
||||||
def get_binaries_version(self) -> dict:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def dump_logs(
|
|
||||||
self, directory_path: str, since: Optional[datetime], until: Optional[datetime]
|
|
||||||
) -> None:
|
|
||||||
# All containers are running on the same host, so we can use 1st node to collect all logs
|
|
||||||
first_node_name = next(iter(NEOFS_NETMAP_DICT))
|
|
||||||
client = self._get_docker_client(first_node_name)
|
|
||||||
|
|
||||||
for container_name in self.ALL_CONTAINERS:
|
|
||||||
try:
|
|
||||||
logs = client.logs(container_name, since=since, until=until)
|
|
||||||
except HTTPError as exc:
|
|
||||||
logger.info(f"Got exception while dumping container '{container_name}' logs: {exc}")
|
|
||||||
continue
|
|
||||||
# Dump logs to the directory
|
|
||||||
file_path = os.path.join(directory_path, f"{container_name}-log.txt")
|
|
||||||
with open(file_path, "wb") as file:
|
|
||||||
file.write(logs)
|
|
||||||
|
|
||||||
def _get_container_by_name(self, node_name: str, container_name: str) -> dict:
|
|
||||||
client = self._get_docker_client(node_name)
|
|
||||||
containers = client.containers(all=True)
|
|
||||||
|
|
||||||
logger.info(f"Current containers state\n:{json.dumps(containers, indent=2)}")
|
|
||||||
|
|
||||||
for container in containers:
|
|
||||||
# Names in local docker environment are prefixed with /
|
|
||||||
clean_names = set(name.strip("/") for name in container["Names"])
|
|
||||||
if container_name in clean_names:
|
|
||||||
return container
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _wait_for_container_to_be_in_state(
|
|
||||||
self, node_name: str, container_name: str, expected_state: str
|
|
||||||
) -> None:
|
|
||||||
for __attempt in range(10):
|
|
||||||
container = self._get_container_by_name(node_name, container_name)
|
|
||||||
logger.info(f"Container info:\n{json.dumps(container, indent=2)}")
|
|
||||||
if container and container["State"] == expected_state:
|
|
||||||
return
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
raise AssertionError(f"Container {container_name} is not in {expected_state} state.")
|
|
||||||
|
|
||||||
def _get_docker_client(self, node_name: str) -> docker.APIClient:
|
|
||||||
# For local docker we use default docker client that talks to unix socket
|
|
||||||
client = docker.APIClient()
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
class CloudVmStorageServiceHelper:
|
|
||||||
STORAGE_SERVICE = "neofs-storage.service"
|
|
||||||
|
|
||||||
def stop_node(self, node_name: str, wait: bool = True) -> None:
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
cmd = f"sudo systemctl stop {self.STORAGE_SERVICE}"
|
|
||||||
output = ssh_client.exec_with_confirmation(cmd, [""])
|
|
||||||
logger.info(f"Stop command output: {output.stdout}")
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
self._wait_for_service_to_be_in_state(node_name, self.STORAGE_SERVICE, "inactive")
|
|
||||||
|
|
||||||
def start_node(self, node_name: str, wait: bool = True) -> None:
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
cmd = f"sudo systemctl start {self.STORAGE_SERVICE}"
|
|
||||||
output = ssh_client.exec_with_confirmation(cmd, [""])
|
|
||||||
logger.info(f"Start command output: {output.stdout}")
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
self._wait_for_service_to_be_in_state(
|
|
||||||
node_name, self.STORAGE_SERVICE, "active (running)"
|
|
||||||
)
|
|
||||||
|
|
||||||
def run_control_command(self, node_name: str, command: str) -> str:
|
|
||||||
control_endpoint = NEOFS_NETMAP_DICT[node_name]["control"]
|
|
||||||
wallet_path = NEOFS_NETMAP_DICT[node_name]["wallet_path"]
|
|
||||||
|
|
||||||
# Private control endpoint is accessible only from the host where storage node is running
|
|
||||||
# So, we connect to storage node host and run CLI command from there
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
# Copy wallet content on storage node host
|
|
||||||
with open(wallet_path, "r") as file:
|
|
||||||
wallet = file.read()
|
|
||||||
remote_wallet_path = f"/tmp/{node_name}-wallet.json"
|
|
||||||
ssh_client.exec_with_confirmation(f"echo '{wallet}' > {remote_wallet_path}", [""])
|
|
||||||
|
|
||||||
# Put config on storage node host
|
|
||||||
remote_config_path = f"/tmp/{node_name}-config.yaml"
|
|
||||||
remote_config = 'password: ""'
|
|
||||||
ssh_client.exec_with_confirmation(
|
|
||||||
f"echo '{remote_config}' > {remote_config_path}", [""]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Execute command
|
|
||||||
cmd = (
|
|
||||||
f"sudo {STORAGE_NODE_BIN_PATH}/neofs-cli {command} --endpoint {control_endpoint} "
|
|
||||||
f"--wallet {remote_wallet_path} --config {remote_config_path}"
|
|
||||||
)
|
|
||||||
output = ssh_client.exec_with_confirmation(cmd, [""])
|
|
||||||
return output.stdout
|
|
||||||
|
|
||||||
def _wait_for_service_to_be_in_state(
|
|
||||||
self, node_name: str, service_name: str, expected_state: str
|
|
||||||
) -> None:
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
for __attempt in range(10):
|
|
||||||
# Run command to get service status (set --lines=0 to suppress logs output)
|
|
||||||
# Also we don't verify return code, because for an inactive service return code will be 3
|
|
||||||
command = f"sudo systemctl status {service_name} --lines=0"
|
|
||||||
output = ssh_client.exec(command, verify=False)
|
|
||||||
if expected_state in output.stdout:
|
|
||||||
return
|
|
||||||
time.sleep(3)
|
|
||||||
raise AssertionError(f"Service {service_name} is not in {expected_state} state")
|
|
||||||
|
|
||||||
def delete_node_data(self, node_name: str) -> None:
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
ssh_client.exec("sudo rm -rf /srv/neofs/*")
|
|
||||||
|
|
||||||
def get_binaries_version(self, binaries: list = None) -> dict:
|
|
||||||
default_binaries = [
|
|
||||||
"neo-go",
|
|
||||||
"neofs-adm",
|
|
||||||
"neofs-cli",
|
|
||||||
"neofs-http-gw",
|
|
||||||
"neofs-ir",
|
|
||||||
"neofs-lens",
|
|
||||||
"neofs-node",
|
|
||||||
"neofs-s3-authmate",
|
|
||||||
"neofs-s3-gw",
|
|
||||||
"neogo-morph-cn",
|
|
||||||
]
|
|
||||||
binaries = binaries or default_binaries
|
|
||||||
|
|
||||||
version_map = {}
|
|
||||||
for node_name in NEOFS_NETMAP_DICT:
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
for binary in binaries:
|
|
||||||
try:
|
|
||||||
out = ssh_client.exec(f"sudo {binary} --version").stdout
|
|
||||||
except AssertionError as err:
|
|
||||||
logger.error(f"Can not get version for {binary} because of\n{err}")
|
|
||||||
version_map[binary] = "Can not get version"
|
|
||||||
continue
|
|
||||||
version = re.search(r"version[:\s]*v?(.+)", out, re.IGNORECASE)
|
|
||||||
version = version.group(1).strip() if version else "Unknown"
|
|
||||||
if not version_map.get(binary):
|
|
||||||
version_map[binary] = version
|
|
||||||
else:
|
|
||||||
assert version_map[binary] == version, (
|
|
||||||
f"Expected binary {binary} to have identical version on all nodes "
|
|
||||||
f"(mismatch on node {node_name})"
|
|
||||||
)
|
|
||||||
return version_map
|
|
||||||
|
|
||||||
def dump_logs(
|
|
||||||
self, directory_path: str, since: Optional[datetime], until: Optional[datetime]
|
|
||||||
) -> None:
|
|
||||||
for node_name, node_info in NEOFS_NETMAP_DICT.items():
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
# We do not filter out logs of neofs services, because system logs might contain
|
|
||||||
# information that is useful for troubleshooting
|
|
||||||
filters = " ".join(
|
|
||||||
[
|
|
||||||
f"--since '{since:%Y-%m-%d %H:%M:%S}'" if since else "",
|
|
||||||
f"--until '{until:%Y-%m-%d %H:%M:%S}'" if until else "",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
result = ssh_client.exec(f"journalctl --no-pager {filters}")
|
|
||||||
logs = result.stdout
|
|
||||||
|
|
||||||
# Dump logs to the directory. We include node endpoint in file name, because almost
|
|
||||||
# everywhere in Allure report we are logging endpoints rather than node names
|
|
||||||
file_path = os.path.join(directory_path, f"{node_name}-{node_info['rpc']}-log.txt")
|
|
||||||
with open(file_path, "w") as file:
|
|
||||||
file.write(logs)
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteDevEnvStorageServiceHelper(LocalDevEnvStorageServiceHelper):
|
|
||||||
"""
|
|
||||||
Manages storage services running on remote devenv.
|
|
||||||
|
|
||||||
Most of operations are identical to local devenv, however, any interactions
|
|
||||||
with host resources (files, etc.) require ssh into the remote host machine.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _get_docker_client(self, node_name: str) -> docker.APIClient:
|
|
||||||
# For remote devenv we use docker client that talks to tcp socket 2375:
|
|
||||||
# https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option
|
|
||||||
host = _get_node_host(node_name)
|
|
||||||
client = docker.APIClient(base_url=f"tcp://{host}:2375")
|
|
||||||
return client
|
|
||||||
|
|
||||||
def delete_node_data(self, node_name: str) -> None:
|
|
||||||
volume_name = _get_storage_volume_name(node_name)
|
|
||||||
|
|
||||||
client = self._get_docker_client(node_name)
|
|
||||||
volume_info = client.inspect_volume(volume_name)
|
|
||||||
volume_path = volume_info["Mountpoint"]
|
|
||||||
|
|
||||||
# SSH into remote machine and delete files in host directory that is mounted as docker volume
|
|
||||||
with _create_ssh_client(node_name) as ssh_client:
|
|
||||||
# TODO: add sudo prefix after we change a user
|
|
||||||
ssh_client.exec(f"rm -rf {volume_path}/*")
|
|
||||||
|
|
||||||
|
|
||||||
def get_storage_service_helper():
|
|
||||||
if INFRASTRUCTURE_TYPE == "LOCAL_DEVENV":
|
|
||||||
return LocalDevEnvStorageServiceHelper()
|
|
||||||
if INFRASTRUCTURE_TYPE == "REMOTE_DEVENV":
|
|
||||||
return RemoteDevEnvStorageServiceHelper()
|
|
||||||
if INFRASTRUCTURE_TYPE == "CLOUD_VM":
|
|
||||||
return CloudVmStorageServiceHelper()
|
|
||||||
|
|
||||||
raise EnvironmentError(f"Infrastructure type is not supported: {INFRASTRUCTURE_TYPE}")
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _create_ssh_client(node_name: str) -> HostClient:
|
|
||||||
host = _get_node_host(node_name)
|
|
||||||
ssh_client = HostClient(
|
|
||||||
host,
|
|
||||||
login=STORAGE_NODE_SSH_USER,
|
|
||||||
password=STORAGE_NODE_SSH_PASSWORD,
|
|
||||||
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield ssh_client
|
|
||||||
finally:
|
|
||||||
ssh_client.drop()
|
|
||||||
|
|
||||||
|
|
||||||
def _get_node_host(node_name: str) -> str:
|
|
||||||
if node_name not in NEOFS_NETMAP_DICT:
|
|
||||||
raise AssertionError(f"Node {node_name} is not found!")
|
|
||||||
|
|
||||||
# We use rpc endpoint to determine host address, because control endpoint
|
|
||||||
# (if it is private) will be a local address on the host machine
|
|
||||||
node_config = NEOFS_NETMAP_DICT.get(node_name)
|
|
||||||
host = node_config.get("rpc").split(":")[0]
|
|
||||||
return host
|
|
||||||
|
|
||||||
|
|
||||||
def _get_storage_container_name(node_name: str) -> str:
|
|
||||||
"""
|
|
||||||
Converts name of storage node (as it is listed in netmap) into the name of docker container
|
|
||||||
that runs instance of this storage node.
|
|
||||||
"""
|
|
||||||
return node_name.split(".")[0]
|
|
||||||
|
|
||||||
|
|
||||||
def _get_storage_volume_name(node_name: str) -> str:
|
|
||||||
"""
|
|
||||||
Converts name of storage node (as it is listed in netmap) into the name of docker volume
|
|
||||||
that contains data of this storage node.
|
|
||||||
"""
|
|
||||||
container_name = _get_storage_container_name(node_name)
|
|
||||||
return f"storage_storage_{container_name}"
|
|
|
@ -35,7 +35,7 @@ neo-mamba==0.10.0
|
||||||
neo3crypto==0.2.1
|
neo3crypto==0.2.1
|
||||||
neo3vm==0.9.0
|
neo3vm==0.9.0
|
||||||
neo3vm-stubs==0.9.0
|
neo3vm-stubs==0.9.0
|
||||||
neofs-testlib==0.1.0
|
neofs-testlib==0.2.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
orjson==3.6.8
|
orjson==3.6.8
|
||||||
packaging==21.3
|
packaging==21.3
|
||||||
|
|
|
@ -2,6 +2,7 @@ import allure
|
||||||
import pytest
|
import pytest
|
||||||
from common import NEOFS_NETMAP_DICT
|
from common import NEOFS_NETMAP_DICT
|
||||||
from failover_utils import wait_object_replication_on_nodes
|
from failover_utils import wait_object_replication_on_nodes
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
from python_keywords.acl import (
|
from python_keywords.acl import (
|
||||||
EACLAccess,
|
EACLAccess,
|
||||||
EACLOperation,
|
EACLOperation,
|
||||||
|
@ -194,7 +195,7 @@ class TestEACLContainer:
|
||||||
|
|
||||||
@allure.title("Testcase to validate NeoFS replication with eACL deny rules.")
|
@allure.title("Testcase to validate NeoFS replication with eACL deny rules.")
|
||||||
def test_extended_acl_deny_replication(
|
def test_extended_acl_deny_replication(
|
||||||
self, wallets, client_shell, eacl_full_placement_container_with_object, file_path
|
self, wallets, client_shell, hosting: Hosting, eacl_full_placement_container_with_object, file_path
|
||||||
):
|
):
|
||||||
user_wallet = wallets.get_wallet()
|
user_wallet = wallets.get_wallet()
|
||||||
cid, oid, file_path = eacl_full_placement_container_with_object
|
cid, oid, file_path = eacl_full_placement_container_with_object
|
||||||
|
@ -217,7 +218,7 @@ class TestEACLContainer:
|
||||||
wait_for_cache_expired()
|
wait_for_cache_expired()
|
||||||
|
|
||||||
with allure.step("Drop object to check replication"):
|
with allure.step("Drop object to check replication"):
|
||||||
drop_object(node_name=[*NEOFS_NETMAP_DICT][0], cid=cid, oid=oid)
|
drop_object(hosting, node_name=[*NEOFS_NETMAP_DICT][0], cid=cid, oid=oid)
|
||||||
|
|
||||||
storage_wallet_path = NEOFS_NETMAP_DICT[[*NEOFS_NETMAP_DICT][0]]["wallet_path"]
|
storage_wallet_path = NEOFS_NETMAP_DICT[[*NEOFS_NETMAP_DICT][0]]["wallet_path"]
|
||||||
with allure.step("Wait for dropped object replicated"):
|
with allure.step("Wait for dropped object replicated"):
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
import wallet
|
import wallet
|
||||||
from cli_helpers import _cmd_run
|
import yaml
|
||||||
|
from binary_version_helper import get_local_binaries_versions, get_remote_binaries_versions
|
||||||
from common import (
|
from common import (
|
||||||
ASSETS_DIR,
|
ASSETS_DIR,
|
||||||
FREE_STORAGE,
|
FREE_STORAGE,
|
||||||
|
HOSTING_CONFIG_FILE,
|
||||||
INFRASTRUCTURE_TYPE,
|
INFRASTRUCTURE_TYPE,
|
||||||
MAINNET_WALLET_PATH,
|
MAINNET_WALLET_PATH,
|
||||||
NEOFS_ADM_EXEC,
|
|
||||||
NEOFS_CLI_EXEC,
|
|
||||||
NEOFS_NETMAP_DICT,
|
NEOFS_NETMAP_DICT,
|
||||||
WALLET_CONFIG,
|
|
||||||
)
|
)
|
||||||
from neofs_testlib.cli import NeofsAdm, NeofsCli
|
|
||||||
|
|
||||||
from env_properties import save_env_properties
|
from env_properties import save_env_properties
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
from neofs_testlib.shell import LocalShell, Shell
|
from neofs_testlib.shell import LocalShell, Shell
|
||||||
from payment_neogo import neofs_deposit, transfer_mainnet_gas
|
from payment_neogo import neofs_deposit, transfer_mainnet_gas
|
||||||
from python_keywords.node_management import node_healthcheck
|
from python_keywords.node_management import node_healthcheck
|
||||||
from service_helper import get_storage_service_helper
|
|
||||||
|
|
||||||
logger = logging.getLogger("NeoLogger")
|
logger = logging.getLogger("NeoLogger")
|
||||||
|
|
||||||
|
@ -41,41 +37,24 @@ def cloud_infrastructure_check():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def hosting() -> Hosting:
|
||||||
|
with open(HOSTING_CONFIG_FILE, "r") as file:
|
||||||
|
hosting_config = yaml.full_load(file)
|
||||||
|
|
||||||
|
hosting_instance = Hosting()
|
||||||
|
hosting_instance.configure(hosting_config)
|
||||||
|
yield hosting_instance
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@allure.title("Check binary versions")
|
@allure.title("Check binary versions")
|
||||||
def check_binary_versions(request, client_shell):
|
def check_binary_versions(request, hosting: Hosting, client_shell: Shell):
|
||||||
# Collect versions of local binaries
|
local_versions = get_local_binaries_versions(client_shell)
|
||||||
binaries = ["neo-go", "neofs-authmate"]
|
remote_versions = get_remote_binaries_versions(hosting)
|
||||||
local_binaries = _get_binaries_version_local(binaries)
|
|
||||||
|
|
||||||
try:
|
all_versions = {**local_versions, **remote_versions}
|
||||||
local_binaries["neofs-adm"] = NeofsAdm(client_shell, NEOFS_ADM_EXEC).version.get()
|
save_env_properties(request.config, all_versions)
|
||||||
except RuntimeError:
|
|
||||||
logger.info(f"neofs-adm not installed")
|
|
||||||
local_binaries["neofs-cli"] = NeofsCli(
|
|
||||||
client_shell, NEOFS_CLI_EXEC, WALLET_CONFIG
|
|
||||||
).version.get()
|
|
||||||
# Collect versions of remote binaries
|
|
||||||
|
|
||||||
helper = get_storage_service_helper()
|
|
||||||
remote_binaries = helper.get_binaries_version()
|
|
||||||
all_binaries = {**local_binaries, **remote_binaries}
|
|
||||||
|
|
||||||
# Get version of aws binary
|
|
||||||
out = _cmd_run("aws --version")
|
|
||||||
out_lines = out.split("\n")
|
|
||||||
all_binaries["AWS"] = out_lines[0] if out_lines else "Unknown"
|
|
||||||
|
|
||||||
save_env_properties(request.config, all_binaries)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_binaries_version_local(binaries: list) -> dict:
|
|
||||||
env_out = {}
|
|
||||||
for binary in binaries:
|
|
||||||
out = _cmd_run(f"{binary} --version")
|
|
||||||
version = re.search(r"version[:\s]*(.+)", out, re.IGNORECASE)
|
|
||||||
env_out[binary] = version.group(1).strip() if version else "Unknown"
|
|
||||||
return env_out
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
@ -90,7 +69,7 @@ def prepare_tmp_dir():
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@allure.title("Collect logs")
|
@allure.title("Collect logs")
|
||||||
def collect_logs(prepare_tmp_dir):
|
def collect_logs(prepare_tmp_dir, hosting: Hosting):
|
||||||
start_time = datetime.utcnow()
|
start_time = datetime.utcnow()
|
||||||
yield
|
yield
|
||||||
end_time = datetime.utcnow()
|
end_time = datetime.utcnow()
|
||||||
|
@ -99,8 +78,8 @@ def collect_logs(prepare_tmp_dir):
|
||||||
logs_dir = os.path.join(prepare_tmp_dir, "logs")
|
logs_dir = os.path.join(prepare_tmp_dir, "logs")
|
||||||
os.makedirs(logs_dir)
|
os.makedirs(logs_dir)
|
||||||
|
|
||||||
helper = get_storage_service_helper()
|
for host in hosting.hosts:
|
||||||
helper.dump_logs(logs_dir, since=start_time, until=end_time)
|
host.dump_logs(logs_dir, since=start_time, until=end_time)
|
||||||
|
|
||||||
# Zip all files and attach to Allure because it is more convenient to download a single
|
# Zip all files and attach to Allure because it is more convenient to download a single
|
||||||
# zip with all logs rather than mess with individual logs files per service or node
|
# zip with all logs rather than mess with individual logs files per service or node
|
||||||
|
@ -110,10 +89,10 @@ def collect_logs(prepare_tmp_dir):
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@allure.title("Run health check for all storage nodes")
|
@allure.title("Run health check for all storage nodes")
|
||||||
def run_health_check(collect_logs):
|
def run_health_check(collect_logs, hosting: Hosting):
|
||||||
failed_nodes = []
|
failed_nodes = []
|
||||||
for node_name in NEOFS_NETMAP_DICT.keys():
|
for node_name in NEOFS_NETMAP_DICT.keys():
|
||||||
health_check = node_healthcheck(node_name)
|
health_check = node_healthcheck(hosting, node_name)
|
||||||
if health_check.health_status != "READY" or health_check.network_status != "ONLINE":
|
if health_check.health_status != "READY" or health_check.network_status != "ONLINE":
|
||||||
failed_nodes.append(node_name)
|
failed_nodes.append(node_name)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from common import (
|
||||||
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
|
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
|
||||||
from file_helper import generate_file, get_file_hash
|
from file_helper import generate_file, get_file_hash
|
||||||
from iptables_helper import IpTablesHelper
|
from iptables_helper import IpTablesHelper
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
from python_keywords.container import create_container
|
from python_keywords.container import create_container
|
||||||
from python_keywords.neofs_verbs import get_object, put_object
|
from python_keywords.neofs_verbs import get_object, put_object
|
||||||
from ssh_helper import HostClient
|
from ssh_helper import HostClient
|
||||||
|
@ -26,7 +27,7 @@ blocked_hosts = []
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@allure.step("Restore network")
|
@allure.step("Restore network")
|
||||||
def restore_network():
|
def restore_network(hosting: Hosting):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
not_empty = len(blocked_hosts) != 0
|
not_empty = len(blocked_hosts) != 0
|
||||||
|
@ -42,7 +43,7 @@ def restore_network():
|
||||||
IpTablesHelper.restore_input_traffic_to_port(client, PORTS_TO_BLOCK)
|
IpTablesHelper.restore_input_traffic_to_port(client, PORTS_TO_BLOCK)
|
||||||
blocked_hosts.remove(host)
|
blocked_hosts.remove(host)
|
||||||
if not_empty:
|
if not_empty:
|
||||||
wait_all_storage_node_returned()
|
wait_all_storage_node_returned(hosting)
|
||||||
|
|
||||||
|
|
||||||
@allure.title("Block Storage node traffic")
|
@allure.title("Block Storage node traffic")
|
||||||
|
|
|
@ -9,9 +9,9 @@ from common import (
|
||||||
)
|
)
|
||||||
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
|
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
|
||||||
from file_helper import generate_file, get_file_hash
|
from file_helper import generate_file, get_file_hash
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
from python_keywords.container import create_container
|
from python_keywords.container import create_container
|
||||||
from python_keywords.neofs_verbs import get_object, put_object
|
from python_keywords.neofs_verbs import get_object, put_object
|
||||||
from sbercloud_helper import SberCloud, SberCloudConfig
|
|
||||||
from ssh_helper import HostClient
|
from ssh_helper import HostClient
|
||||||
from wellknown_acl import PUBLIC_ACL
|
from wellknown_acl import PUBLIC_ACL
|
||||||
|
|
||||||
|
@ -19,21 +19,11 @@ logger = logging.getLogger("NeoLogger")
|
||||||
stopped_hosts = []
|
stopped_hosts = []
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def sbercloud_client():
|
|
||||||
with allure.step("Connect to SberCloud"):
|
|
||||||
try:
|
|
||||||
config = SberCloudConfig.from_env()
|
|
||||||
yield SberCloud(config)
|
|
||||||
except Exception as err:
|
|
||||||
pytest.fail(f"SberCloud infrastructure not available. Error\n{err}")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@allure.step("Return all storage nodes")
|
@allure.step("Return all storage nodes")
|
||||||
def return_all_storage_nodes_fixture(sbercloud_client):
|
def return_all_storage_nodes_fixture(hosting: Hosting):
|
||||||
yield
|
yield
|
||||||
return_all_storage_nodes(sbercloud_client)
|
return_all_storage_nodes(hosting)
|
||||||
|
|
||||||
|
|
||||||
def panic_reboot_host(ip: str = None):
|
def panic_reboot_host(ip: str = None):
|
||||||
|
@ -50,13 +40,14 @@ def panic_reboot_host(ip: str = None):
|
||||||
ssh_stdin.close()
|
ssh_stdin.close()
|
||||||
|
|
||||||
|
|
||||||
def return_all_storage_nodes(sbercloud_client: SberCloud) -> None:
|
def return_all_storage_nodes(hosting: Hosting) -> None:
|
||||||
for host in list(stopped_hosts):
|
for host_address in list(stopped_hosts):
|
||||||
with allure.step(f"Start storage node {host}"):
|
with allure.step(f"Start host {host_address}"):
|
||||||
sbercloud_client.start_node(node_ip=host.split(":")[-2])
|
host = hosting.get_host_by_address(host_address)
|
||||||
stopped_hosts.remove(host)
|
host.start_host()
|
||||||
|
stopped_hosts.remove(host_address)
|
||||||
|
|
||||||
wait_all_storage_node_returned()
|
wait_all_storage_node_returned(hosting)
|
||||||
|
|
||||||
|
|
||||||
@allure.title("Lost and returned nodes")
|
@allure.title("Lost and returned nodes")
|
||||||
|
@ -65,8 +56,7 @@ def return_all_storage_nodes(sbercloud_client: SberCloud) -> None:
|
||||||
def test_lost_storage_node(
|
def test_lost_storage_node(
|
||||||
prepare_wallet_and_deposit,
|
prepare_wallet_and_deposit,
|
||||||
client_shell,
|
client_shell,
|
||||||
sbercloud_client: SberCloud,
|
hosting: Hosting,
|
||||||
cloud_infrastructure_check,
|
|
||||||
hard_reboot: bool,
|
hard_reboot: bool,
|
||||||
):
|
):
|
||||||
wallet = prepare_wallet_and_deposit
|
wallet = prepare_wallet_and_deposit
|
||||||
|
@ -78,19 +68,18 @@ def test_lost_storage_node(
|
||||||
|
|
||||||
new_nodes = []
|
new_nodes = []
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
stopped_hosts.append(node)
|
host = hosting.get_host_by_service(node)
|
||||||
|
stopped_hosts.append(host.config.address)
|
||||||
with allure.step(f"Stop storage node {node}"):
|
with allure.step(f"Stop storage node {node}"):
|
||||||
sbercloud_client.stop_node(node_ip=node.split(":")[-2], hard=hard_reboot)
|
host.stop_host("hard" if hard_reboot else "soft")
|
||||||
new_nodes = wait_object_replication_on_nodes(
|
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell, excluded_nodes=[node])
|
||||||
wallet, cid, oid, 2, shell=client_shell, excluded_nodes=[node]
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not [node for node in nodes if node in new_nodes]
|
assert not [node for node in nodes if node in new_nodes]
|
||||||
got_file_path = get_object(wallet, cid, oid, shell=client_shell, endpoint=new_nodes[0])
|
got_file_path = get_object(wallet, cid, oid, shell=client_shell, endpoint=new_nodes[0])
|
||||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||||
|
|
||||||
with allure.step(f"Return storage nodes"):
|
with allure.step(f"Return storage nodes"):
|
||||||
return_all_storage_nodes(sbercloud_client)
|
return_all_storage_nodes(hosting)
|
||||||
|
|
||||||
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from random import choice
|
from random import choice
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -17,6 +18,7 @@ from data_formatters import get_wallet_public_key
|
||||||
from epoch import tick_epoch
|
from epoch import tick_epoch
|
||||||
from file_helper import generate_file
|
from file_helper import generate_file
|
||||||
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
|
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
from python_keywords.container import create_container, get_container
|
from python_keywords.container import create_container, get_container
|
||||||
from python_keywords.failover_utils import wait_object_replication_on_nodes
|
from python_keywords.failover_utils import wait_object_replication_on_nodes
|
||||||
from python_keywords.neofs_verbs import delete_object, get_object, head_object, put_object
|
from python_keywords.neofs_verbs import delete_object, get_object, head_object, put_object
|
||||||
|
@ -35,7 +37,6 @@ from python_keywords.node_management import (
|
||||||
start_nodes,
|
start_nodes,
|
||||||
stop_nodes,
|
stop_nodes,
|
||||||
)
|
)
|
||||||
from service_helper import get_storage_service_helper
|
|
||||||
from storage_policy import get_nodes_with_object, get_simple_object_copies
|
from storage_policy import get_nodes_with_object, get_simple_object_copies
|
||||||
from utility import parse_time, placement_policy_from_container, wait_for_gc_pass_on_storage_nodes
|
from utility import parse_time, placement_policy_from_container, wait_for_gc_pass_on_storage_nodes
|
||||||
from wellknown_acl import PUBLIC_ACL
|
from wellknown_acl import PUBLIC_ACL
|
||||||
|
@ -46,7 +47,7 @@ check_nodes = []
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@allure.title("Create container and pick the node with data")
|
@allure.title("Create container and pick the node with data")
|
||||||
def create_container_and_pick_node(prepare_wallet_and_deposit, client_shell):
|
def create_container_and_pick_node(prepare_wallet_and_deposit, client_shell, hosting: Hosting):
|
||||||
wallet = prepare_wallet_and_deposit
|
wallet = prepare_wallet_and_deposit
|
||||||
file_path = generate_file()
|
file_path = generate_file()
|
||||||
placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
|
placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
|
||||||
|
@ -64,45 +65,45 @@ def create_container_and_pick_node(prepare_wallet_and_deposit, client_shell):
|
||||||
|
|
||||||
yield cid, node_name
|
yield cid, node_name
|
||||||
|
|
||||||
shards = node_shard_list(node_name)
|
shards = node_shard_list(hosting, node_name)
|
||||||
assert shards
|
assert shards
|
||||||
|
|
||||||
for shard in shards:
|
for shard in shards:
|
||||||
node_shard_set_mode(node_name, shard, "read-write")
|
node_shard_set_mode(hosting, node_name, shard, "read-write")
|
||||||
|
|
||||||
node_shard_list(node_name)
|
node_shard_list(hosting, node_name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def after_run_start_all_nodes():
|
def after_run_start_all_nodes(hosting: Hosting):
|
||||||
yield
|
yield
|
||||||
try:
|
try:
|
||||||
start_nodes(list(NEOFS_NETMAP_DICT.keys()))
|
start_nodes(hosting, list(NEOFS_NETMAP_DICT.keys()))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"Node start fails with error:\n{err}")
|
logger.error(f"Node start fails with error:\n{err}")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def return_nodes_after_test_run(client_shell):
|
def return_nodes_after_test_run(client_shell: Shell, hosting: Hosting):
|
||||||
yield
|
yield
|
||||||
return_nodes(shell=client_shell)
|
return_nodes(client_shell, hosting)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Return node to cluster")
|
@allure.step("Return node to cluster")
|
||||||
def return_nodes(shell: Shell, alive_node: str = None):
|
def return_nodes(shell: Shell, hosting: Hosting, alive_node: Optional[str] = None) -> None:
|
||||||
helper = get_storage_service_helper()
|
|
||||||
for node in list(check_nodes):
|
for node in list(check_nodes):
|
||||||
with allure.step(f"Start node {node}"):
|
with allure.step(f"Start node {node}"):
|
||||||
helper.start_node(node)
|
host = hosting.get_host_by_service(node)
|
||||||
|
host.start_service(node)
|
||||||
with allure.step(f"Waiting status ready for node {node}"):
|
with allure.step(f"Waiting status ready for node {node}"):
|
||||||
wait_for_node_to_be_ready(node)
|
wait_for_node_to_be_ready(hosting, node)
|
||||||
|
|
||||||
# We need to wait for node to establish notifications from morph-chain
|
# We need to wait for node to establish notifications from morph-chain
|
||||||
# Otherwise it will hang up when we will try to set status
|
# Otherwise it will hang up when we will try to set status
|
||||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
|
|
||||||
with allure.step(f"Move node {node} to online state"):
|
with allure.step(f"Move node {node} to online state"):
|
||||||
node_set_status(node, status="online", retries=2)
|
node_set_status(hosting, node, status="online", retries=2)
|
||||||
|
|
||||||
check_nodes.remove(node)
|
check_nodes.remove(node)
|
||||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
|
@ -120,7 +121,7 @@ def return_nodes(shell: Shell, alive_node: str = None):
|
||||||
@pytest.mark.add_nodes
|
@pytest.mark.add_nodes
|
||||||
@pytest.mark.node_mgmt
|
@pytest.mark.node_mgmt
|
||||||
def test_add_nodes(
|
def test_add_nodes(
|
||||||
prepare_tmp_dir, client_shell, prepare_wallet_and_deposit, return_nodes_after_test_run
|
prepare_tmp_dir, client_shell, prepare_wallet_and_deposit, return_nodes_after_test_run, hosting: Hosting
|
||||||
):
|
):
|
||||||
wallet = prepare_wallet_and_deposit
|
wallet = prepare_wallet_and_deposit
|
||||||
placement_rule_3 = "REP 3 IN X CBF 1 SELECT 3 FROM * AS X"
|
placement_rule_3 = "REP 3 IN X CBF 1 SELECT 3 FROM * AS X"
|
||||||
|
@ -140,8 +141,8 @@ def test_add_nodes(
|
||||||
|
|
||||||
# Add node to recovery list before messing with it
|
# Add node to recovery list before messing with it
|
||||||
check_nodes.append(additional_node)
|
check_nodes.append(additional_node)
|
||||||
exclude_node_from_network_map(additional_node, alive_node)
|
exclude_node_from_network_map(hosting, additional_node, alive_node)
|
||||||
delete_node_data(additional_node)
|
delete_node_data(hosting, additional_node)
|
||||||
|
|
||||||
cid = create_container(wallet, rule=placement_rule_3, basic_acl=PUBLIC_ACL)
|
cid = create_container(wallet, rule=placement_rule_3, basic_acl=PUBLIC_ACL)
|
||||||
oid = put_object(
|
oid = put_object(
|
||||||
|
@ -149,16 +150,16 @@ def test_add_nodes(
|
||||||
)
|
)
|
||||||
wait_object_replication_on_nodes(wallet, cid, oid, 3)
|
wait_object_replication_on_nodes(wallet, cid, oid, 3)
|
||||||
|
|
||||||
return_nodes(shell=client_shell, alive_node=alive_node)
|
return_nodes(shell=client_shell, hosting=hosting, alive_node=alive_node)
|
||||||
|
|
||||||
with allure.step("Check data could be replicated to new node"):
|
with allure.step("Check data could be replicated to new node"):
|
||||||
random_node = choice(
|
random_node = choice(
|
||||||
[node for node in NEOFS_NETMAP_DICT if node not in (additional_node, alive_node)]
|
[node for node in NEOFS_NETMAP_DICT if node not in (additional_node, alive_node)]
|
||||||
)
|
)
|
||||||
exclude_node_from_network_map(random_node, alive_node)
|
exclude_node_from_network_map(hosting, random_node, alive_node)
|
||||||
|
|
||||||
wait_object_replication_on_nodes(wallet, cid, oid, 3, excluded_nodes=[random_node])
|
wait_object_replication_on_nodes(wallet, cid, oid, 3, excluded_nodes=[random_node])
|
||||||
include_node_to_network_map(random_node, alive_node, shell=client_shell)
|
include_node_to_network_map(hosting, random_node, alive_node, shell=client_shell)
|
||||||
wait_object_replication_on_nodes(wallet, cid, oid, 3)
|
wait_object_replication_on_nodes(wallet, cid, oid, 3)
|
||||||
|
|
||||||
with allure.step("Check container could be created with new node"):
|
with allure.step("Check container could be created with new node"):
|
||||||
|
@ -171,7 +172,7 @@ def test_add_nodes(
|
||||||
|
|
||||||
@allure.title("Control Operations with storage nodes")
|
@allure.title("Control Operations with storage nodes")
|
||||||
@pytest.mark.node_mgmt
|
@pytest.mark.node_mgmt
|
||||||
def test_nodes_management(prepare_tmp_dir, client_shell):
|
def test_nodes_management(prepare_tmp_dir, client_shell, hosting: Hosting):
|
||||||
"""
|
"""
|
||||||
This test checks base control operations with storage nodes (healthcheck, netmap-snapshot, set-status).
|
This test checks base control operations with storage nodes (healthcheck, netmap-snapshot, set-status).
|
||||||
"""
|
"""
|
||||||
|
@ -188,29 +189,29 @@ def test_nodes_management(prepare_tmp_dir, client_shell):
|
||||||
|
|
||||||
with allure.step("Run health check for all storage nodes"):
|
with allure.step("Run health check for all storage nodes"):
|
||||||
for node_name in NEOFS_NETMAP_DICT.keys():
|
for node_name in NEOFS_NETMAP_DICT.keys():
|
||||||
health_check = node_healthcheck(node_name)
|
health_check = node_healthcheck(hosting, node_name)
|
||||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||||
|
|
||||||
with allure.step(f"Move node {random_node} to offline state"):
|
with allure.step(f"Move node {random_node} to offline state"):
|
||||||
node_set_status(random_node, status="offline")
|
node_set_status(hosting, random_node, status="offline")
|
||||||
|
|
||||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
tick_epoch()
|
tick_epoch()
|
||||||
|
|
||||||
with allure.step(f"Check node {random_node} went to offline"):
|
with allure.step(f"Check node {random_node} went to offline"):
|
||||||
health_check = node_healthcheck(random_node)
|
health_check = node_healthcheck(hosting, random_node)
|
||||||
assert health_check.health_status == "READY" and health_check.network_status == "OFFLINE"
|
assert health_check.health_status == "READY" and health_check.network_status == "OFFLINE"
|
||||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||||
assert random_node_netmap_key not in snapshot, f"Expected node {random_node} not in netmap"
|
assert random_node_netmap_key not in snapshot, f"Expected node {random_node} not in netmap"
|
||||||
|
|
||||||
with allure.step(f"Check node {random_node} went to online"):
|
with allure.step(f"Check node {random_node} went to online"):
|
||||||
node_set_status(random_node, status="online")
|
node_set_status(hosting, random_node, status="online")
|
||||||
|
|
||||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
tick_epoch()
|
tick_epoch()
|
||||||
|
|
||||||
with allure.step(f"Check node {random_node} went to online"):
|
with allure.step(f"Check node {random_node} went to online"):
|
||||||
health_check = node_healthcheck(random_node)
|
health_check = node_healthcheck(hosting, random_node)
|
||||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||||
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
||||||
|
@ -325,7 +326,7 @@ def test_placement_policy_negative(prepare_wallet_and_deposit, placement_rule, e
|
||||||
@pytest.mark.skip(reason="We cover this scenario for Sbercloud in failover tests")
|
@pytest.mark.skip(reason="We cover this scenario for Sbercloud in failover tests")
|
||||||
@pytest.mark.node_mgmt
|
@pytest.mark.node_mgmt
|
||||||
@allure.title("NeoFS object replication on node failover")
|
@allure.title("NeoFS object replication on node failover")
|
||||||
def test_replication(prepare_wallet_and_deposit, after_run_start_all_nodes):
|
def test_replication(prepare_wallet_and_deposit, after_run_start_all_nodes, hosting: Hosting):
|
||||||
"""
|
"""
|
||||||
Test checks object replication on storage not failover and come back.
|
Test checks object replication on storage not failover and come back.
|
||||||
"""
|
"""
|
||||||
|
@ -342,22 +343,22 @@ def test_replication(prepare_wallet_and_deposit, after_run_start_all_nodes):
|
||||||
), f"Expected {expected_nodes_count} copies, got {len(nodes)}"
|
), f"Expected {expected_nodes_count} copies, got {len(nodes)}"
|
||||||
|
|
||||||
node_names = [name for name, config in NEOFS_NETMAP_DICT.items() if config.get("rpc") in nodes]
|
node_names = [name for name, config in NEOFS_NETMAP_DICT.items() if config.get("rpc") in nodes]
|
||||||
stopped_nodes = stop_nodes(1, node_names)
|
stopped_nodes = stop_nodes(hosting, 1, node_names)
|
||||||
|
|
||||||
wait_for_expected_object_copies(wallet, cid, oid)
|
wait_for_expected_object_copies(wallet, cid, oid)
|
||||||
|
|
||||||
start_nodes(stopped_nodes)
|
start_nodes(hosting, stopped_nodes)
|
||||||
tick_epoch()
|
tick_epoch()
|
||||||
|
|
||||||
for node_name in node_names:
|
for node_name in node_names:
|
||||||
wait_for_node_go_online(node_name)
|
wait_for_node_go_online(hosting, node_name)
|
||||||
|
|
||||||
wait_for_expected_object_copies(wallet, cid, oid)
|
wait_for_expected_object_copies(wallet, cid, oid)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.node_mgmt
|
@pytest.mark.node_mgmt
|
||||||
@allure.title("NeoFS object could be dropped using control command")
|
@allure.title("NeoFS object could be dropped using control command")
|
||||||
def test_drop_object(prepare_wallet_and_deposit):
|
def test_drop_object(prepare_wallet_and_deposit, hosting: Hosting):
|
||||||
"""
|
"""
|
||||||
Test checks object could be dropped using `neofs-cli control drop-objects` command.
|
Test checks object could be dropped using `neofs-cli control drop-objects` command.
|
||||||
"""
|
"""
|
||||||
|
@ -383,7 +384,7 @@ def test_drop_object(prepare_wallet_and_deposit):
|
||||||
with allure.step(f"Drop object {oid}"):
|
with allure.step(f"Drop object {oid}"):
|
||||||
get_object(wallet, cid, oid)
|
get_object(wallet, cid, oid)
|
||||||
head_object(wallet, cid, oid)
|
head_object(wallet, cid, oid)
|
||||||
drop_object(node_name, cid, oid)
|
drop_object(hosting, node_name, cid, oid)
|
||||||
wait_for_obj_dropped(wallet, cid, oid, get_object)
|
wait_for_obj_dropped(wallet, cid, oid, get_object)
|
||||||
wait_for_obj_dropped(wallet, cid, oid, head_object)
|
wait_for_obj_dropped(wallet, cid, oid, head_object)
|
||||||
|
|
||||||
|
@ -391,7 +392,7 @@ def test_drop_object(prepare_wallet_and_deposit):
|
||||||
@pytest.mark.node_mgmt
|
@pytest.mark.node_mgmt
|
||||||
@pytest.mark.skip(reason="Need to clarify scenario")
|
@pytest.mark.skip(reason="Need to clarify scenario")
|
||||||
@allure.title("Control Operations with storage nodes")
|
@allure.title("Control Operations with storage nodes")
|
||||||
def test_shards(prepare_wallet_and_deposit, create_container_and_pick_node):
|
def test_shards(prepare_wallet_and_deposit, create_container_and_pick_node, hosting: Hosting):
|
||||||
wallet = prepare_wallet_and_deposit
|
wallet = prepare_wallet_and_deposit
|
||||||
file_path = generate_file()
|
file_path = generate_file()
|
||||||
|
|
||||||
|
@ -400,13 +401,13 @@ def test_shards(prepare_wallet_and_deposit, create_container_and_pick_node):
|
||||||
|
|
||||||
# for mode in ('read-only', 'degraded'):
|
# for mode in ('read-only', 'degraded'):
|
||||||
for mode in ("degraded",):
|
for mode in ("degraded",):
|
||||||
shards = node_shard_list(node_name)
|
shards = node_shard_list(hosting, node_name)
|
||||||
assert shards
|
assert shards
|
||||||
|
|
||||||
for shard in shards:
|
for shard in shards:
|
||||||
node_shard_set_mode(node_name, shard, mode)
|
node_shard_set_mode(hosting, node_name, shard, mode)
|
||||||
|
|
||||||
shards = node_shard_list(node_name)
|
shards = node_shard_list(hosting, node_name)
|
||||||
assert shards
|
assert shards
|
||||||
|
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
|
@ -419,9 +420,9 @@ def test_shards(prepare_wallet_and_deposit, create_container_and_pick_node):
|
||||||
get_object(wallet, cid, original_oid)
|
get_object(wallet, cid, original_oid)
|
||||||
|
|
||||||
for shard in shards:
|
for shard in shards:
|
||||||
node_shard_set_mode(node_name, shard, "read-write")
|
node_shard_set_mode(hosting, node_name, shard, "read-write")
|
||||||
|
|
||||||
shards = node_shard_list(node_name)
|
shards = node_shard_list(hosting, node_name)
|
||||||
assert shards
|
assert shards
|
||||||
|
|
||||||
oid = put_object(wallet, file_path, cid)
|
oid = put_object(wallet, file_path, cid)
|
||||||
|
@ -442,11 +443,11 @@ def validate_object_copies(wallet: str, placement_rule: str, file_path: str, exp
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Wait for node {node_name} goes online")
|
@allure.step("Wait for node {node_name} goes online")
|
||||||
def wait_for_node_go_online(node_name: str) -> None:
|
def wait_for_node_go_online(hosting: Hosting, node_name: str) -> None:
|
||||||
timeout, attempts = 5, 20
|
timeout, attempts = 5, 20
|
||||||
for _ in range(attempts):
|
for _ in range(attempts):
|
||||||
try:
|
try:
|
||||||
health_check = node_healthcheck(node_name)
|
health_check = node_healthcheck(hosting, node_name)
|
||||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||||
return
|
return
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
@ -458,11 +459,11 @@ def wait_for_node_go_online(node_name: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Wait for node {node_name} is ready")
|
@allure.step("Wait for node {node_name} is ready")
|
||||||
def wait_for_node_to_be_ready(node_name: str) -> None:
|
def wait_for_node_to_be_ready(hosting: Hosting, node_name: str) -> None:
|
||||||
timeout, attempts = 30, 6
|
timeout, attempts = 30, 6
|
||||||
for _ in range(attempts):
|
for _ in range(attempts):
|
||||||
try:
|
try:
|
||||||
health_check = node_healthcheck(node_name)
|
health_check = node_healthcheck(hosting, node_name)
|
||||||
if health_check.health_status == "READY":
|
if health_check.health_status == "READY":
|
||||||
return
|
return
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
|
|
@ -5,9 +5,10 @@ from re import match
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
from binary_version_helper import get_remote_binaries_versions
|
||||||
from common import BIN_VERSIONS_FILE
|
from common import BIN_VERSIONS_FILE
|
||||||
from env_properties import read_env_properties, save_env_properties
|
from env_properties import read_env_properties, save_env_properties
|
||||||
from service_helper import get_storage_service_helper
|
from neofs_testlib.hosting import Hosting
|
||||||
|
|
||||||
logger = logging.getLogger("NeoLogger")
|
logger = logging.getLogger("NeoLogger")
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ logger = logging.getLogger("NeoLogger")
|
||||||
@allure.title("Check binaries versions")
|
@allure.title("Check binaries versions")
|
||||||
@pytest.mark.check_binaries
|
@pytest.mark.check_binaries
|
||||||
@pytest.mark.skip("Skipped due to https://j.yadro.com/browse/OBJECT-628")
|
@pytest.mark.skip("Skipped due to https://j.yadro.com/browse/OBJECT-628")
|
||||||
def test_binaries_versions(request):
|
def test_binaries_versions(request, hosting: Hosting):
|
||||||
"""
|
"""
|
||||||
Compare binaries versions from external source (url) and deployed on servers.
|
Compare binaries versions from external source (url) and deployed on servers.
|
||||||
"""
|
"""
|
||||||
|
@ -24,8 +25,7 @@ def test_binaries_versions(request):
|
||||||
|
|
||||||
binaries_to_check = download_versions_info(BIN_VERSIONS_FILE)
|
binaries_to_check = download_versions_info(BIN_VERSIONS_FILE)
|
||||||
with allure.step("Get binaries versions from servers"):
|
with allure.step("Get binaries versions from servers"):
|
||||||
helper = get_storage_service_helper()
|
got_versions = get_remote_binaries_versions(hosting)
|
||||||
got_versions = helper.get_binaries_version(binaries=list(binaries_to_check.keys()))
|
|
||||||
|
|
||||||
env_properties = read_env_properties(request.config)
|
env_properties = read_env_properties(request.config)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ from typing import Optional
|
||||||
import allure
|
import allure
|
||||||
from common import NEOFS_NETMAP_DICT
|
from common import NEOFS_NETMAP_DICT
|
||||||
from neofs_testlib.shell import Shell
|
from neofs_testlib.shell import Shell
|
||||||
|
from neofs_testlib.hosting import Hosting
|
||||||
from python_keywords.node_management import node_healthcheck
|
from python_keywords.node_management import node_healthcheck
|
||||||
from storage_policy import get_nodes_with_object
|
from storage_policy import get_nodes_with_object
|
||||||
|
|
||||||
|
@ -35,20 +36,20 @@ def wait_object_replication_on_nodes(
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Wait for storage node returned to cluster")
|
@allure.step("Wait for storage node returned to cluster")
|
||||||
def wait_all_storage_node_returned():
|
def wait_all_storage_node_returned(hosting: Hosting) -> None:
|
||||||
sleep_interval, attempts = 15, 20
|
sleep_interval, attempts = 15, 20
|
||||||
for __attempt in range(attempts):
|
for __attempt in range(attempts):
|
||||||
if is_all_storage_node_returned():
|
if is_all_storage_node_returned(hosting):
|
||||||
return
|
return
|
||||||
sleep(sleep_interval)
|
sleep(sleep_interval)
|
||||||
raise AssertionError("Storage node(s) is broken")
|
raise AssertionError("Storage node(s) is broken")
|
||||||
|
|
||||||
|
|
||||||
def is_all_storage_node_returned() -> bool:
|
def is_all_storage_node_returned(hosting: Hosting) -> bool:
|
||||||
with allure.step("Run health check for all storage nodes"):
|
with allure.step("Run health check for all storage nodes"):
|
||||||
for node_name in NEOFS_NETMAP_DICT.keys():
|
for node_name in NEOFS_NETMAP_DICT.keys():
|
||||||
try:
|
try:
|
||||||
health_check = node_healthcheck(node_name)
|
health_check = node_healthcheck(hosting, node_name)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.warning(f"Node healthcheck fails with error {err}")
|
logger.warning(f"Node healthcheck fails with error {err}")
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#!/usr/bin/python3.9
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
@ -19,7 +17,7 @@ from data_formatters import get_wallet_public_key
|
||||||
from epoch import tick_epoch
|
from epoch import tick_epoch
|
||||||
from neofs_testlib.cli import NeofsCli
|
from neofs_testlib.cli import NeofsCli
|
||||||
from neofs_testlib.shell import Shell
|
from neofs_testlib.shell import Shell
|
||||||
from service_helper import get_storage_service_helper
|
from neofs_testlib.hosting import Hosting
|
||||||
from utility import parse_time
|
from utility import parse_time
|
||||||
|
|
||||||
logger = logging.getLogger("NeoLogger")
|
logger = logging.getLogger("NeoLogger")
|
||||||
|
@ -42,7 +40,7 @@ class HealthStatus:
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Stop storage nodes")
|
@allure.step("Stop storage nodes")
|
||||||
def stop_nodes(number: int, nodes: list[str]) -> list[str]:
|
def stop_nodes(hosting: Hosting, number: int, nodes: list[str]) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Shuts down the given number of randomly selected storage nodes.
|
Shuts down the given number of randomly selected storage nodes.
|
||||||
Args:
|
Args:
|
||||||
|
@ -51,23 +49,23 @@ def stop_nodes(number: int, nodes: list[str]) -> list[str]:
|
||||||
Returns:
|
Returns:
|
||||||
(list): the list of nodes that were shut down
|
(list): the list of nodes that were shut down
|
||||||
"""
|
"""
|
||||||
helper = get_storage_service_helper()
|
|
||||||
nodes_to_stop = random.sample(nodes, number)
|
nodes_to_stop = random.sample(nodes, number)
|
||||||
for node in nodes_to_stop:
|
for node in nodes_to_stop:
|
||||||
helper.stop_node(node)
|
host = hosting.get_host_by_service(node)
|
||||||
|
host.stop_service(node)
|
||||||
return nodes_to_stop
|
return nodes_to_stop
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Start storage nodes")
|
@allure.step("Start storage nodes")
|
||||||
def start_nodes(nodes: list[str]) -> None:
|
def start_nodes(hosting: Hosting, nodes: list[str]) -> None:
|
||||||
"""
|
"""
|
||||||
The function starts specified storage nodes.
|
The function starts specified storage nodes.
|
||||||
Args:
|
Args:
|
||||||
nodes (list): the list of nodes to start
|
nodes (list): the list of nodes to start
|
||||||
"""
|
"""
|
||||||
helper = get_storage_service_helper()
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
helper.start(node)
|
host = hosting.get_host_by_service(node)
|
||||||
|
host.start_service(node)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Get Locode")
|
@allure.step("Get Locode")
|
||||||
|
@ -79,7 +77,7 @@ def get_locode() -> str:
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Healthcheck for node {node_name}")
|
@allure.step("Healthcheck for node {node_name}")
|
||||||
def node_healthcheck(node_name: str) -> HealthStatus:
|
def node_healthcheck(hosting: Hosting, node_name: str) -> HealthStatus:
|
||||||
"""
|
"""
|
||||||
The function returns node's health status.
|
The function returns node's health status.
|
||||||
Args:
|
Args:
|
||||||
|
@ -88,12 +86,12 @@ def node_healthcheck(node_name: str) -> HealthStatus:
|
||||||
health status as HealthStatus object.
|
health status as HealthStatus object.
|
||||||
"""
|
"""
|
||||||
command = "control healthcheck"
|
command = "control healthcheck"
|
||||||
output = _run_control_command(node_name, command)
|
output = _run_control_command_with_retries(hosting, node_name, command)
|
||||||
return HealthStatus.from_stdout(output)
|
return HealthStatus.from_stdout(output)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Set status for node {node_name}")
|
@allure.step("Set status for node {node_name}")
|
||||||
def node_set_status(node_name: str, status: str, retries: int = 0) -> None:
|
def node_set_status(hosting: Hosting, node_name: str, status: str, retries: int = 0) -> None:
|
||||||
"""
|
"""
|
||||||
The function sets particular status for given node.
|
The function sets particular status for given node.
|
||||||
Args:
|
Args:
|
||||||
|
@ -102,7 +100,7 @@ def node_set_status(node_name: str, status: str, retries: int = 0) -> None:
|
||||||
retries (optional, int): number of retry attempts if it didn't work from the first time
|
retries (optional, int): number of retry attempts if it didn't work from the first time
|
||||||
"""
|
"""
|
||||||
command = f"control set-status --status {status}"
|
command = f"control set-status --status {status}"
|
||||||
_run_control_command(node_name, command, retries)
|
_run_control_command_with_retries(hosting, node_name, command, retries)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Get netmap snapshot")
|
@allure.step("Get netmap snapshot")
|
||||||
|
@ -123,7 +121,7 @@ def get_netmap_snapshot(node_name: str, shell: Shell) -> str:
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Get shard list for node {node_name}")
|
@allure.step("Get shard list for node {node_name}")
|
||||||
def node_shard_list(node_name: str) -> list[str]:
|
def node_shard_list(hosting: Hosting, node_name: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
The function returns list of shards for specified node.
|
The function returns list of shards for specified node.
|
||||||
Args:
|
Args:
|
||||||
|
@ -132,46 +130,46 @@ def node_shard_list(node_name: str) -> list[str]:
|
||||||
list of shards.
|
list of shards.
|
||||||
"""
|
"""
|
||||||
command = "control shards list"
|
command = "control shards list"
|
||||||
output = _run_control_command(node_name, command)
|
output = _run_control_command_with_retries(hosting, node_name, command)
|
||||||
return re.findall(r"Shard (.*):", output)
|
return re.findall(r"Shard (.*):", output)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Shard set for node {node_name}")
|
@allure.step("Shard set for node {node_name}")
|
||||||
def node_shard_set_mode(node_name: str, shard: str, mode: str) -> str:
|
def node_shard_set_mode(hosting: Hosting, node_name: str, shard: str, mode: str) -> str:
|
||||||
"""
|
"""
|
||||||
The function sets mode for specified shard.
|
The function sets mode for specified shard.
|
||||||
Args:
|
Args:
|
||||||
node_name str: node name on which shard mode should be set.
|
node_name str: node name on which shard mode should be set.
|
||||||
"""
|
"""
|
||||||
command = f"control shards set-mode --id {shard} --mode {mode}"
|
command = f"control shards set-mode --id {shard} --mode {mode}"
|
||||||
return _run_control_command(node_name, command)
|
return _run_control_command_with_retries(hosting, node_name, command)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Drop object from node {node_name}")
|
@allure.step("Drop object from node {node_name}")
|
||||||
def drop_object(node_name: str, cid: str, oid: str) -> str:
|
def drop_object(hosting: Hosting, node_name: str, cid: str, oid: str) -> str:
|
||||||
"""
|
"""
|
||||||
The function drops object from specified node.
|
The function drops object from specified node.
|
||||||
Args:
|
Args:
|
||||||
node_name str: node name from which object should be dropped.
|
node_name str: node name from which object should be dropped.
|
||||||
"""
|
"""
|
||||||
command = f"control drop-objects -o {cid}/{oid}"
|
command = f"control drop-objects -o {cid}/{oid}"
|
||||||
return _run_control_command(node_name, command)
|
return _run_control_command_with_retries(hosting, node_name, command)
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Delete data of node {node_name}")
|
@allure.step("Delete data of node {node_name}")
|
||||||
def delete_node_data(node_name: str) -> None:
|
def delete_node_data(hosting: Hosting, node_name: str) -> None:
|
||||||
helper = get_storage_service_helper()
|
host = hosting.get_host_by_service(node_name)
|
||||||
helper.stop_node(node_name)
|
host.stop_service(node_name)
|
||||||
helper.delete_node_data(node_name)
|
host.delete_storage_node_data(node_name)
|
||||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Exclude node {node_to_exclude} from network map")
|
@allure.step("Exclude node {node_to_exclude} from network map")
|
||||||
def exclude_node_from_network_map(node_to_exclude: str, alive_node: str, shell: Shell) -> None:
|
def exclude_node_from_network_map(hosting: Hosting, node_to_exclude: str, alive_node: str, shell: Shell) -> None:
|
||||||
node_wallet_path = NEOFS_NETMAP_DICT[node_to_exclude]["wallet_path"]
|
node_wallet_path = NEOFS_NETMAP_DICT[node_to_exclude]["wallet_path"]
|
||||||
node_netmap_key = get_wallet_public_key(node_wallet_path, STORAGE_WALLET_PASS)
|
node_netmap_key = get_wallet_public_key(node_wallet_path, STORAGE_WALLET_PASS)
|
||||||
|
|
||||||
node_set_status(node_to_exclude, status="offline")
|
node_set_status(hosting, node_to_exclude, status="offline")
|
||||||
|
|
||||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
tick_epoch()
|
tick_epoch()
|
||||||
|
@ -183,8 +181,8 @@ def exclude_node_from_network_map(node_to_exclude: str, alive_node: str, shell:
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Include node {node_to_include} into network map")
|
@allure.step("Include node {node_to_include} into network map")
|
||||||
def include_node_to_network_map(node_to_include: str, alive_node: str, shell: Shell) -> None:
|
def include_node_to_network_map(hosting: Hosting, node_to_include: str, alive_node: str, shell: Shell) -> None:
|
||||||
node_set_status(node_to_include, status="online")
|
node_set_status(hosting, node_to_include, status="online")
|
||||||
|
|
||||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||||
tick_epoch()
|
tick_epoch()
|
||||||
|
@ -204,13 +202,38 @@ def check_node_in_map(node_name: str, shell: Shell, alive_node: Optional[str] =
|
||||||
assert node_netmap_key in snapshot, f"Expected node with key {node_netmap_key} in network map"
|
assert node_netmap_key in snapshot, f"Expected node with key {node_netmap_key} in network map"
|
||||||
|
|
||||||
|
|
||||||
def _run_control_command(node_name: str, command: str, retries: int = 0) -> str:
|
def _run_control_command_with_retries(
|
||||||
helper = get_storage_service_helper()
|
hosting: Hosting, node_name: str, command: str, retries: int = 0
|
||||||
|
) -> str:
|
||||||
for attempt in range(1 + retries): # original attempt + specified retries
|
for attempt in range(1 + retries): # original attempt + specified retries
|
||||||
try:
|
try:
|
||||||
return helper.run_control_command(node_name, command)
|
return _run_control_command(hosting, node_name, command)
|
||||||
except AssertionError as err:
|
except AssertionError as err:
|
||||||
if attempt < retries:
|
if attempt < retries:
|
||||||
logger.warning(f"Command {command} failed with error {err} and will be retried")
|
logger.warning(f"Command {command} failed with error {err} and will be retried")
|
||||||
continue
|
continue
|
||||||
raise AssertionError(f"Command {command} failed with error {err}") from err
|
raise AssertionError(f"Command {command} failed with error {err}") from err
|
||||||
|
|
||||||
|
|
||||||
|
def _run_control_command(hosting: Hosting, service_name: str, command: str) -> None:
|
||||||
|
host = hosting.get_host_by_service(service_name)
|
||||||
|
|
||||||
|
service_config = host.get_service_config(service_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/{service_name}-config.yaml"
|
||||||
|
wallet_config = f'password: "{wallet_password}"'
|
||||||
|
shell.exec(f"echo '{wallet_config}' > {wallet_config_path}")
|
||||||
|
|
||||||
|
cli_config = host.get_cli_config("neofs-cli")
|
||||||
|
|
||||||
|
# TODO: implement cli.control
|
||||||
|
# cli = NeofsCli(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
|
||||||
|
|
|
@ -17,12 +17,13 @@ NEOFS_CONTRACT_CACHE_TIMEOUT = os.getenv("NEOFS_CONTRACT_CACHE_TIMEOUT", "30s")
|
||||||
# of 1min plus 15 seconds for GC pass itself)
|
# of 1min plus 15 seconds for GC pass itself)
|
||||||
STORAGE_GC_TIME = os.getenv("STORAGE_GC_TIME", "75s")
|
STORAGE_GC_TIME = os.getenv("STORAGE_GC_TIME", "75s")
|
||||||
|
|
||||||
|
# TODO: we should use hosting instead of these endpoints
|
||||||
NEOFS_ENDPOINT = os.getenv("NEOFS_ENDPOINT", "s01.neofs.devenv:8080")
|
NEOFS_ENDPOINT = os.getenv("NEOFS_ENDPOINT", "s01.neofs.devenv:8080")
|
||||||
|
|
||||||
NEO_MAINNET_ENDPOINT = os.getenv("NEO_MAINNET_ENDPOINT", "http://main-chain.neofs.devenv:30333")
|
NEO_MAINNET_ENDPOINT = os.getenv("NEO_MAINNET_ENDPOINT", "http://main-chain.neofs.devenv:30333")
|
||||||
MORPH_ENDPOINT = os.getenv("MORPH_ENDPOINT", "http://morph-chain.neofs.devenv:30333")
|
MORPH_ENDPOINT = os.getenv("MORPH_ENDPOINT", "http://morph-chain.neofs.devenv:30333")
|
||||||
HTTP_GATE = os.getenv("HTTP_GATE", "http://http.neofs.devenv")
|
HTTP_GATE = os.getenv("HTTP_GATE", "http://http.neofs.devenv")
|
||||||
S3_GATE = os.getenv("S3_GATE", "https://s3.neofs.devenv:8080")
|
S3_GATE = os.getenv("S3_GATE", "https://s3.neofs.devenv:8080")
|
||||||
|
|
||||||
GAS_HASH = os.getenv("GAS_HASH", "0xd2a4cff31913016155e38e474a2c06d08be276cf")
|
GAS_HASH = os.getenv("GAS_HASH", "0xd2a4cff31913016155e38e474a2c06d08be276cf")
|
||||||
|
|
||||||
NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
|
NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
|
||||||
|
@ -30,12 +31,11 @@ NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
|
||||||
ASSETS_DIR = os.getenv("ASSETS_DIR", "TemporaryDir")
|
ASSETS_DIR = os.getenv("ASSETS_DIR", "TemporaryDir")
|
||||||
DEVENV_PATH = os.getenv("DEVENV_PATH", os.path.join("..", "neofs-dev-env"))
|
DEVENV_PATH = os.getenv("DEVENV_PATH", os.path.join("..", "neofs-dev-env"))
|
||||||
|
|
||||||
MORPH_MAGIC = os.getenv("MORPH_MAGIC")
|
|
||||||
|
|
||||||
# Password of wallet owned by user on behalf of whom we are running tests
|
# Password of wallet owned by user on behalf of whom we are running tests
|
||||||
WALLET_PASS = os.getenv("WALLET_PASS", "")
|
WALLET_PASS = os.getenv("WALLET_PASS", "")
|
||||||
|
|
||||||
# Configuration of storage nodes
|
# Configuration of storage nodes
|
||||||
|
# TODO: we should use hosting instead of all these variables
|
||||||
STORAGE_RPC_ENDPOINT_1 = os.getenv("STORAGE_RPC_ENDPOINT_1", "s01.neofs.devenv:8080")
|
STORAGE_RPC_ENDPOINT_1 = os.getenv("STORAGE_RPC_ENDPOINT_1", "s01.neofs.devenv:8080")
|
||||||
STORAGE_RPC_ENDPOINT_2 = os.getenv("STORAGE_RPC_ENDPOINT_2", "s02.neofs.devenv:8080")
|
STORAGE_RPC_ENDPOINT_2 = os.getenv("STORAGE_RPC_ENDPOINT_2", "s02.neofs.devenv:8080")
|
||||||
STORAGE_RPC_ENDPOINT_3 = os.getenv("STORAGE_RPC_ENDPOINT_3", "s03.neofs.devenv:8080")
|
STORAGE_RPC_ENDPOINT_3 = os.getenv("STORAGE_RPC_ENDPOINT_3", "s03.neofs.devenv:8080")
|
||||||
|
@ -89,7 +89,13 @@ NEOFS_NETMAP_DICT = {
|
||||||
}
|
}
|
||||||
NEOFS_NETMAP = [node["rpc"] for node in NEOFS_NETMAP_DICT.values()]
|
NEOFS_NETMAP = [node["rpc"] for node in NEOFS_NETMAP_DICT.values()]
|
||||||
|
|
||||||
# Paths to CLI executables
|
# Parameters that control SSH connection to storage node
|
||||||
|
# TODO: we should use hosting instead
|
||||||
|
STORAGE_NODE_SSH_USER = os.getenv("STORAGE_NODE_SSH_USER")
|
||||||
|
STORAGE_NODE_SSH_PASSWORD = os.getenv("STORAGE_NODE_SSH_PASSWORD")
|
||||||
|
STORAGE_NODE_SSH_PRIVATE_KEY_PATH = os.getenv("STORAGE_NODE_SSH_PRIVATE_KEY_PATH")
|
||||||
|
|
||||||
|
# Paths to CLI executables on machine that runs tests
|
||||||
NEOGO_EXECUTABLE = os.getenv("NEOGO_EXECUTABLE", "neo-go")
|
NEOGO_EXECUTABLE = os.getenv("NEOGO_EXECUTABLE", "neo-go")
|
||||||
NEOFS_CLI_EXEC = os.getenv("NEOFS_CLI_EXEC", "neofs-cli")
|
NEOFS_CLI_EXEC = os.getenv("NEOFS_CLI_EXEC", "neofs-cli")
|
||||||
NEOFS_AUTHMATE_EXEC = os.getenv("NEOFS_AUTHMATE_EXEC", "neofs-authmate")
|
NEOFS_AUTHMATE_EXEC = os.getenv("NEOFS_AUTHMATE_EXEC", "neofs-authmate")
|
||||||
|
@ -109,21 +115,17 @@ S3_GATE_WALLET_PATH = os.getenv(
|
||||||
)
|
)
|
||||||
S3_GATE_WALLET_PASS = os.getenv("S3_GATE_WALLET_PASS", "s3")
|
S3_GATE_WALLET_PASS = os.getenv("S3_GATE_WALLET_PASS", "s3")
|
||||||
|
|
||||||
# Parameters that control SSH connection to storage node
|
|
||||||
STORAGE_NODE_SSH_USER = os.getenv("STORAGE_NODE_SSH_USER")
|
|
||||||
STORAGE_NODE_SSH_PASSWORD = os.getenv("STORAGE_NODE_SSH_PASSWORD")
|
|
||||||
STORAGE_NODE_SSH_PRIVATE_KEY_PATH = os.getenv("STORAGE_NODE_SSH_PRIVATE_KEY_PATH")
|
|
||||||
|
|
||||||
# Path to directory with CLI binaries on storage node (currently we need only neofs-cli)
|
|
||||||
STORAGE_NODE_BIN_PATH = os.getenv("STORAGE_NODE_BIN_PATH", f"{DEVENV_PATH}/vendor")
|
|
||||||
|
|
||||||
# Config for neofs-adm utility. Optional if tests are running against devenv
|
# Config for neofs-adm utility. Optional if tests are running against devenv
|
||||||
NEOFS_ADM_CONFIG_PATH = os.getenv("NEOFS_ADM_CONFIG_PATH")
|
NEOFS_ADM_CONFIG_PATH = os.getenv("NEOFS_ADM_CONFIG_PATH")
|
||||||
|
|
||||||
INFRASTRUCTURE_TYPE = os.getenv("INFRASTRUCTURE_TYPE", "LOCAL_DEVENV")
|
|
||||||
FREE_STORAGE = os.getenv("FREE_STORAGE", "false").lower() == "true"
|
FREE_STORAGE = os.getenv("FREE_STORAGE", "false").lower() == "true"
|
||||||
BIN_VERSIONS_FILE = os.getenv("BIN_VERSIONS_FILE")
|
BIN_VERSIONS_FILE = os.getenv("BIN_VERSIONS_FILE")
|
||||||
|
|
||||||
|
# TODO: instead of specifying infrastructure type we should use attributes of hosts
|
||||||
|
INFRASTRUCTURE_TYPE = os.getenv("INFRASTRUCTURE_TYPE", "LOCAL_DEVENV")
|
||||||
|
|
||||||
|
HOSTING_CONFIG_FILE = os.getenv("HOSTING_CONFIG_FILE", ".devenv.hosting.yaml")
|
||||||
|
|
||||||
# Generate wallet configs
|
# Generate wallet configs
|
||||||
# TODO: we should move all info about wallet configs to fixtures
|
# TODO: we should move all info about wallet configs to fixtures
|
||||||
WALLET_CONFIG = os.path.join(os.getcwd(), "wallet_config.yml")
|
WALLET_CONFIG = os.path.join(os.getcwd(), "wallet_config.yml")
|
||||||
|
|
Loading…
Reference in a new issue