[#314] Format all files with black and isort

Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
support/v0.34
Vladimir Domnich 2022-09-28 16:07:16 +04:00 committed by Vladimir
parent 26032a67ec
commit 147cac0ebc
46 changed files with 1506 additions and 1100 deletions

View File

@ -1,6 +1,6 @@
[tool.isort]
profile = "black"
src_paths = ["pytest_tests", "robot"]
src_paths = ["neofs-keywords", "pytest_tests", "robot"]
line_length = 100
[tool.black]

View File

@ -4,37 +4,37 @@ import re
import allure
from pytest import Config
logger = logging.getLogger('NeoLogger')
logger = logging.getLogger("NeoLogger")
@allure.step('Read environment.properties')
@allure.step("Read environment.properties")
def read_env_properties(config: Config) -> dict:
environment_dir = config.getoption('--alluredir')
environment_dir = config.getoption("--alluredir")
if not environment_dir:
return None
file_path = f'{environment_dir}/environment.properties'
with open(file_path, 'r') as file:
file_path = f"{environment_dir}/environment.properties"
with open(file_path, "r") as file:
raw_content = file.read()
env_properties = {}
for line in raw_content.split('\n'):
m = re.match('(.*?)=(.*)', line)
for line in raw_content.split("\n"):
m = re.match("(.*?)=(.*)", line)
if not m:
logger.warning(f'Could not parse env property from {line}')
logger.warning(f"Could not parse env property from {line}")
continue
key, value = m.group(1), m.group(2)
env_properties[key] = value
return env_properties
@allure.step('Update data in environment.properties')
@allure.step("Update data in environment.properties")
def save_env_properties(config: Config, env_data: dict) -> None:
environment_dir = config.getoption('--alluredir')
environment_dir = config.getoption("--alluredir")
if not environment_dir:
return None
file_path = f'{environment_dir}/environment.properties'
with open(file_path, 'a+') as env_file:
file_path = f"{environment_dir}/environment.properties"
with open(file_path, "a+") as env_file:
for env, env_value in env_data.items():
env_file.write(f'{env}={env_value}\n')
env_file.write(f"{env}={env_value}\n")

View File

@ -1,6 +1,5 @@
import re
# Regex patterns of status codes of Container service (https://github.com/nspcc-dev/neofs-spec/blob/98b154848116223e486ce8b43eaa35fec08b4a99/20-api-v2/container.md)
CONTAINER_NOT_FOUND = "code = 3072.*message = container not found"

View File

@ -2,15 +2,14 @@ from ssh_helper import HostClient
class IpTablesHelper:
@staticmethod
def drop_input_traffic_to_port(client: HostClient, ports: list[str]):
for port in ports:
cmd_output = client.exec(cmd=f'sudo iptables -A INPUT -p tcp --dport {port} -j DROP')
cmd_output = client.exec(cmd=f"sudo iptables -A INPUT -p tcp --dport {port} -j DROP")
assert cmd_output.rc == 0
@staticmethod
def restore_input_traffic_to_port(client: HostClient, ports: list[str]):
for port in ports:
cmd_output = client.exec(cmd=f'sudo iptables -D INPUT -p tcp --dport {port} -j DROP')
cmd_output = client.exec(cmd=f"sudo iptables -D INPUT -p tcp --dport {port} -j DROP")
assert cmd_output.rc == 0

View File

@ -1,9 +1,11 @@
from dataclasses import dataclass
import allure
from contextlib import contextmanager
from dataclasses import dataclass
from time import sleep
from pytest_tests.helpers.ssh_helper import HostClient
import allure
from pytest_tests.helpers.remote_process import RemoteProcess
from pytest_tests.helpers.ssh_helper import HostClient
EXIT_RESULT_CODE = 0
@ -22,11 +24,7 @@ class LoadParams:
class K6:
def __init__(
self,
load_params: LoadParams,
host_client: HostClient
):
def __init__(self, load_params: LoadParams, host_client: HostClient):
self.load_params = load_params
self.host_client = host_client
@ -51,10 +49,7 @@ class K6:
@allure.step("Prepare containers and objects")
def prepare(self) -> str:
self._k6_dir = self.k6_dir
if (
self.load_params.load_type == "http"
or self.load_params.load_type == "grpc"
):
if self.load_params.load_type == "http" or self.load_params.load_type == "grpc":
command = (
f"{self.k6_dir}/scenarios/preset/preset_grpc.py "
f"--size {self.load_params.obj_size} "

View File

@ -1,8 +1,11 @@
from __future__ import annotations
import uuid
from typing import Optional
import allure
from tenacity import retry, stop_after_attempt, wait_fixed
from typing import Optional
from pytest_tests.helpers.ssh_helper import HostClient
@ -179,8 +182,6 @@ class RemoteProcess:
f"echo $? > {self.process_dir}/rc"
)
self.host_client.exec(
f'echo "{script}" > {self.process_dir}/command.sh'
)
self.host_client.exec(f'echo "{script}" > {self.process_dir}/command.sh')
self.host_client.exec(f"cat {self.process_dir}/command.sh")
self.host_client.exec(f"chmod +x {self.process_dir}/command.sh")

View File

@ -1,7 +1,7 @@
from typing import Optional
import allure
import pytest
from steps import s3_gate_bucket, s3_gate_object

View File

@ -20,17 +20,17 @@ class SberCloudConfig:
project_id: Optional[str] = None
@staticmethod
def from_dict(config_dict: dict) -> 'SberCloudConfig':
def from_dict(config_dict: dict) -> "SberCloudConfig":
return SberCloudConfig(**config_dict)
@staticmethod
def from_yaml(config_path: str) -> 'SberCloudConfig':
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':
def from_env() -> "SberCloudConfig":
config_dict = {
"access_key_id": os.getenv("SBERCLOUD_ACCESS_KEY_ID"),
"secret_key": os.getenv("SBERCLOUD_SECRET_KEY"),
@ -53,7 +53,9 @@ class SberCloudAuthRequests:
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:
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
@ -62,12 +64,14 @@ class SberCloudAuthRequests:
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:
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:
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
@ -82,8 +86,9 @@ class SberCloudAuthRequests:
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)
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)
@ -92,8 +97,10 @@ class SberCloudAuthRequests:
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})")
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]:
@ -105,21 +112,30 @@ class SberCloudAuthRequests:
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:
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
])
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 = {}
@ -166,7 +182,7 @@ class SberCloudAuthRequests:
hmac_digest = hmac.new(
key=self.secret_key.encode(self.ENCODING),
msg=string_to_sign.encode(self.ENCODING),
digestmod=hashlib.sha256
digestmod=hashlib.sha256,
).digest()
signature = binascii.hexlify(hmac_digest).decode()
@ -185,6 +201,7 @@ class SberCloud:
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,
@ -198,42 +215,29 @@ class SberCloud:
if not self.ecs_nodes or no_cache:
self.ecs_nodes = self.get_ecs_nodes()
nodes_by_ip = [
node for node in self.ecs_nodes
if ip in [
node_ip['addr']
for node_ips in node['addresses'].values()
for node_ip in node_ips
]
node
for node in self.ecs_nodes
if ip
in [node_ip["addr"] for node_ips in node["addresses"].values() for node_ip in node_ips]
]
assert len(nodes_by_ip) == 1
return nodes_by_ip[0]['id']
return nodes_by_ip[0]["id"]
def get_ecs_nodes(self) -> list[dict]:
response = self.ecs_requests.get("/detail", {"limit": "1000"}).json()
return response["servers"]
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)
}
]
}
}
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:
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)
}
]
"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)

View File

@ -6,18 +6,23 @@ import time
from contextlib import contextmanager
from datetime import datetime
from typing import Optional
from requests import HTTPError
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 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')
logger = logging.getLogger("NeoLogger")
class LocalDevEnvStorageServiceHelper:
@ -59,8 +64,8 @@ class LocalDevEnvStorageServiceHelper:
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}'
f"{NEOFS_CLI_EXEC} {command} --endpoint {control_endpoint} "
f"--wallet {wallet_path} --config {WALLET_CONFIG}"
)
output = _cmd_run(cmd)
return output
@ -77,8 +82,9 @@ class LocalDevEnvStorageServiceHelper:
def get_binaries_version(self) -> dict:
return {}
def dump_logs(self, directory_path: str, since: Optional[datetime],
until: Optional[datetime]) -> None:
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)
@ -107,8 +113,9 @@ class LocalDevEnvStorageServiceHelper:
return container
return None
def _wait_for_container_to_be_in_state(self, node_name: str, container_name: str,
expected_state: str) -> 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)}")
@ -116,7 +123,7 @@ class LocalDevEnvStorageServiceHelper:
return
time.sleep(5)
raise AssertionError(f'Container {container_name} is not in {expected_state} state.')
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
@ -143,7 +150,9 @@ class CloudVmStorageServiceHelper:
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)")
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"]
@ -161,28 +170,31 @@ class CloudVmStorageServiceHelper:
# 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}", [""])
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}'
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:
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'
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')
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:
@ -190,16 +202,16 @@ class CloudVmStorageServiceHelper:
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',
"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
@ -208,31 +220,35 @@ class CloudVmStorageServiceHelper:
with _create_ssh_client(node_name) as ssh_client:
for binary in binaries:
try:
out = ssh_client.exec(f'sudo {binary} --version').stdout
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'
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'
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})'
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:
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 "",
])
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
@ -250,6 +266,7 @@ class RemoteDevEnvStorageServiceHelper(LocalDevEnvStorageServiceHelper):
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
@ -299,12 +316,12 @@ def _create_ssh_client(node_name: str) -> HostClient:
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!')
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]
host = node_config.get("rpc").split(":")[0]
return host
@ -313,7 +330,7 @@ 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]
return node_name.split(".")[0]
def _get_storage_volume_name(node_name: str) -> str:

View File

@ -10,7 +10,7 @@ from time import sleep
from typing import ClassVar, Optional
import allure
from paramiko import AutoAddPolicy, SFTPClient, SSHClient, SSHException, ssh_exception, RSAKey
from paramiko import AutoAddPolicy, RSAKey, SFTPClient, SSHClient, SSHException, ssh_exception
from paramiko.ssh_exception import AuthenticationException
@ -20,34 +20,37 @@ class HostIsNotAvailable(Exception):
def __init__(self, ip: str = None, exc: Exception = None):
msg = f'Host is not available{f" by ip: {ip}" if ip else ""}'
if exc:
msg = f'{msg}. {exc}'
msg = f"{msg}. {exc}"
super().__init__(msg)
def log_command(func):
@wraps(func)
def wrapper(host: 'HostClient', command: str, *args, **kwargs):
def wrapper(host: "HostClient", command: str, *args, **kwargs):
display_length = 60
short = command.removeprefix("$ProgressPreference='SilentlyContinue'\n")
short = short[:display_length]
short += '...' if short != command else ''
with allure.step(f'SSH: {short}'):
short += "..." if short != command else ""
with allure.step(f"SSH: {short}"):
logging.info(f'Execute command "{command}" on "{host.ip}"')
start_time = datetime.utcnow()
cmd_result = func(host, command, *args, **kwargs)
end_time = datetime.utcnow()
log_message = f'HOST: {host.ip}\n' \
f'COMMAND:\n{textwrap.indent(command, " ")}\n' \
f'RC:\n {cmd_result.rc}\n' \
f'STDOUT:\n{textwrap.indent(cmd_result.stdout, " ")}\n' \
f'STDERR:\n{textwrap.indent(cmd_result.stderr, " ")}\n' \
f'Start / End / Elapsed\t {start_time.time()} / {end_time.time()} / {end_time - start_time}'
log_message = (
f"HOST: {host.ip}\n"
f'COMMAND:\n{textwrap.indent(command, " ")}\n'
f"RC:\n {cmd_result.rc}\n"
f'STDOUT:\n{textwrap.indent(cmd_result.stdout, " ")}\n'
f'STDERR:\n{textwrap.indent(cmd_result.stderr, " ")}\n'
f"Start / End / Elapsed\t {start_time.time()} / {end_time.time()} / {end_time - start_time}"
)
logging.info(log_message)
allure.attach(log_message, 'SSH command', allure.attachment_type.TEXT)
allure.attach(log_message, "SSH command", allure.attachment_type.TEXT)
return cmd_result
return wrapper
@ -65,9 +68,15 @@ class HostClient:
TIMEOUT_RESTORE_CONNECTION = 10, 24
def __init__(self, ip: str, login: str, password: Optional[str] = None,
private_key_path: Optional[str] = None, private_key_passphrase: Optional[str] = None,
init_ssh_client=True) -> None:
def __init__(
self,
ip: str,
login: str,
password: Optional[str] = None,
private_key_path: Optional[str] = None,
private_key_passphrase: Optional[str] = None,
init_ssh_client=True,
) -> None:
self.ip = ip
self.login = login
self.password = password
@ -83,21 +92,25 @@ class HostClient:
return cmd_result
@log_command
def exec_with_confirmation(self, cmd: str, confirmation: list, verify=True, timeout=90) -> SSHCommand:
def exec_with_confirmation(
self, cmd: str, confirmation: list, verify=True, timeout=90
) -> SSHCommand:
ssh_stdin, ssh_stdout, ssh_stderr = self.ssh_client.exec_command(cmd, timeout=timeout)
for line in confirmation:
if not line.endswith('\n'):
line = f'{line}\n'
if not line.endswith("\n"):
line = f"{line}\n"
try:
ssh_stdin.write(line)
except OSError as err:
logging.error(f'Got error {err} executing command {cmd}')
logging.error(f"Got error {err} executing command {cmd}")
ssh_stdin.close()
output = SSHCommand(stdout=ssh_stdout.read().decode(errors='ignore'),
stderr=ssh_stderr.read().decode(errors='ignore'),
rc=ssh_stdout.channel.recv_exit_status())
output = SSHCommand(
stdout=ssh_stdout.read().decode(errors="ignore"),
stderr=ssh_stderr.read().decode(errors="ignore"),
rc=ssh_stdout.channel.recv_exit_status(),
)
if verify:
debug_info = f'\nSTDOUT: {output.stdout}\nSTDERR: {output.stderr}\nRC: {output.rc}'
debug_info = f"\nSTDOUT: {output.stdout}\nSTDERR: {output.stderr}\nRC: {output.rc}"
assert output.rc == 0, f'Non zero rc from command: "{cmd}"{debug_info}'
return output
@ -111,7 +124,7 @@ class HostClient:
self.create_connection()
@contextmanager
def create_ssh_connection(self) -> 'SSHClient':
def create_ssh_connection(self) -> "SSHClient":
if not self.ssh_client:
self.create_connection()
try:
@ -119,38 +132,40 @@ class HostClient:
finally:
self.drop()
@allure.step('Restore connection')
@allure.step("Restore connection")
def restore_ssh_connection(self):
retry_time, retry_count = self.TIMEOUT_RESTORE_CONNECTION
for _ in range(retry_count):
try:
self.create_connection()
except AssertionError:
logging.warning(f'Host: Cant reach host: {self.ip}.')
logging.warning(f"Host: Cant reach host: {self.ip}.")
sleep(retry_time)
else:
logging.info(f'Host: Cant reach host: {self.ip}.')
logging.info(f"Host: Cant reach host: {self.ip}.")
return
raise AssertionError(f'Host: Cant reach host: {self.ip} after 240 seconds..')
raise AssertionError(f"Host: Cant reach host: {self.ip} after 240 seconds..")
@allure.step('Copy file {host_path_to_file} to local file {path_to_file}')
@allure.step("Copy file {host_path_to_file} to local file {path_to_file}")
def copy_file_from_host(self, host_path_to_file: str, path_to_file: str):
with self._sftp_client() as sftp_client:
sftp_client.get(host_path_to_file, path_to_file)
def copy_file_to_host(self, path_to_file: str, host_path_to_file: str):
with allure.step(f'Copy local file {path_to_file} to remote file {host_path_to_file} on host {self.ip}'):
with allure.step(
f"Copy local file {path_to_file} to remote file {host_path_to_file} on host {self.ip}"
):
with self._sftp_client() as sftp_client:
sftp_client.put(path_to_file, host_path_to_file)
@allure.step('Save string to remote file {host_path_to_file}')
@allure.step("Save string to remote file {host_path_to_file}")
def copy_str_to_host_file(self, string: str, host_path_to_file: str):
with tempfile.NamedTemporaryFile(mode='r+') as temp:
with tempfile.NamedTemporaryFile(mode="r+") as temp:
temp.writelines(string)
temp.flush()
with self._sftp_client() as client:
client.put(temp.name, host_path_to_file)
self.exec(f'cat {host_path_to_file}', verify=False)
self.exec(f"cat {host_path_to_file}", verify=False)
def create_connection(self, attempts=SSH_CONNECTION_ATTEMPTS):
exc_err = None
@ -166,8 +181,10 @@ class HostClient:
self.ssh_client.connect(
hostname=self.ip,
username=self.login,
pkey=RSAKey.from_private_key_file(self.private_key_path, self.private_key_passphrase),
timeout=self.CONNECTION_TIMEOUT
pkey=RSAKey.from_private_key_file(
self.private_key_path, self.private_key_passphrase
),
timeout=self.CONNECTION_TIMEOUT,
)
else:
logging.info(
@ -178,25 +195,25 @@ class HostClient:
hostname=self.ip,
username=self.login,
password=self.password,
timeout=self.CONNECTION_TIMEOUT
timeout=self.CONNECTION_TIMEOUT,
)
return True
except AuthenticationException as auth_err:
logging.error(f'Host: {self.ip}. {auth_err}')
logging.error(f"Host: {self.ip}. {auth_err}")
self.drop()
raise auth_err
except (
SSHException,
ssh_exception.NoValidConnectionsError,
AttributeError,
socket.timeout,
OSError
SSHException,
ssh_exception.NoValidConnectionsError,
AttributeError,
socket.timeout,
OSError,
) as ssh_err:
exc_err = ssh_err
self.drop()
logging.error(f'Host: {self.ip}, connection error. {exc_err}')
logging.error(f"Host: {self.ip}, connection error. {exc_err}")
raise HostIsNotAvailable(self.ip, exc_err)
@ -211,21 +228,21 @@ class HostClient:
try:
_, stdout, stderr = self.ssh_client.exec_command(cmd, timeout=timeout)
return SSHCommand(
stdout=stdout.read().decode(errors='ignore'),
stderr=stderr.read().decode(errors='ignore'),
rc=stdout.channel.recv_exit_status()
stdout=stdout.read().decode(errors="ignore"),
stderr=stderr.read().decode(errors="ignore"),
rc=stdout.channel.recv_exit_status(),
)
except (
SSHException,
TimeoutError,
ssh_exception.NoValidConnectionsError,
ConnectionResetError,
AttributeError,
socket.timeout,
SSHException,
TimeoutError,
ssh_exception.NoValidConnectionsError,
ConnectionResetError,
AttributeError,
socket.timeout,
) as ssh_err:
logging.error(f'Host: {self.ip}, exec command error {ssh_err}')
logging.error(f"Host: {self.ip}, exec command error {ssh_err}")
self.create_connection()
raise HostIsNotAvailable(f'Host: {self.ip} is not reachable.')
raise HostIsNotAvailable(f"Host: {self.ip} is not reachable.")
@contextmanager
def _sftp_client(self) -> SFTPClient:

View File

@ -3,6 +3,7 @@ import os
import allure
import pytest
from python_keywords.container import list_containers
from steps import s3_gate_bucket
from steps.aws_cli_client import AwsCliClient
@ -10,29 +11,31 @@ from steps.aws_cli_client import AwsCliClient
class TestS3GateBase:
s3_client = None
@pytest.fixture(scope='class', autouse=True)
@allure.title('[Class/Autouse]: Create S3 client')
@pytest.fixture(scope="class", autouse=True)
@allure.title("[Class/Autouse]: Create S3 client")
def s3_client(self, prepare_wallet_and_deposit, request):
wallet = prepare_wallet_and_deposit
s3_bearer_rules_file = f"{os.getcwd()}/robot/resources/files/s3_bearer_rules.json"
cid, bucket, access_key_id, secret_access_key, owner_private_key = \
s3_gate_bucket.init_s3_credentials(
wallet, s3_bearer_rules_file=s3_bearer_rules_file)
(
cid,
bucket,
access_key_id,
secret_access_key,
owner_private_key,
) = s3_gate_bucket.init_s3_credentials(wallet, s3_bearer_rules_file=s3_bearer_rules_file)
containers_list = list_containers(wallet)
assert cid in containers_list, f'Expected cid {cid} in {containers_list}'
assert cid in containers_list, f"Expected cid {cid} in {containers_list}"
if request.param == 'aws cli':
if request.param == "aws cli":
try:
client = AwsCliClient(access_key_id, secret_access_key)
except Exception as err:
if 'command was not found or was not executable' in str(err):
pytest.skip('AWS CLI was not found')
if "command was not found or was not executable" in str(err):
pytest.skip("AWS CLI was not found")
else:
raise RuntimeError(
'Error on creating instance for AwsCliClient') from err
raise RuntimeError("Error on creating instance for AwsCliClient") from err
else:
client = s3_gate_bucket.config_s3_client(
access_key_id, secret_access_key)
client = s3_gate_bucket.config_s3_client(access_key_id, secret_access_key)
TestS3GateBase.s3_client = client
TestS3GateBase.wallet = wallet

View File

@ -3,9 +3,14 @@ from typing import Dict, List, Optional
import allure
import pytest
from common import ASSETS_DIR, IR_WALLET_CONFIG, IR_WALLET_PATH, WALLET_CONFIG
from common import STORAGE_WALLET_PATH, STORAGE_WALLET_CONFIG
from common import (
ASSETS_DIR,
IR_WALLET_CONFIG,
IR_WALLET_PATH,
STORAGE_WALLET_CONFIG,
STORAGE_WALLET_PATH,
WALLET_CONFIG,
)
from python_keywords.acl import EACLRole
from python_keywords.container import create_container
from python_keywords.neofs_verbs import put_object
@ -38,21 +43,15 @@ def wallets(prepare_wallet_and_deposit):
yield Wallets(
wallets={
EACLRole.USER: [
Wallet(
wallet_path=prepare_wallet_and_deposit, config_path=WALLET_CONFIG
)
Wallet(wallet_path=prepare_wallet_and_deposit, config_path=WALLET_CONFIG)
],
EACLRole.OTHERS: [
Wallet(
wallet_path=init_wallet(ASSETS_DIR)[0], config_path=WALLET_CONFIG
),
Wallet(
wallet_path=init_wallet(ASSETS_DIR)[0], config_path=WALLET_CONFIG
),
Wallet(wallet_path=init_wallet(ASSETS_DIR)[0], config_path=WALLET_CONFIG),
Wallet(wallet_path=init_wallet(ASSETS_DIR)[0], config_path=WALLET_CONFIG),
],
EACLRole.SYSTEM: [
Wallet(wallet_path=IR_WALLET_PATH, config_path=IR_WALLET_CONFIG),
Wallet(wallet_path=STORAGE_WALLET_PATH, config_path=STORAGE_WALLET_CONFIG)
Wallet(wallet_path=STORAGE_WALLET_PATH, config_path=STORAGE_WALLET_CONFIG),
],
}
)

View File

@ -1,17 +1,18 @@
import logging
from typing import Optional
import allure
import pytest
from common import (
ASSETS_DIR,
COMPLEX_OBJ_SIZE,
FREE_STORAGE,
IR_WALLET_CONFIG,
IR_WALLET_PASS,
IR_WALLET_PATH,
SIMPLE_OBJ_SIZE,
ASSETS_DIR,
FREE_STORAGE
)
from epoch import tick_epoch
from typing import Optional
from grpc_responses import OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND
from python_keywords.acl import (
EACLAccess,
@ -36,8 +37,6 @@ from python_keywords.storage_group import (
from python_keywords.utility_keywords import generate_file
from wallet import init_wallet
import allure
logger = logging.getLogger("NeoLogger")
deposit = 30
@ -65,9 +64,7 @@ class TestStorageGroup:
objects = [oid]
storage_group = put_storagegroup(self.main_wallet, cid, objects)
self.expect_success_for_storagegroup_operations(
self.main_wallet, cid, objects, object_size
)
self.expect_success_for_storagegroup_operations(self.main_wallet, cid, objects, object_size)
self.expect_failure_for_storagegroup_operations(
self.other_wallet, cid, objects, storage_group
)
@ -81,9 +78,7 @@ class TestStorageGroup:
file_path = generate_file(object_size)
oid = put_object(self.main_wallet, file_path, cid)
objects = [oid]
self.expect_success_for_storagegroup_operations(
self.main_wallet, cid, objects, object_size
)
self.expect_success_for_storagegroup_operations(self.main_wallet, cid, objects, object_size)
self.expect_success_for_storagegroup_operations(
self.other_wallet, cid, objects, object_size
)
@ -97,9 +92,7 @@ class TestStorageGroup:
file_path = generate_file(object_size)
oid = put_object(self.main_wallet, file_path, cid)
objects = [oid]
self.expect_success_for_storagegroup_operations(
self.main_wallet, cid, objects, object_size
)
self.expect_success_for_storagegroup_operations(self.main_wallet, cid, objects, object_size)
self.storagegroup_operations_by_other_ro_container(
self.main_wallet, self.other_wallet, cid, objects, object_size
)
@ -113,14 +106,12 @@ class TestStorageGroup:
file_path = generate_file(object_size)
oid = put_object(self.main_wallet, file_path, cid)
objects = [oid]
self.expect_success_for_storagegroup_operations(
self.main_wallet, cid, objects, object_size
)
self.expect_success_for_storagegroup_operations(self.main_wallet, cid, objects, object_size)
storage_group = put_storagegroup(self.main_wallet, cid, objects)
eacl_deny = [
EACLRule(access=EACLAccess.DENY, role=role, operation=op)
for op in EACLOperation
for role in EACLRole
EACLRule(access=EACLAccess.DENY, role=role, operation=op)
for op in EACLOperation
for role in EACLRole
]
set_eacl(self.main_wallet, cid, create_eacl(cid, eacl_deny))
self.expect_failure_for_storagegroup_operations(
@ -208,15 +199,11 @@ class TestStorageGroup:
that System key is granted to make all operations except PUT and DELETE.
"""
if not FREE_STORAGE:
transfer_mainnet_gas(
IR_WALLET_PATH, deposit + 1, wallet_password=IR_WALLET_PASS
)
transfer_mainnet_gas(IR_WALLET_PATH, deposit + 1, wallet_password=IR_WALLET_PASS)
neofs_deposit(IR_WALLET_PATH, deposit, wallet_password=IR_WALLET_PASS)
storage_group = put_storagegroup(wallet, cid, obj_list)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
put_storagegroup(
IR_WALLET_PATH, cid, obj_list, wallet_config=IR_WALLET_CONFIG
)
put_storagegroup(IR_WALLET_PATH, cid, obj_list, wallet_config=IR_WALLET_CONFIG)
verify_list_storage_group(
IR_WALLET_PATH, cid, storage_group, wallet_config=IR_WALLET_CONFIG
)
@ -229,6 +216,4 @@ class TestStorageGroup:
wallet_config=IR_WALLET_CONFIG,
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
delete_storagegroup(
IR_WALLET_PATH, cid, storage_group, wallet_config=IR_WALLET_CONFIG
)
delete_storagegroup(IR_WALLET_PATH, cid, storage_group, wallet_config=IR_WALLET_CONFIG)

View File

@ -1,6 +1,5 @@
import allure
import pytest
from python_keywords.acl import EACLRole
from python_keywords.container import create_container
from python_keywords.container_access import (
@ -31,9 +30,7 @@ class TestACLBasic:
def private_container(self, wallets):
user_wallet = wallets.get_wallet()
with allure.step("Create private container"):
cid_private = create_container(
user_wallet.wallet_path, basic_acl=PRIVATE_ACL_F
)
cid_private = create_container(user_wallet.wallet_path, basic_acl=PRIVATE_ACL_F)
yield cid_private
@ -44,9 +41,7 @@ class TestACLBasic:
def read_only_container(self, wallets):
user_wallet = wallets.get_wallet()
with allure.step("Create public readonly container"):
cid_read_only = create_container(
user_wallet.wallet_path, basic_acl=READONLY_ACL_F
)
cid_read_only = create_container(user_wallet.wallet_path, basic_acl=READONLY_ACL_F)
yield cid_read_only
@ -78,12 +73,8 @@ class TestACLBasic:
attributes={"created": "other"},
)
with allure.step(f"Check {desc} has full access to public container"):
check_full_access_to_container(
wallet.wallet_path, cid, owner_object_oid, file_path
)
check_full_access_to_container(
wallet.wallet_path, cid, other_object_oid, file_path
)
check_full_access_to_container(wallet.wallet_path, cid, owner_object_oid, file_path)
check_full_access_to_container(wallet.wallet_path, cid, other_object_oid, file_path)
@allure.title("Test basic ACL on private container")
def test_basic_acl_private(self, wallets, private_container, file_path):
@ -97,9 +88,7 @@ class TestACLBasic:
owner_object_oid = put_object(user_wallet.wallet_path, file_path, cid)
with allure.step("Check only owner has full access to private container"):
with allure.step(
"Check no one except owner has access to operations with container"
):
with allure.step("Check no one except owner has access to operations with container"):
check_no_access_to_container(
other_wallet.wallet_path, cid, owner_object_oid, file_path
)
@ -121,14 +110,8 @@ class TestACLBasic:
with allure.step("Add test objects to container"):
object_oid = put_object(user_wallet.wallet_path, file_path, cid)
with allure.step(
"Check other has read-only access to operations with container"
):
check_read_only_container(
other_wallet.wallet_path, cid, object_oid, file_path
)
with allure.step("Check other has read-only access to operations with container"):
check_read_only_container(other_wallet.wallet_path, cid, object_oid, file_path)
with allure.step("Check owner has full access to public container"):
check_full_access_to_container(
user_wallet.wallet_path, cid, object_oid, file_path
)
check_full_access_to_container(user_wallet.wallet_path, cid, object_oid, file_path)

View File

@ -1,6 +1,5 @@
import allure
import pytest
from python_keywords.acl import (
EACLAccess,
EACLOperation,
@ -24,16 +23,12 @@ from python_keywords.container_access import (
class TestACLBearer:
@pytest.mark.parametrize("role", [EACLRole.USER, EACLRole.OTHERS])
def test_bearer_token_operations(self, wallets, eacl_container_with_objects, role):
allure.dynamic.title(
f"Testcase to validate NeoFS operations with {role.value} BearerToken"
)
allure.dynamic.title(f"Testcase to validate NeoFS operations with {role.value} BearerToken")
cid, objects_oids, file_path = eacl_container_with_objects
user_wallet = wallets.get_wallet()
deny_wallet = wallets.get_wallet(role)
with allure.step(
f"Check {role.value} has full access to container without bearer token"
):
with allure.step(f"Check {role.value} has full access to container without bearer token"):
check_full_access_to_container(
deny_wallet.wallet_path,
cid,
@ -44,16 +39,13 @@ class TestACLBearer:
with allure.step(f"Set deny all operations for {role.value} via eACL"):
eacl = [
EACLRule(access=EACLAccess.DENY, role=role, operation=op)
for op in EACLOperation
EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in EACLOperation
]
eacl_file = create_eacl(cid, eacl)
set_eacl(user_wallet.wallet_path, cid, eacl_file)
wait_for_cache_expired()
with allure.step(
f"Create bearer token for {role.value} with all operations allowed"
):
with allure.step(f"Create bearer token for {role.value} with all operations allowed"):
bearer_token = form_bearertoken_file(
user_wallet.wallet_path,
cid,
@ -88,8 +80,7 @@ class TestACLBearer:
with allure.step(f"Set allow all operations for {role.value} via eACL"):
eacl = [
EACLRule(access=EACLAccess.ALLOW, role=role, operation=op)
for op in EACLOperation
EACLRule(access=EACLAccess.ALLOW, role=role, operation=op) for op in EACLOperation
]
eacl_file = create_eacl(cid, eacl)
set_eacl(user_wallet.wallet_path, cid, eacl_file)
@ -107,9 +98,7 @@ class TestACLBearer:
)
@allure.title("BearerToken Operations for compound Operations")
def test_bearer_token_compound_operations(
self, wallets, eacl_container_with_objects
):
def test_bearer_token_compound_operations(self, wallets, eacl_container_with_objects):
cid, objects_oids, file_path = eacl_container_with_objects
user_wallet = wallets.get_wallet()
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
@ -140,26 +129,19 @@ class TestACLBearer:
deny_map_with_bearer = {
EACLRole.USER: [
op
for op in deny_map[EACLRole.USER]
if op not in bearer_map[EACLRole.USER]
op for op in deny_map[EACLRole.USER] if op not in bearer_map[EACLRole.USER]
],
EACLRole.OTHERS: [
op
for op in deny_map[EACLRole.OTHERS]
if op not in bearer_map[EACLRole.OTHERS]
op for op in deny_map[EACLRole.OTHERS] if op not in bearer_map[EACLRole.OTHERS]
],
}
eacl_deny = []
for role, operations in deny_map.items():
eacl_deny += [
EACLRule(access=EACLAccess.DENY, role=role, operation=op)
for op in operations
EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in operations
]
set_eacl(
user_wallet.wallet_path, cid, eacl_table_path=create_eacl(cid, eacl_deny)
)
set_eacl(user_wallet.wallet_path, cid, eacl_table_path=create_eacl(cid, eacl_deny))
wait_for_cache_expired()
with allure.step("Check rule consistency without bearer"):
@ -194,9 +176,7 @@ class TestACLBearer:
user_wallet.wallet_path,
cid,
[
EACLRule(
operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS
)
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS)
for op in bearer_map[EACLRole.OTHERS]
],
)

View File

@ -1,6 +1,5 @@
import allure
import pytest
from common import NEOFS_NETMAP_DICT
from failover_utils import wait_object_replication_on_nodes
from python_keywords.acl import (
@ -20,12 +19,12 @@ from python_keywords.container_access import (
from python_keywords.neofs_verbs import put_object
from python_keywords.node_management import drop_object
from python_keywords.object_access import (
can_get_object,
can_put_object,
can_delete_object,
can_get_head_object,
can_get_object,
can_get_range_hash_of_object,
can_get_range_of_object,
can_put_object,
can_search_object,
)
from wellknown_acl import PUBLIC_ACL

View File

@ -1,6 +1,5 @@
import allure
import pytest
from python_keywords.acl import (
EACLAccess,
EACLFilter,
@ -21,11 +20,7 @@ from python_keywords.container_access import (
check_no_access_to_container,
)
from python_keywords.neofs_verbs import put_object
from python_keywords.object_access import (
can_get_head_object,
can_get_object,
can_put_object,
)
from python_keywords.object_access import can_get_head_object, can_get_object, can_put_object
from wellknown_acl import PUBLIC_ACL
@ -112,12 +107,8 @@ class TestEACLFilters:
@pytest.mark.parametrize(
"match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]
)
def test_extended_acl_filters_request(
self, wallets, eacl_container_with_objects, match_type
):
allure.dynamic.title(
f"Validate NeoFS operations with request filter: {match_type.name}"
)
def test_extended_acl_filters_request(self, wallets, eacl_container_with_objects, match_type):
allure.dynamic.title(f"Validate NeoFS operations with request filter: {match_type.name}")
user_wallet = wallets.get_wallet()
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
(
@ -147,14 +138,10 @@ class TestEACLFilters:
# is STRING_EQUAL, then requests with "check_key=OTHER_ATTRIBUTE" will be allowed while
# requests with "check_key=ATTRIBUTE" will be denied, and vice versa
allow_headers = (
self.OTHER_ATTRIBUTE
if match_type == EACLMatchType.STRING_EQUAL
else self.ATTRIBUTE
self.OTHER_ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.ATTRIBUTE
)
deny_headers = (
self.ATTRIBUTE
if match_type == EACLMatchType.STRING_EQUAL
else self.OTHER_ATTRIBUTE
self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE
)
# We test on 3 groups of objects with various headers,
# but eACL rule should ignore object headers and
@ -164,12 +151,8 @@ class TestEACLFilters:
objects_with_other_header,
objects_without_header,
):
with allure.step(
"Check other has full access when sending request without headers"
):
check_full_access_to_container(
other_wallet.wallet_path, cid, oid.pop(), file_path
)
with allure.step("Check other has full access when sending request without headers"):
check_full_access_to_container(other_wallet.wallet_path, cid, oid.pop(), file_path)
with allure.step(
"Check other has full access when sending request with allowed headers"
@ -182,9 +165,7 @@ class TestEACLFilters:
xhdr=allow_headers,
)
with allure.step(
"Check other has no access when sending request with denied headers"
):
with allure.step("Check other has no access when sending request with denied headers"):
check_no_access_to_container(
other_wallet.wallet_path,
cid,
@ -201,9 +182,7 @@ class TestEACLFilters:
user_wallet.wallet_path,
cid,
[
EACLRule(
operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS
)
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS)
for op in EACLOperation
],
)
@ -265,9 +244,7 @@ class TestEACLFilters:
# but eACL rule should ignore request headers and validate
# only object headers
for xhdr in (self.ATTRIBUTE, self.OTHER_ATTRIBUTE, None):
with allure.step(
"Check other have full access to objects without attributes"
):
with allure.step("Check other have full access to objects without attributes"):
check_full_access_to_container(
other_wallet.wallet_path,
cid,
@ -276,9 +253,7 @@ class TestEACLFilters:
xhdr=xhdr,
)
with allure.step(
"Check other have full access to objects without deny attribute"
):
with allure.step("Check other have full access to objects without deny attribute"):
check_full_access_to_container(
other_wallet.wallet_path,
cid,
@ -287,9 +262,7 @@ class TestEACLFilters:
xhdr=xhdr,
)
with allure.step(
"Check other have no access to objects with deny attribute"
):
with allure.step("Check other have no access to objects with deny attribute"):
with pytest.raises(AssertionError):
assert can_get_head_object(
other_wallet.wallet_path, cid, deny_objects[0], xhdr=xhdr
@ -328,9 +301,7 @@ class TestEACLFilters:
)
allow_attribute = (
self.OTHER_ATTRIBUTE
if match_type == EACLMatchType.STRING_EQUAL
else self.ATTRIBUTE
self.OTHER_ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.ATTRIBUTE
)
with allure.step("Check other can PUT objects without denied attribute"):
assert can_put_object(
@ -339,9 +310,7 @@ class TestEACLFilters:
assert can_put_object(other_wallet.wallet_path, cid, file_path)
deny_attribute = (
self.ATTRIBUTE
if match_type == EACLMatchType.STRING_EQUAL
else self.OTHER_ATTRIBUTE
self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE
)
with allure.step("Check other can not PUT objects with denied attribute"):
with pytest.raises(AssertionError):
@ -432,8 +401,7 @@ class TestEACLFilters:
assert can_put_object(other_wallet.wallet_path, cid, file_path)
with allure.step(
"Check other can get and put objects without attributes "
"and using bearer token"
"Check other can get and put objects without attributes and using bearer token"
):
bearer_token_other = form_bearertoken_file(
user_wallet.wallet_path,
@ -464,9 +432,7 @@ class TestEACLFilters:
other_wallet.wallet_path, cid, file_path, bearer=bearer_token_other
)
with allure.step(
f"Check other can get objects with attributes matching the filter"
):
with allure.step(f"Check other can get objects with attributes matching the filter"):
oid = allow_objects.pop()
assert can_get_head_object(other_wallet.wallet_path, cid, oid)
assert can_get_object(other_wallet.wallet_path, cid, oid, file_path)
@ -474,17 +440,11 @@ class TestEACLFilters:
other_wallet.wallet_path, cid, file_path, attributes=allow_attribute
)
with allure.step(
"Check other cannot get objects without attributes matching the filter"
):
with allure.step("Check other cannot get objects without attributes matching the filter"):
with pytest.raises(AssertionError):
assert can_get_head_object(
other_wallet.wallet_path, cid, deny_objects[0]
)
assert can_get_head_object(other_wallet.wallet_path, cid, deny_objects[0])
with pytest.raises(AssertionError):
assert can_get_object(
other_wallet.wallet_path, cid, deny_objects[0], file_path
)
assert can_get_object(other_wallet.wallet_path, cid, deny_objects[0], file_path)
with pytest.raises(AssertionError):
assert can_put_object(
other_wallet.wallet_path, cid, file_path, attributes=deny_attribute

View File

@ -3,75 +3,87 @@ import json
import allure
import pytest
from epoch import tick_epoch
from python_keywords.container import (create_container, delete_container, get_container, list_containers,
wait_for_container_creation, wait_for_container_deletion)
from python_keywords.container import (
create_container,
delete_container,
get_container,
list_containers,
wait_for_container_creation,
wait_for_container_deletion,
)
from utility import placement_policy_from_container
from wellknown_acl import PRIVATE_ACL_F
@pytest.mark.parametrize('name', ['', 'test-container'], ids=['No name', 'Set particular name'])
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
@pytest.mark.sanity
@pytest.mark.container
def test_container_creation(prepare_wallet_and_deposit, name):
scenario_title = f'with name {name}' if name else 'without name'
allure.dynamic.title(f'User can create container {scenario_title}')
scenario_title = f"with name {name}" if name else "without name"
allure.dynamic.title(f"User can create container {scenario_title}")
wallet = prepare_wallet_and_deposit
with open(wallet) as file:
json_wallet = json.load(file)
placement_rule = 'REP 2 IN X CBF 1 SELECT 2 FROM * AS X'
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
cid = create_container(wallet, rule=placement_rule, name=name)
containers = list_containers(wallet)
assert cid in containers, f'Expected container {cid} in containers: {containers}'
assert cid in containers, f"Expected container {cid} in containers: {containers}"
container_info: str = get_container(wallet, cid, json_mode=False)
container_info = container_info.casefold() # To ignore case when comparing with expected values
info_to_check = {
f'basic ACL: {PRIVATE_ACL_F} (private)',
f'owner ID: {json_wallet.get("accounts")[0].get("address")}',
f'container ID: {cid}',
f"basic ACL: {PRIVATE_ACL_F} (private)",
f"owner ID: {json_wallet.get('accounts')[0].get('address')}",
f"container ID: {cid}",
}
if name:
info_to_check.add(f'Name={name}')
info_to_check.add(f"Name={name}")
with allure.step('Check container has correct information'):
with allure.step("Check container has correct information"):
expected_policy = placement_rule.casefold()
actual_policy = placement_policy_from_container(container_info)
assert actual_policy == expected_policy, \
f'Expected policy\n{expected_policy} but got policy\n{actual_policy}'
assert (
actual_policy == expected_policy
), f"Expected policy\n{expected_policy} but got policy\n{actual_policy}"
for info in info_to_check:
expected_info = info.casefold()
assert expected_info in container_info, \
f'Expected {expected_info} in container info:\n{container_info}'
assert (
expected_info in container_info
), f"Expected {expected_info} in container info:\n{container_info}"
with allure.step('Delete container and check it was deleted'):
with allure.step("Delete container and check it was deleted"):
delete_container(wallet, cid)
tick_epoch()
wait_for_container_deletion(wallet, cid)
@allure.title('Parallel container creation and deletion')
@allure.title("Parallel container creation and deletion")
@pytest.mark.sanity
@pytest.mark.container
def test_container_creation_deletion_parallel(prepare_wallet_and_deposit):
containers_count = 3
wallet = prepare_wallet_and_deposit
placement_rule = 'REP 2 IN X CBF 1 SELECT 2 FROM * AS X'
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
cids: list[str] = []
with allure.step(f'Create {containers_count} containers'):
with allure.step(f"Create {containers_count} containers"):
for _ in range(containers_count):
cids.append(create_container(wallet, rule=placement_rule, await_mode=False, wait_for_creation=False))
cids.append(
create_container(
wallet, rule=placement_rule, await_mode=False, wait_for_creation=False
)
)
with allure.step(f'Wait for containers occur in container list'):
with allure.step(f"Wait for containers occur in container list"):
for cid in cids:
wait_for_container_creation(wallet, cid, sleep_interval=containers_count)
with allure.step('Delete containers and check they were deleted'):
with allure.step("Delete containers and check they were deleted"):
for cid in cids:
delete_container(wallet, cid)
tick_epoch()

View File

@ -4,8 +4,11 @@ from time import sleep
import allure
import pytest
from common import (STORAGE_NODE_SSH_PASSWORD, STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
STORAGE_NODE_SSH_USER)
from common import (
STORAGE_NODE_SSH_PASSWORD,
STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
STORAGE_NODE_SSH_USER,
)
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
from iptables_helper import IpTablesHelper
from python_keywords.container import create_container
@ -14,24 +17,27 @@ from python_keywords.utility_keywords import generate_file, get_file_hash
from ssh_helper import HostClient
from wellknown_acl import PUBLIC_ACL
logger = logging.getLogger('NeoLogger')
STORAGE_NODE_COMMUNICATION_PORT = '8080'
STORAGE_NODE_COMMUNICATION_PORT_TLS = '8082'
logger = logging.getLogger("NeoLogger")
STORAGE_NODE_COMMUNICATION_PORT = "8080"
STORAGE_NODE_COMMUNICATION_PORT_TLS = "8082"
PORTS_TO_BLOCK = [STORAGE_NODE_COMMUNICATION_PORT, STORAGE_NODE_COMMUNICATION_PORT_TLS]
blocked_hosts = []
@pytest.fixture(autouse=True)
@allure.step('Restore network')
@allure.step("Restore network")
def restore_network():
yield
not_empty = len(blocked_hosts) != 0
for host in list(blocked_hosts):
with allure.step(f'Start storage node {host}'):
client = HostClient(ip=host, login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH)
with allure.step(f"Start storage node {host}"):
client = HostClient(
ip=host,
login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
)
with client.create_ssh_connection():
IpTablesHelper.restore_input_traffic_to_port(client, PORTS_TO_BLOCK)
blocked_hosts.remove(host)
@ -39,7 +45,7 @@ def restore_network():
wait_all_storage_node_returned()
@allure.title('Block Storage node traffic')
@allure.title("Block Storage node traffic")
@pytest.mark.failover
@pytest.mark.failover_net
def test_block_storage_node_traffic(prepare_wallet_and_deposit, cloud_infrastructure_check):
@ -47,7 +53,7 @@ def test_block_storage_node_traffic(prepare_wallet_and_deposit, cloud_infrastruc
Block storage nodes traffic using iptables and wait for replication for objects.
"""
wallet = prepare_wallet_and_deposit
placement_rule = 'REP 2 IN X CBF 2 SELECT 2 FROM * AS X'
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
excluded_nodes = []
wakeup_node_timeout = 10 # timeout to let nodes detect that traffic has blocked
nodes_to_block_count = 2
@ -57,24 +63,29 @@ def test_block_storage_node_traffic(prepare_wallet_and_deposit, cloud_infrastruc
oid = put_object(wallet, source_file_path, cid)
nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2)
logger.info(f'Nodes are {nodes}')
random_nodes = [(node, node.split(':')[0]) for node in nodes]
logger.info(f"Nodes are {nodes}")
random_nodes = [(node, node.split(":")[0]) for node in nodes]
if nodes_to_block_count > len(nodes):
random_nodes = [(node, node.split(':')[0]) for node in choices(nodes, k=2)]
random_nodes = [(node, node.split(":")[0]) for node in choices(nodes, k=2)]
for random_node, random_node_ip in random_nodes:
client = HostClient(ip=random_node_ip, login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH)
client = HostClient(
ip=random_node_ip,
login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
)
with allure.step(f'Block incoming traffic for node {random_node} on port {PORTS_TO_BLOCK}'):
with allure.step(f"Block incoming traffic for node {random_node} on port {PORTS_TO_BLOCK}"):
with client.create_ssh_connection():
IpTablesHelper.drop_input_traffic_to_port(client, PORTS_TO_BLOCK)
blocked_hosts.append(random_node_ip)
excluded_nodes.append(random_node)
sleep(wakeup_node_timeout)
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, excluded_nodes=excluded_nodes)
new_nodes = wait_object_replication_on_nodes(
wallet, cid, oid, 2, excluded_nodes=excluded_nodes
)
assert random_node not in new_nodes
@ -82,11 +93,16 @@ def test_block_storage_node_traffic(prepare_wallet_and_deposit, cloud_infrastruc
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
for random_node, random_node_ip in random_nodes:
client = HostClient(ip=random_node_ip, login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH)
client = HostClient(
ip=random_node_ip,
login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
)
with allure.step(f'Unblock incoming traffic for node {random_node} on port {PORTS_TO_BLOCK}'):
with allure.step(
f"Unblock incoming traffic for node {random_node} on port {PORTS_TO_BLOCK}"
):
with client.create_ssh_connection():
IpTablesHelper.restore_input_traffic_to_port(client, PORTS_TO_BLOCK)
blocked_hosts.remove(random_node_ip)

View File

@ -2,7 +2,11 @@ import logging
import allure
import pytest
from common import STORAGE_NODE_SSH_PASSWORD, STORAGE_NODE_SSH_PRIVATE_KEY_PATH, STORAGE_NODE_SSH_USER
from common import (
STORAGE_NODE_SSH_PASSWORD,
STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
STORAGE_NODE_SSH_USER,
)
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
from python_keywords.container import create_container
from python_keywords.neofs_verbs import get_object, put_object
@ -11,52 +15,61 @@ from sbercloud_helper import SberCloud, SberCloudConfig
from ssh_helper import HostClient
from wellknown_acl import PUBLIC_ACL
logger = logging.getLogger('NeoLogger')
logger = logging.getLogger("NeoLogger")
stopped_hosts = []
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def sbercloud_client():
with allure.step('Connect to SberCloud'):
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.fail(f"SberCloud infrastructure not available. Error\n{err}")
@pytest.fixture(autouse=True)
@allure.step('Return all storage nodes')
@allure.step("Return all storage nodes")
def return_all_storage_nodes_fixture(sbercloud_client):
yield
return_all_storage_nodes(sbercloud_client)
def panic_reboot_host(ip: str = None):
ssh = HostClient(ip=ip, login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH)
ssh = HostClient(
ip=ip,
login=STORAGE_NODE_SSH_USER,
password=STORAGE_NODE_SSH_PASSWORD,
private_key_path=STORAGE_NODE_SSH_PRIVATE_KEY_PATH,
)
ssh.exec('sudo sh -c "echo 1 > /proc/sys/kernel/sysrq"')
ssh_stdin, _, _ = ssh.ssh_client.exec_command('sudo sh -c "echo b > /proc/sysrq-trigger"', timeout=1)
ssh_stdin, _, _ = ssh.ssh_client.exec_command(
'sudo sh -c "echo b > /proc/sysrq-trigger"', timeout=1
)
ssh_stdin.close()
def return_all_storage_nodes(sbercloud_client: SberCloud) -> None:
for host in list(stopped_hosts):
with allure.step(f'Start storage node {host}'):
sbercloud_client.start_node(node_ip=host.split(':')[-2])
with allure.step(f"Start storage node {host}"):
sbercloud_client.start_node(node_ip=host.split(":")[-2])
stopped_hosts.remove(host)
wait_all_storage_node_returned()
@allure.title('Lost and returned nodes')
@pytest.mark.parametrize('hard_reboot', [True, False])
@allure.title("Lost and returned nodes")
@pytest.mark.parametrize("hard_reboot", [True, False])
@pytest.mark.failover
def test_lost_storage_node(prepare_wallet_and_deposit, sbercloud_client: SberCloud, cloud_infrastructure_check,
hard_reboot: bool):
def test_lost_storage_node(
prepare_wallet_and_deposit,
sbercloud_client: SberCloud,
cloud_infrastructure_check,
hard_reboot: bool,
):
wallet = prepare_wallet_and_deposit
placement_rule = 'REP 2 IN X CBF 2 SELECT 2 FROM * AS X'
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
source_file_path = generate_file()
cid = create_container(wallet, rule=placement_rule, basic_acl=PUBLIC_ACL)
oid = put_object(wallet, source_file_path, cid)
@ -65,15 +78,15 @@ def test_lost_storage_node(prepare_wallet_and_deposit, sbercloud_client: SberClo
new_nodes = []
for node in nodes:
stopped_hosts.append(node)
with allure.step(f'Stop storage node {node}'):
sbercloud_client.stop_node(node_ip=node.split(':')[-2], hard=hard_reboot)
with allure.step(f"Stop storage node {node}"):
sbercloud_client.stop_node(node_ip=node.split(":")[-2], hard=hard_reboot)
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, excluded_nodes=[node])
assert not [node for node in nodes if node in new_nodes]
got_file_path = get_object(wallet, cid, oid, endpoint=new_nodes[0])
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)
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2)
@ -82,36 +95,42 @@ def test_lost_storage_node(prepare_wallet_and_deposit, sbercloud_client: SberClo
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
@allure.title('Panic storage node(s)')
@pytest.mark.parametrize('sequence', [True, False])
@allure.title("Panic storage node(s)")
@pytest.mark.parametrize("sequence", [True, False])
@pytest.mark.failover_panic
@pytest.mark.failover
def test_panic_storage_node(prepare_wallet_and_deposit, cloud_infrastructure_check,
sequence: bool):
def test_panic_storage_node(prepare_wallet_and_deposit, cloud_infrastructure_check, sequence: bool):
wallet = prepare_wallet_and_deposit
placement_rule = 'REP 2 IN X CBF 2 SELECT 2 FROM * AS X'
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
source_file_path = generate_file()
cid = create_container(wallet, rule=placement_rule, basic_acl=PUBLIC_ACL)
oid = put_object(wallet, source_file_path, cid)
nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2)
new_nodes: list[str] = []
allure.attach('\n'.join(nodes), 'Current nodes with object', allure.attachment_type.TEXT)
allure.attach("\n".join(nodes), "Current nodes with object", allure.attachment_type.TEXT)
for node in nodes:
with allure.step(f'Hard reboot host {node} via magic SysRq option'):
panic_reboot_host(ip=node.split(':')[-2])
with allure.step(f"Hard reboot host {node} via magic SysRq option"):
panic_reboot_host(ip=node.split(":")[-2])
if sequence:
try:
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, excluded_nodes=[node])
new_nodes = wait_object_replication_on_nodes(
wallet, cid, oid, 2, excluded_nodes=[node]
)
except AssertionError:
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2)
allure.attach('\n'.join(new_nodes), f'Nodes with object after {node} fail',
allure.attachment_type.TEXT)
allure.attach(
"\n".join(new_nodes),
f"Nodes with object after {node} fail",
allure.attachment_type.TEXT,
)
if not sequence:
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2)
allure.attach('\n'.join(new_nodes), 'Nodes with object after nodes fail', allure.attachment_type.TEXT)
allure.attach(
"\n".join(new_nodes), "Nodes with object after nodes fail", allure.attachment_type.TEXT
)
got_file_path = get_object(wallet, cid, oid, endpoint=new_nodes[0])
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)

View File

@ -7,82 +7,98 @@ from common import COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE
from container import create_container
from epoch import get_epoch, tick_epoch
from grpc_responses import OBJECT_ALREADY_REMOVED, OBJECT_NOT_FOUND, error_matches_status
from python_keywords.neofs_verbs import (delete_object, get_object, get_range, get_range_hash, head_object, put_object,
search_object)
from python_keywords.neofs_verbs import (
delete_object,
get_object,
get_range,
get_range_hash,
head_object,
put_object,
search_object,
)
from python_keywords.storage_policy import get_simple_object_copies
from python_keywords.utility_keywords import generate_file, get_file_hash
from tombstone import verify_head_tombstone
from utility import get_file_content, wait_for_gc_pass_on_storage_nodes
logger = logging.getLogger('NeoLogger')
logger = logging.getLogger("NeoLogger")
CLEANUP_TIMEOUT = 10
@allure.title('Test native object API')
@allure.title("Test native object API")
@pytest.mark.sanity
@pytest.mark.grpc_api
@pytest.mark.parametrize('object_size', [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=['simple object', 'complex object'])
@pytest.mark.parametrize(
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
)
def test_object_api(prepare_wallet_and_deposit, request, object_size):
"""
Test common gRPC API for object (put/get/head/get_range_hash/get_range/search/delete).
"""
wallet = prepare_wallet_and_deposit
cid = create_container(wallet)
wallet_cid = {'wallet': wallet, 'cid': cid}
file_usr_header = {'key1': 1, 'key2': 'abc', 'common_key': 'common_value'}
file_usr_header_oth = {'key1': 2, 'common_key': 'common_value'}
common_header = {'common_key': 'common_value'}
wallet_cid = {"wallet": wallet, "cid": cid}
file_usr_header = {"key1": 1, "key2": "abc", "common_key": "common_value"}
file_usr_header_oth = {"key1": 2, "common_key": "common_value"}
common_header = {"common_key": "common_value"}
range_len = 10
range_cut = f'0:{range_len}'
range_cut = f"0:{range_len}"
oids = []
allure.dynamic.title(f'Test native object API for {request.node.callspec.id}')
allure.dynamic.title(f"Test native object API for {request.node.callspec.id}")
file_path = generate_file(object_size)
file_hash = get_file_hash(file_path)
search_object(**wallet_cid, expected_objects_list=oids)
with allure.step('Put objects'):
with allure.step("Put objects"):
oids.append(put_object(wallet=wallet, path=file_path, cid=cid))
oids.append(put_object(wallet=wallet, path=file_path, cid=cid, attributes=file_usr_header))
oids.append(put_object(wallet=wallet, path=file_path, cid=cid, attributes=file_usr_header_oth))
oids.append(
put_object(wallet=wallet, path=file_path, cid=cid, attributes=file_usr_header_oth)
)
with allure.step('Validate storage policy for objects'):
with allure.step("Validate storage policy for objects"):
for oid_to_check in oids:
assert get_simple_object_copies(wallet=wallet, cid=cid, oid=oid_to_check) == 2, 'Expected 2 copies'
assert (
get_simple_object_copies(wallet=wallet, cid=cid, oid=oid_to_check) == 2
), "Expected 2 copies"
with allure.step('Get objects and compare hashes'):
with allure.step("Get objects and compare hashes"):
for oid_to_check in oids:
got_file_path = get_object(wallet=wallet, cid=cid, oid=oid_to_check)
got_file_hash = get_file_hash(got_file_path)
assert file_hash == got_file_hash
with allure.step('Get range/range hash'):
range_hash = get_range_hash(**wallet_cid, oid=oids[0], bearer_token='', range_cut=range_cut)
assert get_file_hash(file_path, range_len) == range_hash, \
f'Expected range hash to match {range_cut} slice of file payload'
with allure.step("Get range/range hash"):
range_hash = get_range_hash(**wallet_cid, oid=oids[0], bearer_token="", range_cut=range_cut)
assert (
get_file_hash(file_path, range_len) == range_hash
), f"Expected range hash to match {range_cut} slice of file payload"
range_hash = get_range_hash(**wallet_cid, oid=oids[1], bearer_token='', range_cut=range_cut)
assert get_file_hash(file_path, range_len) == range_hash, \
f'Expected range hash to match {range_cut} slice of file payload'
range_hash = get_range_hash(**wallet_cid, oid=oids[1], bearer_token="", range_cut=range_cut)
assert (
get_file_hash(file_path, range_len) == range_hash
), f"Expected range hash to match {range_cut} slice of file payload"
_, range_content = get_range(**wallet_cid, oid=oids[1], bearer='', range_cut=range_cut)
assert get_file_content(file_path, content_len=range_len, mode='rb') == range_content, \
f'Expected range content to match {range_cut} slice of file payload'
_, range_content = get_range(**wallet_cid, oid=oids[1], bearer="", range_cut=range_cut)
assert (
get_file_content(file_path, content_len=range_len, mode="rb") == range_content
), f"Expected range content to match {range_cut} slice of file payload"
with allure.step('Search objects'):
with allure.step("Search objects"):
search_object(**wallet_cid, expected_objects_list=oids)
search_object(**wallet_cid, filters=file_usr_header, expected_objects_list=oids[1:2])
search_object(**wallet_cid, filters=file_usr_header_oth, expected_objects_list=oids[2:3])
search_object(**wallet_cid, filters=common_header, expected_objects_list=oids[1:3])
with allure.step('Head object and validate'):
with allure.step("Head object and validate"):
head_object(**wallet_cid, oid=oids[0])
head_info = head_object(**wallet_cid, oid=oids[1])
check_header_is_presented(head_info, file_usr_header)
with allure.step('Delete objects'):
with allure.step("Delete objects"):
tombstone_s = delete_object(**wallet_cid, oid=oids[0])
tombstone_h = delete_object(**wallet_cid, oid=oids[1])
@ -92,15 +108,17 @@ def test_object_api(prepare_wallet_and_deposit, request, object_size):
tick_epoch()
sleep(CLEANUP_TIMEOUT)
with allure.step('Get objects and check errors'):
with allure.step("Get objects and check errors"):
get_object_and_check_error(**wallet_cid, oid=oids[0], error_pattern=OBJECT_ALREADY_REMOVED)
get_object_and_check_error(**wallet_cid, oid=oids[1], error_pattern=OBJECT_ALREADY_REMOVED)
@allure.title('Test object life time')
@allure.title("Test object life time")
@pytest.mark.sanity
@pytest.mark.grpc_api
@pytest.mark.parametrize('object_size', [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=['simple object', 'complex object'])
@pytest.mark.parametrize(
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
)
def test_object_api_lifetime(prepare_wallet_and_deposit, request, object_size):
"""
Test object deleted after expiration epoch.
@ -108,7 +126,7 @@ def test_object_api_lifetime(prepare_wallet_and_deposit, request, object_size):
wallet = prepare_wallet_and_deposit
cid = create_container(wallet)
allure.dynamic.title(f'Test object life time for {request.node.callspec.id}')
allure.dynamic.title(f"Test object life time for {request.node.callspec.id}")
file_path = generate_file(object_size)
file_hash = get_file_hash(file_path)
@ -118,14 +136,14 @@ def test_object_api_lifetime(prepare_wallet_and_deposit, request, object_size):
got_file = get_object(wallet, cid, oid)
assert get_file_hash(got_file) == file_hash
with allure.step('Tick two epochs'):
with allure.step("Tick two epochs"):
for _ in range(2):
tick_epoch()
# Wait for GC, because object with expiration is counted as alive until GC removes it
wait_for_gc_pass_on_storage_nodes()
with allure.step('Check object deleted because it expires-on epoch'):
with allure.step("Check object deleted because it expires-on epoch"):
with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_object(wallet, cid, oid)
@ -133,14 +151,17 @@ def test_object_api_lifetime(prepare_wallet_and_deposit, request, object_size):
def get_object_and_check_error(wallet: str, cid: str, oid: str, error_pattern: str) -> None:
try:
get_object(wallet=wallet, cid=cid, oid=oid)
raise AssertionError(f'Expected object {oid} removed, but it is not')
raise AssertionError(f"Expected object {oid} removed, but it is not")
except Exception as err:
logger.info(f'Error is {err}')
assert error_matches_status(err, error_pattern), f'Expected {err} to match {error_pattern}'
logger.info(f"Error is {err}")
assert error_matches_status(err, error_pattern), f"Expected {err} to match {error_pattern}"
def check_header_is_presented(head_info: dict, object_header: dict):
for key_to_check, val_to_check in object_header.items():
assert key_to_check in head_info['header']['attributes'], f'Key {key_to_check} is found in {head_object}'
assert head_info['header']['attributes'].get(key_to_check) == str(
val_to_check), f'Value {val_to_check} is equal'
assert (
key_to_check in head_info["header"]["attributes"]
), f"Key {key_to_check} is found in {head_object}"
assert head_info["header"]["attributes"].get(key_to_check) == str(
val_to_check
), f"Value {val_to_check} is equal"

View File

@ -11,11 +11,12 @@ from python_keywords.container import list_containers
from python_keywords.payment_neogo import neofs_deposit, transfer_mainnet_gas
from python_keywords.utility_keywords import concat_files, generate_file, get_file_hash
from s3_helper import check_objects_in_bucket, set_bucket_versioning
from utility import create_file_with_content
from wallet import init_wallet
from steps import s3_gate_bucket, s3_gate_object
from steps.aws_cli_client import AwsCliClient
from steps.s3_gate_base import TestS3GateBase
from utility import create_file_with_content
from wallet import init_wallet
def pytest_generate_tests(metafunc):

View File

@ -5,26 +5,25 @@ from re import match
import allure
import pytest
import requests
from common import BIN_VERSIONS_FILE
from env_properties import read_env_properties, save_env_properties
from service_helper import get_storage_service_helper
logger = logging.getLogger('NeoLogger')
logger = logging.getLogger("NeoLogger")
@allure.title('Check binaries versions')
@allure.title("Check binaries versions")
@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):
"""
Compare binaries versions from external source (url) and deployed on servers.
"""
if not BIN_VERSIONS_FILE:
pytest.skip('File with binaries and versions was not provided')
pytest.skip("File with binaries and versions was not provided")
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 = helper.get_binaries_version(binaries=list(binaries_to_check.keys()))
@ -36,7 +35,7 @@ def test_binaries_versions(request):
for binary, version in binaries_to_check.items():
actual_version = got_versions.get(binary)
if actual_version != version:
failed_versions[binary] = f'Expected version {version}, found version {actual_version}'
failed_versions[binary] = f"Expected version {version}, found version {actual_version}"
# If some binary was not listed in the env properties file, let's add it
# so that we have full information about versions in allure report
@ -48,26 +47,27 @@ def test_binaries_versions(request):
# create clear beautiful error with aggregation info
if failed_versions:
msg = '\n'.join({f'{binary}: {error}' for binary, error in failed_versions.items()})
raise AssertionError(f'Found binaries with unexpected versions:\n{msg}')
msg = "\n".join({f"{binary}: {error}" for binary, error in failed_versions.items()})
raise AssertionError(f"Found binaries with unexpected versions:\n{msg}")
@allure.step('Download versions info from {url}')
@allure.step("Download versions info from {url}")
def download_versions_info(url: str) -> dict:
binaries_to_version = {}
response = requests.get(url)
assert response.status_code == HTTPStatus.OK, \
f'Got {response.status_code} code. Content {response.json()}'
assert (
response.status_code == HTTPStatus.OK
), f"Got {response.status_code} code. Content {response.json()}"
content = response.text
assert content, f'Expected file with content, got {response}'
assert content, f"Expected file with content, got {response}"
for line in content.split('\n'):
m = match('(.*)=(.*)', line)
for line in content.split("\n"):
m = match("(.*)=(.*)", line)
if not m:
logger.warning(f'Could not get binary/version from {line}')
logger.warning(f"Could not get binary/version from {line}")
continue
bin_name, bin_version = m.group(1), m.group(2)
binaries_to_version[bin_name] = bin_version

View File

@ -1,5 +1,6 @@
import random
import allure
import pytest
from common import COMPLEX_OBJ_SIZE, NEOFS_NETMAP_DICT, SIMPLE_OBJ_SIZE
from grpc_responses import SESSION_NOT_FOUND
@ -16,8 +17,6 @@ from python_keywords.neofs_verbs import (
from python_keywords.session_token import create_session_token
from python_keywords.utility_keywords import generate_file
import allure
@allure.title("Test Object Operations with Session Token")
@pytest.mark.session_token

View File

@ -15,41 +15,41 @@ from cli_utils import NeofsCli
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
from data_formatters import get_wallet_public_key
logger = logging.getLogger('NeoLogger')
logger = logging.getLogger("NeoLogger")
EACL_LIFETIME = 100500
NEOFS_CONTRACT_CACHE_TIMEOUT = 30
class EACLOperation(Enum):
PUT = 'put'
GET = 'get'
HEAD = 'head'
GET_RANGE = 'getrange'
GET_RANGE_HASH = 'getrangehash'
SEARCH = 'search'
DELETE = 'delete'
PUT = "put"
GET = "get"
HEAD = "head"
GET_RANGE = "getrange"
GET_RANGE_HASH = "getrangehash"
SEARCH = "search"
DELETE = "delete"
class EACLAccess(Enum):
ALLOW = 'allow'
DENY = 'deny'
ALLOW = "allow"
DENY = "deny"
class EACLRole(Enum):
OTHERS = 'others'
USER = 'user'
SYSTEM = 'system'
OTHERS = "others"
USER = "user"
SYSTEM = "system"
class EACLHeaderType(Enum):
REQUEST = 'req' # Filter request headers
OBJECT = 'obj' # Filter object headers
SERVICE = 'SERVICE' # Filter service headers. These are not processed by NeoFS nodes and exist for service use only
REQUEST = "req" # Filter request headers
OBJECT = "obj" # Filter object headers
SERVICE = "SERVICE" # Filter service headers. These are not processed by NeoFS nodes and exist for service use only
class EACLMatchType(Enum):
STRING_EQUAL = '=' # Return true if strings are equal
STRING_NOT_EQUAL = '!=' # Return true if strings are different
STRING_EQUAL = "=" # Return true if strings are equal
STRING_NOT_EQUAL = "!=" # Return true if strings are different
@dataclass
@ -60,7 +60,12 @@ class EACLFilter:
value: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
return {'headerType': self.header_type, 'matchType': self.match_type, 'key': self.key, 'value': self.value}
return {
"headerType": self.header_type,
"matchType": self.match_type,
"key": self.key,
"value": self.value,
}
@dataclass
@ -68,10 +73,16 @@ class EACLFilters:
filters: Optional[List[EACLFilter]] = None
def __str__(self):
return ','.join(
[f'{filter.header_type.value}:{filter.key}{filter.match_type.value}{filter.value}'
for filter in self.filters]
) if self.filters else []
return (
",".join(
[
f"{filter.header_type.value}:{filter.key}{filter.match_type.value}{filter.value}"
for filter in self.filters
]
)
if self.filters
else []
)
@dataclass
@ -87,15 +98,23 @@ class EACLRule:
filters: Optional[EACLFilters] = None
def to_dict(self) -> Dict[str, Any]:
return {'Operation': self.operation, 'Access': self.access, 'Role': self.role,
'Filters': self.filters or []}
return {
"Operation": self.operation,
"Access": self.access,
"Role": self.role,
"Filters": self.filters or [],
}
def __str__(self):
role = self.role.value if isinstance(self.role, EACLRole) else f'pubkey:{get_wallet_public_key(self.role, "")}'
role = (
self.role.value
if isinstance(self.role, EACLRole)
else f'pubkey:{get_wallet_public_key(self.role, "")}'
)
return f'{self.access.value} {self.operation.value} {self.filters or ""} {role}'
@allure.title('Get extended ACL')
@allure.title("Get extended ACL")
def get_eacl(wallet_path: str, cid: str) -> Optional[str]:
cli = NeofsCli(config=WALLET_CONFIG)
try:
@ -104,16 +123,21 @@ def get_eacl(wallet_path: str, cid: str) -> Optional[str]:
logger.info("Extended ACL table is not set for this container")
logger.info(f"Got exception while getting eacl: {exc}")
return None
if 'extended ACL table is not set for this container' in output:
if "extended ACL table is not set for this container" in output:
return None
return output
@allure.title('Set extended ACL')
@allure.title("Set extended ACL")
def set_eacl(wallet_path: str, cid: str, eacl_table_path: str) -> None:
cli = NeofsCli(config=WALLET_CONFIG, timeout=60)
cli.container.set_eacl(wallet=wallet_path, rpc_endpoint=NEOFS_ENDPOINT, cid=cid, table=eacl_table_path,
await_mode=True)
cli.container.set_eacl(
wallet=wallet_path,
rpc_endpoint=NEOFS_ENDPOINT,
cid=cid,
table=eacl_table_path,
await_mode=True,
)
def _encode_cid_for_eacl(cid: str) -> str:
@ -125,14 +149,16 @@ def create_eacl(cid: str, rules_list: List[EACLRule]) -> str:
table_file_path = f"{os.getcwd()}/{ASSETS_DIR}/eacl_table_{str(uuid.uuid4())}.json"
NeofsCli().acl.extended_create(cid=cid, out=table_file_path, rule=rules_list)
with open(table_file_path, 'r') as file:
with open(table_file_path, "r") as file:
table_data = file.read()
logger.info(f"Generated eACL:\n{table_data}")
return table_file_path
def form_bearertoken_file(wif: str, cid: str, eacl_rule_list: List[Union[EACLRule, EACLPubKey]]) -> str:
def form_bearertoken_file(
wif: str, cid: str, eacl_rule_list: List[Union[EACLRule, EACLPubKey]]
) -> str:
"""
This function fetches eACL for given <cid> on behalf of <wif>,
then extends it with filters taken from <eacl_rules>, signs
@ -144,50 +170,29 @@ def form_bearertoken_file(wif: str, cid: str, eacl_rule_list: List[Union[EACLRul
eacl = get_eacl(wif, cid)
json_eacl = dict()
if eacl:
eacl = eacl.replace('eACL: ', '').split('Signature')[0]
eacl = eacl.replace("eACL: ", "").split("Signature")[0]
json_eacl = json.loads(eacl)
logger.info(json_eacl)
eacl_result = {
"body":
{
"eaclTable":
{
"containerID":
{
"value": enc_cid
},
"records": []
},
"lifetime":
{
"exp": EACL_LIFETIME,
"nbf": "1",
"iat": "0"
}
}
"body": {
"eaclTable": {"containerID": {"value": enc_cid}, "records": []},
"lifetime": {"exp": EACL_LIFETIME, "nbf": "1", "iat": "0"},
}
}
assert eacl_rules, 'Got empty eacl_records list'
assert eacl_rules, "Got empty eacl_records list"
for rule in eacl_rule_list:
op_data = {
"operation": rule.operation.value.upper(),
"action": rule.access.value.upper(),
"filters": rule.filters or [],
"targets": []
"targets": [],
}
if isinstance(rule.role, EACLRole):
op_data['targets'] = [
{
"role": rule.role.value.upper()
}
]
op_data["targets"] = [{"role": rule.role.value.upper()}]
elif isinstance(rule.role, EACLPubKey):
op_data['targets'] = [
{
'keys': rule.role.keys
}
]
op_data["targets"] = [{"keys": rule.role.keys}]
eacl_result["body"]["eaclTable"]["records"].append(op_data)
@ -196,7 +201,7 @@ def form_bearertoken_file(wif: str, cid: str, eacl_rule_list: List[Union[EACLRul
for record in json_eacl["records"]:
eacl_result["body"]["eaclTable"]["records"].append(record)
with open(file_path, 'w', encoding='utf-8') as eacl_file:
with open(file_path, "w", encoding="utf-8") as eacl_file:
json.dump(eacl_result, eacl_file, ensure_ascii=False, indent=4)
logger.info(f"Got these extended ACL records: {eacl_result}")
@ -206,17 +211,17 @@ def form_bearertoken_file(wif: str, cid: str, eacl_rule_list: List[Union[EACLRul
def eacl_rules(access: str, verbs: list, user: str) -> list[str]:
"""
This function creates a list of eACL rules.
Args:
access (str): identifies if the following operation(s)
is allowed or denied
verbs (list): a list of operations to set rules for
user (str): a group of users (user/others) or a wallet of
a certain user for whom rules are set
Returns:
(list): a list of eACL rules
This function creates a list of eACL rules.
Args:
access (str): identifies if the following operation(s)
is allowed or denied
verbs (list): a list of operations to set rules for
user (str): a group of users (user/others) or a wallet of
a certain user for whom rules are set
Returns:
(list): a list of eACL rules
"""
if user not in ('others', 'user'):
if user not in ("others", "user"):
pubkey = get_wallet_public_key(user, wallet_password="")
user = f"pubkey:{pubkey}"
@ -229,13 +234,13 @@ def eacl_rules(access: str, verbs: list, user: str) -> list[str]:
def sign_bearer_token(wallet_path: str, eacl_rules_file: str) -> None:
cmd = (
f'{NEOFS_CLI_EXEC} util sign bearer-token --from {eacl_rules_file} '
f'--to {eacl_rules_file} --wallet {wallet_path} --config {WALLET_CONFIG} --json'
f"{NEOFS_CLI_EXEC} util sign bearer-token --from {eacl_rules_file} "
f"--to {eacl_rules_file} --wallet {wallet_path} --config {WALLET_CONFIG} --json"
)
_cmd_run(cmd)
@allure.title('Wait for eACL cache expired')
@allure.title("Wait for eACL cache expired")
def wait_for_cache_expired():
sleep(NEOFS_CONTRACT_CACHE_TIMEOUT)
return

View File

@ -6,8 +6,8 @@ from .completion import NeofsAdmCompletion
from .config import NeofsAdmConfig
from .gendoc import NeofsAdmGenDoc
from .morph import NeofsAdmMorph
from .subnet import NeofsAdmMorphSubnet
from .storage_config import NeofsAdmStorageConfig
from .subnet import NeofsAdmMorphSubnet
from .version import NeofsAdmVersion
@ -23,14 +23,27 @@ class NeofsAdm:
storage_config: Optional[NeofsAdmStorageConfig] = None
version: Optional[NeofsAdmVersion] = None
def __init__(self, neofs_adm_exec_path: Optional[str] = None, config_file: Optional[str] = None, timeout: int = 30):
def __init__(
self,
neofs_adm_exec_path: Optional[str] = None,
config_file: Optional[str] = None,
timeout: int = 30,
):
self.config_file = config_file
self.neofs_adm_exec_path = neofs_adm_exec_path or NEOFS_ADM_EXEC
self.completion = NeofsAdmCompletion(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.completion = NeofsAdmCompletion(
self.neofs_adm_exec_path, timeout=timeout, config=config_file
)
self.config = NeofsAdmConfig(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.gendoc = NeofsAdmGenDoc(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.morph = NeofsAdmMorph(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.subnet = NeofsAdmMorphSubnet(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.storage_config = NeofsAdmStorageConfig(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.version = NeofsAdmVersion(self.neofs_adm_exec_path, timeout=timeout, config=config_file)
self.subnet = NeofsAdmMorphSubnet(
self.neofs_adm_exec_path, timeout=timeout, config=config_file
)
self.storage_config = NeofsAdmStorageConfig(
self.neofs_adm_exec_path, timeout=timeout, config=config_file
)
self.version = NeofsAdmVersion(
self.neofs_adm_exec_path, timeout=timeout, config=config_file
)

View File

@ -27,4 +27,4 @@ class NeofsAdmCompletion(NeofsCliCommand):
str: Command string
"""
return self._execute('completion ' + completion_type.value)
return self._execute("completion " + completion_type.value)

View File

@ -2,7 +2,7 @@ from enum import Enum
class CompletionType(Enum):
BASH = 'bash'
ZHS = 'zsh'
FISH = 'fish'
POWERSHELL = 'powershell'
BASH = "bash"
ZHS = "zsh"
FISH = "fish"
POWERSHELL = "powershell"

View File

@ -2,7 +2,7 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsAdmConfig(NeofsCliCommand):
def init(self, path: str = '~/.neofs/adm/config.yml') -> str:
def init(self, path: str = "~/.neofs/adm/config.yml") -> str:
"""Initialize basic neofs-adm configuration file.
Args:
@ -14,6 +14,6 @@ class NeofsAdmConfig(NeofsCliCommand):
"""
return self._execute(
'config init',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"config init",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -4,7 +4,9 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsAdmGenDoc(NeofsCliCommand):
def get(self, doc_file: str, depth: int = 1, doc_type: str = 'md', extension: Optional[str] = None) -> str:
def get(
self, doc_file: str, depth: int = 1, doc_type: str = "md", extension: Optional[str] = None
) -> str:
"""Generate documentation for this command. If the template is not provided,
builtin cobra generator is used and each subcommand is placed in
a separate file in the same directory.
@ -29,6 +31,10 @@ class NeofsAdmGenDoc(NeofsCliCommand):
"""
return self._execute(
f'gendoc {doc_file}',
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'doc_file']}
f"gendoc {doc_file}",
**{
param: value
for param, value in locals().items()
if param not in ["self", "doc_file"]
},
)

View File

@ -4,9 +4,16 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsAdmMorph(NeofsCliCommand):
def deposit_notary(self, rpc_endpoint: str, account: str, gas: str, storage_wallet: Optional[str] = None,
till: Optional[str] = None) -> str:
"""Deposit GAS for notary service.
def deposit_notary(
self,
rpc_endpoint: str,
account: str,
gas: str,
storage_wallet: Optional[str] = None,
till: Optional[str] = None,
) -> str:
"""
Deposit GAS for notary service.
Args:
account (str): wallet account address
@ -15,19 +22,24 @@ class NeofsAdmMorph(NeofsCliCommand):
storage_wallet (str): path to storage node wallet
till (str): notary deposit duration in blocks
Returns:
str: Command string
"""
return self._execute(
'morph deposit-notary',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph deposit-notary",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def dump_balances(self, rpc_endpoint: str, alphabet: Optional[str] = None, proxy: Optional[str] = None,
script_hash: Optional[str] = None, storage: Optional[str] = None) -> str:
"""Dump GAS balances
def dump_balances(
self,
rpc_endpoint: str,
alphabet: Optional[str] = None,
proxy: Optional[str] = None,
script_hash: Optional[str] = None,
storage: Optional[str] = None,
) -> str:
"""
Dump GAS balances.
Args:
alphabet (str): dump balances of alphabet contracts
@ -36,35 +48,38 @@ class NeofsAdmMorph(NeofsCliCommand):
script_hash (str): use script-hash format for addresses
storage (str): dump balances of storage nodes from the current netmap
Returns:
str: Command string
"""
return self._execute(
'morph dump-balances',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph dump-balances",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def dump_config(self, rpc_endpoint: str) -> str:
"""Section for morph network configuration commands.
"""
Dump NeoFS network config.
Args:
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph dump-config',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph dump-config",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def dump_containers(self, rpc_endpoint: str, cid: Optional[str] = None, container_contract: Optional[str] = None,
dump: Optional[str] = None) -> str:
"""Dump NeoFS containers to file.
def dump_containers(
self,
rpc_endpoint: str,
cid: Optional[str] = None,
container_contract: Optional[str] = None,
dump: Optional[str] = None,
) -> str:
"""
Dump NeoFS containers to file.
Args:
cid (str): containers to dump
@ -72,70 +87,73 @@ class NeofsAdmMorph(NeofsCliCommand):
dump (str): file where to save dumped containers
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph dump-containers',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph dump-containers",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def dump_hashes(self, rpc_endpoint: str) -> str:
"""Dump deployed contract hashes.
"""
Dump deployed contract hashes.
Args:
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph dump-hashes',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph dump-hashes",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def force_new_epoch(self, rpc_endpoint: Optional[str] = None, alphabet: Optional[str] = None) -> str:
"""Create new NeoFS epoch event in the side chain
def force_new_epoch(
self, rpc_endpoint: Optional[str] = None, alphabet: Optional[str] = None
) -> str:
"""
Create new NeoFS epoch event in the side chain
Args:
alphabet (str): path to alphabet wallets dir
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph force-new-epoch',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph force-new-epoch",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def generate_alphabet(self, rpc_endpoint: str, alphabet_wallets: str, size: int = 7) -> str:
"""Generate alphabet wallets for consensus nodes of the morph network
"""
Generate alphabet wallets for consensus nodes of the morph network.
Args:
alphabet_wallets (str): path to alphabet wallets dir
size (int): amount of alphabet wallets to generate (default 7)
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph generate-alphabet',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph generate-alphabet",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def generate_storage_wallet(self, rpc_endpoint: str, alphabet_wallets: str, storage_wallet: str,
initial_gas: Optional[str] = None) -> str:
"""Generate storage node wallet for the morph network
def generate_storage_wallet(
self,
rpc_endpoint: str,
alphabet_wallets: str,
storage_wallet: str,
initial_gas: Optional[str] = None,
) -> str:
"""
Generate storage node wallet for the morph network.
Args:
alphabet_wallets (str): path to alphabet wallets dir
@ -143,21 +161,29 @@ class NeofsAdmMorph(NeofsCliCommand):
rpc_endpoint (str): N3 RPC node endpoint
storage_wallet (str): path to new storage node wallet
Returns:
str: Command string
"""
return self._execute(
'morph generate-storage-wallet',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph generate-storage-wallet",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def init(self, rpc_endpoint: str, alphabet_wallets: str, contracts: str, protocol: str,
container_alias_fee: int = 500, container_fee: int = 1000, epoch_duration: int = 240,
homomorphic_disabled: bool = False, local_dump: Optional[str] = None, max_object_size: int = 67108864
) -> str:
"""Section for morph network configuration commands.
def init(
self,
rpc_endpoint: str,
alphabet_wallets: str,
contracts: str,
protocol: str,
container_alias_fee: int = 500,
container_fee: int = 1000,
epoch_duration: int = 240,
homomorphic_disabled: bool = False,
local_dump: Optional[str] = None,
max_object_size: int = 67108864,
) -> str:
"""
Initialize side chain network with smart-contracts and network settings.
Args:
alphabet_wallets (str): path to alphabet wallets dir
@ -172,19 +198,23 @@ class NeofsAdmMorph(NeofsCliCommand):
protocol (str): path to the consensus node configuration
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph init',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph init",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def refill_gas(self, rpc_endpoint: str, alphabet_wallets: str, storage_wallet: str, gas: Optional[str] = None
) -> str:
"""Refill GAS of storage node's wallet in the morph network
def refill_gas(
self,
rpc_endpoint: str,
alphabet_wallets: str,
storage_wallet: str,
gas: Optional[str] = None,
) -> str:
"""
Refill GAS of storage node's wallet in the morph network.
Args:
alphabet_wallets (str): path to alphabet wallets dir
@ -192,18 +222,19 @@ class NeofsAdmMorph(NeofsCliCommand):
rpc_endpoint (str): N3 RPC node endpoint
storage_wallet (str): path to new storage node wallet
Returns:
str: Command string
"""
return self._execute(
'morph refill-gas',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph refill-gas",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def restore_containers(self, rpc_endpoint: str, alphabet_wallets: str, cid: str, dump: str) -> str:
"""Restore NeoFS containers from file.
def restore_containers(
self, rpc_endpoint: str, alphabet_wallets: str, cid: str, dump: str
) -> str:
"""
Restore NeoFS containers from file.
Args:
alphabet_wallets (str): path to alphabet wallets dir
@ -211,19 +242,24 @@ class NeofsAdmMorph(NeofsCliCommand):
dump (str): file to restore containers from
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph restore-containers',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph restore-containers",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def set_policy(self, rpc_endpoint: str, alphabet_wallets: str, exec_fee_factor: Optional[int] = None,
storage_price: Optional[int] = None, fee_per_byte: Optional[int] = None) -> str:
"""Set global policy values
def set_policy(
self,
rpc_endpoint: str,
alphabet_wallets: str,
exec_fee_factor: Optional[int] = None,
storage_price: Optional[int] = None,
fee_per_byte: Optional[int] = None,
) -> str:
"""
Set global policy values.
Args:
alphabet_wallets (str): path to alphabet wallets dir
@ -232,28 +268,30 @@ class NeofsAdmMorph(NeofsCliCommand):
fee_per_byte (int): FeePerByte=<n3>
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
non_param_attribute = ''
non_param_attribute = ""
if exec_fee_factor:
non_param_attribute += f'ExecFeeFactor={exec_fee_factor} '
non_param_attribute += f"ExecFeeFactor={exec_fee_factor} "
if storage_price:
non_param_attribute += f'StoragePrice={storage_price} '
non_param_attribute += f"StoragePrice={storage_price} "
if fee_per_byte:
non_param_attribute += f'FeePerByte={fee_per_byte} '
non_param_attribute += f"FeePerByte={fee_per_byte} "
return self._execute(
f'morph restore-containers {non_param_attribute}',
**{param: param_value for param, param_value in locals().items() if param not in [
'self', 'exec_fee_factor', 'storage_price', 'fee_per_byte'
]}
f"morph restore-containers {non_param_attribute}",
**{
param: value
for param, value in locals().items()
if param not in ["self", "exec_fee_factor", "storage_price", "fee_per_byte"]
},
)
def update_contracts(self, rpc_endpoint: str, alphabet_wallets: str, contracts: Optional[str] = None
) -> str:
"""Update NeoFS contracts.
def update_contracts(
self, rpc_endpoint: str, alphabet_wallets: str, contracts: Optional[str] = None
) -> str:
"""
Update NeoFS contracts.
Args:
alphabet_wallets (str): path to alphabet wallets dir
@ -261,12 +299,10 @@ class NeofsAdmMorph(NeofsCliCommand):
(default fetched from latest github release)
rpc_endpoint (str): N3 RPC node endpoint
Returns:
str: Command string
"""
return self._execute(
'morph update-contracts',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph update-contracts",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -15,6 +15,6 @@ class NeofsAdmStorageConfig(NeofsCliCommand):
"""
return self._execute(
'storage-config',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"storage-config",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -19,29 +19,31 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
"""
return self._execute(
'morph subnet create',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet create",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def get(self, rpc_endpoint: str, subnet: str) -> str:
"""Read information about the NeoFS subnet.
"""
Read information about the NeoFS subnet.
Args:
rpc_endpoint (str): N3 RPC node endpoint
subnet (str): ID of the subnet to read
Returns:
str: Command string
"""
return self._execute(
'morph subnet get',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet get",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def remove(self, rpc_endpoint: str, wallet: str, subnet: str, address: Optional[str] = None) -> str:
"""Remove NeoFS subnet.
def remove(
self, rpc_endpoint: str, wallet: str, subnet: str, address: Optional[str] = None
) -> str:
"""
Remove NeoFS subnet.
Args:
address (str): Address in the wallet, optional
@ -49,19 +51,26 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet remove',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet remove",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def admin_add(self, rpc_endpoint: str, wallet: str, admin: str, subnet: str, client: Optional[str] = None,
group: Optional[str] = None, address: Optional[str] = None) -> str:
"""Add admin to the NeoFS subnet.
def admin_add(
self,
rpc_endpoint: str,
wallet: str,
admin: str,
subnet: str,
client: Optional[str] = None,
group: Optional[str] = None,
address: Optional[str] = None,
) -> str:
"""
Add admin to the NeoFS subnet.
Args:
address (str): Address in the wallet, optional
@ -72,19 +81,25 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet admin add',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet admin add",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def admin_remove(self, rpc_endpoint: str, wallet: str, admin: str, subnet: str, client: Optional[str] = None,
address: Optional[str] = None) -> str:
"""Remove admin of the NeoFS subnet.
def admin_remove(
self,
rpc_endpoint: str,
wallet: str,
admin: str,
subnet: str,
client: Optional[str] = None,
address: Optional[str] = None,
) -> str:
"""
Remove admin of the NeoFS subnet.
Args:
address (str): Address in the wallet, optional
@ -94,19 +109,25 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet admin remove',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet admin remove",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def client_add(self, rpc_endpoint: str, wallet: str, subnet: str, client: Optional[str] = None,
group: Optional[str] = None, address: Optional[str] = None) -> str:
"""Add client to the NeoFS subnet.
def client_add(
self,
rpc_endpoint: str,
wallet: str,
subnet: str,
client: Optional[str] = None,
group: Optional[str] = None,
address: Optional[str] = None,
) -> str:
"""
Add client to the NeoFS subnet.
Args:
address (str): Address in the wallet, optional
@ -116,19 +137,25 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet client add',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet client add",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def client_remove(self, rpc_endpoint: str, wallet: str, client: str, group: str, subnet: str,
address: Optional[str] = None) -> str:
"""Remove client of the NeoFS subnet.
def client_remove(
self,
rpc_endpoint: str,
wallet: str,
client: str,
group: str,
subnet: str,
address: Optional[str] = None,
) -> str:
"""
Remove client of the NeoFS subnet.
Args:
address (str): Address in the wallet, optional
@ -138,18 +165,17 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet client remove',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet client remove",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def node_add(self, rpc_endpoint: str, wallet: str, node: str, subnet: str) -> str:
"""Add node to the NeoFS subnet.
"""
Add node to the NeoFS subnet.
Args:
node (str): Hex-encoded public key of the node
@ -157,18 +183,17 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet node add',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet node add",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def node_remove(self, rpc_endpoint: str, wallet: str, node: str, subnet: str) -> str:
"""Remove node from the NeoFS subnet.
"""
Remove node from the NeoFS subnet.
Args:
node (str): Hex-encoded public key of the node
@ -176,12 +201,10 @@ class NeofsAdmMorphSubnet(NeofsCliCommand):
subnet (str): ID of the subnet to read
wallet (str): Path to file with wallet
Returns:
str: Command string
"""
return self._execute(
'morph subnet node remove',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"morph subnet node remove",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -9,4 +9,4 @@ class NeofsAdmVersion(NeofsCliCommand):
str: Command string
"""
return self._execute('', version=True)
return self._execute("", version=True)

View File

@ -25,9 +25,5 @@ class NeofsCliAccounting(NeofsCliCommand):
"""
return self._execute(
"accounting balance",
**{
param: param_value
for param, param_value in locals().items()
if param not in ["self"]
}
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -4,7 +4,9 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsCliACL(NeofsCliCommand):
def extended_create(self, cid: str, out: str, file: Optional[str] = None, rule: Optional[list] = None) -> str:
def extended_create(
self, cid: str, out: str, file: Optional[str] = None, rule: Optional[list] = None
) -> str:
"""Create extended ACL from the text representation.
@ -42,6 +44,6 @@ class NeofsCliACL(NeofsCliCommand):
"""
return self._execute(
'acl extended create',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"acl extended create",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -4,12 +4,25 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsCliContainer(NeofsCliCommand):
def create(self, rpc_endpoint: str, wallet: str, address: Optional[str] = None, attributes: Optional[dict] = None,
basic_acl: Optional[str] = None, await_mode: bool = False, disable_timestamp: bool = False,
name: Optional[str] = None, nonce: Optional[str] = None, policy: Optional[str] = None,
session: Optional[str] = None, subnet: Optional[str] = None, ttl: Optional[int] = None,
xhdr: Optional[dict] = None) -> str:
"""Create a new container and register it in the NeoFS.
def create(
self,
rpc_endpoint: str,
wallet: str,
address: Optional[str] = None,
attributes: Optional[dict] = None,
basic_acl: Optional[str] = None,
await_mode: bool = False,
disable_timestamp: bool = False,
name: Optional[str] = None,
nonce: Optional[str] = None,
policy: Optional[str] = None,
session: Optional[str] = None,
subnet: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Create a new container and register it in the NeoFS.
It will be stored in the sidechain when the Inner Ring accepts it.
Args:
@ -31,17 +44,26 @@ class NeofsCliContainer(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'container create',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container create",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def delete(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None, await_mode: bool = False,
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None,
force: bool = False) -> str:
"""Delete an existing container.
def delete(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
address: Optional[str] = None,
await_mode: bool = False,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
force: bool = False,
) -> str:
"""
Delete an existing container.
Only the owner of the container has permission to remove the container.
Args:
@ -55,20 +77,29 @@ class NeofsCliContainer(NeofsCliCommand):
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Returns:
str: Command string
"""
return self._execute(
'container delete',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container delete",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def get(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None, await_mode: bool = False,
to: Optional[str] = None, json_mode: bool = False, ttl: Optional[int] = None,
xhdr: Optional[dict] = None) -> str:
"""Get container field info
def get(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
address: Optional[str] = None,
await_mode: bool = False,
to: Optional[str] = None,
json_mode: bool = False,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Get container field info.
Args:
address: address of wallet account
@ -83,18 +114,26 @@ class NeofsCliContainer(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'container get',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container get",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def get_eacl(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None,
await_mode: bool = False, to: Optional[str] = None, session: Optional[str] = None,
ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
"""Get extended ACL talbe of container
def get_eacl(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
address: Optional[str] = None,
await_mode: bool = False,
to: Optional[str] = None,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Get extended ACL table of container.
Args:
address: address of wallet account
@ -112,13 +151,22 @@ class NeofsCliContainer(NeofsCliCommand):
"""
return self._execute(
'container get-eacl',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container get-eacl",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def list(self, rpc_endpoint: str, wallet: str, address: Optional[str] = None,
owner: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None, **params) -> str:
"""List all created containers
def list(
self,
rpc_endpoint: str,
wallet: str,
address: Optional[str] = None,
owner: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
**params,
) -> str:
"""
List all created containers.
Args:
address: address of wallet account
@ -130,16 +178,23 @@ class NeofsCliContainer(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'container list',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container list",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def list_objects(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None,
ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
"""List existing objects in container
def list_objects(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
address: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
List existing objects in container.
Args:
address: address of wallet account
@ -151,18 +206,26 @@ class NeofsCliContainer(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'container list-objects',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container list-objects",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def set_eacl(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None,
await_mode: bool = False, table: Optional[str] = None, session: Optional[str] = None,
ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
"""Set a new extended ACL table for the container.
def set_eacl(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
address: Optional[str] = None,
await_mode: bool = False,
table: Optional[str] = None,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Set a new extended ACL table for the container.
Container ID in the EACL table will be substituted with the ID from the CLI.
Args:
@ -178,9 +241,8 @@ class NeofsCliContainer(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'container set-eacl',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"container set-eacl",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -4,10 +4,20 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsCliObject(NeofsCliCommand):
def delete(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
bearer: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
xhdr: Optional[dict] = None) -> str:
"""Delete object from NeoFS
def delete(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
oid: str,
address: Optional[str] = None,
bearer: Optional[str] = None,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Delete object from NeoFS.
Args:
address: address of wallet account
@ -22,18 +32,30 @@ class NeofsCliObject(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'object delete',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object delete",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def get(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
bearer: Optional[str] = None, file: Optional[str] = None,
header: Optional[str] = None, no_progress: bool = False, raw: bool = False,
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
"""Get object from NeoFS
def get(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
oid: str,
address: Optional[str] = None,
bearer: Optional[str] = None,
file: Optional[str] = None,
header: Optional[str] = None,
no_progress: bool = False,
raw: bool = False,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Get object from NeoFS.
Args:
address: address of wallet account
@ -52,17 +74,28 @@ class NeofsCliObject(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'object get',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object get",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def hash(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
bearer: Optional[str] = None, range: Optional[str] = None, salt: Optional[str] = None,
ttl: Optional[int] = None, hash_type: Optional[str] = None, xhdr: Optional[dict] = None) -> str:
"""Get object hash
def hash(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
oid: str,
address: Optional[str] = None,
bearer: Optional[str] = None,
range: Optional[str] = None,
salt: Optional[str] = None,
ttl: Optional[int] = None,
hash_type: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Get object hash.
Args:
address: address of wallet account
@ -79,78 +112,114 @@ class NeofsCliObject(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'object hash',
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
"object hash",
**{
param: value for param, value in locals().items() if param not in ["self", "params"]
},
)
def head(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
bearer: Optional[str] = None, file: Optional[str] = None,
json_mode: bool = False, main_only: bool = False, proto: bool = False, raw: bool = False,
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
"""Get object header
def head(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
oid: str,
address: Optional[str] = None,
bearer: Optional[str] = None,
file: Optional[str] = None,
json_mode: bool = False,
main_only: bool = False,
proto: bool = False,
raw: bool = False,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Get object header.
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
file: File to write object payload to. Default: stdout.
json_mode: Marshal output in JSON
main_only: Return only main fields
oid: Object ID
proto: Marshal output in Protobuf
raw: Set raw request option
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
file: File to write object payload to. Default: stdout.
json_mode: Marshal output in JSON
main_only: Return only main fields
oid: Object ID
proto: Marshal output in Protobuf
raw: Set raw request option
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Returns:
str: Command string
"""
Returns:
str: Command string
"""
return self._execute(
'object head',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object head",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def lock(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, lifetime: int, address: Optional[str] = None,
bearer: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
xhdr: Optional[dict] = None) -> str:
"""Lock object in container
def lock(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
oid: str,
lifetime: int,
address: Optional[str] = None,
bearer: Optional[str] = None,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Lock object in container.
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
oid: Object ID
lifetime: Object lifetime
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
oid: Object ID
lifetime: Object lifetime
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Returns:
str: Command string
"""
Returns:
str: Command string
"""
return self._execute(
'object lock',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object lock",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def put(self, rpc_endpoint: str, wallet: str, cid: str, file: str, address: Optional[str] = None,
attributes: Optional[dict] = None, bearer: Optional[str] = None, disable_filename: bool = False,
disable_timestamp: bool = False, expire_at: Optional[int] = None, no_progress: bool = False,
notify: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
xhdr: Optional[dict] = None) -> str:
"""Put object to NeoFS
def put(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
file: str,
address: Optional[str] = None,
attributes: Optional[dict] = None,
bearer: Optional[str] = None,
disable_filename: bool = False,
disable_timestamp: bool = False,
expire_at: Optional[int] = None,
no_progress: bool = False,
notify: Optional[str] = None,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Put object to NeoFS.
Args:
address: address of wallet account
@ -171,69 +240,90 @@ class NeofsCliObject(NeofsCliCommand):
Returns:
str: Command string
"""
return self._execute(
'object put',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object put",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def range(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, range: str, address: Optional[str] = None,
bearer: Optional[str] = None, file: Optional[str] = None, json_mode: bool = False, raw: bool = False,
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
"""Get payload range data of an object
def range(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
oid: str,
range: str,
address: Optional[str] = None,
bearer: Optional[str] = None,
file: Optional[str] = None,
json_mode: bool = False,
raw: bool = False,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Get payload range data of an object.
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
file: File to write object payload to. Default: stdout.
json_mode: Marshal output in JSON
oid: Object ID
range: Range to take data from in the form offset:length
raw: Set raw request option
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
file: File to write object payload to. Default: stdout.
json_mode: Marshal output in JSON
oid: Object ID
range: Range to take data from in the form offset:length
raw: Set raw request option
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Returns:
str: Command string
"""
Returns:
str: Command string
"""
return self._execute(
'object range',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object range",
**{param: value for param, value in locals().items() if param not in ["self"]},
)
def search(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None,
bearer: Optional[str] = None, filters: Optional[list] = None, oid: Optional[str] = None,
phy: bool = False, root: bool = False, session: Optional[str] = None, ttl: Optional[int] = None,
xhdr: Optional[dict] = None) -> str:
"""Search object
def search(
self,
rpc_endpoint: str,
wallet: str,
cid: str,
address: Optional[str] = None,
bearer: Optional[str] = None,
filters: Optional[list] = None,
oid: Optional[str] = None,
phy: bool = False,
root: bool = False,
session: Optional[str] = None,
ttl: Optional[int] = None,
xhdr: Optional[dict] = None,
) -> str:
"""
Search object.
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
filters: Repeated filter expressions or files with protobuf JSON
oid: Object ID
phy: Search physically stored objects
root: Search for user objects
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Args:
address: address of wallet account
bearer: File with signed JSON or binary encoded bearer token
cid: Container ID
filters: Repeated filter expressions or files with protobuf JSON
oid: Object ID
phy: Search physically stored objects
root: Search for user objects
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
session: path to a JSON-encoded container session token
ttl: TTL value in request meta header (default 2)
wallet: WIF (NEP-2) string or path to the wallet or binary key
xhdr: Request X-Headers in form of Key=Value
Returns:
str: Command string
"""
Returns:
str: Command string
"""
return self._execute(
'object search',
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
"object search",
**{param: value for param, value in locals().items() if param not in ["self"]},
)

View File

@ -3,10 +3,10 @@ from cli_utils.cli_command import NeofsCliCommand
class NeofsCliVersion(NeofsCliCommand):
def get(self) -> str:
"""Application version and NeoFS API compatibility
"""
Application version and NeoFS API compatibility.
Returns:
str: Command string
"""
return self._execute('', version=True)
return self._execute("", version=True)

View File

@ -7,31 +7,40 @@ class NeofsCliCommand:
neofs_cli_exec: Optional[str] = None
timeout: Optional[int] = None
__base_params: Optional[str] = None
map_params = {'json_mode': 'json', 'await_mode': 'await', 'hash_type': 'hash', 'doc_type': 'type'}
map_params = {
"json_mode": "json",
"await_mode": "await",
"hash_type": "hash",
"doc_type": "type",
}
def __init__(self, neofs_cli_exec: str, timeout: int, **base_params):
self.neofs_cli_exec = neofs_cli_exec
self.timeout = timeout
self.__base_params = ' '.join([f'--{param} {value}' for param, value in base_params.items() if value])
self.__base_params = " ".join(
[f"--{param} {value}" for param, value in base_params.items() if value]
)
def _format_command(self, command: str, **params) -> str:
param_str = []
for param, value in params.items():
if param in self.map_params.keys():
param = self.map_params[param]
param = param.replace('_', '-')
param = param.replace("_", "-")
if not value:
continue
if isinstance(value, bool):
param_str.append(f'--{param}')
param_str.append(f"--{param}")
elif isinstance(value, int):
param_str.append(f'--{param} {value}')
param_str.append(f"--{param} {value}")
elif isinstance(value, list):
for value_item in value:
val_str = str(value_item).replace("'", "\\'")
param_str.append(f"--{param} '{val_str}'")
elif isinstance(value, dict):
param_str.append(f'--{param} \'{",".join(f"{key}={val}" for key, val in value.items())}\'')
param_str.append(
f"--{param} '{','.join(f'{key}={val}' for key, val in value.items())}'"
)
else:
if "'" in str(value):
value_str = str(value).replace('"', '\\"')
@ -39,7 +48,7 @@ class NeofsCliCommand:
else:
param_str.append(f"--{param} '{value}'")
param_str = ' '.join(param_str)
param_str = " ".join(param_str)
return f'{self.neofs_cli_exec} {self.__base_params} {command or ""} {param_str}'

View File

@ -1,13 +1,26 @@
from typing import List, Optional
from acl import EACLOperation
from python_keywords.object_access import (can_get_object, can_put_object, can_delete_object, can_get_head_object,
can_get_range_hash_of_object, can_get_range_of_object, can_search_object)
from python_keywords.object_access import (
can_delete_object,
can_get_head_object,
can_get_object,
can_get_range_hash_of_object,
can_get_range_of_object,
can_put_object,
can_search_object,
)
def check_full_access_to_container(wallet: str, cid: str, oid: str, file_name: str,
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None):
def check_full_access_to_container(
wallet: str,
cid: str,
oid: str,
file_name: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
):
assert can_put_object(wallet, cid, file_name, bearer, wallet_config, xhdr)
assert can_get_head_object(wallet, cid, oid, bearer, wallet_config, xhdr)
assert can_get_range_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
@ -17,9 +30,15 @@ def check_full_access_to_container(wallet: str, cid: str, oid: str, file_name: s
assert can_delete_object(wallet, cid, oid, bearer, wallet_config, xhdr)
def check_no_access_to_container(wallet: str, cid: str, oid: str, file_name: str,
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None):
def check_no_access_to_container(
wallet: str,
cid: str,
oid: str,
file_name: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
):
assert not can_put_object(wallet, cid, file_name, bearer, wallet_config, xhdr)
assert not can_get_head_object(wallet, cid, oid, bearer, wallet_config, xhdr)
assert not can_get_range_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
@ -29,42 +48,78 @@ def check_no_access_to_container(wallet: str, cid: str, oid: str, file_name: str
assert not can_delete_object(wallet, cid, oid, bearer, wallet_config, xhdr)
def check_custom_access_to_container(wallet: str, cid: str, oid: str, file_name: str,
deny_operations: Optional[List[EACLOperation]] = None,
ignore_operations: Optional[List[EACLOperation]] = None,
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None):
def check_custom_access_to_container(
wallet: str,
cid: str,
oid: str,
file_name: str,
deny_operations: Optional[List[EACLOperation]] = None,
ignore_operations: Optional[List[EACLOperation]] = None,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
):
deny_operations = [op.value for op in deny_operations or []]
ignore_operations = [op.value for op in ignore_operations or []]
checks: dict = {}
if EACLOperation.PUT.value not in ignore_operations:
checks[EACLOperation.PUT.value] = can_put_object(wallet, cid, file_name, bearer, wallet_config, xhdr)
checks[EACLOperation.PUT.value] = can_put_object(
wallet, cid, file_name, bearer, wallet_config, xhdr
)
if EACLOperation.HEAD.value not in ignore_operations:
checks[EACLOperation.HEAD.value] = can_get_head_object(wallet, cid, oid, bearer, wallet_config, xhdr)
checks[EACLOperation.HEAD.value] = can_get_head_object(
wallet, cid, oid, bearer, wallet_config, xhdr
)
if EACLOperation.GET_RANGE.value not in ignore_operations:
checks[EACLOperation.GET_RANGE.value] = can_get_range_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
checks[EACLOperation.GET_RANGE.value] = can_get_range_of_object(
wallet, cid, oid, bearer, wallet_config, xhdr
)
if EACLOperation.GET_RANGE_HASH.value not in ignore_operations:
checks[EACLOperation.GET_RANGE_HASH.value] = can_get_range_hash_of_object(wallet, cid, oid, bearer,
wallet_config, xhdr)
checks[EACLOperation.GET_RANGE_HASH.value] = can_get_range_hash_of_object(
wallet, cid, oid, bearer, wallet_config, xhdr
)
if EACLOperation.SEARCH.value not in ignore_operations:
checks[EACLOperation.SEARCH.value] = can_search_object(wallet, cid, oid, bearer, wallet_config, xhdr)
checks[EACLOperation.SEARCH.value] = can_search_object(
wallet, cid, oid, bearer, wallet_config, xhdr
)
if EACLOperation.GET.value not in ignore_operations:
checks[EACLOperation.GET.value] = can_get_object(wallet, cid, oid, file_name, bearer, wallet_config, xhdr)
checks[EACLOperation.GET.value] = can_get_object(
wallet, cid, oid, file_name, bearer, wallet_config, xhdr
)
if EACLOperation.DELETE.value not in ignore_operations:
checks[EACLOperation.DELETE.value] = can_delete_object(wallet, cid, oid, bearer, wallet_config, xhdr)
checks[EACLOperation.DELETE.value] = can_delete_object(
wallet, cid, oid, bearer, wallet_config, xhdr
)
failed_checks = (
[f'allowed {action} failed' for action, success in checks.items() if
not success and action not in deny_operations] +
[f'denied {action} succeeded' for action, success in checks.items() if
success and action in deny_operations])
failed_checks = [
f"allowed {action} failed"
for action, success in checks.items()
if not success and action not in deny_operations
] + [
f"denied {action} succeeded"
for action, success in checks.items()
if success and action in deny_operations
]
assert not failed_checks, ", ".join(failed_checks)
def check_read_only_container(wallet: str, cid: str, oid: str, file_name: str,
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None):
return check_custom_access_to_container(wallet, cid, oid, file_name,
deny_operations=[EACLOperation.PUT, EACLOperation.DELETE],
bearer=bearer, wallet_config=wallet_config, xhdr=xhdr)
def check_read_only_container(
wallet: str,
cid: str,
oid: str,
file_name: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
):
return check_custom_access_to_container(
wallet,
cid,
oid,
file_name,
deny_operations=[EACLOperation.PUT, EACLOperation.DELETE],
bearer=bearer,
wallet_config=wallet_config,
xhdr=xhdr,
)

View File

@ -3,17 +3,21 @@ from time import sleep
from typing import Optional
import allure
from common import NEOFS_NETMAP_DICT
from python_keywords.node_management import node_healthcheck
from storage_policy import get_nodes_with_object
logger = logging.getLogger('NeoLogger')
logger = logging.getLogger("NeoLogger")
@allure.step('Wait for object replication')
def wait_object_replication_on_nodes(wallet: str, cid: str, oid: str, expected_copies: int,
excluded_nodes: Optional[list[str]] = None) -> list[str]:
@allure.step("Wait for object replication")
def wait_object_replication_on_nodes(
wallet: str,
cid: str,
oid: str,
expected_copies: int,
excluded_nodes: Optional[list[str]] = None,
) -> list[str]:
excluded_nodes = excluded_nodes or []
sleep_interval, attempts = 10, 18
nodes = []
@ -22,28 +26,30 @@ def wait_object_replication_on_nodes(wallet: str, cid: str, oid: str, expected_c
if len(nodes) == expected_copies:
return nodes
sleep(sleep_interval)
raise AssertionError(f'Expected {expected_copies} copies of object, but found {len(nodes)}. '
f'Waiting time {sleep_interval * attempts}')
raise AssertionError(
f"Expected {expected_copies} copies of object, but found {len(nodes)}. "
f"Waiting time {sleep_interval * attempts}"
)
@allure.step('Wait for storage node returned to cluster')
@allure.step("Wait for storage node returned to cluster")
def wait_all_storage_node_returned():
sleep_interval, attempts = 10, 12
for __attempt in range(attempts):
if is_all_storage_node_returned():
return
sleep(sleep_interval)
raise AssertionError('Storage node(s) is broken')
raise AssertionError("Storage node(s) is broken")
def is_all_storage_node_returned() -> 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():
try:
health_check = node_healthcheck(node_name)
except Exception as err:
logger.warning(f'Node healthcheck fails with error {err}')
logger.warning(f"Node healthcheck fails with error {err}")
return False
if health_check.health_status != 'READY' or health_check.network_status != 'ONLINE':
if health_check.health_status != "READY" or health_check.network_status != "ONLINE":
return False
return True

View File

@ -1,99 +1,181 @@
from typing import Optional
import allure
from grpc_responses import OBJECT_ACCESS_DENIED, error_matches_status
from python_keywords.neofs_verbs import (delete_object, get_object, get_range, get_range_hash, head_object, put_object,
search_object)
from python_keywords.neofs_verbs import (
delete_object,
get_object,
get_range,
get_range_hash,
head_object,
put_object,
search_object,
)
from python_keywords.utility_keywords import get_file_hash
OPERATION_ERROR_TYPE = RuntimeError
def can_get_object(wallet: str, cid: str, oid: str, file_name: str, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
) -> bool:
with allure.step('Try get object from container'):
def can_get_object(
wallet: str,
cid: str,
oid: str,
file_name: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> bool:
with allure.step("Try get object from container"):
try:
got_file_path = get_object(wallet, cid, oid, bearer_token=bearer, wallet_config=wallet_config, xhdr=xhdr)
got_file_path = get_object(
wallet, cid, oid, bearer_token=bearer, wallet_config=wallet_config, xhdr=xhdr
)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
assert get_file_hash(file_name) == get_file_hash(got_file_path)
return True
def can_put_object(wallet: str, cid: str, file_name: str, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None, attributes: Optional[dict] = None,
) -> bool:
with allure.step('Try put object to container'):
def can_put_object(
wallet: str,
cid: str,
file_name: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
attributes: Optional[dict] = None,
) -> bool:
with allure.step("Try put object to container"):
try:
put_object(wallet, file_name, cid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr,
attributes=attributes)
put_object(
wallet,
file_name,
cid,
bearer=bearer,
wallet_config=wallet_config,
xhdr=xhdr,
attributes=attributes,
)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
return True
def can_delete_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
) -> bool:
with allure.step('Try delete object from container'):
def can_delete_object(
wallet: str,
cid: str,
oid: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> bool:
with allure.step("Try delete object from container"):
try:
delete_object(wallet, cid, oid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
return True
def can_get_head_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
) -> bool:
with allure.step('Try get head of object'):
def can_get_head_object(
wallet: str,
cid: str,
oid: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> bool:
with allure.step("Try get head of object"):
try:
head_object(wallet, cid, oid, bearer_token=bearer, wallet_config=wallet_config, xhdr=xhdr)
head_object(
wallet, cid, oid, bearer_token=bearer, wallet_config=wallet_config, xhdr=xhdr
)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
return True
def can_get_range_of_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
) -> bool:
with allure.step('Try get range of object'):
def can_get_range_of_object(
wallet: str,
cid: str,
oid: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> bool:
with allure.step("Try get range of object"):
try:
get_range(wallet, cid, oid, bearer=bearer, range_cut='0:10', wallet_config=wallet_config,
xhdr=xhdr)
get_range(
wallet,
cid,
oid,
bearer=bearer,
range_cut="0:10",
wallet_config=wallet_config,
xhdr=xhdr,
)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
return True
def can_get_range_hash_of_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
) -> bool:
with allure.step('Try get range hash of object'):
def can_get_range_hash_of_object(
wallet: str,
cid: str,
oid: str,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> bool:
with allure.step("Try get range hash of object"):
try:
get_range_hash(wallet, cid, oid, bearer_token=bearer, range_cut='0:10', wallet_config=wallet_config,
xhdr=xhdr)
get_range_hash(
wallet,
cid,
oid,
bearer_token=bearer,
range_cut="0:10",
wallet_config=wallet_config,
xhdr=xhdr,
)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
return True
def can_search_object(wallet: str, cid: str, oid: Optional[str] = None, bearer: Optional[str] = None,
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
) -> bool:
with allure.step('Try search object in container'):
def can_search_object(
wallet: str,
cid: str,
oid: Optional[str] = None,
bearer: Optional[str] = None,
wallet_config: Optional[str] = None,
xhdr: Optional[dict] = None,
) -> bool:
with allure.step("Try search object in container"):
try:
oids = search_object(wallet, cid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr)
except OPERATION_ERROR_TYPE as err:
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
assert error_matches_status(
err, OBJECT_ACCESS_DENIED
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
return False
if oid:
return oid in oids

View File

@ -6,22 +6,16 @@
"""
import logging
import allure
from cli_helpers import _cmd_run
from common import (
COMPLEX_OBJ_SIZE,
NEOFS_CLI_EXEC,
NEOFS_ENDPOINT,
SIMPLE_OBJ_SIZE,
WALLET_CONFIG,
)
from common import COMPLEX_OBJ_SIZE, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, SIMPLE_OBJ_SIZE, WALLET_CONFIG
from complex_object_actions import get_link_object
from neofs_verbs import head_object
import allure
logger = logging.getLogger("NeoLogger")
@allure.step('Put Storagegroup')
@allure.step("Put Storagegroup")
def put_storagegroup(
wallet: str,
cid: str,
@ -55,7 +49,7 @@ def put_storagegroup(
return oid
@allure.step('List Storagegroup')
@allure.step("List Storagegroup")
def list_storagegroup(
wallet: str, cid: str, bearer_token: str = "", wallet_config: str = WALLET_CONFIG
):
@ -82,7 +76,7 @@ def list_storagegroup(
return found_objects
@allure.step('Get Storagegroup')
@allure.step("Get Storagegroup")
def get_storagegroup(
wallet: str,
cid: str,
@ -128,7 +122,7 @@ def get_storagegroup(
return sg_dict
@allure.step('Delete Storagegroup')
@allure.step("Delete Storagegroup")
def delete_storagegroup(
wallet: str,
cid: str,
@ -159,7 +153,7 @@ def delete_storagegroup(
return tombstone_id
@allure.step('Verify list operation over Storagegroup')
@allure.step("Verify list operation over Storagegroup")
def verify_list_storage_group(
wallet: str,
cid: str,
@ -173,7 +167,7 @@ def verify_list_storage_group(
assert storagegroup in storage_groups
@allure.step('Verify get operation over Storagegroup')
@allure.step("Verify get operation over Storagegroup")
def verify_get_storage_group(
wallet: str,
cid: str,

View File

@ -9,9 +9,9 @@ CONTAINER_WAIT_INTERVAL = "1m"
SIMPLE_OBJ_SIZE = int(os.getenv("SIMPLE_OBJ_SIZE", "1000"))
COMPLEX_OBJ_SIZE = int(os.getenv("COMPLEX_OBJ_SIZE", "2000"))
MAINNET_BLOCK_TIME = os.getenv('MAINNET_BLOCK_TIME', "1s")
MAINNET_TIMEOUT = os.getenv('MAINNET_TIMEOUT', "1min")
MORPH_BLOCK_TIME = os.getenv("MORPH_BLOCK_TIME", '1s')
MAINNET_BLOCK_TIME = os.getenv("MAINNET_BLOCK_TIME", "1s")
MAINNET_TIMEOUT = os.getenv("MAINNET_TIMEOUT", "1min")
MORPH_BLOCK_TIME = os.getenv("MORPH_BLOCK_TIME", "1s")
NEOFS_CONTRACT_CACHE_TIMEOUT = os.getenv("NEOFS_CONTRACT_CACHE_TIMEOUT", "30s")
# Time interval that allows a GC pass on storage node (this includes GC sleep interval
@ -20,11 +20,11 @@ STORAGE_GC_TIME = os.getenv("STORAGE_GC_TIME", "75s")
NEOFS_ENDPOINT = os.getenv("NEOFS_ENDPOINT", "s01.neofs.devenv:8080")
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')
HTTP_GATE = os.getenv("HTTP_GATE", 'http://http.neofs.devenv')
S3_GATE = os.getenv("S3_GATE", 'https://s3.neofs.devenv:8080')
GAS_HASH = '0xd2a4cff31913016155e38e474a2c06d08be276cf'
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")
HTTP_GATE = os.getenv("HTTP_GATE", "http://http.neofs.devenv")
S3_GATE = os.getenv("S3_GATE", "https://s3.neofs.devenv:8080")
GAS_HASH = "0xd2a4cff31913016155e38e474a2c06d08be276cf"
NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
@ -43,53 +43,67 @@ STORAGE_CONTROL_ENDPOINT_1 = os.getenv("STORAGE_CONTROL_ENDPOINT_1", "s01.neofs.
STORAGE_CONTROL_ENDPOINT_2 = os.getenv("STORAGE_CONTROL_ENDPOINT_2", "s02.neofs.devenv:8081")
STORAGE_CONTROL_ENDPOINT_3 = os.getenv("STORAGE_CONTROL_ENDPOINT_3", "s03.neofs.devenv:8081")
STORAGE_CONTROL_ENDPOINT_4 = os.getenv("STORAGE_CONTROL_ENDPOINT_4", "s04.neofs.devenv:8081")
STORAGE_CONTROL_ENDPOINT_PRIVATE = os.getenv("STORAGE_CONTROL_ENDPOINT_PRIVATE", "false").lower() == "true"
STORAGE_CONTROL_ENDPOINT_PRIVATE = (
os.getenv("STORAGE_CONTROL_ENDPOINT_PRIVATE", "false").lower() == "true"
)
STORAGE_WALLET_PATH_1 = os.getenv("STORAGE_WALLET_PATH_1", f"{DEVENV_PATH}/services/storage/wallet01.json")
STORAGE_WALLET_PATH_2 = os.getenv("STORAGE_WALLET_PATH_2", f"{DEVENV_PATH}/services/storage/wallet02.json")
STORAGE_WALLET_PATH_3 = os.getenv("STORAGE_WALLET_PATH_3", f"{DEVENV_PATH}/services/storage/wallet03.json")
STORAGE_WALLET_PATH_4 = os.getenv("STORAGE_WALLET_PATH_4", f"{DEVENV_PATH}/services/storage/wallet04.json")
STORAGE_WALLET_PATH_1 = os.getenv(
"STORAGE_WALLET_PATH_1", f"{DEVENV_PATH}/services/storage/wallet01.json"
)
STORAGE_WALLET_PATH_2 = os.getenv(
"STORAGE_WALLET_PATH_2", f"{DEVENV_PATH}/services/storage/wallet02.json"
)
STORAGE_WALLET_PATH_3 = os.getenv(
"STORAGE_WALLET_PATH_3", f"{DEVENV_PATH}/services/storage/wallet03.json"
)
STORAGE_WALLET_PATH_4 = os.getenv(
"STORAGE_WALLET_PATH_4", f"{DEVENV_PATH}/services/storage/wallet04.json"
)
STORAGE_WALLET_PATH = STORAGE_WALLET_PATH_1
STORAGE_WALLET_PASS = os.getenv("STORAGE_WALLET_PASS", "")
STORAGE_WALLET_CONFIG = f"{CLI_CONFIGS_PATH}/empty_passwd.yml"
NEOFS_NETMAP_DICT = {
's01': {
'rpc': STORAGE_RPC_ENDPOINT_1,
'control': STORAGE_CONTROL_ENDPOINT_1,
'wallet_path': STORAGE_WALLET_PATH_1,
'UN-LOCODE': 'RU MOW'
"s01": {
"rpc": STORAGE_RPC_ENDPOINT_1,
"control": STORAGE_CONTROL_ENDPOINT_1,
"wallet_path": STORAGE_WALLET_PATH_1,
"UN-LOCODE": "RU MOW",
},
's02': {
'rpc': STORAGE_RPC_ENDPOINT_2,
'control': STORAGE_CONTROL_ENDPOINT_2,
'wallet_path': STORAGE_WALLET_PATH_2,
'UN-LOCODE': 'RU LED'
"s02": {
"rpc": STORAGE_RPC_ENDPOINT_2,
"control": STORAGE_CONTROL_ENDPOINT_2,
"wallet_path": STORAGE_WALLET_PATH_2,
"UN-LOCODE": "RU LED",
},
's03': {
'rpc': STORAGE_RPC_ENDPOINT_3,
'control': STORAGE_CONTROL_ENDPOINT_3,
'wallet_path': STORAGE_WALLET_PATH_3,
'UN-LOCODE': 'SE STO'
"s03": {
"rpc": STORAGE_RPC_ENDPOINT_3,
"control": STORAGE_CONTROL_ENDPOINT_3,
"wallet_path": STORAGE_WALLET_PATH_3,
"UN-LOCODE": "SE STO",
},
's04': {
'rpc': STORAGE_RPC_ENDPOINT_4,
'control': STORAGE_CONTROL_ENDPOINT_4,
'wallet_path': STORAGE_WALLET_PATH_4,
'UN-LOCODE': 'FI HEL'
"s04": {
"rpc": STORAGE_RPC_ENDPOINT_4,
"control": STORAGE_CONTROL_ENDPOINT_4,
"wallet_path": STORAGE_WALLET_PATH_4,
"UN-LOCODE": "FI HEL",
},
}
NEOFS_NETMAP = [i['rpc'] for i in NEOFS_NETMAP_DICT.values()]
NEOGO_EXECUTABLE = os.getenv('NEOGO_EXECUTABLE', 'neo-go')
NEOFS_CLI_EXEC = os.getenv('NEOFS_CLI_EXEC', 'neofs-cli')
NEOFS_NETMAP = [i["rpc"] for i in NEOFS_NETMAP_DICT.values()]
NEOGO_EXECUTABLE = os.getenv("NEOGO_EXECUTABLE", "neo-go")
NEOFS_CLI_EXEC = os.getenv("NEOFS_CLI_EXEC", "neofs-cli")
# Config of wallet with empty password. We use it for:
# - for wallet of user on behalf of whom we are running tests
# - for wallets of storage nodes
WALLET_CONFIG = f"{CLI_CONFIGS_PATH}/empty_passwd.yml"
MAINNET_WALLET_PATH = os.getenv("MAINNET_WALLET_PATH", f"{DEVENV_PATH}/services/chain/node-wallet.json")
MAINNET_WALLET_CONFIG = os.getenv("MAINNET_WALLET_CONFIG", f"{CLI_CONFIGS_PATH}/one_wallet_password.yml")
MAINNET_WALLET_PATH = os.getenv(
"MAINNET_WALLET_PATH", f"{DEVENV_PATH}/services/chain/node-wallet.json"
)
MAINNET_WALLET_CONFIG = os.getenv(
"MAINNET_WALLET_CONFIG", f"{CLI_CONFIGS_PATH}/one_wallet_password.yml"
)
MAINNET_SINGLE_ADDR = os.getenv("MAINNET_SINGLE_ADDR", "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP")
MAINNET_WALLET_PASS = os.getenv("MAINNET_WALLET_PASS", "one")
@ -97,7 +111,9 @@ IR_WALLET_PATH = os.getenv("IR_WALLET_PATH", f"{DEVENV_PATH}/services/ir/az.json
IR_WALLET_CONFIG = os.getenv("IR_WALLET_CONFIG", f"{CLI_CONFIGS_PATH}/one_wallet_password.yml")
IR_WALLET_PASS = os.getenv("IR_WALLET_PASS", "one")
S3_GATE_WALLET_PATH = os.getenv("S3_GATE_WALLET_PATH", f"{DEVENV_PATH}/services/s3_gate/wallet.json")
S3_GATE_WALLET_PATH = os.getenv(
"S3_GATE_WALLET_PATH", f"{DEVENV_PATH}/services/s3_gate/wallet.json"
)
S3_GATE_WALLET_PASS = os.getenv("S3_GATE_WALLET_PASS", "s3")
# Parameters that control SSH connection to storage node
@ -109,7 +125,7 @@ STORAGE_NODE_SSH_PRIVATE_KEY_PATH = os.getenv("STORAGE_NODE_SSH_PRIVATE_KEY_PATH
STORAGE_NODE_BIN_PATH = os.getenv("STORAGE_NODE_BIN_PATH", f"{DEVENV_PATH}/vendor")
# Path and config for neofs-adm utility. Optional if tests are running against devenv
NEOFS_ADM_EXEC = os.getenv('NEOFS_ADM_EXEC', 'neofs-adm')
NEOFS_ADM_EXEC = os.getenv("NEOFS_ADM_EXEC", "neofs-adm")
NEOFS_ADM_CONFIG_PATH = os.getenv("NEOFS_ADM_CONFIG_PATH")

View File

@ -1,21 +1,23 @@
EACL_OBJ_FILTERS = {'$Object:objectID': 'objectID',
'$Object:containerID': 'containerID',
'$Object:ownerID': 'ownerID',
'$Object:creationEpoch': 'creationEpoch',
'$Object:payloadLength': 'payloadLength',
'$Object:payloadHash': 'payloadHash',
'$Object:objectType': 'objectType',
'$Object:homomorphicHash': 'homomorphicHash',
'$Object:version': 'version'}
EACL_OBJ_FILTERS = {
"$Object:objectID": "objectID",
"$Object:containerID": "containerID",
"$Object:ownerID": "ownerID",
"$Object:creationEpoch": "creationEpoch",
"$Object:payloadLength": "payloadLength",
"$Object:payloadHash": "payloadHash",
"$Object:objectType": "objectType",
"$Object:homomorphicHash": "homomorphicHash",
"$Object:version": "version",
}
VERB_FILTER_DEP = {
'$Object:objectID': ['GET', 'HEAD', 'DELETE', 'RANGE', 'RANGEHASH'],
'$Object:containerID': ['GET', 'PUT', 'HEAD', 'DELETE', 'SEARCH', 'RANGE', 'RANGEHASH'],
'$Object:ownerID': ['GET', 'HEAD'],
'$Object:creationEpoch': ['GET', 'PUT', 'HEAD'],
'$Object:payloadLength': ['GET', 'PUT', 'HEAD'],
'$Object:payloadHash': ['GET', 'PUT', 'HEAD'],
'$Object:objectType': ['GET', 'PUT', 'HEAD'],
'$Object:homomorphicHash': ['GET', 'PUT', 'HEAD'],
'$Object:version': ['GET', 'PUT', 'HEAD']
}
"$Object:objectID": ["GET", "HEAD", "DELETE", "RANGE", "RANGEHASH"],
"$Object:containerID": ["GET", "PUT", "HEAD", "DELETE", "SEARCH", "RANGE", "RANGEHASH"],
"$Object:ownerID": ["GET", "HEAD"],
"$Object:creationEpoch": ["GET", "PUT", "HEAD"],
"$Object:payloadLength": ["GET", "PUT", "HEAD"],
"$Object:payloadHash": ["GET", "PUT", "HEAD"],
"$Object:objectType": ["GET", "PUT", "HEAD"],
"$Object:homomorphicHash": ["GET", "PUT", "HEAD"],
"$Object:version": ["GET", "PUT", "HEAD"],
}