Compare commits

...

10 commits

Author SHA1 Message Date
4779d2be88 update headers names
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
2023-03-27 19:49:41 +03:00
5684d11408 Fix dataloss test
Signed-off-by: anikeev-yadro <a.anikeev@yadro.com>
2023-03-24 13:54:08 +00:00
bb831697f7 [#25] testcases: Fix test_static_session_search
Lists should be compared sorted
Enable test after bugfix

Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-03-21 16:19:01 +03:00
2b950f41cd Add test cycles feature
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
2023-03-20 19:03:29 +03:00
eb464f422c Add tests with empty map
Signed-off-by: anikeev-yadro <a.anikeev@yadro.com>
2023-03-17 17:15:01 +03:00
c3947b0716 Remove payments and storagegroupe tests
Signed-off-by: Liza <e.chichindaeva@yadro.com>
2023-03-14 14:46:25 +00:00
c97855dcee Fix __FROSTFS__EXPIRATION*
Signed-off-by: Liza <e.chichindaeva@yadro.com>
2023-03-14 16:24:29 +03:00
c997e23194 Updates for testcases
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
2023-03-14 12:21:40 +03:00
cff0e0f23e Update session token tests related to expiration rules
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
2023-03-13 17:05:24 +03:00
ef5e142015 Add timeout for cli commands
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
2023-03-09 14:43:14 +03:00
18 changed files with 495 additions and 839 deletions

View file

@ -48,20 +48,8 @@ To setup development environment for `frosfs-testcases`, please, take the follow
1. Prepare virtualenv
```shell
$ virtualenv --python=python3.9 venv
$ source venv/bin/activate
```
2. Install all dependencies:
```shell
$ pip install -r requirements.txt
```
3. Setup pre-commit hooks to run code formatters on staged files before you run a `git commit` command:
```shell
$ pre-commit install
$ make venv
$ source frostfs-testcases-3.10/bin/activate
```
Optionally you might want to integrate code formatters with your code editor to apply formatters to code files as you go:

View file

@ -1,28 +0,0 @@
#!/usr/bin/make -f
.DEFAULT_GOAL := help
SHELL ?= bash
VENVS = $(shell ls -1d venv/*/ | sort -u | xargs basename -a)
.PHONY: all
all: venvs
include venv_template.mk
.PHONY: venvs
venvs:
$(foreach venv,$(VENVS),venv.$(venv))
$(foreach venv,$(VENVS),$(eval $(call VENV_template,$(venv))))
clean:
rm -rf venv.*
pytest-local:
@echo "⇒ Run Pytest"
python -m pytest pytest_tests/testsuites/
help:
@echo "⇒ run Run testcases ${R}"

View file

@ -49,17 +49,11 @@ As we use frostfs-dev-env, you'll also need to install
6. Prepare virtualenv
```shell
$ make venv.local-pytest
$ . venv.local-pytest/bin/activate
$ make venv
$ source venv.frostfs-testcases-3.10/bin/activate
```
7. Setup pre-commit hooks to run code formatters on staged files before you run a `git commit` command:
```shell
$ pre-commit install
```
Optionally you might want to integrate code formatters with your code editor to apply formatters to code files as you go:
7. Optionally you might want to integrate code formatters with your code editor to apply formatters to code files as you go:
* isort is supported by [PyCharm](https://plugins.jetbrains.com/plugin/15434-isortconnect), [VS Code](https://cereblanco.medium.com/setup-black-and-isort-in-vscode-514804590bf9). Plugins exist for other IDEs/editors as well.
* black can be integrated with multiple editors, please, instructions are available [here](https://black.readthedocs.io/en/stable/integrations/editors.html).

View file

@ -1,4 +1,5 @@
import random
import pathlib
import re
from dataclasses import dataclass
from typing import Any
@ -110,7 +111,14 @@ class InnerRingNode(NodeBase):
since frostfs network will still treat it as "node"
"""
pass
def get_netmap_cleaner_threshold(self) -> str:
config_file = self.get_remote_config_path()
contents = self.host.get_shell().exec(f"cat {config_file}").stdout
config = yaml.safe_load(contents)
value = config["netmap_cleaner"]["threshold"]
return value
class S3Gate(NodeBase):

View file

@ -2,17 +2,24 @@ import logging
import random
import re
import time
from time import sleep
from dataclasses import dataclass
from typing import Optional
import allure
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.cli import FrostfsAdm, FrostfsCli
from frostfs_testlib.shell import Shell
from frostfs_testlib.utils import datetime_utils
from pytest_tests.helpers.cluster import Cluster, StorageNode
from pytest_tests.helpers.epoch import tick_epoch
from pytest_tests.resources.common import FROSTFS_CLI_EXEC, MORPH_BLOCK_TIME
from pytest_tests.resources.common import (
FROSTFS_CLI_EXEC,
FROSTFS_ADM_CONFIG_PATH,
FROSTFS_ADM_EXEC,
FROSTFS_CLI_EXEC,
MORPH_BLOCK_TIME,
)
logger = logging.getLogger("NeoLogger")
@ -211,6 +218,62 @@ def check_node_in_map(
node_netmap_key in snapshot
), f"Expected node with key {node_netmap_key} to be in network map"
@allure.step("Check node {node} NOT in network map")
def check_node_not_in_map(
node: StorageNode, shell: Shell, alive_node: Optional[StorageNode] = None
) -> None:
alive_node = alive_node or node
node_netmap_key = node.get_wallet_public_key()
logger.info(f"Node ({node.label}) netmap key: {node_netmap_key}")
snapshot = get_netmap_snapshot(alive_node, shell)
assert (
node_netmap_key not in snapshot
), f"Expected node with key {node_netmap_key} to be NOT in network map"
@allure.step("Wait for node {node} is ready")
def wait_for_node_to_be_ready(node: StorageNode) -> None:
timeout, attempts = 30, 6
for _ in range(attempts):
try:
health_check = storage_node_healthcheck(node)
if health_check.health_status == "READY":
return
except Exception as err:
logger.warning(f"Node {node} is not ready:\n{err}")
sleep(timeout)
raise AssertionError(
f"Node {node} hasn't gone to the READY state after {timeout * attempts} seconds"
)
@allure.step("Remove nodes from network map trough cli-adm morph command")
def remove_nodes_from_map_morph(shell: Shell, cluster: Cluster, remove_nodes: list[StorageNode], alive_node: Optional[StorageNode] = None):
"""
Move node to the Offline state in the candidates list and tick an epoch to update the netmap
using frostfs-adm
Args:
shell: local shell to make queries about current epoch. Remote shell will be used to tick new one
cluster: cluster instance under test
alive_node: node to send requests to (first node in cluster by default)
remove_nodes: list of nodes which would be removed from map
"""
alive_node = alive_node if alive_node else remove_nodes[0]
remote_shell = alive_node.host.get_shell()
node_netmap_keys = list(map(StorageNode.get_wallet_public_key, remove_nodes))
logger.info(f"Nodes netmap keys are: {' '.join(node_netmap_keys)}")
if FROSTFS_ADM_EXEC and FROSTFS_ADM_CONFIG_PATH:
# If frostfs-adm is available, then we tick epoch with it (to be consistent with UAT tests)
frostfsadm = FrostfsAdm(
shell=remote_shell,
frostfs_adm_exec_path=FROSTFS_ADM_EXEC,
config_file=FROSTFS_ADM_CONFIG_PATH,
)
frostfsadm.morph.remove_nodes(node_netmap_keys)
def _run_control_command_with_retries(node: StorageNode, command: str, retries: int = 0) -> str:
for attempt in range(1 + retries): # original attempt + specified retries

View file

@ -8,6 +8,8 @@ SIMPLE_OBJECT_SIZE = os.getenv("SIMPLE_OBJECT_SIZE", "1000")
COMPLEX_OBJECT_CHUNKS_COUNT = os.getenv("COMPLEX_OBJECT_CHUNKS_COUNT", "3")
COMPLEX_OBJECT_TAIL_SIZE = os.getenv("COMPLEX_OBJECT_TAIL_SIZE", "1000")
TEST_CYCLES_COUNT = int(os.getenv("TEST_CYCLES_COUNT", "1"))
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")

View file

@ -1,473 +0,0 @@
import logging
import os
import uuid
from typing import Optional
import allure
import pytest
from frostfs_testlib.resources.common import OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND
from frostfs_testlib.utils import wallet_utils
from pytest_tests.helpers.acl import (
EACLAccess,
EACLOperation,
EACLRole,
EACLRule,
create_eacl,
form_bearertoken_file,
set_eacl,
)
from pytest_tests.helpers.container import create_container
from pytest_tests.helpers.file_helper import generate_file
from pytest_tests.helpers.frostfs_verbs import put_object_to_random_node
from pytest_tests.helpers.payment_neogo import deposit_gas, transfer_gas
from pytest_tests.helpers.storage_group import (
delete_storagegroup,
get_storagegroup,
list_storagegroup,
put_storagegroup,
verify_get_storage_group,
verify_list_storage_group,
)
from pytest_tests.resources.common import ASSETS_DIR, FREE_STORAGE, WALLET_PASS
from pytest_tests.steps.cluster_test_base import ClusterTestBase
logger = logging.getLogger("NeoLogger")
deposit = 30
@pytest.mark.parametrize(
"object_size",
[pytest.lazy_fixture("simple_object_size"), pytest.lazy_fixture("complex_object_size")],
ids=["simple object", "complex object"],
)
@pytest.mark.sanity
@pytest.mark.acl
@pytest.mark.storage_group
class TestStorageGroup(ClusterTestBase):
@pytest.fixture(autouse=True)
def prepare_two_wallets(self, default_wallet):
self.main_wallet = default_wallet
self.other_wallet = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
wallet_utils.init_wallet(self.other_wallet, WALLET_PASS)
if not FREE_STORAGE:
main_chain = self.cluster.main_chain_nodes[0]
deposit = 30
transfer_gas(
shell=self.shell,
amount=deposit + 1,
main_chain=main_chain,
wallet_to_path=self.other_wallet,
wallet_to_password=WALLET_PASS,
)
deposit_gas(
shell=self.shell,
amount=deposit,
main_chain=main_chain,
wallet_from_path=self.other_wallet,
wallet_from_password=WALLET_PASS,
)
@allure.title("Test Storage Group in Private Container")
def test_storagegroup_basic_private_container(self, object_size, max_object_size):
cid = create_container(
self.main_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
)
file_path = generate_file(object_size)
oid = put_object_to_random_node(self.main_wallet, file_path, cid, self.shell, self.cluster)
objects = [oid]
storage_group = put_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=self.main_wallet,
cid=cid,
objects=objects,
)
self.expect_success_for_storagegroup_operations(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
self.expect_failure_for_storagegroup_operations(
wallet=self.other_wallet,
cid=cid,
obj_list=objects,
gid=storage_group,
)
self.storagegroup_operations_by_system_ro_container(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
@allure.title("Test Storage Group in Public Container")
def test_storagegroup_basic_public_container(self, object_size, max_object_size):
cid = create_container(
self.main_wallet,
basic_acl="public-read-write",
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
)
file_path = generate_file(object_size)
oid = put_object_to_random_node(
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
)
objects = [oid]
self.expect_success_for_storagegroup_operations(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
self.expect_success_for_storagegroup_operations(
wallet=self.other_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
self.storagegroup_operations_by_system_ro_container(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
@allure.title("Test Storage Group in Read-Only Container")
def test_storagegroup_basic_ro_container(self, object_size, max_object_size):
cid = create_container(
self.main_wallet,
basic_acl="public-read",
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
)
file_path = generate_file(object_size)
oid = put_object_to_random_node(
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
)
objects = [oid]
self.expect_success_for_storagegroup_operations(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
self.storagegroup_operations_by_other_ro_container(
owner_wallet=self.main_wallet,
other_wallet=self.other_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
self.storagegroup_operations_by_system_ro_container(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
@allure.title("Test Storage Group with Bearer Allow")
def test_storagegroup_bearer_allow(self, object_size, max_object_size):
cid = create_container(
self.main_wallet,
basic_acl="eacl-public-read-write",
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
)
file_path = generate_file(object_size)
oid = put_object_to_random_node(
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
)
objects = [oid]
self.expect_success_for_storagegroup_operations(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
)
storage_group = put_storagegroup(
self.shell, self.cluster.default_rpc_endpoint, self.main_wallet, cid, objects
)
eacl_deny = [
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, shell=self.shell),
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
)
self.expect_failure_for_storagegroup_operations(
self.main_wallet, cid, objects, storage_group
)
bearer_file = form_bearertoken_file(
self.main_wallet,
cid,
[
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.USER)
for op in EACLOperation
],
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
)
self.expect_success_for_storagegroup_operations(
wallet=self.main_wallet,
cid=cid,
obj_list=objects,
object_size=object_size,
max_object_size=max_object_size,
bearer=bearer_file,
)
@allure.title("Test to check Storage Group lifetime")
def test_storagegroup_lifetime(self, object_size):
cid = create_container(
self.main_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
)
file_path = generate_file(object_size)
oid = put_object_to_random_node(
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
)
objects = [oid]
storage_group = put_storagegroup(
self.shell,
self.cluster.default_rpc_endpoint,
self.main_wallet,
cid,
objects,
lifetime=1,
)
with allure.step("Tick two epochs"):
for _ in range(2):
self.tick_epoch()
self.wait_for_epochs_align()
with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=self.main_wallet,
cid=cid,
gid=storage_group,
)
@allure.step("Run Storage Group Operations And Expect Success")
def expect_success_for_storagegroup_operations(
self,
wallet: str,
cid: str,
obj_list: list,
object_size: int,
max_object_size: int,
bearer: Optional[str] = None,
):
"""
This func verifies if the Object's owner is allowed to
Put, List, Get and Delete the Storage Group which contains
the Object.
"""
storage_group = put_storagegroup(
self.shell, self.cluster.default_rpc_endpoint, wallet, cid, obj_list, bearer
)
verify_list_storage_group(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=wallet,
cid=cid,
gid=storage_group,
bearer=bearer,
)
verify_get_storage_group(
shell=self.shell,
cluster=self.cluster,
wallet=wallet,
cid=cid,
gid=storage_group,
obj_list=obj_list,
object_size=object_size,
max_object_size=max_object_size,
bearer=bearer,
)
delete_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=wallet,
cid=cid,
gid=storage_group,
bearer=bearer,
)
@allure.step("Run Storage Group Operations And Expect Failure")
def expect_failure_for_storagegroup_operations(
self, wallet: str, cid: str, obj_list: list, gid: str
):
"""
This func verifies if the Object's owner isn't allowed to
Put, List, Get and Delete the Storage Group which contains
the Object.
"""
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
put_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=wallet,
cid=cid,
objects=obj_list,
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
list_storagegroup(
shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, wallet=wallet, cid=cid
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
get_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=wallet,
cid=cid,
gid=gid,
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
delete_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=wallet,
cid=cid,
gid=gid,
)
@allure.step("Run Storage Group Operations On Other's Behalf In RO Container")
def storagegroup_operations_by_other_ro_container(
self,
owner_wallet: str,
other_wallet: str,
cid: str,
obj_list: list,
object_size: int,
max_object_size: int,
):
storage_group = put_storagegroup(
self.shell, self.cluster.default_rpc_endpoint, owner_wallet, cid, obj_list
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
put_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=other_wallet,
cid=cid,
objects=obj_list,
)
verify_list_storage_group(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=other_wallet,
cid=cid,
gid=storage_group,
)
verify_get_storage_group(
shell=self.shell,
cluster=self.cluster,
wallet=other_wallet,
cid=cid,
gid=storage_group,
obj_list=obj_list,
object_size=object_size,
max_object_size=max_object_size,
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
delete_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=other_wallet,
cid=cid,
gid=storage_group,
)
@allure.step("Run Storage Group Operations On Systems's Behalf In RO Container")
def storagegroup_operations_by_system_ro_container(
self,
wallet: str,
cid: str,
obj_list: list,
object_size: int,
max_object_size: int,
):
"""
In this func we create a Storage Group on Inner Ring's key behalf
and include an Object created on behalf of some user. We expect
that System key is granted to make all operations except PUT and DELETE.
"""
ir_node = self.cluster.ir_nodes[0]
ir_wallet_path = ir_node.get_wallet_path()
ir_wallet_password = ir_node.get_wallet_password()
ir_wallet_config = ir_node.get_wallet_config_path()
if not FREE_STORAGE:
main_chain = self.cluster.main_chain_nodes[0]
deposit = 30
transfer_gas(
shell=self.shell,
amount=deposit + 1,
main_chain=main_chain,
wallet_to_path=ir_wallet_path,
wallet_to_password=ir_wallet_password,
)
deposit_gas(
shell=self.shell,
amount=deposit,
main_chain=main_chain,
wallet_from_path=ir_wallet_path,
wallet_from_password=ir_wallet_password,
)
storage_group = put_storagegroup(
self.shell, self.cluster.default_rpc_endpoint, wallet, cid, obj_list
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
put_storagegroup(
self.shell,
self.cluster.default_rpc_endpoint,
ir_wallet_path,
cid,
obj_list,
wallet_config=ir_wallet_config,
)
verify_list_storage_group(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=ir_wallet_path,
cid=cid,
gid=storage_group,
wallet_config=ir_wallet_config,
)
verify_get_storage_group(
shell=self.shell,
cluster=self.cluster,
wallet=ir_wallet_path,
cid=cid,
gid=storage_group,
obj_list=obj_list,
object_size=object_size,
max_object_size=max_object_size,
wallet_config=ir_wallet_config,
)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
delete_storagegroup(
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
wallet=ir_wallet_path,
cid=cid,
gid=storage_group,
wallet_config=ir_wallet_config,
)

View file

@ -1,6 +1,5 @@
import logging
import os
import re
import shutil
import uuid
from datetime import datetime
@ -28,6 +27,7 @@ from pytest_tests.resources.common import (
HOSTING_CONFIG_FILE,
SIMPLE_OBJECT_SIZE,
STORAGE_NODE_SERVICE_NAME_REGEX,
TEST_CYCLES_COUNT,
WALLET_PASS,
)
from pytest_tests.resources.load_params import (
@ -44,15 +44,40 @@ from pytest_tests.steps.load import get_services_endpoints, prepare_k6_instances
logger = logging.getLogger("NeoLogger")
def pytest_collection_modifyitems(items):
# Make network tests last based on @pytest.mark.node_mgmt
# Add logs check test even if it's not fit to mark selectors
def pytest_configure(config: pytest.Config):
markers = config.option.markexpr
if markers != "":
config.option.markexpr = f"logs_after_session or ({markers})"
# pytest hook. Do not rename
def pytest_collection_modifyitems(items: list[pytest.Item]):
# Make network tests last based on @pytest.mark.node_mgmt and logs_test to be latest
def priority(item: pytest.Item) -> int:
is_node_mgmt_test = item.get_closest_marker("node_mgmt")
return 0 if not is_node_mgmt_test else 1
is_node_mgmt_test = 1 if item.get_closest_marker("node_mgmt") else 0
is_logs_check_test = 100 if item.get_closest_marker("logs_after_session") else 0
return is_node_mgmt_test + is_logs_check_test
items.sort(key=lambda item: priority(item))
def pytest_generate_tests(metafunc: pytest.Metafunc):
if (
TEST_CYCLES_COUNT <= 1
or metafunc.definition.get_closest_marker("logs_after_session")
or metafunc.definition.get_closest_marker("no_cycles")
):
return
metafunc.fixturenames.append("cycle")
metafunc.parametrize(
"cycle",
range(1, TEST_CYCLES_COUNT + 1),
ids=[f"cycle {cycle}" for cycle in range(1, TEST_CYCLES_COUNT + 1)],
)
@pytest.fixture(scope="session")
def configure_testlib():
get_reporter().register_handler(AllureHandler())
@ -145,23 +170,16 @@ def temp_directory():
shutil.rmtree(full_path)
@allure.step("[Autouse/Session] Test session start time")
@pytest.fixture(scope="session", autouse=True)
@allure.title("Collect logs")
def collect_logs(temp_directory, hosting: Hosting):
def session_start_time():
start_time = datetime.utcnow()
yield
end_time = datetime.utcnow()
# Dump logs to temp directory (because they might be too large to keep in RAM)
logs_dir = os.path.join(temp_directory, "logs")
dump_logs(hosting, logs_dir, start_time, end_time)
attach_logs(logs_dir)
check_logs(logs_dir)
return start_time
@pytest.fixture(scope="session", autouse=True)
@allure.title("Run health check for all storage nodes")
def run_health_check(collect_logs, cluster: Cluster):
def run_health_check(session_start_time, cluster: Cluster):
failed_nodes = []
for node in cluster.storage_nodes:
health_check = storage_node_healthcheck(node)
@ -263,44 +281,3 @@ def default_wallet(client_shell: Shell, temp_directory: str, cluster: Cluster):
)
return wallet_path
@allure.title("Check logs for OOM and PANIC entries in {logs_dir}")
def check_logs(logs_dir: str):
problem_pattern = r"\Wpanic\W|\Woom\W|\Wtoo many open files\W"
log_file_paths = []
for directory_path, _, file_names in os.walk(logs_dir):
log_file_paths += [
os.path.join(directory_path, file_name)
for file_name in file_names
if re.match(r"\.(txt|log)", os.path.splitext(file_name)[-1], flags=re.IGNORECASE)
]
logs_with_problem = []
for file_path in log_file_paths:
with allure.step(f"Check log file {file_path}"):
with open(file_path, "r") as log_file:
if re.search(problem_pattern, log_file.read(), flags=re.IGNORECASE):
logs_with_problem.append(file_path)
if logs_with_problem:
raise pytest.fail(f"System logs {', '.join(logs_with_problem)} contain critical errors")
def dump_logs(hosting: Hosting, logs_dir: str, since: datetime, until: datetime) -> None:
# Dump logs to temp directory (because they might be too large to keep in RAM)
os.makedirs(logs_dir)
for host in hosting.hosts:
with allure.step(f"Dump logs from host {host.config.address}"):
try:
host.dump_logs(logs_dir, since=since, until=until)
except Exception as ex:
logger.warning(f"Exception during logs collection: {ex}")
def attach_logs(logs_dir: str) -> None:
# Zip all files and attach to Allure because it is more convenient to download a single
# zip with all logs rather than mess with individual logs files per service or node
logs_zip_file_path = shutil.make_archive(logs_dir, "zip", logs_dir)
allure.attach.file(logs_zip_file_path, name="logs.zip", extension="zip")

View file

@ -18,7 +18,6 @@ from pytest_tests.steps.cluster_test_base import ClusterTestBase
@pytest.mark.container
@pytest.mark.sanity
@pytest.mark.container
class TestContainer(ClusterTestBase):
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
@pytest.mark.smoke

View file

@ -1,11 +1,15 @@
import os
import logging
from time import sleep
import allure
import pytest
from frostfs_testlib.analytics import test_case
from frostfs_testlib.hosting import Host
from frostfs_testlib.resources.common import PUBLIC_ACL
from frostfs_testlib.shell import CommandOptions
from frostfs_testlib.utils import datetime_utils
from pytest_tests.resources.common import FROSTFS_CONTRACT_CACHE_TIMEOUT, MORPH_BLOCK_TIME
from pytest_tests.helpers.cluster import Cluster, StorageNode
from pytest_tests.helpers.container import create_container
from pytest_tests.helpers.failover_utils import (
@ -16,12 +20,40 @@ from pytest_tests.helpers.file_helper import generate_file, get_file_hash
from pytest_tests.helpers.frostfs_verbs import get_object, put_object_to_random_node
from pytest_tests.steps.cluster_test_base import ClusterTestBase
from pytest_tests.helpers.node_management import (
check_node_in_map,
check_node_not_in_map,
exclude_node_from_network_map,
include_node_to_network_map,
stop_random_storage_nodes,
wait_for_node_to_be_ready,
remove_nodes_from_map_morph
)
from pytest_tests.helpers.s3_helper import (
check_objects_in_bucket
)
from pytest_tests.steps import s3_gate_object
from pytest_tests.steps.s3_gate_base import TestS3GateBase
from pytest_tests.helpers.aws_cli_client import AwsCliClient
from pytest_tests.helpers.file_helper import (
generate_file,
get_file_hash,
)
from pytest_tests.helpers.node_management import (
check_node_in_map,
exclude_node_from_network_map,
include_node_to_network_map,
)
logger = logging.getLogger("NeoLogger")
stopped_nodes: list[StorageNode] = []
@pytest.fixture(scope="function", autouse=True)
@allure.step("Return all stopped hosts")
@pytest.fixture(scope="function", autouse=True)
def after_run_return_all_stopped_hosts(cluster: Cluster):
yield
return_stopped_hosts(cluster)
@ -173,3 +205,170 @@ class TestFailoverStorage(ClusterTestBase):
wallet, cid, oid, shell=self.shell, endpoint=new_nodes[0].get_rpc_endpoint()
)
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
def pytest_generate_tests(metafunc):
if "s3_client" in metafunc.fixturenames:
metafunc.parametrize("s3_client", ["aws cli", "boto3"], indirect=True)
@pytest.mark.failover
@pytest.mark.failover_empty_map
class TestEmptyMap(TestS3GateBase):
"""
A set of tests for makes map empty and verify that we can read objects after that
"""
@allure.step("Teardown after EmptyMap offline test")
@pytest.fixture()
def empty_map_offline_teardown(self):
yield
with allure.step("Return all storage nodes to network map"):
for node in list(stopped_nodes):
include_node_to_network_map(
node, node, shell=self.shell, cluster=self.cluster
)
stopped_nodes.remove(node)
@staticmethod
def object_key_from_file_path(full_path: str) -> str:
return os.path.basename(full_path)
@test_case.title("Test makes network map empty (offline all storage nodes)")
@test_case.priority(test_case.TestCasePriority.HIGH)
@test_case.suite_name("failovers")
@test_case.suite_section("test_failover_storage")
@pytest.mark.failover_empty_map_offlne
@allure.title("Test makes network map empty (offline all storage nodes)")
def test_offline_all_storage_nodes(self, bucket, simple_object_size, empty_map_offline_teardown):
"""
The test makes network map empty (set offline status on all storage nodes) then returns all nodes to map and checks that object can read through s3.
Steps:
1. Check that bucket is empty
2: PUT object into bucket
3: Check that object exists in bucket
4: Exclude all storage nodes from network map (set status OFFLINE)
5: Return all storage nodes to network map
6: Check that we can read object from #2
Args:
bucket: bucket which contains tested object
simple_object_size: size of object
"""
file_path = generate_file(simple_object_size)
file_name = self.object_key_from_file_path(file_path)
bucket_objects = [file_name]
objects_list = s3_gate_object.list_objects_s3(self.s3_client, bucket)
assert not objects_list, f"Expected empty bucket, got {objects_list}"
with allure.step("Put object into bucket"):
s3_gate_object.put_object_s3(self.s3_client, bucket, file_path)
with allure.step("Check that object exists in bucket"):
check_objects_in_bucket(self.s3_client, bucket, bucket_objects)
storage_nodes = self.cluster.storage_nodes
with allure.step("Exclude all storage nodes from network map"):
for node in storage_nodes:
exclude_node_from_network_map(
node, node, shell=self.shell, cluster=self.cluster
)
stopped_nodes.append(node)
with allure.step("Return all storage nodes to network map"):
for node in storage_nodes:
include_node_to_network_map(
node, node, shell=self.shell, cluster=self.cluster
)
stopped_nodes.remove(node)
with allure.step("Check that we can read object"):
check_objects_in_bucket(self.s3_client, bucket, bucket_objects)
@allure.step("Teardown after EmptyMap stop service test")
@pytest.fixture()
def empty_map_stop_service_teardown(self):
yield
with allure.step("Return all storage nodes to network map"):
for node in list(list(stopped_nodes)):
with allure.step(f"Start node {node}"):
node.start_service()
with allure.step(f"Waiting status ready for node {node}"):
wait_for_node_to_be_ready(node)
sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME))
self.tick_epochs(1)
check_node_in_map(node, shell=self.shell, alive_node=node)
stopped_nodes.remove(node)
@test_case.title("Test makes network map empty (stop storage service on all nodes)")
@test_case.priority(test_case.TestCasePriority.HIGH)
@test_case.suite_name("failovers")
@test_case.suite_section("test_failover_storage")
@pytest.mark.failover_empty_map_stop_service
@allure.title("Test makes network map empty (stop storage service on all nodes)")
def test_stop_all_storage_nodes(self, bucket, simple_object_size, empty_map_stop_service_teardown):
"""
The test makes network map empty (stop storage service on all nodes
then use 'frostfs-adm morph delete-nodes' to delete nodes from map)
then start all services and checks that object can read through s3.
Steps:
1. Check that bucket is empty
2: PUT object into bucket
3: Check that object exists in bucket
4: Exclude all storage nodes from network map (stop storage service
and manual exclude from map)
5: Return all storage nodes to network map
6: Check that we can read object from #2
Args:
bucket: bucket which contains tested object
simple_object_size: size of object
"""
file_path = generate_file(simple_object_size)
file_name = self.object_key_from_file_path(file_path)
bucket_objects = [file_name]
objects_list = s3_gate_object.list_objects_s3(self.s3_client, bucket)
assert not objects_list, f"Expected empty bucket, got {objects_list}"
with allure.step("Put object into bucket"):
s3_gate_object.put_object_s3(self.s3_client, bucket, file_path)
with allure.step("Check that object exists in bucket"):
check_objects_in_bucket(self.s3_client, bucket, bucket_objects)
with allure.step("Stop all storage nodes"):
for node in self.cluster.storage_nodes:
with allure.step(f"Stop storage service on node: {node}"):
node.stop_service()
stopped_nodes.append(node)
with allure.step(f"Remove all nodes from network map"):
remove_nodes_from_map_morph(shell=self.shell, cluster=self.cluster, remove_nodes=stopped_nodes)
with allure.step("Return all storage nodes to network map"):
self.return_nodes_after_stop_with_check_empty_map(stopped_nodes)
with allure.step("Check that object exists in bucket"):
check_objects_in_bucket(self.s3_client, bucket, bucket_objects)
@allure.step("Return all nodes to cluster with check empty map first")
def return_nodes_after_stop_with_check_empty_map(self, return_nodes = None) -> None:
first_node = True
for node in list(return_nodes):
with allure.step(f"Start node {node}"):
node.start_service()
with allure.step(f"Waiting status ready for node {node}"):
wait_for_node_to_be_ready(node)
with allure.step(f"We need to make sure that network map is empty"):
if first_node:
for check_node in list(return_nodes):
check_node_not_in_map(check_node, shell=self.shell, alive_node=node)
first_node = False
sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME))
self.tick_epochs(1)
check_node_in_map(node, shell=self.shell, alive_node=node)
stopped_nodes.remove(node)

View file

@ -32,6 +32,7 @@ from pytest_tests.helpers.node_management import (
node_shard_set_mode,
storage_node_healthcheck,
storage_node_set_status,
wait_for_node_to_be_ready
)
from pytest_tests.helpers.storage_policy import get_nodes_with_object, get_simple_object_copies
from pytest_tests.helpers.utility import (
@ -109,7 +110,7 @@ class TestNodeManagement(ClusterTestBase):
with allure.step(f"Start node {node}"):
node.start_service()
with allure.step(f"Waiting status ready for node {node}"):
self.wait_for_node_to_be_ready(node)
wait_for_node_to_be_ready(node)
# We need to wait for node to establish notifications from morph-chain
# Otherwise it will hang up when we will try to set status
@ -451,21 +452,6 @@ class TestNodeManagement(ClusterTestBase):
f"Node {node} hasn't gone to the READY and ONLINE state after {timeout * attempts} second"
)
@allure.step("Wait for node {node} is ready")
def wait_for_node_to_be_ready(self, node: StorageNode) -> None:
timeout, attempts = 30, 6
for _ in range(attempts):
try:
health_check = storage_node_healthcheck(node)
if health_check.health_status == "READY":
return
except Exception as err:
logger.warning(f"Node {node} is not ready:\n{err}")
sleep(timeout)
raise AssertionError(
f"Node {node} hasn't gone to the READY state after {timeout * attempts} seconds"
)
@allure.step("Wait for {expected_copies} object copies in the wallet")
def wait_for_expected_object_copies(
self, wallet: str, cid: str, oid: str, expected_copies: int = 2

View file

@ -1,102 +0,0 @@
import logging
import os
import allure
import pytest
import yaml
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.shell import CommandResult, Shell
from pytest_tests.helpers.wallet import WalletFactory, WalletFile
from pytest_tests.resources.common import FREE_STORAGE, FROSTFS_CLI_EXEC, WALLET_CONFIG
from pytest_tests.steps.cluster_test_base import ClusterTestBase
logger = logging.getLogger("NeoLogger")
DEPOSIT_AMOUNT = 30
@pytest.mark.sanity
@pytest.mark.payments
@pytest.mark.skipif(FREE_STORAGE, reason="Test only works on public network with paid storage")
class TestBalanceAccounting(ClusterTestBase):
@pytest.fixture(scope="class")
def main_wallet(self, wallet_factory: WalletFactory) -> WalletFile:
return wallet_factory.create_wallet()
@pytest.fixture(scope="class")
def other_wallet(self, wallet_factory: WalletFactory) -> WalletFile:
return wallet_factory.create_wallet()
@pytest.fixture(scope="class")
def cli(self, client_shell: Shell) -> FrostfsCli:
return FrostfsCli(client_shell, FROSTFS_CLI_EXEC, WALLET_CONFIG)
@allure.step("Check deposit amount")
def check_amount(self, result: CommandResult) -> None:
amount_str = result.stdout.rstrip()
try:
amount = int(amount_str)
except Exception as ex:
pytest.fail(
f"Amount parse error, should be parsable as int({DEPOSIT_AMOUNT}), but given {amount_str}: {ex}"
)
assert amount == DEPOSIT_AMOUNT
@staticmethod
@allure.step("Write config with API endpoint")
def write_api_config(config_dir: str, endpoint: str, wallet: str) -> str:
with open(WALLET_CONFIG, "r") as file:
wallet_config = yaml.full_load(file)
api_config = {
**wallet_config,
"rpc-endpoint": endpoint,
"wallet": wallet,
}
api_config_file = os.path.join(config_dir, "frostfs-cli-api-config.yaml")
with open(api_config_file, "w") as file:
yaml.dump(api_config, file)
return api_config_file
@allure.title("Test balance request with wallet and address")
def test_balance_wallet_address(self, main_wallet: WalletFile, cli: FrostfsCli):
result = cli.accounting.balance(
wallet=main_wallet.path,
rpc_endpoint=self.cluster.default_rpc_endpoint,
address=main_wallet.get_address(),
)
self.check_amount(result)
@allure.title("Test balance request with wallet only")
def test_balance_wallet(self, main_wallet: WalletFile, cli: FrostfsCli):
result = cli.accounting.balance(
wallet=main_wallet.path, rpc_endpoint=self.cluster.default_rpc_endpoint
)
self.check_amount(result)
@allure.title("Test balance request with wallet and wrong address")
def test_balance_wrong_address(
self, main_wallet: WalletFile, other_wallet: WalletFile, cli: FrostfsCli
):
with pytest.raises(Exception, match="address option must be specified and valid"):
cli.accounting.balance(
wallet=main_wallet.path,
rpc_endpoint=self.cluster.default_rpc_endpoint,
address=other_wallet.get_address(),
)
@allure.title("Test balance request with config file")
def test_balance_api(self, temp_directory: str, main_wallet: WalletFile, client_shell: Shell):
config_file = self.write_api_config(
config_dir=temp_directory,
endpoint=self.cluster.default_rpc_endpoint,
wallet=main_wallet.path,
)
logger.info(f"Config with API endpoint: {config_file}")
cli = FrostfsCli(client_shell, FROSTFS_CLI_EXEC, config_file=config_file)
result = cli.accounting.balance()
self.check_amount(result)

View file

@ -217,7 +217,7 @@ class TestHttpGate(ClusterTestBase):
epochs = (curr_epoch, curr_epoch + 1, curr_epoch + 2, curr_epoch + 100)
for epoch in epochs:
headers = {"X-Attribute-Frostfs-Expiration-Epoch": str(epoch)}
headers = {"X-Attribute-System-Expiration-Epoch": str(epoch)}
with allure.step("Put objects using HTTP with attribute Expiration-Epoch"):
oids.append(

View file

@ -51,6 +51,7 @@ class Test_http_object(ClusterTestBase):
Expected result:
Hashes must be the same.
"""
with allure.step("Create public container"):
cid = create_container(
self.wallet,

View file

@ -24,14 +24,14 @@ from pytest_tests.helpers.http_gate import (
from pytest_tests.steps.cluster_test_base import ClusterTestBase
logger = logging.getLogger("NeoLogger")
EXPIRATION_TIMESTAMP_HEADER = "__FROSRFS__EXPIRATION_TIMESTAMP"
EXPIRATION_EPOCH_HEADER = "__FROSRFS__EXPIRATION_EPOCH"
EXPIRATION_DURATION_HEADER = "__FROSRFS__EXPIRATION_DURATION"
EXPIRATION_EXPIRATION_RFC = "__FROSRFS__EXPIRATION_RFC3339"
FROSTFS_EXPIRATION_EPOCH = "Frostfs-Expiration-Epoch"
FROSTFS_EXPIRATION_DURATION = "Frostfs-Expiration-Duration"
FROSTFS_EXPIRATION_TIMESTAMP = "Frostfs-Expiration-Timestamp"
FROSTFS_EXPIRATION_RFC3339 = "Frostfs-Expiration-RFC3339"
EXPIRATION_TIMESTAMP_HEADER = "__SYSTEM__EXPIRATION_TIMESTAMP"
EXPIRATION_EPOCH_HEADER = "__SYSTEM__EXPIRATION_EPOCH"
EXPIRATION_DURATION_HEADER = "__SYSTEM__EXPIRATION_DURATION"
EXPIRATION_EXPIRATION_RFC = "__SYSTEM__EXPIRATION_RFC3339"
SYSTEM_EXPIRATION_EPOCH = "System-Expiration-Epoch"
SYSTEM_EXPIRATION_DURATION = "System-Expiration-Duration"
SYSTEM_EXPIRATION_TIMESTAMP = "System-Expiration-Timestamp"
SYSTEM_EXPIRATION_RFC3339 = "System-Expiration-RFC3339"
@pytest.mark.sanity
@ -97,7 +97,7 @@ class Test_http_system_header(ClusterTestBase):
f"Validate that only {EXPIRATION_EPOCH_HEADER} exists in header and other headers are abesent"
)
def validation_for_http_header_attr(self, head_info: dict, expected_epoch: int) -> None:
# check that __FROSTFS__EXPIRATION_EPOCH attribute has corresponding epoch
# check that __SYSTEM__EXPIRATION_EPOCH attribute has corresponding epoch
assert self.check_key_value_presented_header(
head_info, {EXPIRATION_EPOCH_HEADER: str(expected_epoch)}
), f'Expected to find {EXPIRATION_EPOCH_HEADER}: {expected_epoch} in: {head_info["header"]["attributes"]}'
@ -143,7 +143,7 @@ class Test_http_system_header(ClusterTestBase):
@allure.title("[negative] attempt to put object with expired epoch")
def test_unable_put_expired_epoch(self, user_container: str, simple_object_size: int):
headers = attr_into_str_header_curl(
{"Frostfs-Expiration-Epoch": str(get_epoch(self.shell, self.cluster) - 1)}
{"System-Expiration-Epoch": str(get_epoch(self.shell, self.cluster) - 1)}
)
file_path = generate_file(simple_object_size)
with allure.step(
@ -154,15 +154,15 @@ class Test_http_system_header(ClusterTestBase):
filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint,
headers=headers,
error_pattern="object has expired",
error_pattern="must be greater than current epoch",
)
@allure.title("[negative] attempt to put object with negative Frostfs-Expiration-Duration")
@allure.title("[negative] attempt to put object with negative System-Expiration-Duration")
def test_unable_put_negative_duration(self, user_container: str, simple_object_size: int):
headers = attr_into_str_header_curl({"Frostfs-Expiration-Duration": "-1h"})
headers = attr_into_str_header_curl({"System-Expiration-Duration": "-1h"})
file_path = generate_file(simple_object_size)
with allure.step(
"Put object using HTTP with attribute Frostfs-Expiration-Duration where duration is negative"
"Put object using HTTP with attribute System-Expiration-Duration where duration is negative"
):
upload_via_http_gate_curl(
cid=user_container,
@ -173,13 +173,13 @@ class Test_http_system_header(ClusterTestBase):
)
@allure.title(
"[negative] attempt to put object with Frostfs-Expiration-Timestamp value in the past"
"[negative] attempt to put object with System-Expiration-Timestamp value in the past"
)
def test_unable_put_expired_timestamp(self, user_container: str, simple_object_size: int):
headers = attr_into_str_header_curl({"Frostfs-Expiration-Timestamp": "1635075727"})
headers = attr_into_str_header_curl({"System-Expiration-Timestamp": "1635075727"})
file_path = generate_file(simple_object_size)
with allure.step(
"Put object using HTTP with attribute Frostfs-Expiration-Timestamp where duration is in the past"
"Put object using HTTP with attribute System-Expiration-Timestamp where duration is in the past"
):
upload_via_http_gate_curl(
cid=user_container,
@ -190,10 +190,10 @@ class Test_http_system_header(ClusterTestBase):
)
@allure.title(
"[negative] Put object using HTTP with attribute Frostfs-Expiration-RFC3339 where duration is in the past"
"[negative] Put object using HTTP with attribute System-Expiration-RFC3339 where duration is in the past"
)
def test_unable_put_expired_rfc(self, user_container: str, simple_object_size: int):
headers = attr_into_str_header_curl({"Frostfs-Expiration-RFC3339": "2021-11-22T09:55:49Z"})
headers = attr_into_str_header_curl({"System-Expiration-RFC3339": "2021-11-22T09:55:49Z"})
file_path = generate_file(simple_object_size)
upload_via_http_gate_curl(
cid=user_container,
@ -218,7 +218,7 @@ class Test_http_system_header(ClusterTestBase):
logger.info(
f"epoch duration={epoch_duration}, current_epoch= {get_epoch(self.shell, self.cluster)} expected_epoch {expected_epoch}"
)
attributes = {FROSTFS_EXPIRATION_EPOCH: expected_epoch, FROSTFS_EXPIRATION_DURATION: "1m"}
attributes = {SYSTEM_EXPIRATION_EPOCH: expected_epoch, SYSTEM_EXPIRATION_DURATION: "1m"}
file_path = generate_file(object_size)
with allure.step(
f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr"
@ -266,10 +266,10 @@ class Test_http_system_header(ClusterTestBase):
f"epoch duration={epoch_duration}, current_epoch= {get_epoch(self.shell, self.cluster)} expected_epoch {expected_epoch}"
)
attributes = {
FROSTFS_EXPIRATION_DURATION: self.epoch_count_into_mins(
SYSTEM_EXPIRATION_DURATION: self.epoch_count_into_mins(
epoch_duration=epoch_duration, epoch=2
),
FROSTFS_EXPIRATION_TIMESTAMP: self.epoch_count_into_timestamp(
SYSTEM_EXPIRATION_TIMESTAMP: self.epoch_count_into_timestamp(
epoch_duration=epoch_duration, epoch=1
),
}
@ -320,10 +320,10 @@ class Test_http_system_header(ClusterTestBase):
f"epoch duration={epoch_duration}, current_epoch= {get_epoch(self.shell, self.cluster)} expected_epoch {expected_epoch}"
)
attributes = {
FROSTFS_EXPIRATION_TIMESTAMP: self.epoch_count_into_timestamp(
SYSTEM_EXPIRATION_TIMESTAMP: self.epoch_count_into_timestamp(
epoch_duration=epoch_duration, epoch=2
),
FROSTFS_EXPIRATION_RFC3339: self.epoch_count_into_timestamp(
SYSTEM_EXPIRATION_RFC3339: self.epoch_count_into_timestamp(
epoch_duration=epoch_duration, epoch=1, rfc3339=True
),
}
@ -372,7 +372,7 @@ class Test_http_system_header(ClusterTestBase):
f"epoch duration={epoch_duration}, current_epoch= {get_epoch(self.shell, self.cluster)} expected_epoch {expected_epoch}"
)
attributes = {
FROSTFS_EXPIRATION_RFC3339: self.epoch_count_into_timestamp(
SYSTEM_EXPIRATION_RFC3339: self.epoch_count_into_timestamp(
epoch_duration=epoch_duration, epoch=2, rfc3339=True
)
}

View file

@ -6,7 +6,6 @@ from random import choices, sample
import allure
import pytest
from frostfs_testlib.utils import wallet_utils
from pytest_tests.helpers.aws_cli_client import AwsCliClient
from pytest_tests.helpers.file_helper import (
@ -662,37 +661,10 @@ class TestS3GateObject(TestS3GateBase):
{"Key": tag_key_3, "Value": str(tag_value_3)}
], "Tags must be the same"
@pytest.fixture
def prepare_two_wallets(self, default_wallet, client_shell):
self.main_wallet = default_wallet
self.main_public_key = wallet_utils.get_wallet_public_key(self.main_wallet, WALLET_PASS)
self.other_wallet = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
wallet_utils.init_wallet(self.other_wallet, WALLET_PASS)
self.other_public_key = wallet_utils.get_wallet_public_key(self.other_wallet, WALLET_PASS)
if not FREE_STORAGE:
main_chain = self.cluster.main_chain_nodes[0]
deposit = 30
transfer_gas(
shell=client_shell,
amount=deposit + 1,
main_chain=main_chain,
wallet_to_path=self.other_wallet,
wallet_to_password=WALLET_PASS,
)
deposit_gas(
shell=client_shell,
main_chain=main_chain,
amount=deposit,
wallet_from_path=self.other_wallet,
wallet_from_password=WALLET_PASS,
)
@allure.title("Test S3: put object with ACL")
@pytest.mark.parametrize("bucket_versioning", ["ENABLED", "SUSPENDED"])
def test_s3_put_object_acl(
self,
prepare_two_wallets,
bucket_versioning,
bucket,
complex_object_size,

View file

@ -187,7 +187,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session with range operations")
@pytest.mark.static_session
@pytest.mark.parametrize(
"method_under_test,verb",
[(get_range, ObjectVerb.RANGE), (get_range_hash, ObjectVerb.RANGEHASH)],
@ -227,9 +226,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session with search operation")
@pytest.mark.static_session
@pytest.mark.xfail
# (see https://github.com/nspcc-dev/neofs-node/issues/2030)
def test_static_session_search(
self,
user_wallet: WalletFile,
@ -252,10 +248,9 @@ class TestObjectStaticSession(ClusterTestBase):
session=static_sessions[ObjectVerb.SEARCH],
root=True,
)
assert expected_object_ids == actual_object_ids
assert sorted(expected_object_ids) == sorted(actual_object_ids)
@allure.title("Validate static session with object id not in session")
@pytest.mark.static_session
def test_static_session_unrelated_object(
self,
user_wallet: WalletFile,
@ -280,7 +275,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session with user id not in session")
@pytest.mark.static_session
def test_static_session_head_unrelated_user(
self,
stranger_wallet: WalletFile,
@ -307,7 +301,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session with wrong verb in session")
@pytest.mark.static_session
def test_static_session_head_wrong_verb(
self,
user_wallet: WalletFile,
@ -334,7 +327,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session with container id not in session")
@pytest.mark.static_session
def test_static_session_unrelated_container(
self,
user_wallet: WalletFile,
@ -362,7 +354,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session which signed by another wallet")
@pytest.mark.static_session
def test_static_session_signed_by_other(
self,
owner_wallet: WalletFile,
@ -401,7 +392,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session which signed for another container")
@pytest.mark.static_session
def test_static_session_signed_for_other_container(
self,
owner_wallet: WalletFile,
@ -440,7 +430,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session which wasn't signed")
@pytest.mark.static_session
def test_static_session_without_sign(
self,
owner_wallet: WalletFile,
@ -477,7 +466,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session which expires at next epoch")
@pytest.mark.static_session
def test_static_session_expiration_at_next(
self,
owner_wallet: WalletFile,
@ -499,40 +487,56 @@ class TestObjectStaticSession(ClusterTestBase):
object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 1, epoch, epoch)
token_expire_at_next_epoch = get_object_signed_token(
owner_wallet,
user_wallet,
container,
storage_objects,
ObjectVerb.HEAD,
self.shell,
temp_directory,
expiration,
)
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_expire_at_next_epoch,
)
self.tick_epoch()
with pytest.raises(Exception, match=EXPIRED_SESSION_TOKEN):
head_object(
user_wallet.path,
with allure.step("Create session token"):
token_expire_at_next_epoch = get_object_signed_token(
owner_wallet,
user_wallet,
container,
object_id,
storage_objects,
ObjectVerb.HEAD,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_expire_at_next_epoch,
temp_directory,
expiration,
)
with allure.step("Object should be available with session token after token creation"):
with expect_not_raises():
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_expire_at_next_epoch,
)
with allure.step(
"Object should be available at last epoch before session token expiration"
):
self.tick_epoch()
with expect_not_raises():
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_expire_at_next_epoch,
)
with allure.step("Object should NOT be available after session token expiration epoch"):
self.tick_epoch()
with pytest.raises(Exception, match=EXPIRED_SESSION_TOKEN):
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_expire_at_next_epoch,
)
@allure.title("Validate static session which is valid starting from next epoch")
@pytest.mark.static_session
def test_static_session_start_at_next(
self,
owner_wallet: WalletFile,
@ -554,50 +558,70 @@ class TestObjectStaticSession(ClusterTestBase):
object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 2, epoch + 1, epoch)
token_start_at_next_epoch = get_object_signed_token(
owner_wallet,
user_wallet,
container,
storage_objects,
ObjectVerb.HEAD,
self.shell,
temp_directory,
expiration,
)
with pytest.raises(Exception, match=MALFORMED_REQUEST):
head_object(
user_wallet.path,
with allure.step("Create session token"):
token_start_at_next_epoch = get_object_signed_token(
owner_wallet,
user_wallet,
container,
object_id,
storage_objects,
ObjectVerb.HEAD,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
temp_directory,
expiration,
)
self.tick_epoch()
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
)
with allure.step("Object should NOT be available with session token after token creation"):
with pytest.raises(Exception, match=MALFORMED_REQUEST):
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
)
self.tick_epoch()
with pytest.raises(Exception, match=EXPIRED_SESSION_TOKEN):
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
)
with allure.step(
"Object should be available with session token starting from token nbf epoch"
):
self.tick_epoch()
with expect_not_raises():
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
)
with allure.step(
"Object should be available at last epoch before session token expiration"
):
self.tick_epoch()
with expect_not_raises():
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
)
with allure.step("Object should NOT be available after session token expiration epoch"):
self.tick_epoch()
with pytest.raises(Exception, match=EXPIRED_SESSION_TOKEN):
head_object(
user_wallet.path,
container,
object_id,
self.shell,
self.cluster.default_rpc_endpoint,
session=token_start_at_next_epoch,
)
@allure.title("Validate static session which is already expired")
@pytest.mark.static_session
def test_static_session_already_expired(
self,
owner_wallet: WalletFile,
@ -691,7 +715,6 @@ class TestObjectStaticSession(ClusterTestBase):
)
@allure.title("Validate static session which is issued in future epoch")
@pytest.mark.static_session
def test_static_session_invalid_issued_epoch(
self,
owner_wallet: WalletFile,

View file

@ -0,0 +1,47 @@
import os
import shutil
from datetime import datetime
import allure
import pytest
from pytest_tests.steps.cluster_test_base import ClusterTestBase
class TestLogs(ClusterTestBase):
@pytest.mark.logs_after_session
def test_logs_after_session(self, temp_directory: str, session_start_time: datetime):
"""
This test automatically added to any test run to check logs from cluster for critical errors.
"""
end_time = datetime.utcnow()
logs_dir = os.path.join(temp_directory, "logs")
os.makedirs(logs_dir)
issues_regex = r"\Wpanic\W|\Woom\W|\Wtoo many open files\W"
hosts_with_problems = []
for host in self.cluster.hosts:
with allure.step(f"Check logs on {host.config.address}"):
if host.is_message_in_logs(issues_regex, session_start_time, end_time):
hosts_with_problems.append(host.config.address)
host.dump_logs(
logs_dir,
since=session_start_time,
until=end_time,
filter_regex=issues_regex,
)
if hosts_with_problems:
self._attach_logs(logs_dir)
assert (
not hosts_with_problems
), f"The following hosts contains contain critical errors in system logs: {', '.join(hosts_with_problems)}"
def _attach_logs(self, logs_dir: str) -> None:
# Zip all files and attach to Allure because it is more convenient to download a single
# zip with all logs rather than mess with individual logs files per service or node
logs_zip_file_path = shutil.make_archive(logs_dir, "zip", logs_dir)
allure.attach.file(logs_zip_file_path, name="logs.zip", extension="zip")