Compare commits

...

6 commits

Author SHA1 Message Date
453286d459 [#332] Remove files for heavy failover test
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-11-27 13:50:45 +03:00
b999d7cf9b [#331] Remove basic-acl from failovers, http and shards tests
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-11-26 19:42:53 +03:00
7a1d2c9e2d [#317] Stop using obsolete .github directory
This commit is a part of multi-repo cleanup effort:
TrueCloudLab/frostfs-infra#136

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-11-26 16:39:54 +00:00
bb796a61d3 [#328] Remove basic-acl from metrics tests
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-11-22 15:09:09 +03:00
abd810cef6 [#327] Remove basic-acl from APE tests
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-11-20 17:11:22 +03:00
d8a3f51787 [#325] Remove basic-acl from policy tests
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-11-18 15:17:52 +03:00
38 changed files with 2537 additions and 2630 deletions

View file

@ -0,0 +1,26 @@
# yamllint disable rule:truthy
name: DCO check
on:
pull_request:
jobs:
dco:
name: DCO
runs-on: docker
container:
image: node:22-bookworm
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.22'
- name: Run commit format checker
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
with:
from: 'origin/${{ github.event.pull_request.base.ref }}'

View file

@ -1,22 +0,0 @@
name: DCO check
on:
pull_request:
branches:
- master
- develop
jobs:
commits_check_job:
runs-on: ubuntu-latest
name: Commits Check
steps:
- name: Get PR Commits
id: 'get-pr-commits'
uses: tim-actions/get-pr-commits@master
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: DCO Check
uses: tim-actions/dco@master
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}

View file

@ -37,11 +37,12 @@ markers =
session_token: tests for operations with session token session_token: tests for operations with session token
static_session: tests for operations with static session token static_session: tests for operations with static session token
bearer: tests for bearer tokens bearer: tests for bearer tokens
acl: All tests for ACL ape: tests for APE
acl_basic: tests for basic ACL ape_allow: tests for APE allow rules
acl_bearer: tests for ACL with bearer ape_deny: tests for APE deny rules
acl_extended: tests for extended ACL ape_container: tests for APE on container operations
acl_filters: tests for extended ACL with filters and headers ape_object: tests for APE on object operations
ape_namespace: tests for APE on namespace scope
storage_group: tests for storage groups storage_group: tests for storage groups
failover: tests for system recovery after a failure failover: tests for system recovery after a failure
failover_panic: tests for system recovery after panic reboot of a node failover_panic: tests for system recovery after panic reboot of a node

View file

View file

@ -9,23 +9,29 @@ from frostfs_testlib.steps.cli.container import create_container, search_nodes_w
from frostfs_testlib.storage.cluster import Cluster from frostfs_testlib.storage.cluster import Cluster
from frostfs_testlib.storage.dataclasses import ape from frostfs_testlib.storage.dataclasses import ape
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.parallel import parallel
from frostfs_testlib.utils import datetime_utils from frostfs_testlib.utils import datetime_utils
from .container_request import ContainerRequest from .container_request import ContainerRequest, MultipleContainersRequest
def create_container_with_ape( def create_container_with_ape(
frostfs_cli: FrostfsCli, wallet: WalletInfo, shell: Shell, cluster: Cluster, endpoint: str, container_request: ContainerRequest container_request: ContainerRequest,
): frostfs_cli: FrostfsCli,
wallet: WalletInfo,
shell: Shell,
cluster: Cluster,
endpoint: str,
) -> str:
with reporter.step("Create container"): with reporter.step("Create container"):
cid = _create_container_by_spec(wallet, shell, cluster, endpoint, container_request) cid = _create_container_by_spec(container_request, wallet, shell, cluster, endpoint)
with reporter.step("Apply APE rules for container"): if container_request.ape_rules:
if container_request.ape_rules: with reporter.step("Apply APE rules for container"):
_apply_ape_rules(frostfs_cli, endpoint, cid, container_request.ape_rules) _apply_ape_rules(cid, frostfs_cli, endpoint, container_request.ape_rules)
with reporter.step("Wait for one block"): with reporter.step("Wait for one block"):
time.sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME)) time.sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME))
with reporter.step("Search nodes holding the container"): with reporter.step("Search nodes holding the container"):
container_holder_nodes = search_nodes_with_container(wallet, cid, shell, cluster.default_rpc_endpoint, cluster) container_holder_nodes = search_nodes_with_container(wallet, cid, shell, cluster.default_rpc_endpoint, cluster)
@ -36,14 +42,31 @@ def create_container_with_ape(
return cid return cid
@reporter.step("Create multiple containers with APE")
def create_containers_with_ape(
frostfs_cli: FrostfsCli,
wallet: WalletInfo,
shell: Shell,
cluster: Cluster,
endpoint: str,
multiple_containers_request: MultipleContainersRequest,
) -> list[str]:
cids_futures = parallel(create_container_with_ape, multiple_containers_request, frostfs_cli, wallet, shell, cluster, endpoint)
return [future.result() for future in cids_futures]
@reporter.step("Create container by spec {container_request}") @reporter.step("Create container by spec {container_request}")
def _create_container_by_spec( def _create_container_by_spec(
wallet: WalletInfo, shell: Shell, cluster: Cluster, endpoint: str, container_request: ContainerRequest container_request: ContainerRequest,
wallet: WalletInfo,
shell: Shell,
cluster: Cluster,
endpoint: str,
) -> str: ) -> str:
return create_container(wallet, shell, endpoint, container_request.parsed_rule(cluster), wait_for_creation=False) return create_container(wallet, shell, endpoint, container_request.parsed_rule(cluster), wait_for_creation=False)
def _apply_ape_rules(frostfs_cli: FrostfsCli, endpoint: str, cid: str, ape_rules: list[ape.Rule]): def _apply_ape_rules(cid: str, frostfs_cli: FrostfsCli, endpoint: str, ape_rules: list[ape.Rule]):
for ape_rule in ape_rules: for ape_rule in ape_rules:
rule_str = ape_rule.as_string() rule_str = ape_rule.as_string()
with reporter.step(f"Apply APE rule '{rule_str}' for container {cid}"): with reporter.step(f"Apply APE rule '{rule_str}' for container {cid}"):

View file

@ -1,5 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from functools import partial
import pytest
from frostfs_testlib.steps.cli.container import DEFAULT_PLACEMENT_RULE from frostfs_testlib.steps.cli.container import DEFAULT_PLACEMENT_RULE
from frostfs_testlib.storage.cluster import Cluster from frostfs_testlib.storage.cluster import Cluster
from frostfs_testlib.storage.dataclasses import ape from frostfs_testlib.storage.dataclasses import ape
@ -53,6 +55,50 @@ class ContainerRequest:
return f"({', '.join(spec_info)})" return f"({', '.join(spec_info)})"
class MultipleContainersRequest(list[ContainerRequest]):
def __init__(self, iterable=None):
"""Override initializer which can accept iterable"""
super(MultipleContainersRequest, self).__init__()
if iterable:
self.extend(iterable)
self.__set_name()
def __set_name(self):
self.__name__ = ", ".join([s.__name__ for s in self])
PUBLIC_WITH_POLICY = partial(ContainerRequest, ape_rules=APE_EVERYONE_ALLOW_ALL, short_name="Custom_policy_with_allow_all_ape_rule")
# REPS
REP_1_1_1 = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
REP_2_1_2 = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
REP_2_1_4 = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
REP_2_2_2 = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
REP_2_2_4 = "REP 2 IN X CBF 2 SELECT 4 FROM * AS X"
#
# Public means it has APE rule which allows everything for everyone
REP_1_1_1_PUBLIC = PUBLIC_WITH_POLICY(REP_1_1_1, short_name="REP 1 CBF 1 SELECT 1 (public)")
REP_2_1_2_PUBLIC = PUBLIC_WITH_POLICY(REP_2_1_2, short_name="REP 2 CBF 1 SELECT 2 (public)")
REP_2_1_4_PUBLIC = PUBLIC_WITH_POLICY(REP_2_1_4, short_name="REP 2 CBF 1 SELECT 4 (public)")
REP_2_2_2_PUBLIC = PUBLIC_WITH_POLICY(REP_2_2_2, short_name="REP 2 CBF 2 SELECT 2 (public)")
REP_2_2_4_PUBLIC = PUBLIC_WITH_POLICY(REP_2_2_4, short_name="REP 2 CBF 2 SELECT 4 (public)")
#
EVERYONE_ALLOW_ALL = ContainerRequest(policy=DEFAULT_PLACEMENT_RULE, ape_rules=APE_EVERYONE_ALLOW_ALL, short_name="Everyone_Allow_All") EVERYONE_ALLOW_ALL = ContainerRequest(policy=DEFAULT_PLACEMENT_RULE, ape_rules=APE_EVERYONE_ALLOW_ALL, short_name="Everyone_Allow_All")
OWNER_ALLOW_ALL = ContainerRequest(policy=DEFAULT_PLACEMENT_RULE, ape_rules=APE_OWNER_ALLOW_ALL, short_name="Owner_Allow_All") OWNER_ALLOW_ALL = ContainerRequest(policy=DEFAULT_PLACEMENT_RULE, ape_rules=APE_OWNER_ALLOW_ALL, short_name="Owner_Allow_All")
PRIVATE = ContainerRequest(policy=DEFAULT_PLACEMENT_RULE, ape_rules=[], short_name="Private_No_APE") PRIVATE = ContainerRequest(policy=DEFAULT_PLACEMENT_RULE, ape_rules=[], short_name="Private_No_APE")
def requires_container(container_request: None | ContainerRequest | list[ContainerRequest] = None) -> pytest.MarkDecorator:
if container_request is None:
container_request = EVERYONE_ALLOW_ALL
if not isinstance(container_request, list):
container_request = [container_request]
return pytest.mark.parametrize("container_request", container_request, indirect=True)

View file

@ -0,0 +1,27 @@
from frostfs_testlib.shell.interfaces import Shell
from frostfs_testlib.steps.cli.container import get_container
from frostfs_testlib.storage.dataclasses.storage_object_info import NodeNetmapInfo
from ..helpers.utility import placement_policy_from_container
def validate_object_policy(wallet: str, shell: Shell, placement_rule: str, cid: str, endpoint: str):
got_policy = placement_policy_from_container(get_container(wallet, cid, shell, endpoint, False))
assert got_policy.replace("'", "") == placement_rule.replace(
"'", ""
), f"Expected \n{placement_rule} and got policy \n{got_policy} are the same"
def get_netmap_param(netmap_info: list[NodeNetmapInfo]) -> dict:
dict_external = dict()
for node in netmap_info:
external_adress = node.external_address[0].split("/")[2]
dict_external[external_adress] = {
"country": node.country,
"country_code": node.country_code,
"Price": node.price,
"continent": node.continent,
"un_locode": node.un_locode,
"location": node.location,
}
return dict_external

View file

View file

@ -0,0 +1,52 @@
import pytest
from frostfs_testlib import reporter
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.resources.cli import FROSTFS_CLI_EXEC
from frostfs_testlib.shell.interfaces import Shell
from frostfs_testlib.steps.cli.object import put_object
from frostfs_testlib.storage.cluster import Cluster
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.utils.file_utils import generate_file
@pytest.fixture(scope="session")
def frostfs_cli_on_first_node(cluster: Cluster) -> FrostfsCli:
node = cluster.cluster_nodes[0]
shell = node.host.get_shell()
return FrostfsCli(shell, FROSTFS_CLI_EXEC, node.storage_node.get_remote_wallet_config_path())
@pytest.fixture
def object_id(
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
cluster: Cluster,
client_shell: Shell,
container: str,
) -> str:
test_file = generate_file(simple_object_size.value)
with reporter.step("Allow PutObject on first node via local override"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowPutObject",
rule=f"allow Object.Put *",
)
with reporter.step("Put objects in container on the first node"):
object_id = put_object(default_wallet, test_file, container, client_shell, cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Remove PutObject local override from first node"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowPutObject",
)
return object_id

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,212 @@
from time import sleep
import allure
import pytest
from frostfs_testlib import reporter
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.resources.cli import FROSTFS_CLI_EXEC
from frostfs_testlib.resources.common import MORPH_BLOCK_TIME
from frostfs_testlib.resources.error_patterns import RULE_ACCESS_DENIED_CONTAINER
from frostfs_testlib.shell.interfaces import Shell
from frostfs_testlib.storage.cluster import Cluster, ClusterNode
from frostfs_testlib.storage.dataclasses.ape import Operations
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.parallel import parallel
from frostfs_testlib.testing.test_control import expect_not_raises
from frostfs_testlib.utils import datetime_utils
from ...helpers.container_request import APE_EVERYONE_ALLOW_ALL, ContainerRequest, MultipleContainersRequest
REP2 = ContainerRequest("REP 2", ape_rules=APE_EVERYONE_ALLOW_ALL, short_name="REP2_allow_all_ape")
def remove_local_overrides_on_node(node: ClusterNode):
target = "Chain ID"
shell: Shell = node.host.get_shell()
remote_config: str = node.storage_node.get_remote_wallet_config_path()
cli = FrostfsCli(shell=shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=remote_config)
with reporter.step(f"Check local overrides on {node.storage_node.id} node"):
rules = cli.control.list_rules(
endpoint=node.storage_node.get_control_endpoint(), target_name="root", target_type="namespace"
).stdout
if target in rules:
with reporter.step("Delete rules"):
chain_ids = [i.split(" ")[2].strip() for i in rules.split("\n") if "Chain ID" in i]
for chain_id in chain_ids:
cli.control.remove_rule(
endpoint=node.storage_node.get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id=chain_id,
)
with reporter.step("Wait for one block"):
sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME))
@pytest.fixture(scope="session")
def remove_local_ape_overrides(cluster: Cluster) -> None:
yield
with reporter.step("Check local overrides on nodes."):
parallel(remove_local_overrides_on_node, cluster.cluster_nodes)
@pytest.mark.ape
@pytest.mark.ape_local
@pytest.mark.ape_container
@pytest.mark.ape_namespace
class TestApeLocalOverrideContainer(ClusterTestBase):
@allure.title("LocalOverride: Deny to GetContainer in root tenant")
def test_local_override_deny_to_get_container_root(
self,
frostfs_cli_on_first_node: FrostfsCli,
frostfs_cli: FrostfsCli,
container: str,
remove_local_ape_overrides: None,
):
with reporter.step("Create a namespace rule for the first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerGet",
rule="deny Container.Get *",
)
with reporter.step("Check get the container property on the first node, expected denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_CONTAINER.format(operation=Operations.GET_CONTAINER)):
frostfs_cli.container.get(self.cluster.storage_nodes[0].get_rpc_endpoint(), container)
with reporter.step("Check get the container property on the second node, expected allow"):
with expect_not_raises():
frostfs_cli.container.get(self.cluster.storage_nodes[1].get_rpc_endpoint(), container)
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerGet",
)
with reporter.step("Check get the container property on the first node, expected allow"):
with expect_not_raises():
frostfs_cli.container.get(self.cluster.storage_nodes[0].get_rpc_endpoint(), container)
@allure.title("LocalOverride: Deny to PutContainer in root tenant")
def test_local_override_deny_to_put_container_root(
self,
frostfs_cli_on_first_node: FrostfsCli,
frostfs_cli: FrostfsCli,
remove_local_ape_overrides: None,
):
with reporter.step("Create a namespace rule for the first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerPut",
rule="deny Container.Put *",
)
with reporter.step("Check create container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_CONTAINER.format(operation=Operations.PUT_CONTAINER)):
frostfs_cli.container.create(
rpc_endpoint=self.cluster.storage_nodes[0].get_rpc_endpoint(),
policy="REP 1",
)
with reporter.step("Check create a container on the second node, expected allow"):
with expect_not_raises():
frostfs_cli.container.create(
rpc_endpoint=self.cluster.storage_nodes[1].get_rpc_endpoint(),
policy="REP 1",
)
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerPut",
)
with reporter.step("Check create a container on the first node, expected allow"):
with expect_not_raises():
frostfs_cli.container.create(
rpc_endpoint=self.cluster.storage_nodes[0].get_rpc_endpoint(),
policy="REP 1",
)
@allure.title("LocalOverride: Deny to ListContainer in root tenant")
def test_local_override_deny_to_list_container_root(
self,
frostfs_cli_on_first_node: FrostfsCli,
frostfs_cli: FrostfsCli,
remove_local_ape_overrides: None,
):
with reporter.step("Create a namespace rule for the first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerList",
rule="deny Container.List *",
)
with reporter.step("Check list the container properties on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_CONTAINER.format(operation=Operations.LIST_CONTAINER)):
frostfs_cli.container.list(rpc_endpoint=self.cluster.storage_nodes[0].get_rpc_endpoint(), ttl=1)
with reporter.step("Check list the container properties on the second node, expected allow"):
with expect_not_raises():
frostfs_cli.container.list(rpc_endpoint=self.cluster.storage_nodes[1].get_rpc_endpoint(), ttl=1)
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerList",
)
with reporter.step("Check display a list of containers on the first node, expected allow"):
with expect_not_raises():
frostfs_cli.container.list(rpc_endpoint=self.cluster.storage_nodes[0].get_rpc_endpoint(), ttl=1)
@allure.title("LocalOverride: Deny to DeleteContainer in root tenant")
@pytest.mark.parametrize("multiple_containers_request", [MultipleContainersRequest([REP2, REP2])], indirect=True)
def test_local_override_deny_to_delete_container_root(
self,
frostfs_cli_on_first_node: FrostfsCli,
frostfs_cli: FrostfsCli,
remove_local_ape_overrides: None,
containers: list[str],
):
with reporter.step("Create a namespace rule for the first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerDelete",
rule="deny Container.Delete *",
)
with reporter.step("Check delete first container from the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_CONTAINER.format(operation=Operations.DELETE_CONTAINER)):
frostfs_cli.container.delete(self.cluster.storage_nodes[0].get_rpc_endpoint(), containers[0], ttl=1)
with reporter.step("Check delete a second container from the second node, expected allow"):
with expect_not_raises():
frostfs_cli.container.delete(self.cluster.storage_nodes[1].get_rpc_endpoint(), containers[1], ttl=1)
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="namespace",
target_name="root",
chain_id="denyContainerDelete",
)
with reporter.step("Check delete first container from the first node, expected allow"):
with expect_not_raises():
frostfs_cli.container.delete(self.cluster.storage_nodes[0].get_rpc_endpoint(), containers[0], ttl=1)

View file

@ -0,0 +1,254 @@
import allure
import pytest
from frostfs_testlib import reporter
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.resources.error_patterns import NO_RULE_FOUND_OBJECT
from frostfs_testlib.steps.cli.object import delete_object, get_object, get_range, get_range_hash, head_object, put_object, search_object
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.test_control import expect_not_raises
from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_request import ContainerRequest
REP1_MSK = ContainerRequest("REP 1 IN MOW CBF 1 SELECT 1 FROM MSK AS MOW FILTER SubDivCode EQ MOW AS MSK", short_name="REP1_MSK_no_ape")
@pytest.mark.ape
@pytest.mark.ape_local
@pytest.mark.ape_object
@pytest.mark.ape_allow
@pytest.mark.parametrize("container_request", [REP1_MSK], indirect=True)
class TestApeLocalOverrideAllow(ClusterTestBase):
@allure.title("LocalOverride: Allow to GetObject in root tenant")
def test_local_override_allow_to_get_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
object_id: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowGetObject",
rule=f"allow Object.Get *",
)
with reporter.step("Check get object in container on the first node, expected allow"):
with expect_not_raises():
get_object(default_wallet, container, object_id, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get object in container on the second node, epxected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
get_object(default_wallet, container, object_id, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowGetObject",
)
@allure.title("LocalOverride: Allow to PutObject in root tenant")
def test_local_override_allow_to_put_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowPutObject",
rule=f"allow Object.Put *",
)
with reporter.step("Check put object in container on the first node, expected allow"):
with expect_not_raises():
put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get object in container on the second node, epxected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowPutObject",
)
@allure.title("LocalOverride: Allow to HeadObject in root tenant")
def test_local_override_allow_to_head_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
object_id: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowHeadObject",
rule=f"allow Object.Head *",
)
with reporter.step("Check head object in container on the first node, expected allow"):
with expect_not_raises():
head_object(default_wallet, container, object_id, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check head object in container on the second node, expected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
head_object(default_wallet, container, object_id, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowHeadObject",
)
@allure.title("LocalOverride: Allow to SearchObject in root tenant")
def test_local_override_allow_to_search_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowSearchObject",
rule=f"allow Object.Search *",
)
with reporter.step("Check search object in container on the first node, expected allow"):
with expect_not_raises():
search_object(default_wallet, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check search object from container on the second node, expected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
search_object(default_wallet, container, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowSearchObject",
)
@allure.title("LocalOverride: Allow to RangeObject in root tenant")
def test_local_override_allow_to_range_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
object_id: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowRangeObject",
rule=f"allow Object.Range *",
)
with reporter.step("Check get range object in container on the first node, expected allow"):
with expect_not_raises():
get_range(default_wallet, container, object_id, "0:10", self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check range object in container on the second node. expected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
get_range(default_wallet, container, object_id, "0:10", self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowRangeObject",
)
@allure.title("LocalOverride: Allow to HashObject in root tenant")
def test_local_override_allow_to_hash_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
object_id: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowHashObject",
rule=f"allow Object.Hash *",
)
with reporter.step("Check get range hash object in container on the first node, expected allow"):
with expect_not_raises():
get_range_hash(default_wallet, container, object_id, "0:10", self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get range hash object in container on the second node, expected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
get_range_hash(default_wallet, container, object_id, "0:10", self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowHashObject",
)
@allure.title("LocalOverride: Allow to DeleteObject in root tenant")
def test_local_override_allow_to_delete_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
object_id: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowDeleteObject",
rule=f"allow Object.Head Object.Delete *",
)
with reporter.step("Check delete object from container on the second node, expected access denied error"):
with pytest.raises(RuntimeError, match=NO_RULE_FOUND_OBJECT):
delete_object(default_wallet, container, object_id, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Check delete object in container on the first node, expected allow"):
with expect_not_raises():
delete_object(default_wallet, container, object_id, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="allowDeleteObject",
)

View file

@ -0,0 +1,333 @@
import allure
import pytest
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.reporter import get_reporter
from frostfs_testlib.resources.error_patterns import OBJECT_ACCESS_DENIED, RULE_ACCESS_DENIED_OBJECT
from frostfs_testlib.steps.cli.object import delete_object, get_object, get_range, get_range_hash, head_object, put_object, search_object
from frostfs_testlib.storage.dataclasses.ape import Operations
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.test_control import expect_not_raises
from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_request import APE_EVERYONE_ALLOW_ALL, ContainerRequest
reporter = get_reporter()
REP2 = ContainerRequest("REP 2", ape_rules=APE_EVERYONE_ALLOW_ALL, short_name="REP2_allow_all_ape")
@pytest.mark.ape
@pytest.mark.ape_local
@pytest.mark.ape_object
@pytest.mark.ape_deny
class TestApeLocalOverrideDeny(ClusterTestBase):
@allure.title("LocalOverride: Deny to GetObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_get_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyGetObject",
rule=f"deny Object.Get /{container}/*",
)
with reporter.step("Put object in container on the first node"):
oid = put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_OBJECT):
get_object(default_wallet, container, oid, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get object from container on the second node, expected allow"):
with expect_not_raises():
get_object(default_wallet, container, oid, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyGetObject",
)
with reporter.step("Check get object in container on the first node, expected allow"):
with expect_not_raises():
get_object(default_wallet, container, oid, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
@allure.title("LocalOverride: Deny to PutObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_put_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyPutObject",
rule=f"deny Object.Put /{container}/*",
)
with reporter.step("Check put object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=OBJECT_ACCESS_DENIED):
put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check put object from container on the second node, expected allow"):
with expect_not_raises():
put_object(
default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint(), copies_number=3
)
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyPutObject",
)
with reporter.step("Check get object in container on the first node, expected allow"):
with expect_not_raises():
put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
@allure.title("LocalOverride: Deny to HeadObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_head_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyHeadObject",
rule=f"deny Object.Head /{container}/*",
)
with reporter.step("Put object in container on the first node"):
oid = put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check head object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_OBJECT):
head_object(default_wallet, container, oid, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check head object from container on the second node, expected allow"):
with expect_not_raises():
head_object(default_wallet, container, oid, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyHeadObject",
)
with reporter.step("Check head object in container on the first node, expected allow"):
with expect_not_raises():
head_object(default_wallet, container, oid, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
@allure.title("LocalOverride: Deny to SearchObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_search_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
container: str,
):
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denySearchObject",
rule=f"deny Object.Search /{container}/*",
)
with reporter.step("Check search object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_OBJECT.format(operation=Operations.SEARCH_OBJECT)):
search_object(default_wallet, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check search object from container on the second node, expected allow"):
with expect_not_raises():
search_object(default_wallet, container, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denySearchObject",
)
with reporter.step("Check search object in container on the first node, expected allow"):
with expect_not_raises():
search_object(default_wallet, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
@allure.title("LocalOverride: Deny to RangeObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_range_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyRangeObject",
rule=f"deny Object.Range /{container}/*",
)
with reporter.step("Put object in container on the first node"):
oid = put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check range object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_OBJECT.format(operation=Operations.RANGE_OBJECT)):
get_range(default_wallet, container, oid, "0:10", self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get range object from container on the second node, expected allow"):
with expect_not_raises():
get_range(default_wallet, container, oid, "0:10", self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyRangeObject",
)
with reporter.step("Check get range object in container on the first node, expected allow"):
with expect_not_raises():
get_range(default_wallet, container, oid, "0:10", self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
@allure.title("LocalOverride: Deny to HashObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_hash_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyHashObject",
rule=f"deny Object.Hash /{container}/*",
)
with reporter.step("Put object in container on the first node"):
oid = put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get range hash object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_OBJECT.format(operation=Operations.HASH_OBJECT)):
get_range_hash(default_wallet, container, oid, "0:10", self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check get range hash object from container on the second node, expected allow"):
with expect_not_raises():
get_range_hash(default_wallet, container, oid, "0:10", self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyHashObject",
)
with reporter.step("Check get range hash object in container on the first node, expected allow"):
with expect_not_raises():
get_range_hash(default_wallet, container, oid, "0:10", self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
@allure.title("LocalOverride: Deny to DeleteObject in root tenant")
@pytest.mark.parametrize("container_request", [REP2], indirect=True)
def test_local_override_deny_to_delete_object_root(
self,
default_wallet: WalletInfo,
frostfs_cli_on_first_node: FrostfsCli,
simple_object_size: ObjectSize,
container: str,
):
test_file = generate_file(simple_object_size.value)
with reporter.step("Create local override on first node"):
frostfs_cli_on_first_node.control.add_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyDeleteObject",
rule=f"deny Object.Delete /{container}/*",
)
with reporter.step("Put objects in container on the first node"):
oid_1 = put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
oid_2 = put_object(default_wallet, test_file, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Search object in container on the first node"):
search_object_in_container_1 = search_object(
default_wallet, container, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint()
)
assert oid_1 in search_object_in_container_1, f"Object {oid_1} was not found"
assert oid_2 in search_object_in_container_1, f"Object {oid_2} was not found"
with reporter.step("Search object from container on the second node"):
search_object_in_container_2 = search_object(
default_wallet, container, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint()
)
assert oid_1 in search_object_in_container_2, f"Object {oid_1} was not found"
assert oid_2 in search_object_in_container_2, f"Object {oid_2} was not found"
with reporter.step("Check delete object from container on the first node, expected access denied error"):
with pytest.raises(RuntimeError, match=RULE_ACCESS_DENIED_OBJECT):
delete_object(default_wallet, container, oid_1, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())
with reporter.step("Check delete object from container on the second node, expected allow"):
with expect_not_raises():
delete_object(default_wallet, container, oid_2, self.shell, self.cluster.storage_nodes[1].get_rpc_endpoint())
with reporter.step("Delete a rule"):
frostfs_cli_on_first_node.control.remove_rule(
endpoint=self.cluster.storage_nodes[0].get_control_endpoint(),
target_type="container",
target_name=container,
chain_id="denyDeleteObject",
)
with reporter.step("Check delete object in container on the first node, expected allow"):
with expect_not_raises():
delete_object(default_wallet, container, oid_1, self.shell, self.cluster.storage_nodes[0].get_rpc_endpoint())

View file

@ -34,8 +34,8 @@ from frostfs_testlib.testing.test_control import cached_fixture, run_optionally,
from frostfs_testlib.utils import env_utils, string_utils, version_utils from frostfs_testlib.utils import env_utils, string_utils, version_utils
from frostfs_testlib.utils.file_utils import TestFile, generate_file from frostfs_testlib.utils.file_utils import TestFile, generate_file
from ..helpers.container_creation import create_container_with_ape from ..helpers.container_creation import create_container_with_ape, create_containers_with_ape
from ..helpers.container_request import EVERYONE_ALLOW_ALL, ContainerRequest from ..helpers.container_request import EVERYONE_ALLOW_ALL, ContainerRequest, MultipleContainersRequest
from ..resources.common import TEST_CYCLES_COUNT from ..resources.common import TEST_CYCLES_COUNT
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -511,6 +511,17 @@ def container_request(request: pytest.FixtureRequest) -> ContainerRequest:
return container_request return container_request
@pytest.fixture(scope="session")
def multiple_containers_request(request: pytest.FixtureRequest) -> ContainerRequest:
if "param" in request.__dict__:
return request.param
raise RuntimeError(
f"""Container specification is empty.
Add @pytest.mark.parametrize("container_requests", [[ContainerRequest(...), ..., ContainerRequest(...)]], indirect=True) decorator."""
)
@pytest.fixture @pytest.fixture
def container( def container(
default_wallet: WalletInfo, default_wallet: WalletInfo,
@ -520,7 +531,19 @@ def container(
rpc_endpoint: str, rpc_endpoint: str,
container_request: ContainerRequest, container_request: ContainerRequest,
) -> str: ) -> str:
return create_container_with_ape(frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint, container_request) return create_container_with_ape(container_request, frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint)
@pytest.fixture
def containers(
default_wallet: WalletInfo,
frostfs_cli: FrostfsCli,
client_shell: Shell,
cluster: Cluster,
rpc_endpoint: str,
multiple_containers_request: MultipleContainersRequest,
) -> list[str]:
return create_containers_with_ape(frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint, multiple_containers_request)
@pytest.fixture() @pytest.fixture()

View file

@ -37,7 +37,6 @@ class TestContainer(ClusterTestBase):
container_info = container_info.casefold() # To ignore case when comparing with expected values container_info = container_info.casefold() # To ignore case when comparing with expected values
info_to_check = { info_to_check = {
f"basic ACL: {PRIVATE_ACL_F} (private)",
f"owner ID: {wallet.get_address_from_json(0)}", f"owner ID: {wallet.get_address_from_json(0)}",
f"CID: {cid}", f"CID: {cid}",
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,640 @@
import itertools
import allure
import pytest
from frostfs_testlib import reporter
from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli
from frostfs_testlib.shell.interfaces import Shell
from frostfs_testlib.steps.cli.container import delete_container
from frostfs_testlib.steps.cli.object import delete_object, put_object_to_random_node
from frostfs_testlib.steps.node_management import get_netmap_snapshot
from frostfs_testlib.steps.storage_policy import get_nodes_with_object
from frostfs_testlib.storage.cluster import Cluster
from frostfs_testlib.storage.controllers.cluster_state_controller import ClusterStateController
from frostfs_testlib.storage.controllers.state_managers.config_state_manager import ConfigStateManager
from frostfs_testlib.storage.dataclasses.frostfs_services import StorageNode
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing import parallel
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.test_control import wait_for_success
from frostfs_testlib.utils.cli_utils import parse_netmap_output
from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_creation import create_container_with_ape
from ...helpers.container_request import PUBLIC_WITH_POLICY, ContainerRequest
from ...helpers.policy_validation import get_netmap_param, validate_object_policy
@pytest.mark.weekly
@pytest.mark.policy
@pytest.mark.policy_price
class TestPolicyWithPrice(ClusterTestBase):
@wait_for_success(1200, 60, title="Wait for full field price on node", expected_result=True)
def await_for_price_attribute_on_nodes(self):
netmap = parse_netmap_output(get_netmap_snapshot(node=self.cluster.storage_nodes[0], shell=self.shell))
netmap = get_netmap_param(netmap)
for node in self.cluster.storage_nodes:
node_address = node.get_rpc_endpoint().split(":")[0]
if netmap[node_address]["Price"] is None:
return False
return True
@pytest.fixture(scope="module")
def fill_field_price(self, cluster: Cluster, cluster_state_controller_session: ClusterStateController):
prices = ["15", "10", "65", "55"]
config_manager = cluster_state_controller_session.manager(ConfigStateManager)
parallel(
config_manager.set_on_node,
cluster.cluster_nodes,
StorageNode,
itertools.cycle([{"node:attribute_5": f"Price:{price}"} for price in prices]),
)
cluster_state_controller_session.wait_after_storage_startup()
self.tick_epoch()
self.await_for_price_attribute_on_nodes()
yield
cluster_state_controller_session.manager(ConfigStateManager).revert_all()
@pytest.fixture
def container(
self,
default_wallet: WalletInfo,
frostfs_cli: FrostfsCli,
client_shell: Shell,
cluster: Cluster,
rpc_endpoint: str,
container_request: ContainerRequest,
# In these set of tests containers should be created after the fill_field_price fixture
fill_field_price,
) -> str:
return create_container_with_ape(container_request, frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint)
@allure.title("Policy with SELECT and FILTER results with 25% of available nodes")
@pytest.mark.parametrize(
"container_request",
[PUBLIC_WITH_POLICY("REP 1 IN Nodes25 SELECT 1 FROM LE10 AS Nodes25 FILTER Price LE 10 AS LE10")],
indirect=True,
)
def test_policy_with_select_and_filter_results_with_25_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and FILTER results with 25% of available nodes.
"""
placement_params = {"Price": 10}
file_path = generate_file(simple_object_size.value)
expected_copies = 1
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
node_address = resulting_copies[0].get_rpc_endpoint().split(":")[0]
with reporter.step(f"Check the node is selected with price <= {placement_params['Price']}"):
assert (
int(netmap[node_address]["Price"]) <= placement_params["Price"]
), f"The node is selected with the wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with select and complex filter results with 25% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"REP 1 IN Nodes25 SELECT 1 FROM BET0AND10 AS Nodes25 FILTER Price LE 10 AS LE10 FILTER Price GT 0 AS GT0 FILTER @LE10 AND @GT0 AS BET0AND10"
)
],
indirect=True,
)
def test_policy_with_select_and_complex_filter_results_with_25_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and Complex FILTER results with 25% of available nodes.
"""
placement_params = {"Price": [10, 0]}
file_path = generate_file(simple_object_size.value)
expected_copies = 1
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check the node is selected with price between 1 and 10"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
int(netmap[node_address]["Price"]) > placement_params["Price"][1]
and int(netmap[node_address]["Price"]) <= placement_params["Price"][0]
), f"The node is selected with the wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with Multi SELECTs and FILTERs results with 25% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"UNIQUE REP 1 IN One REP 1 IN One CBF 1 SELECT 1 FROM MINMAX AS One FILTER Price LT 15 AS LT15 FILTER Price GT 55 AS GT55 FILTER @LT15 OR @GT55 AS MINMAX"
)
],
indirect=True,
)
def test_policy_with_multi_selects_and_filters_results_with_25_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with Multi SELECTs and FILTERs results with 25% of available nodes.
"""
placement_params = {"Price": [15, 55]}
file_path = generate_file(simple_object_size.value)
expected_copies = 2
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check two nodes are selected with max and min prices"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
int(netmap[node_address]["Price"]) > placement_params["Price"][1]
or int(netmap[node_address]["Price"]) < placement_params["Price"][0]
), f"The node is selected with the wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with SELECT and FILTER results with 50% of available nodes")
@pytest.mark.parametrize(
"container_request",
[PUBLIC_WITH_POLICY("REP 2 IN HALF CBF 1 SELECT 2 FROM GT15 AS HALF FILTER Price GT 15 AS GT15")],
indirect=True,
)
def test_policy_with_select_and_filter_results_with_50_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and FILTER results with 50% of available nodes.
"""
placement_params = {"Price": 15}
file_path = generate_file(simple_object_size.value)
expected_copies = 2
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check two nodes are selected with price > {placement_params['Price']}"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
int(netmap[node_address]["Price"]) > placement_params["Price"]
), f"The node is selected with the wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with SELECT and Complex FILTER results with 50% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"REP 2 IN HALF CBF 2 SELECT 2 FROM GE15 AS HALF FILTER CountryCode NE RU AS NOTRU FILTER @NOTRU AND Price GE 15 AS GE15"
)
],
indirect=True,
)
def test_policy_with_select_and_complex_filter_results_with_50_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and Complex FILTER results with 50% of available nodes.
"""
placement_params = {"Price": 15, "country_code": "RU"}
file_path = generate_file(simple_object_size.value)
expected_copies = 2
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check two nodes are selected not with country code '{placement_params['country_code']}'"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
not netmap[node_address]["country_code"] == placement_params["country_code"]
or not netmap[node_address]["country_code"] == placement_params["country_code"]
and int(netmap[node_address]["Price"]) >= placement_params["Price"]
), f"The node is selected with the wrong price or country code. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with Multi SELECTs and FILTERs results with 50% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"REP 2 IN FH REP 1 IN SH CBF 2 SELECT 2 FROM LE55 AS FH SELECT 2 FROM GE15 AS SH FILTER 'UN-LOCODE' EQ 'RU LED' OR 'UN-LOCODE' EQ 'RU MOW' AS RU FILTER NOT (@RU) AS NOTRU FILTER @NOTRU AND Price GE 15 AS GE15 FILTER @RU AND Price LE 55 AS LE55"
)
],
indirect=True,
)
def test_policy_with_multi_selects_and_filters_results_with_50_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with Multi SELECTs and FILTERs results with 50% of available nodes.
"""
placement_params = {"un_locode": ["RU LED", "RU MOW"], "Price": [15, 55]}
file_path = generate_file(simple_object_size.value)
expected_copies = 3
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check all nodes are selected"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
netmap[node_address]["un_locode"] in placement_params["un_locode"]
or not netmap[node_address]["un_locode"] == placement_params["un_locode"][1]
or (
not netmap[node_address]["un_locode"] == placement_params["un_locode"][1]
and int(netmap[node_address]["Price"]) >= placement_params["Price"][0]
)
or (
netmap[node_address]["un_locode"] == placement_params["un_locode"][1]
and int(netmap[node_address]["Price"]) <= placement_params["Price"][1]
)
), f"The node is selected with the wrong price or un_locode. Expected {placement_params} and got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with SELECT and FILTER results with 75% of available nodes")
@pytest.mark.parametrize(
"container_request",
[PUBLIC_WITH_POLICY("REP 2 IN NODES75 SELECT 2 FROM LT65 AS NODES75 FILTER Price LT 65 AS LT65")],
indirect=True,
)
def test_policy_with_select_and_filter_results_with_75_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and FILTER results with 75% of available nodes.
"""
placement_params = {"Price": 65}
file_path = generate_file(simple_object_size.value)
expected_copies = 2
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check two nodes are selected with price < {placement_params['Price']}"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
int(netmap[node_address]["Price"]) < placement_params["Price"]
), f"The node is selected with the wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with SELECT and Complex FILTER results with 75% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"REP 2 IN NODES75 SELECT 2 FROM LT65 AS NODES75 FILTER Continent NE America AS NOAM FILTER @NOAM AND Price LT 65 AS LT65"
)
],
indirect=True,
)
def test_policy_with_select_and_complex_filter_results_with_75_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and Complex FILTER results with 75% of available nodes.
"""
placement_params = {"Price": 65, "continent": "America"}
file_path = generate_file(simple_object_size.value)
expected_copies = 2
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check three nodes are selected not from {placement_params['continent']}"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
int(netmap[node_address]["Price"]) < placement_params["Price"]
and not netmap[node_address]["continent"] == placement_params["continent"]
) or (
not netmap[node_address]["continent"] == placement_params["continent"]
), f"The node is selected with the wrong price or continent. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with Multi SELECTs and FILTERs results with 75% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"REP 3 IN EXPNSV REP 3 IN CHEAP SELECT 3 FROM GT10 AS EXPNSV SELECT 3 FROM LT65 AS CHEAP FILTER NOT (Continent EQ America) AS NOAM FILTER @NOAM AND Price LT 65 AS LT65 FILTER @NOAM AND Price GT 10 AS GT10"
)
],
indirect=True,
)
def test_policy_with_multi_selects_and_filters_results_with_75_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with Multi SELECTs and FILTERs results with 75% of available nodes.
"""
placement_params = {"Price": [65, 10], "continent": "America"}
file_path = generate_file(simple_object_size.value)
expected_copies = 4
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check all nodes are selected"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (
(
int(netmap[node_address]["Price"]) > placement_params["Price"][1]
and not netmap[node_address]["continent"] == placement_params["continent"]
)
or (
int(netmap[node_address]["Price"]) < placement_params["Price"][0]
and not netmap[node_address]["continent"] == placement_params["continent"]
)
or not (netmap[node_address]["continent"] == placement_params["continent"])
), f"The node is selected with the wrong price or continent. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with SELECT and FILTER results with 100% of available nodes")
@pytest.mark.parametrize(
"container_request", [PUBLIC_WITH_POLICY("REP 1 IN All SELECT 4 FROM AllNodes AS All FILTER Price GE 0 AS AllNodes")], indirect=True
)
def test_policy_with_select_and_filter_results_with_100_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with SELECT and FILTER results with 100% of available nodes.
"""
placement_params = {"Price": 0}
file_path = generate_file(simple_object_size.value)
expected_copies = 1
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
node_address = resulting_copies[0].get_rpc_endpoint().split(":")[0]
with reporter.step(f"Check the node is selected with price >= {placement_params['Price']}"):
assert (
int(netmap[node_address]["Price"]) >= placement_params["Price"]
), f"The node is selected with the wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)
@allure.title("Policy with Multi SELECTs and FILTERs results with 100% of available nodes")
@pytest.mark.parametrize(
"container_request",
[
PUBLIC_WITH_POLICY(
"REP 4 IN AllOne REP 4 IN AllTwo CBF 4 SELECT 2 FROM GEZero AS AllOne SELECT 2 FROM AllCountries AS AllTwo FILTER Country EQ Russia OR Country EQ Sweden OR Country EQ Finland AS AllCountries FILTER Price GE 0 AS GEZero"
)
],
indirect=True,
)
def test_policy_with_multi_selects_and_filters_results_with_100_of_available_nodes(
self,
default_wallet: WalletInfo,
rpc_endpoint: str,
simple_object_size: ObjectSize,
container: str,
container_request: ContainerRequest,
):
"""
This test checks object's copies based on container's placement policy with Multi SELECTs and FILTERs results with 100% of available nodes.
"""
placement_params = {"country": ["Russia", "Sweden", "Finland"], "Price": 0}
file_path = generate_file(simple_object_size.value)
expected_copies = 4
with reporter.step(f"Check container policy"):
validate_object_policy(default_wallet, self.shell, container_request.policy, container, rpc_endpoint)
with reporter.step(f"Put object in container"):
oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
with reporter.step(f"Check object expected copies"):
resulting_copies = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(resulting_copies) == expected_copies, f"Expected {expected_copies} copies, got {len(resulting_copies)}"
with reporter.step(f"Check the object appearance"):
netmap = parse_netmap_output(get_netmap_snapshot(node=resulting_copies[0], shell=self.shell))
netmap = get_netmap_param(netmap)
with reporter.step(f"Check all node are selected"):
for node in resulting_copies:
node_address = node.get_rpc_endpoint().split(":")[0]
assert (netmap[node_address]["country"] in placement_params["country"]) or (
int(netmap[node_address]["Price"]) >= placement_params["Price"]
), f"The node is selected from the wrong country or with wrong price. Got {netmap[node_address]}"
with reporter.step(f"Delete the object from the container"):
delete_object(default_wallet, container, oid, self.shell, rpc_endpoint)
with reporter.step(f"Delete the container"):
delete_container(default_wallet, container, self.shell, rpc_endpoint, await_mode=False)

View file

@ -6,11 +6,11 @@ import random
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli
from frostfs_testlib.steps.cli.container import StorageContainer, StorageContainerInfo, create_container from frostfs_testlib.steps.cli.container import StorageContainer, StorageContainerInfo, create_container
from frostfs_testlib.steps.cli.object import get_object, get_object_nodes, put_object from frostfs_testlib.steps.cli.object import get_object, get_object_nodes, put_object
from frostfs_testlib.steps.node_management import check_node_in_map, check_node_not_in_map from frostfs_testlib.steps.node_management import check_node_in_map, check_node_not_in_map
from frostfs_testlib.storage.cluster import ClusterNode, StorageNode from frostfs_testlib.storage.cluster import Cluster, ClusterNode, StorageNode
from frostfs_testlib.storage.controllers import ClusterStateController from frostfs_testlib.storage.controllers import ClusterStateController
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.storage_object_info import StorageObjectInfo from frostfs_testlib.storage.dataclasses.storage_object_info import StorageObjectInfo
@ -21,6 +21,9 @@ from frostfs_testlib.testing.test_control import wait_for_success
from frostfs_testlib.utils.file_utils import get_file_hash from frostfs_testlib.utils.file_utils import get_file_hash
from pytest import FixtureRequest from pytest import FixtureRequest
from ...helpers.container_creation import create_container_with_ape
from ...helpers.container_request import APE_EVERYONE_ALLOW_ALL, ContainerRequest
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -41,18 +44,21 @@ class TestFailoverServer(ClusterTestBase):
self, self,
request: FixtureRequest, request: FixtureRequest,
default_wallet: WalletInfo, default_wallet: WalletInfo,
frostfs_cli: FrostfsCli,
cluster: Cluster,
) -> list[StorageContainer]: ) -> list[StorageContainer]:
placement_rule = "REP 2 CBF 2 SELECT 2 FROM *" placement_rule = "REP 2 CBF 2 SELECT 2 FROM *"
container_request = ContainerRequest(placement_rule, APE_EVERYONE_ALLOW_ALL)
containers_count = request.param containers_count = request.param
results = parallel( results = parallel(
[create_container for _ in range(containers_count)], [create_container_with_ape for _ in range(containers_count)],
container_request=container_request,
frostfs_cli=frostfs_cli,
wallet=default_wallet, wallet=default_wallet,
shell=self.shell, shell=self.shell,
cluster=cluster,
endpoint=self.cluster.default_rpc_endpoint, endpoint=self.cluster.default_rpc_endpoint,
rule=placement_rule,
basic_acl=PUBLIC_ACL,
) )
containers = [ containers = [
@ -63,17 +69,18 @@ class TestFailoverServer(ClusterTestBase):
@allure.title("[Test] Create container") @allure.title("[Test] Create container")
@pytest.fixture() @pytest.fixture()
def container(self, default_wallet: WalletInfo) -> StorageContainer: def container(self, default_wallet: WalletInfo, frostfs_cli: FrostfsCli) -> StorageContainer:
select = len(self.cluster.cluster_nodes) select = len(self.cluster.cluster_nodes)
placement_rule = f"REP {select - 1} CBF 1 SELECT {select} FROM *" placement_rule = f"REP {select - 1} CBF 1 SELECT {select} FROM *"
cont_id = create_container( cid = create_container_with_ape(
ContainerRequest(placement_rule, APE_EVERYONE_ALLOW_ALL),
frostfs_cli,
default_wallet, default_wallet,
shell=self.shell, self.shell,
endpoint=self.cluster.default_rpc_endpoint, self.cluster,
rule=placement_rule, self.cluster.default_rpc_endpoint,
basic_acl=PUBLIC_ACL,
) )
storage_cont_info = StorageContainerInfo(cont_id, default_wallet) storage_cont_info = StorageContainerInfo(cid, default_wallet)
return StorageContainer(storage_cont_info, self.shell, self.cluster) return StorageContainer(storage_cont_info, self.shell, self.cluster)
@allure.title("[Class] Create objects") @allure.title("[Class] Create objects")
@ -92,14 +99,25 @@ class TestFailoverServer(ClusterTestBase):
sizes_weights = [2, 1] sizes_weights = [2, 1]
sizes = sizes_samples + random.choices(sizes_samples, weights=sizes_weights, k=object_count - samples_count) sizes = sizes_samples + random.choices(sizes_samples, weights=sizes_weights, k=object_count - samples_count)
total_objects = len(containers) * object_count
results = parallel( with reporter.step(f"Upload {total_objects} in total to containers"):
[container.generate_object for _ in sizes for container in containers], results = parallel(
size=itertools.cycle([size.value for size in sizes]), [self._generate_files_and_remove_physical_copy for _ in range(total_objects)],
) container=itertools.cycle(containers),
size=itertools.cycle(sizes),
)
return [result.result() for result in results] return [result.result() for result in results]
def _generate_files_and_remove_physical_copy(self, container: StorageContainer, size: ObjectSize) -> StorageObjectInfo:
storage_object = container.generate_object(size.value)
# Deliberately remove physical copy of the file for this test since it can generate multibytes of test objects
os.remove(storage_object.file_path)
return storage_object
@allure.title("[Test] Create objects and get nodes with object") @allure.title("[Test] Create objects and get nodes with object")
@pytest.fixture() @pytest.fixture()
def object_and_nodes(self, simple_object_size: ObjectSize, container: StorageContainer) -> tuple[StorageObjectInfo, list[ClusterNode]]: def object_and_nodes(self, simple_object_size: ObjectSize, container: StorageContainer) -> tuple[StorageObjectInfo, list[ClusterNode]]:
@ -127,7 +145,7 @@ class TestFailoverServer(ClusterTestBase):
parallel(self._verify_object, storage_objects * len(nodes), node=itertools.cycle(nodes)) parallel(self._verify_object, storage_objects * len(nodes), node=itertools.cycle(nodes))
@allure.title("Full shutdown node") @allure.title("Full shutdown node")
@pytest.mark.parametrize("containers, storage_objects", [(5, 10)], indirect=True) @pytest.mark.parametrize("containers, storage_objects", [(4, 5)], indirect=True)
def test_complete_node_shutdown( def test_complete_node_shutdown(
self, self,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
@ -221,17 +239,19 @@ class TestFailoverServer(ClusterTestBase):
self, self,
default_wallet: WalletInfo, default_wallet: WalletInfo,
cluster_state_controller: ClusterStateController, cluster_state_controller: ClusterStateController,
frostfs_cli: FrostfsCli,
simple_file: str, simple_file: str,
): ):
with reporter.step("Create container with full network map"): with reporter.step("Create container with full network map"):
node_count = len(self.cluster.cluster_nodes) node_count = len(self.cluster.cluster_nodes)
placement_rule = f"REP {node_count - 2} IN X CBF 2 SELECT {node_count} FROM * AS X" placement_rule = f"REP {node_count - 2} IN X CBF 2 SELECT {node_count} FROM * AS X"
cid = create_container( cid = create_container_with_ape(
ContainerRequest(placement_rule, APE_EVERYONE_ALLOW_ALL),
frostfs_cli,
default_wallet, default_wallet,
self.shell, self.shell,
self.cluster,
self.cluster.default_rpc_endpoint, self.cluster.default_rpc_endpoint,
rule=placement_rule,
basic_acl=PUBLIC_ACL,
) )
with reporter.step("Put object"): with reporter.step("Put object"):
@ -255,10 +275,4 @@ class TestFailoverServer(ClusterTestBase):
get_object(default_wallet, cid, oid_2, self.shell, alive_endpoint_with_object) get_object(default_wallet, cid, oid_2, self.shell, alive_endpoint_with_object)
with reporter.step("Create container on alive node"): with reporter.step("Create container on alive node"):
create_container( create_container(default_wallet, self.shell, alive_endpoint_with_object, placement_rule)
default_wallet,
self.shell,
alive_endpoint_with_object,
rule=placement_rule,
basic_acl=PUBLIC_ACL,
)

View file

@ -7,7 +7,6 @@ import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.common import MORPH_BLOCK_TIME from frostfs_testlib.resources.common import MORPH_BLOCK_TIME
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.s3 import S3ClientWrapper, VersioningStatus from frostfs_testlib.s3 import S3ClientWrapper, VersioningStatus
from frostfs_testlib.s3.interfaces import BucketContainerResolver from frostfs_testlib.s3.interfaces import BucketContainerResolver
from frostfs_testlib.steps.cli.container import StorageContainer, StorageContainerInfo, create_container from frostfs_testlib.steps.cli.container import StorageContainer, StorageContainerInfo, create_container
@ -34,6 +33,7 @@ from frostfs_testlib.utils.failover_utils import wait_object_replication
from frostfs_testlib.utils.file_keeper import FileKeeper from frostfs_testlib.utils.file_keeper import FileKeeper
from frostfs_testlib.utils.file_utils import generate_file, get_file_hash from frostfs_testlib.utils.file_utils import generate_file, get_file_hash
from ...helpers.container_request import REP_2_2_2_PUBLIC, requires_container
from ...resources.common import S3_POLICY_FILE_LOCATION from ...resources.common import S3_POLICY_FILE_LOCATION
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -54,32 +54,26 @@ class TestFailoverStorage(ClusterTestBase):
@allure.title("Shutdown and start node (stop_mode={stop_mode})") @allure.title("Shutdown and start node (stop_mode={stop_mode})")
@pytest.mark.parametrize("stop_mode", ["hard", "soft"]) @pytest.mark.parametrize("stop_mode", ["hard", "soft"])
@pytest.mark.failover_reboot @pytest.mark.failover_reboot
@requires_container(REP_2_2_2_PUBLIC)
def test_lose_storage_node_host( def test_lose_storage_node_host(
self, self,
default_wallet, default_wallet,
stop_mode: str, stop_mode: str,
container: str,
require_multiple_hosts, require_multiple_hosts,
simple_object_size: ObjectSize, simple_object_size: ObjectSize,
cluster: Cluster, cluster: Cluster,
cluster_state_controller: ClusterStateController, cluster_state_controller: ClusterStateController,
): ):
wallet = default_wallet wallet = default_wallet
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
source_file_path = generate_file(simple_object_size.value) source_file_path = generate_file(simple_object_size.value)
stopped_hosts_nodes = [] stopped_hosts_nodes = []
with reporter.step(f"Create container and put object"): with reporter.step(f"Put object"):
cid = create_container( oid = put_object_to_random_node(wallet, source_file_path, container, shell=self.shell, cluster=self.cluster)
wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=placement_rule,
basic_acl=PUBLIC_ACL,
)
oid = put_object_to_random_node(wallet, source_file_path, cid, shell=self.shell, cluster=self.cluster)
with reporter.step(f"Wait for replication and get nodes with object"): with reporter.step(f"Wait for replication and get nodes with object"):
nodes_with_object = wait_object_replication(cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes) nodes_with_object = wait_object_replication(container, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes)
with reporter.step(f"Stop 2 nodes with object and wait replication one by one"): with reporter.step(f"Stop 2 nodes with object and wait replication one by one"):
for storage_node in random.sample(nodes_with_object, 2): for storage_node in random.sample(nodes_with_object, 2):
@ -89,7 +83,7 @@ class TestFailoverStorage(ClusterTestBase):
cluster_state_controller.stop_node_host(cluster_node, stop_mode) cluster_state_controller.stop_node_host(cluster_node, stop_mode)
replicated_nodes = wait_object_replication( replicated_nodes = wait_object_replication(
cid, container,
oid, oid,
2, 2,
shell=self.shell, shell=self.shell,
@ -97,15 +91,15 @@ class TestFailoverStorage(ClusterTestBase):
) )
with reporter.step("Check object data is not corrupted"): with reporter.step("Check object data is not corrupted"):
got_file_path = get_object(wallet, cid, oid, endpoint=replicated_nodes[0].get_rpc_endpoint(), shell=self.shell) got_file_path = get_object(wallet, container, oid, endpoint=replicated_nodes[0].get_rpc_endpoint(), shell=self.shell)
assert get_file_hash(source_file_path) == get_file_hash(got_file_path) assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
with reporter.step("Return all hosts"): with reporter.step("Return all hosts"):
cluster_state_controller.start_stopped_hosts() cluster_state_controller.start_stopped_hosts()
with reporter.step("Check object data is not corrupted"): with reporter.step("Check object data is not corrupted"):
replicated_nodes = wait_object_replication(cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes) replicated_nodes = wait_object_replication(container, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes)
got_file_path = get_object(wallet, cid, oid, shell=self.shell, endpoint=replicated_nodes[0].get_rpc_endpoint()) got_file_path = get_object(wallet, container, oid, shell=self.shell, endpoint=replicated_nodes[0].get_rpc_endpoint())
assert get_file_hash(source_file_path) == get_file_hash(got_file_path) assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
@pytest.mark.parametrize("s3_policy", [S3_POLICY_FILE_LOCATION], indirect=True) @pytest.mark.parametrize("s3_policy", [S3_POLICY_FILE_LOCATION], indirect=True)

View file

@ -6,10 +6,8 @@ import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.healthcheck.interfaces import Healthcheck from frostfs_testlib.healthcheck.interfaces import Healthcheck
from frostfs_testlib.resources.wellknown_acl import EACL_PUBLIC_READ_WRITE, PUBLIC_ACL
from frostfs_testlib.steps.cli.container import create_container from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.cli.object import get_object, get_object_nodes, neo_go_query_height, put_object, put_object_to_random_node from frostfs_testlib.steps.cli.object import get_object, get_object_nodes, neo_go_query_height, put_object, put_object_to_random_node
from frostfs_testlib.steps.storage_object import delete_objects
from frostfs_testlib.storage.cluster import ClusterNode from frostfs_testlib.storage.cluster import ClusterNode
from frostfs_testlib.storage.controllers import ClusterStateController from frostfs_testlib.storage.controllers import ClusterStateController
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
@ -20,6 +18,8 @@ from frostfs_testlib.testing.parallel import parallel
from frostfs_testlib.utils.failover_utils import wait_object_replication from frostfs_testlib.utils.failover_utils import wait_object_replication
from frostfs_testlib.utils.file_utils import generate_file, get_file_hash from frostfs_testlib.utils.file_utils import generate_file, get_file_hash
from ...helpers.container_request import PUBLIC_WITH_POLICY, REP_2_2_2_PUBLIC, requires_container
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
STORAGE_NODE_COMMUNICATION_PORT = "8080" STORAGE_NODE_COMMUNICATION_PORT = "8080"
STORAGE_NODE_COMMUNICATION_PORT_TLS = "8082" STORAGE_NODE_COMMUNICATION_PORT_TLS = "8082"
@ -59,23 +59,13 @@ class TestFailoverNetwork(ClusterTestBase):
def storage_objects( def storage_objects(
self, self,
simple_object_size: ObjectSize, simple_object_size: ObjectSize,
container: str,
default_wallet: WalletInfo, default_wallet: WalletInfo,
) -> list[StorageObjectInfo]: ) -> list[StorageObjectInfo]:
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
file_hash = get_file_hash(file_path) file_hash = get_file_hash(file_path)
with reporter.step("Create container"):
placement_rule = "REP 1 CBF 1"
cid = create_container(
wallet=default_wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=placement_rule,
await_mode=True,
basic_acl=EACL_PUBLIC_READ_WRITE,
)
storage_objects = [] storage_objects = []
with reporter.step("Put object"): with reporter.step("Put object"):
@ -83,12 +73,12 @@ class TestFailoverNetwork(ClusterTestBase):
oid = put_object_to_random_node( oid = put_object_to_random_node(
wallet=default_wallet, wallet=default_wallet,
path=file_path, path=file_path,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
cluster=self.cluster, cluster=self.cluster,
) )
storage_object = StorageObjectInfo(cid=cid, oid=oid) storage_object = StorageObjectInfo(cid=container, oid=oid)
storage_object.size = simple_object_size.value storage_object.size = simple_object_size.value
storage_object.wallet = default_wallet storage_object.wallet = default_wallet
storage_object.file_path = file_path storage_object.file_path = file_path
@ -100,9 +90,11 @@ class TestFailoverNetwork(ClusterTestBase):
return storage_objects return storage_objects
@allure.title("Block Storage node traffic") @allure.title("Block Storage node traffic")
@requires_container(REP_2_2_2_PUBLIC)
def test_block_storage_node_traffic( def test_block_storage_node_traffic(
self, self,
default_wallet: WalletInfo, default_wallet: WalletInfo,
container: str,
require_multiple_hosts, require_multiple_hosts,
simple_object_size: ObjectSize, simple_object_size: ObjectSize,
cluster_state_controller: ClusterStateController, cluster_state_controller: ClusterStateController,
@ -111,21 +103,13 @@ class TestFailoverNetwork(ClusterTestBase):
Block storage nodes traffic using iptables and wait for replication for objects. Block storage nodes traffic using iptables and wait for replication for objects.
""" """
wallet = default_wallet wallet = default_wallet
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
wakeup_node_timeout = 10 # timeout to let nodes detect that traffic has blocked wakeup_node_timeout = 10 # timeout to let nodes detect that traffic has blocked
nodes_to_block_count = 2 nodes_to_block_count = 2
source_file_path = generate_file(simple_object_size.value) source_file_path = generate_file(simple_object_size.value)
cid = create_container( oid = put_object_to_random_node(wallet, source_file_path, container, self.shell, self.cluster)
wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=placement_rule,
basic_acl=PUBLIC_ACL,
)
oid = put_object_to_random_node(wallet, source_file_path, cid, shell=self.shell, cluster=self.cluster)
nodes = wait_object_replication(cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes) nodes = wait_object_replication(container, oid, 2, self.shell, self.cluster.storage_nodes)
logger.info(f"Nodes are {nodes}") logger.info(f"Nodes are {nodes}")
nodes_to_block = nodes nodes_to_block = nodes
@ -147,7 +131,7 @@ class TestFailoverNetwork(ClusterTestBase):
with reporter.step(f"Check object is not stored on node {node}"): with reporter.step(f"Check object is not stored on node {node}"):
new_nodes = wait_object_replication( new_nodes = wait_object_replication(
cid, container,
oid, oid,
2, 2,
shell=self.shell, shell=self.shell,
@ -156,7 +140,7 @@ class TestFailoverNetwork(ClusterTestBase):
assert node.storage_node not in new_nodes assert node.storage_node not in new_nodes
with reporter.step("Check object data is not corrupted"): with reporter.step("Check object data is not corrupted"):
got_file_path = get_object(wallet, cid, oid, endpoint=new_nodes[0].get_rpc_endpoint(), shell=self.shell) got_file_path = get_object(wallet, container, oid, endpoint=new_nodes[0].get_rpc_endpoint(), shell=self.shell)
assert get_file_hash(source_file_path) == get_file_hash(got_file_path) assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
with reporter.step(f"Unblock incoming traffic"): with reporter.step(f"Unblock incoming traffic"):
@ -170,13 +154,14 @@ class TestFailoverNetwork(ClusterTestBase):
sleep(wakeup_node_timeout) sleep(wakeup_node_timeout)
with reporter.step("Check object data is not corrupted"): with reporter.step("Check object data is not corrupted"):
new_nodes = wait_object_replication(cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes) new_nodes = wait_object_replication(container, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes)
got_file_path = get_object(wallet, cid, oid, shell=self.shell, endpoint=new_nodes[0].get_rpc_endpoint()) got_file_path = get_object(wallet, container, oid, shell=self.shell, endpoint=new_nodes[0].get_rpc_endpoint())
assert get_file_hash(source_file_path) == get_file_hash(got_file_path) assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
@pytest.mark.interfaces @pytest.mark.interfaces
@allure.title("Block DATA interface node") @allure.title("Block DATA interface node")
@requires_container(PUBLIC_WITH_POLICY("REP 1 CBF 1", short_name="REP 1 CBF 1"))
def test_block_data_interface( def test_block_data_interface(
self, self,
cluster_state_controller: ClusterStateController, cluster_state_controller: ClusterStateController,
@ -284,7 +269,7 @@ class TestFailoverNetwork(ClusterTestBase):
) )
with reporter.step(f"Get object nodes with object, expect true"): with reporter.step(f"Get object nodes with object, expect true"):
input_file = get_object( _ = get_object(
wallet=default_wallet, wallet=default_wallet,
cid=storage_object.cid, cid=storage_object.cid,
oid=storage_object.oid, oid=storage_object.oid,

View file

@ -1,7 +1,7 @@
import logging import logging
import random import random
from time import sleep from time import sleep
from typing import Callable, Optional, Tuple from typing import Callable, Optional
import allure import allure
import pytest import pytest
@ -10,7 +10,6 @@ from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.cli.netmap_parser import NetmapParser from frostfs_testlib.cli.netmap_parser import NetmapParser
from frostfs_testlib.resources.cli import FROSTFS_CLI_EXEC from frostfs_testlib.resources.cli import FROSTFS_CLI_EXEC
from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.cli.container import create_container, search_nodes_with_container from frostfs_testlib.steps.cli.container import create_container, search_nodes_with_container
from frostfs_testlib.steps.cli.object import ( from frostfs_testlib.steps.cli.object import (
delete_object, delete_object,
@ -44,6 +43,8 @@ from frostfs_testlib.utils import string_utils
from frostfs_testlib.utils.failover_utils import wait_object_replication from frostfs_testlib.utils.failover_utils import wait_object_replication
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_creation import create_container_with_ape
from ...helpers.container_request import APE_EVERYONE_ALLOW_ALL, REP_1_1_1_PUBLIC, ContainerRequest, requires_container
from ...helpers.utility import wait_for_gc_pass_on_storage_nodes from ...helpers.utility import wait_for_gc_pass_on_storage_nodes
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -55,26 +56,16 @@ check_nodes: list[StorageNode] = []
@pytest.mark.order(10) @pytest.mark.order(10)
class TestNodeManagement(ClusterTestBase): class TestNodeManagement(ClusterTestBase):
@pytest.fixture @pytest.fixture
@allure.title("Create container and pick the node with data") @allure.title("Pick the node with data")
def create_container_and_pick_node(self, default_wallet: WalletInfo, simple_object_size: ObjectSize) -> Tuple[str, StorageNode]: def node_with_data(self, container: str, default_wallet: WalletInfo, simple_object_size: ObjectSize) -> StorageNode:
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X" oid = put_object_to_random_node(default_wallet, file_path, container, self.shell, self.cluster)
endpoint = self.cluster.default_rpc_endpoint
cid = create_container( nodes = get_nodes_with_object(container, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
default_wallet,
shell=self.shell,
endpoint=endpoint,
rule=placement_rule,
basic_acl=PUBLIC_ACL,
)
oid = put_object_to_random_node(default_wallet, file_path, cid, self.shell, self.cluster)
nodes = get_nodes_with_object(cid, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
assert len(nodes) == 1 assert len(nodes) == 1
node = nodes[0] node = nodes[0]
yield cid, node yield node
shards = node_shard_list(node) shards = node_shard_list(node)
assert shards assert shards
@ -126,6 +117,7 @@ class TestNodeManagement(ClusterTestBase):
self, self,
default_wallet: WalletInfo, default_wallet: WalletInfo,
simple_object_size: ObjectSize, simple_object_size: ObjectSize,
frostfs_cli: FrostfsCli,
return_nodes_after_test_run, return_nodes_after_test_run,
): ):
""" """
@ -147,20 +139,16 @@ class TestNodeManagement(ClusterTestBase):
exclude_node_from_network_map(random_node, alive_node, shell=self.shell, cluster=self.cluster) exclude_node_from_network_map(random_node, alive_node, shell=self.shell, cluster=self.cluster)
delete_node_data(random_node) delete_node_data(random_node)
cid = create_container( cid = create_container_with_ape(
wallet, ContainerRequest(placement_rule_3, APE_EVERYONE_ALLOW_ALL),
rule=placement_rule_3, frostfs_cli,
basic_acl=PUBLIC_ACL, default_wallet,
shell=self.shell, self.shell,
endpoint=alive_node.get_rpc_endpoint(), self.cluster,
) alive_node.get_rpc_endpoint(),
oid = put_object(
wallet,
source_file_path,
cid,
shell=self.shell,
endpoint=alive_node.get_rpc_endpoint(),
) )
oid = put_object(wallet, source_file_path, cid, self.shell, alive_node.get_rpc_endpoint())
wait_object_replication(cid, oid, 3, shell=self.shell, nodes=storage_nodes) wait_object_replication(cid, oid, 3, shell=self.shell, nodes=storage_nodes)
self.return_nodes(alive_node) self.return_nodes(alive_node)
@ -182,12 +170,13 @@ class TestNodeManagement(ClusterTestBase):
wait_object_replication(cid, oid, 3, shell=self.shell, nodes=storage_nodes) wait_object_replication(cid, oid, 3, shell=self.shell, nodes=storage_nodes)
with reporter.step("Check container could be created with new node"): with reporter.step("Check container could be created with new node"):
cid = create_container( cid = create_container_with_ape(
wallet, ContainerRequest(placement_rule_4, APE_EVERYONE_ALLOW_ALL),
rule=placement_rule_4, frostfs_cli,
basic_acl=PUBLIC_ACL, default_wallet,
shell=self.shell, self.shell,
endpoint=alive_node.get_rpc_endpoint(), self.cluster,
alive_node.get_rpc_endpoint(),
) )
oid = put_object( oid = put_object(
wallet, wallet,
@ -231,48 +220,53 @@ class TestNodeManagement(ClusterTestBase):
@pytest.mark.skip(reason="Need to clarify scenario") @pytest.mark.skip(reason="Need to clarify scenario")
@allure.title("Control Operations with storage nodes") @allure.title("Control Operations with storage nodes")
@requires_container(REP_1_1_1_PUBLIC)
def test_shards( def test_shards(
self, self,
default_wallet, default_wallet: WalletInfo,
create_container_and_pick_node, container: str,
node_with_data: StorageNode,
simple_object_size: ObjectSize, simple_object_size: ObjectSize,
): ):
wallet = default_wallet wallet = default_wallet
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
cid, node = create_container_and_pick_node original_oid = put_object_to_random_node(wallet, file_path, container, self.shell, self.cluster)
original_oid = put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster)
# for mode in ('read-only', 'degraded'): # for mode in ('read-only', 'degraded'):
for mode in ("degraded",): for mode in ("degraded",):
shards = node_shard_list(node) shards = node_shard_list(node_with_data)
assert shards assert shards
for shard in shards: for shard in shards:
node_shard_set_mode(node, shard, mode) node_shard_set_mode(node_with_data, shard, mode)
shards = node_shard_list(node) shards = node_shard_list(node_with_data)
assert shards assert shards
# TODO: Add match for error
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster) put_object_to_random_node(wallet, file_path, container, self.shell, self.cluster)
# TODO: Add match for error
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
delete_object(wallet, cid, original_oid, self.shell, self.cluster.default_rpc_endpoint) delete_object(wallet, container, original_oid, self.shell, self.cluster.default_rpc_endpoint)
get_object_from_random_node(wallet, cid, original_oid, self.shell, self.cluster) get_object_from_random_node(wallet, container, original_oid, self.shell, self.cluster)
for shard in shards: for shard in shards:
node_shard_set_mode(node, shard, "read-write") node_shard_set_mode(node_with_data, shard, "read-write")
shards = node_shard_list(node) shards = node_shard_list(node_with_data)
assert shards assert shards
oid = put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster) oid = put_object_to_random_node(wallet, file_path, container, self.shell, self.cluster)
delete_object(wallet, cid, oid, self.shell, self.cluster.default_rpc_endpoint) delete_object(wallet, container, oid, self.shell, self.cluster.default_rpc_endpoint)
@allure.title("Put object with stopped node") @allure.title("Put object with stopped node")
def test_stop_node(self, default_wallet, return_nodes_after_test_run, simple_object_size: ObjectSize): def test_stop_node(
self, default_wallet: WalletInfo, frostfs_cli: FrostfsCli, return_nodes_after_test_run, simple_object_size: ObjectSize
):
wallet = default_wallet wallet = default_wallet
placement_rule = "REP 3 IN X SELECT 4 FROM * AS X" placement_rule = "REP 3 IN X SELECT 4 FROM * AS X"
source_file_path = generate_file(simple_object_size.value) source_file_path = generate_file(simple_object_size.value)
@ -280,16 +274,20 @@ class TestNodeManagement(ClusterTestBase):
random_node = random.choice(storage_nodes[1:]) random_node = random.choice(storage_nodes[1:])
alive_node = random.choice([storage_node for storage_node in storage_nodes if storage_node.id != random_node.id]) alive_node = random.choice([storage_node for storage_node in storage_nodes if storage_node.id != random_node.id])
cid = create_container( with reporter.step("Create container from random node endpoint"):
wallet, cid = create_container_with_ape(
rule=placement_rule, ContainerRequest(placement_rule, APE_EVERYONE_ALLOW_ALL),
basic_acl=PUBLIC_ACL, frostfs_cli,
shell=self.shell, default_wallet,
endpoint=random_node.get_rpc_endpoint(), self.shell,
) self.cluster,
random_node.get_rpc_endpoint(),
)
with reporter.step("Stop the random node"): with reporter.step("Stop the random node"):
check_nodes.append(random_node) check_nodes.append(random_node)
random_node.stop_service() random_node.stop_service()
with reporter.step("Try to put an object and expect success"): with reporter.step("Try to put an object and expect success"):
put_object( put_object(
wallet, wallet,

View file

@ -3,7 +3,7 @@ import math
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.steps.cli.container import create_container, delete_container, search_nodes_with_container, wait_for_container_deletion from frostfs_testlib.steps.cli.container import delete_container, search_nodes_with_container, wait_for_container_deletion
from frostfs_testlib.steps.cli.object import delete_object, head_object, put_object_to_random_node from frostfs_testlib.steps.cli.object import delete_object, head_object, put_object_to_random_node
from frostfs_testlib.steps.metrics import calc_metrics_count_from_stdout, check_metrics_counter, get_metrics_value from frostfs_testlib.steps.metrics import calc_metrics_count_from_stdout, check_metrics_counter, get_metrics_value
from frostfs_testlib.steps.storage_policy import get_nodes_with_object from frostfs_testlib.steps.storage_policy import get_nodes_with_object
@ -12,8 +12,9 @@ from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.parallel import parallel from frostfs_testlib.testing.parallel import parallel
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import TestFile, generate_file
from ...helpers.container_request import PUBLIC_WITH_POLICY, ContainerRequest, requires_container
from ...helpers.utility import are_numbers_similar from ...helpers.utility import are_numbers_similar
@ -33,124 +34,118 @@ class TestContainerMetrics(ClusterTestBase):
except Exception as e: except Exception as e:
return None return None
@allure.title("Container metrics (obj_size={object_size},policy={policy})") @allure.title("Container metrics (obj_size={object_size}, policy={container_request})")
@pytest.mark.parametrize("placement_policy, policy", [("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", "REP"), ("EC 1.1 CBF 1", "EC")]) @pytest.mark.parametrize(
"container_request, copies",
[
(PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP"), 2),
(PUBLIC_WITH_POLICY("EC 1.1 CBF 1", short_name="EC"), 1),
],
indirect=["container_request"],
)
def test_container_metrics( def test_container_metrics(
self, self,
object_size: ObjectSize, object_size: ObjectSize,
max_object_size: int, max_object_size: int,
default_wallet: WalletInfo, default_wallet: WalletInfo,
cluster: Cluster, cluster: Cluster,
placement_policy: str, copies: int,
policy: str, container: str,
test_file: TestFile,
container_request: ContainerRequest,
): ):
file_path = generate_file(object_size.value)
copies = 2 if policy == "REP" else 1
object_chunks = 1 object_chunks = 1
link_object = 0 link_object = 0
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, placement_policy)
if object_size.value > max_object_size: if object_size.value > max_object_size:
object_chunks = math.ceil(object_size.value / max_object_size) object_chunks = math.ceil(object_size.value / max_object_size)
link_object = len(search_nodes_with_container(default_wallet, cid, self.shell, cluster.default_rpc_endpoint, cluster)) link_object = len(search_nodes_with_container(default_wallet, container, self.shell, cluster.default_rpc_endpoint, cluster))
with reporter.step("Put object to random node"): with reporter.step("Put object to random node"):
oid = put_object_to_random_node( oid = put_object_to_random_node(default_wallet, test_file.path, container, self.shell, cluster)
wallet=default_wallet,
path=file_path,
cid=cid,
shell=self.shell,
cluster=cluster,
)
with reporter.step("Get object nodes"): with reporter.step("Get object nodes"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, cluster.storage_nodes)
object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes] object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes]
with reporter.step("Check metric appears in node where the object is located"): with reporter.step("Check metric appears in node where the object is located"):
count_metrics = (object_chunks * copies) + link_object count_metrics = (object_chunks * copies) + link_object
if policy == "EC": if container_request.short_name == "EC":
count_metrics = (object_chunks * 2) + link_object count_metrics = (object_chunks * 2) + link_object
check_metrics_counter(object_nodes, counter_exp=count_metrics, command="container_objects_total", cid=cid, type="phy") check_metrics_counter(object_nodes, counter_exp=count_metrics, command="container_objects_total", cid=container, type="phy")
check_metrics_counter(object_nodes, counter_exp=count_metrics, command="container_objects_total", cid=cid, type="logic") check_metrics_counter(object_nodes, counter_exp=count_metrics, command="container_objects_total", cid=container, type="logic")
check_metrics_counter(object_nodes, counter_exp=copies, command="container_objects_total", cid=cid, type="user") check_metrics_counter(object_nodes, counter_exp=copies, command="container_objects_total", cid=container, type="user")
with reporter.step("Delete file, wait until gc remove object"): with reporter.step("Delete file, wait until gc remove object"):
delete_object(default_wallet, cid, oid, self.shell, cluster.default_rpc_endpoint) delete_object(default_wallet, container, oid, self.shell, cluster.default_rpc_endpoint)
with reporter.step(f"Check container metrics 'the counter should equal {len(object_nodes)}' in object nodes"): with reporter.step(f"Check container metrics 'the counter should equal {len(object_nodes)}' in object nodes"):
check_metrics_counter(object_nodes, counter_exp=len(object_nodes), command="container_objects_total", cid=cid, type="phy") check_metrics_counter(object_nodes, counter_exp=len(object_nodes), command="container_objects_total", cid=container, type="phy")
check_metrics_counter(object_nodes, counter_exp=len(object_nodes), command="container_objects_total", cid=cid, type="logic") check_metrics_counter(
check_metrics_counter(object_nodes, counter_exp=0, command="container_objects_total", cid=cid, type="user") object_nodes, counter_exp=len(object_nodes), command="container_objects_total", cid=container, type="logic"
)
check_metrics_counter(object_nodes, counter_exp=0, command="container_objects_total", cid=container, type="user")
with reporter.step("Check metrics(Phy, Logic, User) in each nodes"): with reporter.step("Check metrics(Phy, Logic, User) in each nodes"):
# Phy and Logic metrics are 4, because in rule 'CBF 2 SELECT 2 FROM', cbf2*sel2=4 # Phy and Logic metrics are x2, because in rule 'CBF 2 SELECT 2 FROM', cbf2*sel2=4
expect_metrics = 4 if policy == "REP" else 2 expect_metrics = copies * 2
check_metrics_counter(cluster.cluster_nodes, counter_exp=expect_metrics, command="container_objects_total", cid=cid, type="phy")
check_metrics_counter( check_metrics_counter(
cluster.cluster_nodes, counter_exp=expect_metrics, command="container_objects_total", cid=cid, type="logic" cluster.cluster_nodes, counter_exp=expect_metrics, command="container_objects_total", cid=container, type="phy"
) )
check_metrics_counter(cluster.cluster_nodes, counter_exp=0, command="container_objects_total", cid=cid, type="user") check_metrics_counter(
cluster.cluster_nodes, counter_exp=expect_metrics, command="container_objects_total", cid=container, type="logic"
)
check_metrics_counter(cluster.cluster_nodes, counter_exp=0, command="container_objects_total", cid=container, type="user")
@allure.title("Container size metrics (obj_size={object_size},policy={policy})") @allure.title("Container size metrics (obj_size={object_size}, policy={container_request})")
@pytest.mark.parametrize("placement_policy, policy", [("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", "REP"), ("EC 1.1 CBF 1", "EC")]) @requires_container(
[PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP"), PUBLIC_WITH_POLICY("EC 1.1 CBF 1", short_name="EC")]
)
def test_container_size_metrics( def test_container_size_metrics(
self, self,
object_size: ObjectSize, object_size: ObjectSize,
default_wallet: WalletInfo, default_wallet: WalletInfo,
placement_policy: str, test_file: TestFile,
policy: str, container: str,
): ):
file_path = generate_file(object_size.value)
with reporter.step(f"Create container with policy {policy}"):
cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, placement_policy)
with reporter.step("Put object to random node"): with reporter.step("Put object to random node"):
oid = put_object_to_random_node( oid = put_object_to_random_node(default_wallet, test_file.path, container, self.shell, self.cluster)
wallet=default_wallet,
path=file_path,
cid=cid,
shell=self.shell,
cluster=self.cluster,
)
with reporter.step("Get object nodes"): with reporter.step("Get object nodes"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, self.cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, self.cluster.storage_nodes)
object_nodes = [ object_nodes = [
cluster_node for cluster_node in self.cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes cluster_node for cluster_node in self.cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes
] ]
with reporter.step("Check metric appears in all node where the object is located"): with reporter.step("Check metric appears in all node where the object is located"):
act_metric = sum( act_metric = sum(
[get_metrics_value(node, command="frostfs_node_engine_container_size_bytes", cid=cid) for node in object_nodes] [get_metrics_value(node, command="frostfs_node_engine_container_size_bytes", cid=container) for node in object_nodes]
) )
assert (act_metric // 2) == object_size.value assert (act_metric // 2) == object_size.value
with reporter.step("Delete file, wait until gc remove object"): with reporter.step("Delete file, wait until gc remove object"):
id_tombstone = delete_object(default_wallet, cid, oid, self.shell, self.cluster.default_rpc_endpoint) id_tombstone = delete_object(default_wallet, container, oid, self.shell, self.cluster.default_rpc_endpoint)
tombstone = head_object(default_wallet, cid, id_tombstone, self.shell, self.cluster.default_rpc_endpoint) tombstone = head_object(default_wallet, container, id_tombstone, self.shell, self.cluster.default_rpc_endpoint)
with reporter.step(f"Check container size metrics"): with reporter.step(f"Check container size metrics"):
act_metric = get_metrics_value(object_nodes[0], command="frostfs_node_engine_container_size_bytes", cid=cid) act_metric = get_metrics_value(object_nodes[0], command="frostfs_node_engine_container_size_bytes", cid=container)
assert act_metric == int(tombstone["header"]["payloadLength"]) assert act_metric == int(tombstone["header"]["payloadLength"])
@allure.title("Container size metrics put {objects_count} objects (obj_size={object_size})") @allure.title("Container size metrics put {objects_count} objects (obj_size={object_size})")
@pytest.mark.parametrize("objects_count", [5, 10, 20]) @pytest.mark.parametrize("objects_count", [5, 10, 20])
def test_container_size_metrics_more_objects(self, object_size: ObjectSize, default_wallet: WalletInfo, objects_count: int): @requires_container
with reporter.step(f"Create container"): def test_container_size_metrics_more_objects(
cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint) self, object_size: ObjectSize, default_wallet: WalletInfo, objects_count: int, container: str
):
with reporter.step(f"Put {objects_count} objects"): with reporter.step(f"Put {objects_count} objects"):
files_path = [generate_file(object_size.value) for _ in range(objects_count)] files_path = [generate_file(object_size.value) for _ in range(objects_count)]
futures = parallel(self.put_object_parallel, files_path, wallet=default_wallet, cid=cid) futures = parallel(self.put_object_parallel, files_path, wallet=default_wallet, cid=container)
oids = [future.result() for future in futures] oids = [future.result() for future in futures]
with reporter.step("Check metric appears in all nodes"): with reporter.step("Check metric appears in all nodes"):
metric_values = [ metric_values = [
get_metrics_value(node, command="frostfs_node_engine_container_size_bytes", cid=cid) for node in self.cluster.cluster_nodes get_metrics_value(node, command="frostfs_node_engine_container_size_bytes", cid=container)
for node in self.cluster.cluster_nodes
] ]
actual_value = sum(metric_values) // 2 # for policy REP 2, value divide by 2 actual_value = sum(metric_values) // 2 # for policy REP 2, value divide by 2
expected_value = object_size.value * objects_count expected_value = object_size.value * objects_count
@ -161,47 +156,61 @@ class TestContainerMetrics(ClusterTestBase):
with reporter.step("Delete file, wait until gc remove object"): with reporter.step("Delete file, wait until gc remove object"):
tombstones_size = 0 tombstones_size = 0
for oid in oids: for oid in oids:
tombstone_id = delete_object(default_wallet, cid, oid, self.shell, self.cluster.default_rpc_endpoint) tombstone_id = delete_object(default_wallet, container, oid, self.shell, self.cluster.default_rpc_endpoint)
tombstone = head_object(default_wallet, cid, tombstone_id, self.shell, self.cluster.default_rpc_endpoint) tombstone = head_object(default_wallet, container, tombstone_id, self.shell, self.cluster.default_rpc_endpoint)
tombstones_size += int(tombstone["header"]["payloadLength"]) tombstones_size += int(tombstone["header"]["payloadLength"])
with reporter.step(f"Check container size metrics, 'should be positive in all nodes'"): with reporter.step(f"Check container size metrics, 'should be positive in all nodes'"):
futures = parallel(get_metrics_value, self.cluster.cluster_nodes, command="frostfs_node_engine_container_size_bytes", cid=cid) futures = parallel(
get_metrics_value, self.cluster.cluster_nodes, command="frostfs_node_engine_container_size_bytes", cid=container
)
metrics_value_nodes = [future.result() for future in futures] metrics_value_nodes = [future.result() for future in futures]
for act_metric in metrics_value_nodes: for act_metric in metrics_value_nodes:
assert act_metric >= 0, "Metrics value is negative" assert act_metric >= 0, "Metrics value is negative"
assert sum(metrics_value_nodes) // len(self.cluster.cluster_nodes) == tombstones_size, "tomstone size of objects not correct" assert sum(metrics_value_nodes) // len(self.cluster.cluster_nodes) == tombstones_size, "tomstone size of objects not correct"
@allure.title("Container metrics (policy={policy})") @allure.title("Container metrics (policy={container_request})")
@pytest.mark.parametrize("placement_policy, policy", [("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", "REP"), ("EC 1.1 CBF 1", "EC")]) @pytest.mark.parametrize(
"container_request, copies",
[
(PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP"), 2),
(PUBLIC_WITH_POLICY("EC 1.1 CBF 1", short_name="EC"), 1),
],
indirect=["container_request"],
)
def test_container_metrics_delete_complex_objects( def test_container_metrics_delete_complex_objects(
self, complex_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster, placement_policy: str, policy: str self,
complex_object_size: ObjectSize,
default_wallet: WalletInfo,
cluster: Cluster,
copies: int,
container: str,
container_request: ContainerRequest,
): ):
copies = 2 if policy == "REP" else 1
objects_count = 2 objects_count = 2
metric_name = "frostfs_node_engine_container_objects_total" metric_name = "frostfs_node_engine_container_objects_total"
with reporter.step(f"Create container"):
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, rule=placement_policy)
with reporter.step(f"Put {objects_count} objects"): with reporter.step(f"Put {objects_count} objects"):
files_path = [generate_file(complex_object_size.value) for _ in range(objects_count)] files_path = [generate_file(complex_object_size.value) for _ in range(objects_count)]
futures = parallel(self.put_object_parallel, files_path, wallet=default_wallet, cid=cid) futures = parallel(self.put_object_parallel, files_path, wallet=default_wallet, cid=container)
oids = [future.result() for future in futures] oids = [future.result() for future in futures]
with reporter.step(f"Check metrics value in each nodes, should be {objects_count} for 'user'"): with reporter.step(f"Check metrics value in each nodes, should be {objects_count} for 'user'"):
check_metrics_counter(cluster.cluster_nodes, counter_exp=objects_count * copies, command=metric_name, cid=cid, type="user") check_metrics_counter(
cluster.cluster_nodes, counter_exp=objects_count * copies, command=metric_name, cid=container, type="user"
)
with reporter.step("Delete objects and container"): with reporter.step("Delete objects and container"):
for oid in oids: for oid in oids:
delete_object(default_wallet, cid, oid, self.shell, cluster.default_rpc_endpoint) delete_object(default_wallet, container, oid, self.shell, cluster.default_rpc_endpoint)
delete_container(default_wallet, cid, self.shell, cluster.default_rpc_endpoint) delete_container(default_wallet, container, self.shell, cluster.default_rpc_endpoint)
with reporter.step("Tick epoch and check container was deleted"): with reporter.step("Tick epoch and check container was deleted"):
self.tick_epoch() self.tick_epoch()
wait_for_container_deletion(default_wallet, cid, shell=self.shell, endpoint=cluster.default_rpc_endpoint) wait_for_container_deletion(default_wallet, container, shell=self.shell, endpoint=cluster.default_rpc_endpoint)
with reporter.step(f"Check metrics value in each nodes, should not be show any result"): with reporter.step(f"Check metrics value in each nodes, should not be show any result"):
futures = parallel(self.get_metrics_search_by_greps_parallel, cluster.cluster_nodes, command=metric_name, cid=cid) futures = parallel(self.get_metrics_search_by_greps_parallel, cluster.cluster_nodes, command=metric_name, cid=container)
metrics_results = [future.result() for future in futures if future.result() is not None] metrics_results = [future.result() for future in futures if future.result() is not None]
assert len(metrics_results) == 0, f"Metrics value is not empty in Prometheus, actual value in nodes: {metrics_results}" assert len(metrics_results) == 0, f"Metrics value is not empty in Prometheus, actual value in nodes: {metrics_results}"

View file

@ -1,11 +1,9 @@
import random
import re import re
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.steps.cli.container import create_container from frostfs_testlib.steps.cli.object import delete_object, put_object_to_random_node
from frostfs_testlib.steps.cli.object import delete_object, put_object, put_object_to_random_node
from frostfs_testlib.steps.metrics import check_metrics_counter, get_metrics_value from frostfs_testlib.steps.metrics import check_metrics_counter, get_metrics_value
from frostfs_testlib.steps.storage_policy import get_nodes_with_object from frostfs_testlib.steps.storage_policy import get_nodes_with_object
from frostfs_testlib.storage.cluster import Cluster, ClusterNode from frostfs_testlib.storage.cluster import Cluster, ClusterNode
@ -15,8 +13,11 @@ from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.test_control import wait_for_success from frostfs_testlib.testing.test_control import wait_for_success
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_request import PUBLIC_WITH_POLICY, requires_container
@pytest.mark.nightly @pytest.mark.nightly
@pytest.mark.metrics
class TestGarbageCollectorMetrics(ClusterTestBase): class TestGarbageCollectorMetrics(ClusterTestBase):
@wait_for_success(interval=10) @wait_for_success(interval=10)
def check_metrics_in_node(self, cluster_node: ClusterNode, counter_exp: int, **metrics_greps: str): def check_metrics_in_node(self, cluster_node: ClusterNode, counter_exp: int, **metrics_greps: str):
@ -34,9 +35,11 @@ class TestGarbageCollectorMetrics(ClusterTestBase):
return sum(map(int, result)) return sum(map(int, result))
@allure.title("Garbage collector expire_at object") @allure.title("Garbage collector expire_at object")
def test_garbage_collector_metrics_expire_at_object(self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster): @requires_container(PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP 2"))
def test_garbage_collector_metrics_expire_at_object(
self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster, container: str
):
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
placement_policy = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
metrics_step = 1 metrics_step = 1
with reporter.step("Get current garbage collector metrics for each nodes"): with reporter.step("Get current garbage collector metrics for each nodes"):
@ -44,22 +47,19 @@ class TestGarbageCollectorMetrics(ClusterTestBase):
for node in cluster.cluster_nodes: for node in cluster.cluster_nodes:
metrics_counter[node] = get_metrics_value(node, command="frostfs_node_garbage_collector_marked_for_removal_objects_total") metrics_counter[node] = get_metrics_value(node, command="frostfs_node_garbage_collector_marked_for_removal_objects_total")
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, placement_policy)
with reporter.step("Put object to random node with expire_at"): with reporter.step("Put object to random node with expire_at"):
current_epoch = self.get_epoch() current_epoch = self.get_epoch()
oid = put_object_to_random_node( oid = put_object_to_random_node(
default_wallet, default_wallet,
file_path, file_path,
cid, container,
self.shell, self.shell,
cluster, cluster,
expire_at=current_epoch + 1, expire_at=current_epoch + 1,
) )
with reporter.step("Get object nodes"): with reporter.step("Get object nodes"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, cluster.storage_nodes)
object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes] object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes]
with reporter.step("Tick Epoch"): with reporter.step("Tick Epoch"):
@ -77,9 +77,11 @@ class TestGarbageCollectorMetrics(ClusterTestBase):
) )
@allure.title("Garbage collector delete object") @allure.title("Garbage collector delete object")
def test_garbage_collector_metrics_deleted_objects(self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster): @requires_container(PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP 2"))
def test_garbage_collector_metrics_deleted_objects(
self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster, container: str
):
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
placement_policy = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
metrics_step = 1 metrics_step = 1
with reporter.step("Get current garbage collector metrics for each nodes"): with reporter.step("Get current garbage collector metrics for each nodes"):
@ -87,24 +89,21 @@ class TestGarbageCollectorMetrics(ClusterTestBase):
for node in cluster.cluster_nodes: for node in cluster.cluster_nodes:
metrics_counter[node] = get_metrics_value(node, command="frostfs_node_garbage_collector_deleted_objects_total") metrics_counter[node] = get_metrics_value(node, command="frostfs_node_garbage_collector_deleted_objects_total")
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, node.storage_node.get_rpc_endpoint(), placement_policy)
with reporter.step("Put object to random node"): with reporter.step("Put object to random node"):
oid = put_object_to_random_node( oid = put_object_to_random_node(
default_wallet, default_wallet,
file_path, file_path,
cid, container,
self.shell, self.shell,
cluster, cluster,
) )
with reporter.step("Get object nodes"): with reporter.step("Get object nodes"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, cluster.storage_nodes)
object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes] object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes]
with reporter.step("Delete file, wait until gc remove object"): with reporter.step("Delete file, wait until gc remove object"):
delete_object(default_wallet, cid, oid, self.shell, node.storage_node.get_rpc_endpoint()) delete_object(default_wallet, container, oid, self.shell, node.storage_node.get_rpc_endpoint())
with reporter.step(f"Check garbage collector metrics 'the counter should increase by {metrics_step}'"): with reporter.step(f"Check garbage collector metrics 'the counter should increase by {metrics_step}'"):
for node in object_nodes: for node in object_nodes:

View file

@ -19,6 +19,7 @@ from frostfs_testlib.utils.file_utils import generate_file
@pytest.mark.nightly @pytest.mark.nightly
@pytest.mark.metrics
class TestGRPCMetrics(ClusterTestBase): class TestGRPCMetrics(ClusterTestBase):
@pytest.fixture @pytest.fixture
def disable_policer(self, cluster_state_controller: ClusterStateController): def disable_policer(self, cluster_state_controller: ClusterStateController):
@ -84,22 +85,18 @@ class TestGRPCMetrics(ClusterTestBase):
@allure.title("GRPC metrics object operations") @allure.title("GRPC metrics object operations")
def test_grpc_metrics_object_operations( def test_grpc_metrics_object_operations(
self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster, disable_policer self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster, container: str, disable_policer
): ):
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
placement_policy = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
with reporter.step("Select random node"): with reporter.step("Select random node"):
node = random.choice(cluster.cluster_nodes) node = random.choice(cluster.cluster_nodes)
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, node.storage_node.get_rpc_endpoint(), placement_policy)
with reporter.step("Get current gRPC metrics for method 'Put'"): with reporter.step("Get current gRPC metrics for method 'Put'"):
metrics_counter_put = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Put") metrics_counter_put = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Put")
with reporter.step("Put object to selected node"): with reporter.step("Put object to selected node"):
oid = put_object(default_wallet, file_path, cid, self.shell, node.storage_node.get_rpc_endpoint()) oid = put_object(default_wallet, file_path, container, self.shell, node.storage_node.get_rpc_endpoint())
with reporter.step(f"Check gRPC metrics method 'Put', 'the counter should increase by 1'"): with reporter.step(f"Check gRPC metrics method 'Put', 'the counter should increase by 1'"):
metrics_counter_put += 1 metrics_counter_put += 1
@ -115,7 +112,7 @@ class TestGRPCMetrics(ClusterTestBase):
metrics_counter_get = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Get") metrics_counter_get = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Get")
with reporter.step(f"Get object"): with reporter.step(f"Get object"):
get_object(default_wallet, cid, oid, self.shell, node.storage_node.get_rpc_endpoint()) get_object(default_wallet, container, oid, self.shell, node.storage_node.get_rpc_endpoint())
with reporter.step(f"Check gRPC metrics method=Get, 'the counter should increase by 1'"): with reporter.step(f"Check gRPC metrics method=Get, 'the counter should increase by 1'"):
metrics_counter_get += 1 metrics_counter_get += 1
@ -131,7 +128,7 @@ class TestGRPCMetrics(ClusterTestBase):
metrics_counter_search = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Search") metrics_counter_search = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Search")
with reporter.step(f"Search object"): with reporter.step(f"Search object"):
search_object(default_wallet, cid, self.shell, node.storage_node.get_rpc_endpoint()) search_object(default_wallet, container, self.shell, node.storage_node.get_rpc_endpoint())
with reporter.step(f"Check gRPC metrics method=Search, 'the counter should increase by 1'"): with reporter.step(f"Check gRPC metrics method=Search, 'the counter should increase by 1'"):
metrics_counter_search += 1 metrics_counter_search += 1
@ -147,7 +144,7 @@ class TestGRPCMetrics(ClusterTestBase):
metrics_counter_head = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Head") metrics_counter_head = get_metrics_value(node, command="grpc_server_handled_total", service="ObjectService", method="Head")
with reporter.step(f"Head object"): with reporter.step(f"Head object"):
head_object(default_wallet, cid, oid, self.shell, node.storage_node.get_rpc_endpoint()) head_object(default_wallet, container, oid, self.shell, node.storage_node.get_rpc_endpoint())
with reporter.step(f"Check gRPC metrics method=Head, 'the counter should increase by 1'"): with reporter.step(f"Check gRPC metrics method=Head, 'the counter should increase by 1'"):
metrics_counter_head += 1 metrics_counter_head += 1

View file

@ -6,7 +6,7 @@ import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.steps.metrics import get_metrics_value from frostfs_testlib.steps.metrics import get_metrics_value
from frostfs_testlib.storage.cluster import Cluster, ClusterNode from frostfs_testlib.storage.cluster import ClusterNode
from frostfs_testlib.storage.controllers.cluster_state_controller import ClusterStateController from frostfs_testlib.storage.controllers.cluster_state_controller import ClusterStateController
from frostfs_testlib.storage.controllers.state_managers.config_state_manager import ConfigStateManager from frostfs_testlib.storage.controllers.state_managers.config_state_manager import ConfigStateManager
from frostfs_testlib.storage.dataclasses.frostfs_services import StorageNode from frostfs_testlib.storage.dataclasses.frostfs_services import StorageNode
@ -15,6 +15,7 @@ from frostfs_testlib.testing.test_control import wait_for_success
@pytest.mark.nightly @pytest.mark.nightly
@pytest.mark.metrics
class TestLogsMetrics(ClusterTestBase): class TestLogsMetrics(ClusterTestBase):
@pytest.fixture @pytest.fixture
def revert_all(self, cluster_state_controller: ClusterStateController): def revert_all(self, cluster_state_controller: ClusterStateController):

View file

@ -4,73 +4,73 @@ import re
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.steps.cli.container import create_container, delete_container, search_nodes_with_container from frostfs_testlib.steps.cli.container import delete_container, search_nodes_with_container
from frostfs_testlib.steps.cli.object import delete_object, lock_object, put_object, put_object_to_random_node from frostfs_testlib.steps.cli.object import delete_object, lock_object, put_object, put_object_to_random_node
from frostfs_testlib.steps.metrics import check_metrics_counter, get_metrics_value from frostfs_testlib.steps.metrics import check_metrics_counter, get_metrics_value
from frostfs_testlib.steps.storage_policy import get_nodes_with_object from frostfs_testlib.steps.storage_policy import get_nodes_with_object
from frostfs_testlib.storage.cluster import Cluster, ClusterNode from frostfs_testlib.storage.cluster import Cluster, ClusterNode
from frostfs_testlib.storage.controllers.cluster_state_controller import ClusterStateController from frostfs_testlib.storage.controllers.cluster_state_controller import ClusterStateController
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import TestFile
from ...helpers.container_request import PUBLIC_WITH_POLICY, ContainerRequest, requires_container
@pytest.mark.nightly @pytest.mark.nightly
@pytest.mark.metrics
class TestObjectMetrics(ClusterTestBase): class TestObjectMetrics(ClusterTestBase):
@allure.title("Object metrics of removed container (obj_size={object_size})") @allure.title("Object metrics of removed container (obj_size={object_size})")
def test_object_metrics_removed_container(self, object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster): @requires_container(PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X"))
file_path = generate_file(object_size.value) def test_object_metrics_removed_container(self, default_wallet: WalletInfo, cluster: Cluster, container: str, test_file: TestFile):
placement_policy = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
copies = 2
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, placement_policy)
with reporter.step("Put object to random node"): with reporter.step("Put object to random node"):
oid = put_object_to_random_node(default_wallet, file_path, cid, self.shell, cluster) oid = put_object_to_random_node(default_wallet, test_file.path, container, self.shell, cluster)
with reporter.step("Check metric appears in node where the object is located"): with reporter.step("Check metric appears in node where the object is located"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, cluster.storage_nodes)
object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes] object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes]
check_metrics_counter( check_metrics_counter(
object_nodes, object_nodes,
counter_exp=copies, counter_exp=2,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
with reporter.step("Delete container"): with reporter.step("Delete container"):
delete_container(default_wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint) delete_container(default_wallet, container, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
with reporter.step("Tick Epoch"): with reporter.step("Tick Epoch"):
self.tick_epochs(epochs_to_tick=2, wait_block=2) self.tick_epochs(epochs_to_tick=2, wait_block=2)
with reporter.step("Check metrics of removed containers doesn't appear in the storage node"): with reporter.step("Check metrics of removed containers doesn't appear in the storage node"):
check_metrics_counter(object_nodes, counter_exp=0, command="frostfs_node_engine_container_objects_total", cid=cid, type="user") check_metrics_counter(
check_metrics_counter(object_nodes, counter_exp=0, command="frostfs_node_engine_container_size_byte", cid=cid) object_nodes, counter_exp=0, command="frostfs_node_engine_container_objects_total", cid=container, type="user"
)
check_metrics_counter(object_nodes, counter_exp=0, command="frostfs_node_engine_container_size_byte", cid=container)
for node in object_nodes: for node in object_nodes:
all_metrics = node.metrics.storage.get_metrics_search_by_greps(command="frostfs_node_engine_container_size_byte") all_metrics = node.metrics.storage.get_metrics_search_by_greps(command="frostfs_node_engine_container_size_byte")
assert cid not in all_metrics.stdout, "metrics of removed containers shouldn't appear in the storage node" assert container not in all_metrics.stdout, "metrics of removed containers shouldn't appear in the storage node"
@allure.title("Object metrics, locked object (obj_size={object_size}, policy={placement_policy})") @allure.title("Object metrics, locked object (obj_size={object_size}, policy={container_request})")
@pytest.mark.parametrize("placement_policy", ["REP 1 IN X CBF 1 SELECT 1 FROM * AS X", "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"]) @requires_container(
[
PUBLIC_WITH_POLICY("REP 1 IN X CBF 1 SELECT 1 FROM * AS X", short_name="REP 1"),
PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP 2"),
]
)
def test_object_metrics_blocked_object( def test_object_metrics_blocked_object(
self, object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster, placement_policy: str self, default_wallet: WalletInfo, cluster: Cluster, container: str, container_request: ContainerRequest, test_file: TestFile
): ):
file_path = generate_file(object_size.value) metric_step = int(re.search(r"REP\s(\d+)", container_request.policy).group(1))
metric_step = int(re.search(r"REP\s(\d+)", placement_policy).group(1))
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, placement_policy)
with reporter.step("Search container nodes"): with reporter.step("Search container nodes"):
container_nodes = search_nodes_with_container( container_nodes = search_nodes_with_container(
wallet=default_wallet, wallet=default_wallet,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint, endpoint=self.cluster.default_rpc_endpoint,
cluster=cluster, cluster=cluster,
@ -82,7 +82,7 @@ class TestObjectMetrics(ClusterTestBase):
objects_metric_counter += get_metrics_value(node, command="frostfs_node_engine_objects_total", type="user") objects_metric_counter += get_metrics_value(node, command="frostfs_node_engine_objects_total", type="user")
with reporter.step("Put object to container node"): with reporter.step("Put object to container node"):
oid = put_object(default_wallet, file_path, cid, self.shell, container_nodes[0].storage_node.get_rpc_endpoint()) oid = put_object(default_wallet, test_file.path, container, self.shell, container_nodes[0].storage_node.get_rpc_endpoint())
with reporter.step(f"Check metric user 'the counter should increase by {metric_step}'"): with reporter.step(f"Check metric user 'the counter should increase by {metric_step}'"):
objects_metric_counter += metric_step objects_metric_counter += metric_step
@ -96,12 +96,12 @@ class TestObjectMetrics(ClusterTestBase):
container_nodes, container_nodes,
counter_exp=metric_step, counter_exp=metric_step,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
with reporter.step("Delete object"): with reporter.step("Delete object"):
delete_object(default_wallet, cid, oid, self.shell, self.cluster.default_rpc_endpoint) delete_object(default_wallet, container, oid, self.shell, self.cluster.default_rpc_endpoint)
with reporter.step(f"Check metric user 'the counter should decrease by {metric_step}'"): with reporter.step(f"Check metric user 'the counter should decrease by {metric_step}'"):
objects_metric_counter -= metric_step objects_metric_counter -= metric_step
@ -115,16 +115,16 @@ class TestObjectMetrics(ClusterTestBase):
container_nodes, container_nodes,
counter_exp=0, counter_exp=0,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
with reporter.step("Put object and lock it to next epoch"): with reporter.step("Put object and lock it to next epoch"):
oid = put_object(default_wallet, file_path, cid, self.shell, container_nodes[0].storage_node.get_rpc_endpoint()) oid = put_object(default_wallet, test_file.path, container, self.shell, container_nodes[0].storage_node.get_rpc_endpoint())
current_epoch = self.get_epoch() current_epoch = self.get_epoch()
lock_object( lock_object(
default_wallet, default_wallet,
cid, container,
oid, oid,
self.shell, self.shell,
container_nodes[0].storage_node.get_rpc_endpoint(), container_nodes[0].storage_node.get_rpc_endpoint(),
@ -143,7 +143,7 @@ class TestObjectMetrics(ClusterTestBase):
container_nodes, container_nodes,
counter_exp=metric_step, counter_exp=metric_step,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
@ -157,7 +157,7 @@ class TestObjectMetrics(ClusterTestBase):
) )
with reporter.step("Delete object"): with reporter.step("Delete object"):
delete_object(default_wallet, cid, oid, self.shell, self.cluster.default_rpc_endpoint) delete_object(default_wallet, container, oid, self.shell, self.cluster.default_rpc_endpoint)
with reporter.step(f"Check metric user 'the counter should decrease by {metric_step}'"): with reporter.step(f"Check metric user 'the counter should decrease by {metric_step}'"):
objects_metric_counter -= metric_step objects_metric_counter -= metric_step
@ -171,7 +171,7 @@ class TestObjectMetrics(ClusterTestBase):
container_nodes, container_nodes,
counter_exp=0, counter_exp=0,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
@ -179,8 +179,8 @@ class TestObjectMetrics(ClusterTestBase):
current_epoch = self.get_epoch() current_epoch = self.get_epoch()
oid = put_object( oid = put_object(
default_wallet, default_wallet,
file_path, test_file.path,
cid, container,
self.shell, self.shell,
container_nodes[0].storage_node.get_rpc_endpoint(), container_nodes[0].storage_node.get_rpc_endpoint(),
expire_at=current_epoch + 1, expire_at=current_epoch + 1,
@ -198,7 +198,7 @@ class TestObjectMetrics(ClusterTestBase):
container_nodes, container_nodes,
counter_exp=metric_step, counter_exp=metric_step,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
@ -217,31 +217,28 @@ class TestObjectMetrics(ClusterTestBase):
container_nodes, container_nodes,
counter_exp=0, counter_exp=0,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
cid=cid, cid=container,
type="user", type="user",
) )
@allure.title("Object metrics, stop the node (obj_size={object_size})") @allure.title("Object metrics, stop the node (obj_size={object_size})")
@requires_container(PUBLIC_WITH_POLICY("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", short_name="REP 2"))
def test_object_metrics_stop_node( def test_object_metrics_stop_node(
self, self,
object_size: ObjectSize,
default_wallet: WalletInfo, default_wallet: WalletInfo,
cluster_state_controller: ClusterStateController, cluster_state_controller: ClusterStateController,
container: str,
test_file: TestFile,
): ):
placement_policy = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
file_path = generate_file(object_size.value)
copies = 2 copies = 2
with reporter.step(f"Create container with policy {placement_policy}"):
cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, placement_policy)
with reporter.step(f"Check object metrics in container 'should be zero'"): with reporter.step(f"Check object metrics in container 'should be zero'"):
check_metrics_counter( check_metrics_counter(
self.cluster.cluster_nodes, self.cluster.cluster_nodes,
counter_exp=0, counter_exp=0,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
type="user", type="user",
cid=cid, cid=container,
) )
with reporter.step("Get current metrics for each nodes"): with reporter.step("Get current metrics for each nodes"):
@ -250,10 +247,10 @@ class TestObjectMetrics(ClusterTestBase):
objects_metric_counter[node] = get_metrics_value(node, command="frostfs_node_engine_objects_total", type="user") objects_metric_counter[node] = get_metrics_value(node, command="frostfs_node_engine_objects_total", type="user")
with reporter.step("Put object"): with reporter.step("Put object"):
oid = put_object(default_wallet, file_path, cid, self.shell, self.cluster.default_rpc_endpoint) oid = put_object(default_wallet, test_file.path, container, self.shell, self.cluster.default_rpc_endpoint)
with reporter.step("Get object nodes"): with reporter.step("Get object nodes"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, self.cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, self.cluster.storage_nodes)
object_nodes = [ object_nodes = [
cluster_node for cluster_node in self.cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes cluster_node for cluster_node in self.cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes
] ]
@ -266,7 +263,7 @@ class TestObjectMetrics(ClusterTestBase):
counter_exp=copies, counter_exp=copies,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
type="user", type="user",
cid=cid, cid=container,
) )
with reporter.step(f"Select node to stop"): with reporter.step(f"Select node to stop"):
@ -290,5 +287,5 @@ class TestObjectMetrics(ClusterTestBase):
counter_exp=copies, counter_exp=copies,
command="frostfs_node_engine_container_objects_total", command="frostfs_node_engine_container_objects_total",
type="user", type="user",
cid=cid, cid=container,
) )

View file

@ -5,8 +5,6 @@ import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND
from frostfs_testlib.resources.wellknown_acl import EACL_PUBLIC_READ_WRITE
from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.cli.object import get_object, put_object from frostfs_testlib.steps.cli.object import get_object, put_object
from frostfs_testlib.steps.metrics import check_metrics_counter from frostfs_testlib.steps.metrics import check_metrics_counter
from frostfs_testlib.steps.node_management import node_shard_list, node_shard_set_mode from frostfs_testlib.steps.node_management import node_shard_list, node_shard_set_mode
@ -18,8 +16,11 @@ from frostfs_testlib.testing import parallel, wait_for_success
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_request import PUBLIC_WITH_POLICY, requires_container
@pytest.mark.nightly @pytest.mark.nightly
@pytest.mark.metrics
class TestShardMetrics(ClusterTestBase): class TestShardMetrics(ClusterTestBase):
@pytest.fixture() @pytest.fixture()
@allure.title("Get two shards for set mode") @allure.title("Get two shards for set mode")
@ -127,28 +128,22 @@ class TestShardMetrics(ClusterTestBase):
) )
@allure.title("Metric for error count on shard") @allure.title("Metric for error count on shard")
def test_shard_metrics_error_count(self, max_object_size: int, default_wallet: WalletInfo, cluster: Cluster, revert_all_shards_mode): @requires_container(PUBLIC_WITH_POLICY("REP 1 CBF 1", short_name="REP 1"))
def test_shard_metrics_error_count(
self, max_object_size: int, default_wallet: WalletInfo, cluster: Cluster, container: str, revert_all_shards_mode
):
file_path = generate_file(round(max_object_size * 0.8)) file_path = generate_file(round(max_object_size * 0.8))
with reporter.step(f"Create container"):
cid = create_container(
wallet=default_wallet,
shell=self.shell,
endpoint=cluster.default_rpc_endpoint,
rule="REP 1 CBF 1",
basic_acl=EACL_PUBLIC_READ_WRITE,
)
with reporter.step("Put object"): with reporter.step("Put object"):
oid = put_object(default_wallet, file_path, cid, self.shell, cluster.default_rpc_endpoint) oid = put_object(default_wallet, file_path, container, self.shell, cluster.default_rpc_endpoint)
with reporter.step("Get object nodes"): with reporter.step("Get object nodes"):
object_storage_nodes = get_nodes_with_object(cid, oid, self.shell, cluster.storage_nodes) object_storage_nodes = get_nodes_with_object(container, oid, self.shell, cluster.storage_nodes)
object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes] object_nodes = [cluster_node for cluster_node in cluster.cluster_nodes if cluster_node.storage_node in object_storage_nodes]
node = random.choice(object_nodes) node = random.choice(object_nodes)
with reporter.step("Search object in system."): with reporter.step("Search object in system."):
object_path, object_name = self.get_object_path_and_name_file(oid, cid, node) object_path, object_name = self.get_object_path_and_name_file(oid, container, node)
with reporter.step("Block read file"): with reporter.step("Block read file"):
node.host.get_shell().exec(f"chmod a-r {object_path}/{object_name}") node.host.get_shell().exec(f"chmod a-r {object_path}/{object_name}")
@ -157,7 +152,7 @@ class TestShardMetrics(ClusterTestBase):
with pytest.raises(RuntimeError, match=OBJECT_NOT_FOUND): with pytest.raises(RuntimeError, match=OBJECT_NOT_FOUND):
get_object( get_object(
wallet=default_wallet, wallet=default_wallet,
cid=cid, cid=container,
oid=oid, oid=oid,
shell=self.shell, shell=self.shell,
endpoint=node.storage_node.get_rpc_endpoint(), endpoint=node.storage_node.get_rpc_endpoint(),

View file

@ -103,7 +103,7 @@ def common_container(
rpc_endpoint: str, rpc_endpoint: str,
) -> str: ) -> str:
container_request = ContainerRequest(COMMON_CONTAINER_RULE, APE_EVERYONE_ALLOW_ALL) container_request = ContainerRequest(COMMON_CONTAINER_RULE, APE_EVERYONE_ALLOW_ALL)
return create_container_with_ape(frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint, container_request) return create_container_with_ape(container_request, frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint)
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@ -131,7 +131,7 @@ def storage_objects(
rpc_endpoint: str, rpc_endpoint: str,
) -> list[StorageObjectInfo]: ) -> list[StorageObjectInfo]:
cid = create_container_with_ape( cid = create_container_with_ape(
frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint, ContainerRequest(placement_policy.value, APE_EVERYONE_ALLOW_ALL) ContainerRequest(placement_policy.value, APE_EVERYONE_ALLOW_ALL), frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint
) )
with reporter.step("Generate file"): with reporter.step("Generate file"):

View file

@ -32,7 +32,7 @@ def user_container(
) -> StorageContainer: ) -> StorageContainer:
policy = request.param if "param" in request.__dict__ else SINGLE_PLACEMENT_RULE policy = request.param if "param" in request.__dict__ else SINGLE_PLACEMENT_RULE
container_request = ContainerRequest(policy) container_request = ContainerRequest(policy)
container_id = create_container_with_ape(frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint, container_request) container_id = create_container_with_ape(container_request, frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint)
# Deliberately using s3gate wallet here to test bearer token # Deliberately using s3gate wallet here to test bearer token
s3_gate_wallet = WalletInfo.from_node(cluster.s3_gates[0]) s3_gate_wallet = WalletInfo.from_node(cluster.s3_gates[0])

View file

@ -43,7 +43,7 @@ FIXTURE_OBJECT_LIFETIME = 10
def user_container( def user_container(
frostfs_cli: FrostfsCli, default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, rpc_endpoint: str frostfs_cli: FrostfsCli, default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, rpc_endpoint: str
) -> StorageContainer: ) -> StorageContainer:
cid = create_container_with_ape(frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint, EVERYONE_ALLOW_ALL) cid = create_container_with_ape(EVERYONE_ALLOW_ALL, frostfs_cli, default_wallet, client_shell, cluster, rpc_endpoint)
return StorageContainer(StorageContainerInfo(cid, default_wallet), client_shell, cluster) return StorageContainer(StorageContainerInfo(cid, default_wallet), client_shell, cluster)

View file

@ -4,9 +4,7 @@ import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.acl import bearer_token_base64_from_file from frostfs_testlib.steps.acl import bearer_token_base64_from_file
from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.http.http_gate import upload_via_http_gate_curl, verify_object_hash from frostfs_testlib.steps.http.http_gate import upload_via_http_gate_curl, verify_object_hash
from frostfs_testlib.storage.cluster import Cluster from frostfs_testlib.storage.cluster import Cluster
from frostfs_testlib.storage.dataclasses import ape from frostfs_testlib.storage.dataclasses import ape
@ -16,6 +14,7 @@ from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ....helpers.bearer_token import create_bearer_token from ....helpers.bearer_token import create_bearer_token
from ....helpers.container_request import APE_EVERYONE_ALLOW_ALL, ContainerRequest, requires_container
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -24,55 +23,43 @@ logger = logging.getLogger("NeoLogger")
@pytest.mark.http_put @pytest.mark.http_put
class Test_http_bearer(ClusterTestBase): class Test_http_bearer(ClusterTestBase):
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"
OWNER_ROLE = ape.Condition.by_role(ape.Role.OWNER)
CUSTOM_APE_RULE = ape.Rule(ape.Verb.DENY, ape.ObjectOperations.PUT, OWNER_ROLE)
@pytest.fixture(scope="class") @pytest.fixture()
def user_container(self, frostfs_cli: FrostfsCli, default_wallet: WalletInfo, cluster: Cluster) -> str: def bearer_token(self, frostfs_cli: FrostfsCli, container: str, temp_directory: str, cluster: Cluster) -> str:
with reporter.step("Create container"):
cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, self.PLACEMENT_RULE, PUBLIC_ACL)
with reporter.step("Deny PUT via APE rule to container"):
role_condition = ape.Condition.by_role(ape.Role.OWNER)
rule = ape.Rule(ape.Verb.DENY, ape.ObjectOperations.PUT, role_condition)
frostfs_cli.ape_manager.add(
cluster.default_rpc_endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()
)
with reporter.step("Wait for one block"):
self.wait_for_blocks()
return cid
@pytest.fixture(scope="class")
def bearer_token(self, frostfs_cli: FrostfsCli, user_container: str, temp_directory: str, cluster: Cluster) -> str:
with reporter.step(f"Create bearer token for {ape.Role.OTHERS} with all operations allowed"): with reporter.step(f"Create bearer token for {ape.Role.OTHERS} with all operations allowed"):
role_condition = ape.Condition.by_role(ape.Role.OTHERS) role_condition = ape.Condition.by_role(ape.Role.OTHERS)
rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.WILDCARD_ALL, role_condition) rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.WILDCARD_ALL, role_condition)
bearer = create_bearer_token(frostfs_cli, temp_directory, user_container, rule, cluster.default_rpc_endpoint) bearer = create_bearer_token(frostfs_cli, temp_directory, container, rule, cluster.default_rpc_endpoint)
return bearer_token_base64_from_file(bearer) return bearer_token_base64_from_file(bearer)
@allure.title(f"[NEGATIVE] Put object without bearer token for {ape.Role.OTHERS}") @allure.title(f"[NEGATIVE] Put object without bearer token for {ape.Role.OTHERS}")
def test_unable_put_without_bearer_token(self, simple_object_size: ObjectSize, user_container: str): def test_unable_put_without_bearer_token(self, simple_object_size: ObjectSize, container: str):
upload_via_http_gate_curl( upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=generate_file(simple_object_size.value), filepath=generate_file(simple_object_size.value),
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
error_pattern="access to object operation denied", error_pattern="access to object operation denied",
) )
@allure.title("Put object via HTTP using bearer token (object_size={object_size})") @allure.title("Put object via HTTP using bearer token (object_size={object_size})")
def test_put_with_bearer_when_eacl_restrict( @requires_container(
ContainerRequest(PLACEMENT_RULE, [APE_EVERYONE_ALLOW_ALL, CUSTOM_APE_RULE], short_name="custom with denied owner put")
)
def test_put_with_bearer_when_ape_restrict(
self, self,
object_size: ObjectSize, object_size: ObjectSize,
default_wallet: WalletInfo, default_wallet: WalletInfo,
user_container: str, container: str,
bearer_token: str, bearer_token: str,
): ):
file_path = generate_file(object_size.value) file_path = generate_file(object_size.value)
with reporter.step(f"Put object with bearer token for {ape.Role.OTHERS}, then get and verify hashes"): with reporter.step(f"Put object with bearer token for {ape.Role.OTHERS}, then get and verify hashes"):
headers = [f" -H 'Authorization: Bearer {bearer_token}'"] headers = [f" -H 'Authorization: Bearer {bearer_token}'"]
oid = upload_via_http_gate_curl( oid = upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=headers, headers=headers,
@ -81,7 +68,7 @@ class Test_http_bearer(ClusterTestBase):
oid=oid, oid=oid,
file_name=file_path, file_name=file_path,
wallet=default_wallet, wallet=default_wallet,
cid=user_container, cid=container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], request_node=self.cluster.cluster_nodes[0],

View file

@ -1,8 +1,6 @@
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.cli.object import put_object_to_random_node from frostfs_testlib.steps.cli.object import put_object_to_random_node
from frostfs_testlib.steps.epoch import get_epoch from frostfs_testlib.steps.epoch import get_epoch
from frostfs_testlib.steps.http.http_gate import ( from frostfs_testlib.steps.http.http_gate import (
@ -17,9 +15,11 @@ from frostfs_testlib.steps.http.http_gate import (
verify_object_hash, verify_object_hash,
) )
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file, get_file_hash from frostfs_testlib.utils.file_utils import TestFile, generate_file, get_file_hash
from ....helpers.container_request import REP_1_1_1_PUBLIC, REP_2_2_2_PUBLIC, requires_container
from ....helpers.utility import wait_for_gc_pass_on_storage_nodes from ....helpers.utility import wait_for_gc_pass_on_storage_nodes
OBJECT_NOT_FOUND_ERROR = "not found" OBJECT_NOT_FOUND_ERROR = "not found"
@ -35,65 +35,36 @@ OBJECT_NOT_FOUND_ERROR = "not found"
@pytest.mark.sanity @pytest.mark.sanity
@pytest.mark.http_gate @pytest.mark.http_gate
class TestHttpGate(ClusterTestBase): class TestHttpGate(ClusterTestBase):
PLACEMENT_RULE_1 = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X" @allure.title("Put over gRPC, Get over HTTP (object_size={object_size})")
PLACEMENT_RULE_2 = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X" @requires_container(REP_1_1_1_PUBLIC)
def test_put_grpc_get_http(self, default_wallet: WalletInfo, container: str, test_file: TestFile):
@pytest.fixture(scope="class", autouse=True)
@allure.title("[Class/Autouse]: Prepare wallet and deposit")
def prepare_wallet(self, default_wallet):
TestHttpGate.wallet = default_wallet
@allure.title("Put over gRPC, Get over HTTP")
def test_put_grpc_get_http(self, complex_object_size: ObjectSize, simple_object_size: ObjectSize):
""" """
Test that object can be put using gRPC interface and get using HTTP. Test that object can be put using gRPC interface and get using HTTP.
Steps: Steps:
1. Create simple and large objects. 1. Create object.
2. Put objects using gRPC (frostfs-cli). 2. Put object using gRPC (frostfs-cli).
3. Download objects using HTTP gate (https://git.frostfs.info/TrueCloudLab/frostfs-http-gw#downloading). 3. Download object using HTTP gate (https://git.frostfs.info/TrueCloudLab/frostfs-http-gw#downloading).
4. Get objects using gRPC (frostfs-cli). 4. Get object using gRPC (frostfs-cli).
5. Compare hashes for got objects. 5. Compare hashes for got object.
6. Compare hashes for got and original objects. 6. Compare hashes for got and original objects.
Expected result: Expected result:
Hashes must be the same. Hashes must be the same.
""" """
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE_1,
basic_acl=PUBLIC_ACL,
)
file_path_simple = generate_file(simple_object_size.value)
file_path_large = generate_file(complex_object_size.value)
with reporter.step("Put objects using gRPC"): with reporter.step("Put object using gRPC"):
oid_simple = put_object_to_random_node( object_id = put_object_to_random_node(default_wallet, test_file.path, container, self.shell, self.cluster)
wallet=self.wallet,
path=file_path_simple,
cid=cid,
shell=self.shell,
cluster=self.cluster,
)
oid_large = put_object_to_random_node(
wallet=self.wallet,
path=file_path_large,
cid=cid,
shell=self.shell,
cluster=self.cluster,
)
for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)): with reporter.step("Get object and check hash"):
verify_object_hash( verify_object_hash(
oid=oid, object_id,
file_name=file_path, test_file.path,
wallet=self.wallet, default_wallet,
cid=cid, container,
shell=self.shell, self.shell,
nodes=self.cluster.storage_nodes, self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], self.cluster.cluster_nodes[0],
) )
@ -108,9 +79,10 @@ class TestHttpGate(ClusterTestBase):
class TestHttpPut(ClusterTestBase): class TestHttpPut(ClusterTestBase):
@allure.link("https://git.frostfs.info/TrueCloudLab/frostfs-http-gw#uploading", name="uploading") @allure.link("https://git.frostfs.info/TrueCloudLab/frostfs-http-gw#uploading", name="uploading")
@allure.link("https://git.frostfs.info/TrueCloudLab/frostfs-http-gw#downloading", name="downloading") @allure.link("https://git.frostfs.info/TrueCloudLab/frostfs-http-gw#downloading", name="downloading")
@allure.title("Put over HTTP, Get over HTTP") @allure.title("Put over HTTP, Get over HTTP (object_size={object_size})")
@pytest.mark.smoke @pytest.mark.smoke
def test_put_http_get_http(self, complex_object_size: ObjectSize, simple_object_size: ObjectSize): @requires_container(REP_2_2_2_PUBLIC)
def test_put_http_get_http(self, container: str, default_wallet: WalletInfo, test_file: TestFile):
""" """
Test that object can be put and get using HTTP interface. Test that object can be put and get using HTTP interface.
@ -123,29 +95,19 @@ class TestHttpPut(ClusterTestBase):
Expected result: Expected result:
Hashes must be the same. Hashes must be the same.
""" """
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE_2,
basic_acl=PUBLIC_ACL,
)
file_path_simple = generate_file(simple_object_size.value)
file_path_large = generate_file(complex_object_size.value)
with reporter.step("Put objects using HTTP"): with reporter.step("Put object using HTTP"):
oid_simple = upload_via_http_gate(cid=cid, path=file_path_simple, endpoint=self.cluster.default_http_gate_endpoint) object_id = upload_via_http_gate(container, test_file.path, self.cluster.default_http_gate_endpoint)
oid_large = upload_via_http_gate(cid=cid, path=file_path_large, endpoint=self.cluster.default_http_gate_endpoint)
for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)): with reporter.step("Get object and check hash"):
verify_object_hash( verify_object_hash(
oid=oid, object_id,
file_name=file_path, test_file.path,
wallet=self.wallet, default_wallet,
cid=cid, container,
shell=self.shell, self.shell,
nodes=self.cluster.storage_nodes, self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], self.cluster.cluster_nodes[0],
) )
@allure.link( @allure.link(
@ -162,7 +124,8 @@ class TestHttpPut(ClusterTestBase):
], ],
ids=["simple", "hyphen", "percent"], ids=["simple", "hyphen", "percent"],
) )
def test_put_http_get_http_with_headers(self, attributes: dict, simple_object_size: ObjectSize, id: str): @requires_container(REP_2_2_2_PUBLIC)
def test_put_http_get_http_with_headers(self, container: str, attributes: dict, simple_object_size: ObjectSize, id: str):
""" """
Test that object can be downloaded using different attributes in HTTP header. Test that object can be downloaded using different attributes in HTTP header.
@ -175,46 +138,27 @@ class TestHttpPut(ClusterTestBase):
Expected result: Expected result:
Hashes must be the same. Hashes must be the same.
""" """
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE_2,
basic_acl=PUBLIC_ACL,
)
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
with reporter.step("Put objects using HTTP with attribute"): with reporter.step("Put objects using HTTP with attribute"):
headers = attr_into_header(attributes) headers = attr_into_header(attributes)
oid = upload_via_http_gate( oid = upload_via_http_gate(container, file_path, self.cluster.default_http_gate_endpoint, headers)
cid=cid,
path=file_path,
headers=headers,
endpoint=self.cluster.default_http_gate_endpoint,
)
get_object_by_attr_and_verify_hashes( get_object_by_attr_and_verify_hashes(
oid=oid, oid,
file_name=file_path, file_path,
cid=cid, container,
attrs=attributes, attributes,
node=self.cluster.cluster_nodes[0], self.cluster.cluster_nodes[0],
) )
@allure.title("Expiration-Epoch in HTTP header (epoch_gap={epoch_gap})") @allure.title("Expiration-Epoch in HTTP header (epoch_gap={epoch_gap})")
@pytest.mark.parametrize("epoch_gap", [0, 1]) @pytest.mark.parametrize("epoch_gap", [0, 1])
def test_expiration_epoch_in_http(self, simple_object_size: ObjectSize, epoch_gap: int): @requires_container(REP_2_2_2_PUBLIC)
endpoint = self.cluster.default_rpc_endpoint def test_expiration_epoch_in_http(self, container: str, simple_object_size: ObjectSize, epoch_gap: int):
http_endpoint = self.cluster.default_http_gate_endpoint http_endpoint = self.cluster.default_http_gate_endpoint
min_valid_epoch = get_epoch(self.shell, self.cluster) + epoch_gap min_valid_epoch = get_epoch(self.shell, self.cluster) + epoch_gap
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=endpoint,
rule=self.PLACEMENT_RULE_2,
basic_acl=PUBLIC_ACL,
)
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
oids_to_be_expired = [] oids_to_be_expired = []
oids_to_be_valid = [] oids_to_be_valid = []
@ -225,7 +169,7 @@ class TestHttpPut(ClusterTestBase):
with reporter.step("Put objects using HTTP with attribute Expiration-Epoch"): with reporter.step("Put objects using HTTP with attribute Expiration-Epoch"):
oid = upload_via_http_gate( oid = upload_via_http_gate(
cid=cid, cid=container,
path=file_path, path=file_path,
headers=headers, headers=headers,
endpoint=http_endpoint, endpoint=http_endpoint,
@ -235,7 +179,7 @@ class TestHttpPut(ClusterTestBase):
else: else:
oids_to_be_expired.append(oid) oids_to_be_expired.append(oid)
with reporter.step("This object can be got"): with reporter.step("This object can be got"):
get_via_http_gate(cid=cid, oid=oid, node=self.cluster.cluster_nodes[0]) get_via_http_gate(container, oid, self.cluster.cluster_nodes[0])
self.tick_epoch() self.tick_epoch()
@ -245,24 +189,18 @@ class TestHttpPut(ClusterTestBase):
for oid in oids_to_be_expired: for oid in oids_to_be_expired:
with reporter.step(f"{oid} shall be expired and cannot be got"): with reporter.step(f"{oid} shall be expired and cannot be got"):
try_to_get_object_and_expect_error( try_to_get_object_and_expect_error(
cid=cid, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern=OBJECT_NOT_FOUND_ERROR, error_pattern=OBJECT_NOT_FOUND_ERROR,
) )
for oid in oids_to_be_valid: for oid in oids_to_be_valid:
with reporter.step(f"{oid} shall be valid and can be got"): with reporter.step(f"{oid} shall be valid and can be got"):
get_via_http_gate(cid=cid, oid=oid, node=self.cluster.cluster_nodes[0]) get_via_http_gate(cid=container, oid=oid, node=self.cluster.cluster_nodes[0])
@allure.title("Zip in HTTP header") @allure.title("Zip in HTTP header")
def test_zip_in_http(self, complex_object_size: ObjectSize, simple_object_size: ObjectSize): @requires_container(REP_2_2_2_PUBLIC)
cid = create_container( def test_zip_in_http(self, container: str, complex_object_size: ObjectSize, simple_object_size: ObjectSize):
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE_2,
basic_acl=PUBLIC_ACL,
)
file_path_simple = generate_file(simple_object_size.value) file_path_simple = generate_file(simple_object_size.value)
file_path_large = generate_file(complex_object_size.value) file_path_large = generate_file(complex_object_size.value)
common_prefix = "my_files" common_prefix = "my_files"
@ -271,45 +209,33 @@ class TestHttpPut(ClusterTestBase):
headers2 = {"X-Attribute-FilePath": f"{common_prefix}/file2"} headers2 = {"X-Attribute-FilePath": f"{common_prefix}/file2"}
upload_via_http_gate( upload_via_http_gate(
cid=cid, cid=container,
path=file_path_simple, path=file_path_simple,
headers=headers1, headers=headers1,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
) )
upload_via_http_gate( upload_via_http_gate(container, file_path_large, headers2, self.cluster.default_http_gate_endpoint)
cid=cid, upload_via_http_gate(container, file_path_large, headers2, self.cluster.default_http_gate_endpoint)
path=file_path_large,
headers=headers2,
endpoint=self.cluster.default_http_gate_endpoint,
)
dir_path = get_via_zip_http_gate(cid=cid, prefix=common_prefix, node=self.cluster.cluster_nodes[0]) dir_path = get_via_zip_http_gate(cid=container, prefix=common_prefix, node=self.cluster.cluster_nodes[0])
with reporter.step("Verify hashes"): with reporter.step("Verify hashes"):
assert get_file_hash(f"{dir_path}/file1") == get_file_hash(file_path_simple) assert get_file_hash(f"{dir_path}/file1") == get_file_hash(file_path_simple)
assert get_file_hash(f"{dir_path}/file2") == get_file_hash(file_path_large) assert get_file_hash(f"{dir_path}/file2") == get_file_hash(file_path_large)
@pytest.mark.long
@allure.title("Put over HTTP/Curl, Get over HTTP/Curl for large object") @allure.title("Put over HTTP/Curl, Get over HTTP/Curl for large object")
def test_put_http_get_http_large_file(self, complex_object_size: ObjectSize): @requires_container(REP_2_2_2_PUBLIC)
def test_put_http_get_http_large_file(self, default_wallet: WalletInfo, container: str, complex_object_size: ObjectSize):
""" """
This test checks upload and download using curl with 'large' object. This test checks upload and download using curl with 'large' object.
Large is object with size up to 20Mb. Large is object with size up to 20Mb.
""" """
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE_2,
basic_acl=PUBLIC_ACL,
)
file_path = generate_file(complex_object_size.value) file_path = generate_file(complex_object_size.value)
with reporter.step("Put objects using HTTP"): with reporter.step("Put objects using HTTP"):
oid_gate = upload_via_http_gate(cid=cid, path=file_path, endpoint=self.cluster.default_http_gate_endpoint) oid_gate = upload_via_http_gate(cid=container, path=file_path, endpoint=self.cluster.default_http_gate_endpoint)
oid_curl = upload_via_http_gate_curl( oid_curl = upload_via_http_gate_curl(
cid=cid, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
) )
@ -317,8 +243,8 @@ class TestHttpPut(ClusterTestBase):
verify_object_hash( verify_object_hash(
oid=oid_gate, oid=oid_gate,
file_name=file_path, file_name=file_path,
wallet=self.wallet, wallet=default_wallet,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], request_node=self.cluster.cluster_nodes[0],
@ -326,45 +252,32 @@ class TestHttpPut(ClusterTestBase):
verify_object_hash( verify_object_hash(
oid=oid_curl, oid=oid_curl,
file_name=file_path, file_name=file_path,
wallet=self.wallet, wallet=default_wallet,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], request_node=self.cluster.cluster_nodes[0],
object_getter=get_via_http_curl, object_getter=get_via_http_curl,
) )
@allure.title("Put/Get over HTTP using Curl utility") @allure.title("Put/Get over HTTP using Curl utility (object_size={object_size})")
def test_put_http_get_http_curl(self, complex_object_size: ObjectSize, simple_object_size: ObjectSize): @requires_container(REP_2_2_2_PUBLIC)
def test_put_http_get_http_curl(self, default_wallet: WalletInfo, container: str, test_file: TestFile):
""" """
Test checks upload and download over HTTP using curl utility. Test checks upload and download over HTTP using curl utility.
""" """
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE_2,
basic_acl=PUBLIC_ACL,
)
file_path_simple = generate_file(simple_object_size.value)
file_path_large = generate_file(complex_object_size.value)
with reporter.step("Put objects using curl utility"): with reporter.step("Put object using curl utility"):
oid_simple = upload_via_http_gate_curl(cid=cid, filepath=file_path_simple, endpoint=self.cluster.default_http_gate_endpoint) object_id = upload_via_http_gate_curl(container, test_file.path, self.cluster.default_http_gate_endpoint)
oid_large = upload_via_http_gate_curl(
cid=cid,
filepath=file_path_large,
endpoint=self.cluster.default_http_gate_endpoint,
)
for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)): with reporter.step("Get object and check hash"):
verify_object_hash( verify_object_hash(
oid=oid, object_id,
file_name=file_path, test_file.path,
wallet=self.wallet, default_wallet,
cid=cid, container,
shell=self.shell, self.shell,
nodes=self.cluster.storage_nodes, self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], self.cluster.cluster_nodes[0],
object_getter=get_via_http_curl, get_via_http_curl,
) )

View file

@ -4,13 +4,7 @@ import os
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL from frostfs_testlib.steps.cli.container import delete_container, list_containers, wait_for_container_deletion
from frostfs_testlib.steps.cli.container import (
create_container,
delete_container,
list_containers,
wait_for_container_deletion,
)
from frostfs_testlib.steps.cli.object import delete_object from frostfs_testlib.steps.cli.object import delete_object
from frostfs_testlib.steps.http.http_gate import ( from frostfs_testlib.steps.http.http_gate import (
attr_into_str_header_curl, attr_into_str_header_curl,
@ -21,9 +15,12 @@ from frostfs_testlib.steps.http.http_gate import (
) )
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.storage_object_info import StorageObjectInfo from frostfs_testlib.storage.dataclasses.storage_object_info import StorageObjectInfo
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ....helpers.container_request import REP_2_1_4_PUBLIC, requires_container
OBJECT_ALREADY_REMOVED_ERROR = "object already removed" OBJECT_ALREADY_REMOVED_ERROR = "object already removed"
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -31,7 +28,6 @@ logger = logging.getLogger("NeoLogger")
@pytest.mark.http_gate @pytest.mark.http_gate
@pytest.mark.http_put @pytest.mark.http_put
class Test_http_headers(ClusterTestBase): class Test_http_headers(ClusterTestBase):
PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
obj1_keys = ["Writer", "Chapter1", "Chapter2"] obj1_keys = ["Writer", "Chapter1", "Chapter2"]
obj2_keys = ["Writer", "Ch@pter1", "chapter2"] obj2_keys = ["Writer", "Ch@pter1", "chapter2"]
values = ["Leo Tolstoy", "peace", "w@r"] values = ["Leo Tolstoy", "peace", "w@r"]
@ -40,34 +36,23 @@ class Test_http_headers(ClusterTestBase):
{obj2_keys[0]: values[0], obj2_keys[1]: values[1], obj2_keys[2]: values[2]}, {obj2_keys[0]: values[0], obj2_keys[1]: values[1], obj2_keys[2]: values[2]},
] ]
@pytest.fixture(scope="class", autouse=True) @pytest.fixture
@allure.title("[Class/Autouse]: Prepare wallet and deposit") def storage_objects_with_attributes(self, container: str, wallet: WalletInfo, object_size: ObjectSize) -> list[StorageObjectInfo]:
def prepare_wallet(self, default_wallet):
Test_http_headers.wallet = default_wallet
def storage_objects_with_attributes(self, object_size: ObjectSize) -> list[StorageObjectInfo]:
# TODO: Deal with http tests # TODO: Deal with http tests
if object_size.value > 1000: if object_size.value > 1000:
pytest.skip("Complex objects for HTTP temporarly disabled for v0.37") pytest.skip("Complex objects for HTTP temporarly disabled for v0.37")
storage_objects = [] storage_objects = []
wallet = self.wallet
cid = create_container(
wallet=self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE,
basic_acl=PUBLIC_ACL,
)
file_path = generate_file(object_size.value) file_path = generate_file(object_size.value)
for attributes in self.OBJECT_ATTRIBUTES: for attributes in self.OBJECT_ATTRIBUTES:
storage_object_id = upload_via_http_gate_curl( storage_object_id = upload_via_http_gate_curl(
cid=cid, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=attr_into_str_header_curl(attributes), headers=attr_into_str_header_curl(attributes),
) )
storage_object = StorageObjectInfo(cid, storage_object_id) storage_object = StorageObjectInfo(container, storage_object_id)
storage_object.size = os.path.getsize(file_path) storage_object.size = os.path.getsize(file_path)
storage_object.wallet = wallet storage_object.wallet = wallet
storage_object.file_path = file_path storage_object.file_path = file_path
@ -75,9 +60,10 @@ class Test_http_headers(ClusterTestBase):
storage_objects.append(storage_object) storage_objects.append(storage_object)
yield storage_objects return storage_objects
@allure.title("Get object1 by attribute") @allure.title("Get object1 by attribute (object_size={object_size})")
@requires_container(REP_2_1_4_PUBLIC)
def test_object1_can_be_get_by_attr(self, storage_objects_with_attributes: list[StorageObjectInfo]): def test_object1_can_be_get_by_attr(self, storage_objects_with_attributes: list[StorageObjectInfo]):
""" """
Test to get object#1 by attribute and comapre hashes Test to get object#1 by attribute and comapre hashes
@ -99,8 +85,9 @@ class Test_http_headers(ClusterTestBase):
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
) )
@allure.title("Get object2 with different attributes, then delete object2 and get object1") @allure.title("Get object2 with different attributes, then delete object2 and get object1 (object_size={object_size})")
def test_object2_can_be_get_by_attr(self, storage_objects_with_attributes: list[StorageObjectInfo]): @requires_container(REP_2_1_4_PUBLIC)
def test_object2_can_be_get_by_attr(self, default_wallet: WalletInfo, storage_objects_with_attributes: list[StorageObjectInfo]):
""" """
Test to get object2 with different attributes, then delete object2 and get object1 using 1st attribute. Note: obj1 and obj2 have the same attribute#1, Test to get object2 with different attributes, then delete object2 and get object1 using 1st attribute. Note: obj1 and obj2 have the same attribute#1,
and when obj2 is deleted you can get obj1 by 1st attribute and when obj2 is deleted you can get obj1 by 1st attribute
@ -131,7 +118,7 @@ class Test_http_headers(ClusterTestBase):
) )
with reporter.step("Delete object#2 and verify is the container deleted"): with reporter.step("Delete object#2 and verify is the container deleted"):
delete_object( delete_object(
wallet=self.wallet, wallet=default_wallet,
cid=storage_object_2.cid, cid=storage_object_2.cid,
oid=storage_object_2.oid, oid=storage_object_2.oid,
shell=self.shell, shell=self.shell,
@ -145,9 +132,7 @@ class Test_http_headers(ClusterTestBase):
) )
storage_objects_with_attributes.remove(storage_object_2) storage_objects_with_attributes.remove(storage_object_2)
with reporter.step( with reporter.step(f'Download object#1 with attributes [Writer={storage_object_1.attributes["Writer"]}] and compare hashes'):
f'Download object#1 with attributes [Writer={storage_object_1.attributes["Writer"]}] and compare hashes'
):
key_value_pair = {"Writer": storage_object_1.attributes["Writer"]} key_value_pair = {"Writer": storage_object_1.attributes["Writer"]}
get_object_by_attr_and_verify_hashes( get_object_by_attr_and_verify_hashes(
oid=storage_object_1.oid, oid=storage_object_1.oid,
@ -157,8 +142,9 @@ class Test_http_headers(ClusterTestBase):
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
) )
@allure.title("[NEGATIVE] Put object and get right after container is deleted") @allure.title("[NEGATIVE] Put object and get right after container is deleted (object_size={object_size})")
def test_negative_put_and_get_object3(self, storage_objects_with_attributes: list[StorageObjectInfo]): @requires_container(REP_2_1_4_PUBLIC)
def test_negative_put_and_get_object3(self, default_wallet: WalletInfo, storage_objects_with_attributes: list[StorageObjectInfo]):
""" """
Test to attempt to put object and try to download it right after the container has been deleted Test to attempt to put object and try to download it right after the container has been deleted
@ -188,7 +174,7 @@ class Test_http_headers(ClusterTestBase):
) )
with reporter.step("Delete container and verify container deletion"): with reporter.step("Delete container and verify container deletion"):
delete_container( delete_container(
wallet=self.wallet, wallet=default_wallet,
cid=storage_object_1.cid, cid=storage_object_1.cid,
shell=self.shell, shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint, endpoint=self.cluster.default_rpc_endpoint,
@ -196,14 +182,12 @@ class Test_http_headers(ClusterTestBase):
) )
self.tick_epoch() self.tick_epoch()
wait_for_container_deletion( wait_for_container_deletion(
self.wallet, default_wallet,
storage_object_1.cid, storage_object_1.cid,
shell=self.shell, shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint, endpoint=self.cluster.default_rpc_endpoint,
) )
assert storage_object_1.cid not in list_containers( assert storage_object_1.cid not in list_containers(default_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
self.wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
)
with reporter.step("[Negative] Try to download (wget) object via wget with attributes [peace=peace]"): with reporter.step("[Negative] Try to download (wget) object via wget with attributes [peace=peace]"):
request = f"/get/{storage_object_1.cid}/peace/peace" request = f"/get/{storage_object_1.cid}/peace/peace"
error_pattern = "404 Not Found" error_pattern = "404 Not Found"

View file

@ -3,9 +3,7 @@ import logging
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.s3 import AwsCliClient, S3ClientWrapper from frostfs_testlib.s3 import AwsCliClient, S3ClientWrapper
from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.cli.object import put_object_to_random_node from frostfs_testlib.steps.cli.object import put_object_to_random_node
from frostfs_testlib.steps.http.http_gate import ( from frostfs_testlib.steps.http.http_gate import (
assert_hashes_are_equal, assert_hashes_are_equal,
@ -15,9 +13,11 @@ from frostfs_testlib.steps.http.http_gate import (
verify_object_hash, verify_object_hash,
) )
from frostfs_testlib.steps.s3 import s3_helper from frostfs_testlib.steps.s3 import s3_helper
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import TestFile
from ....helpers.container_request import REP_2_1_4_PUBLIC, requires_container
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -26,15 +26,9 @@ logger = logging.getLogger("NeoLogger")
@pytest.mark.sanity @pytest.mark.sanity
@pytest.mark.http_gate @pytest.mark.http_gate
class Test_http_object(ClusterTestBase): class Test_http_object(ClusterTestBase):
PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
@pytest.fixture(scope="class", autouse=True)
@allure.title("[Class/Autouse]: Prepare wallet and deposit")
def prepare_wallet(self, default_wallet):
Test_http_object.wallet = default_wallet
@allure.title("Put over gRPC, Get over HTTP with attributes (obj_size={object_size})") @allure.title("Put over gRPC, Get over HTTP with attributes (obj_size={object_size})")
def test_object_put_get_attributes(self, object_size: ObjectSize): @requires_container(REP_2_1_4_PUBLIC)
def test_object_put_get_attributes(self, default_wallet: WalletInfo, container: str, test_file: TestFile):
""" """
Test that object can be put using gRPC interface and got using HTTP. Test that object can be put using gRPC interface and got using HTTP.
@ -53,18 +47,6 @@ class Test_http_object(ClusterTestBase):
Hashes must be the same. Hashes must be the same.
""" """
with reporter.step("Create public container"):
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE,
basic_acl=PUBLIC_ACL,
)
# Generate file
file_path = generate_file(object_size.value)
# List of Key=Value attributes # List of Key=Value attributes
obj_key1 = "chapter1" obj_key1 = "chapter1"
obj_value1 = "peace" obj_value1 = "peace"
@ -77,9 +59,9 @@ class Test_http_object(ClusterTestBase):
with reporter.step("Put objects using gRPC [--attributes chapter1=peace,chapter2=war]"): with reporter.step("Put objects using gRPC [--attributes chapter1=peace,chapter2=war]"):
oid = put_object_to_random_node( oid = put_object_to_random_node(
wallet=self.wallet, wallet=default_wallet,
path=file_path, path=test_file.path,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
cluster=self.cluster, cluster=self.cluster,
attributes=f"{key_value1},{key_value2}", attributes=f"{key_value1},{key_value2}",
@ -87,9 +69,9 @@ class Test_http_object(ClusterTestBase):
with reporter.step("Get object and verify hashes [ get/$CID/$OID ]"): with reporter.step("Get object and verify hashes [ get/$CID/$OID ]"):
verify_object_hash( verify_object_hash(
oid=oid, oid=oid,
file_name=file_path, file_name=test_file.path,
wallet=self.wallet, wallet=default_wallet,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], request_node=self.cluster.cluster_nodes[0],
@ -97,10 +79,10 @@ class Test_http_object(ClusterTestBase):
with reporter.step("[Negative] try to get object: [get/$CID/chapter1/peace]"): with reporter.step("[Negative] try to get object: [get/$CID/chapter1/peace]"):
attrs = {obj_key1: obj_value1, obj_key2: obj_value2} attrs = {obj_key1: obj_value1, obj_key2: obj_value2}
request = f"/get/{cid}/{obj_key1}/{obj_value1}" request = f"/get/{container}/{obj_key1}/{obj_value1}"
expected_err_msg = "Failed to get object via HTTP gate:" expected_err_msg = "Failed to get object via HTTP gate:"
try_to_get_object_via_passed_request_and_expect_error( try_to_get_object_via_passed_request_and_expect_error(
cid=cid, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern=expected_err_msg, error_pattern=expected_err_msg,
@ -111,15 +93,15 @@ class Test_http_object(ClusterTestBase):
with reporter.step("Download the object with attribute [get_by_attribute/$CID/chapter1/peace]"): with reporter.step("Download the object with attribute [get_by_attribute/$CID/chapter1/peace]"):
get_object_by_attr_and_verify_hashes( get_object_by_attr_and_verify_hashes(
oid=oid, oid=oid,
file_name=file_path, file_name=test_file.path,
cid=cid, cid=container,
attrs=attrs, attrs=attrs,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
) )
with reporter.step("[Negative] try to get object: get_by_attribute/$CID/$OID"): with reporter.step("[Negative] try to get object: get_by_attribute/$CID/$OID"):
request = f"/get_by_attribute/{cid}/{oid}" request = f"/get_by_attribute/{container}/{oid}"
try_to_get_object_via_passed_request_and_expect_error( try_to_get_object_via_passed_request_and_expect_error(
cid=cid, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern=expected_err_msg, error_pattern=expected_err_msg,
@ -128,7 +110,7 @@ class Test_http_object(ClusterTestBase):
@allure.title("Put over s3, Get over HTTP with bucket name and key (object_size={object_size})") @allure.title("Put over s3, Get over HTTP with bucket name and key (object_size={object_size})")
@pytest.mark.parametrize("s3_client", [AwsCliClient], indirect=True) @pytest.mark.parametrize("s3_client", [AwsCliClient], indirect=True)
def test_object_put_get_bucketname_key(self, object_size: ObjectSize, s3_client: S3ClientWrapper): def test_object_put_get_bucketname_key(self, test_file: TestFile, s3_client: S3ClientWrapper):
""" """
Test that object can be put using s3-gateway interface and got via HTTP with bucket name and object key. Test that object can be put using s3-gateway interface and got via HTTP with bucket name and object key.
@ -143,10 +125,9 @@ class Test_http_object(ClusterTestBase):
Hashes must be the same. Hashes must be the same.
""" """
file_path = generate_file(object_size.value) object_key = s3_helper.object_key_from_file_path(test_file.path)
object_key = s3_helper.object_key_from_file_path(file_path)
bucket = s3_client.create_bucket(acl="public-read-write") bucket = s3_client.create_bucket(acl="public-read-write")
s3_client.put_object(bucket=bucket, filepath=file_path, key=object_key) s3_client.put_object(bucket=bucket, filepath=test_file.path, key=object_key)
obj_s3 = s3_client.get_object(bucket=bucket, key=object_key) obj_s3 = s3_client.get_object(bucket=bucket, key=object_key)
request = f"/get/{bucket}/{object_key}" request = f"/get/{bucket}/{object_key}"
@ -157,4 +138,4 @@ class Test_http_object(ClusterTestBase):
request_path=request, request_path=request,
) )
with reporter.step("Verify hashes"): with reporter.step("Verify hashes"):
assert_hashes_are_equal(file_path, obj_http, obj_s3) assert_hashes_are_equal(test_file.path, obj_http, obj_s3)

View file

@ -3,28 +3,23 @@ import logging
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.http.http_gate import upload_via_http_gate_curl, verify_object_hash from frostfs_testlib.steps.http.http_gate import upload_via_http_gate_curl, verify_object_hash
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ....helpers.container_request import REP_2_1_4_PUBLIC, requires_container
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@pytest.mark.http_gate @pytest.mark.http_gate
@pytest.mark.http_put @pytest.mark.http_put
class Test_http_streaming(ClusterTestBase): class Test_http_streaming(ClusterTestBase):
PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
@pytest.fixture(scope="class", autouse=True)
@allure.title("[Class/Autouse]: Prepare wallet and deposit")
def prepare_wallet(self, default_wallet):
Test_http_streaming.wallet = default_wallet
@allure.title("Put via pipe (streaming), Get over HTTP and verify hashes") @allure.title("Put via pipe (streaming), Get over HTTP and verify hashes")
def test_object_can_be_put_get_by_streaming(self, complex_object_size: ObjectSize): @requires_container(REP_2_1_4_PUBLIC)
def test_object_can_be_put_get_by_streaming(self, default_wallet: WalletInfo, container: str, complex_object_size: ObjectSize):
""" """
Test that object can be put using gRPC interface and get using HTTP. Test that object can be put using gRPC interface and get using HTTP.
@ -37,27 +32,20 @@ class Test_http_streaming(ClusterTestBase):
Expected result: Expected result:
Hashes must be the same. Hashes must be the same.
""" """
with reporter.step("Create public container and verify container creation"):
cid = create_container(
self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE,
basic_acl=PUBLIC_ACL,
)
with reporter.step("Allocate big object"): with reporter.step("Allocate big object"):
# Generate file # Generate file
file_path = generate_file(complex_object_size.value) file_path = generate_file(complex_object_size.value)
with reporter.step("Put objects using curl utility and Get object and verify hashes [ get/$CID/$OID ]"): with reporter.step("Put objects using curl utility"):
oid = upload_via_http_gate_curl( oid = upload_via_http_gate_curl(container, file_path, self.cluster.default_http_gate_endpoint)
cid=cid, filepath=file_path, endpoint=self.cluster.default_http_gate_endpoint
) with reporter.step("Get object and verify hashes [ get/$CID/$OID ]"):
verify_object_hash( verify_object_hash(
oid=oid, oid=oid,
file_name=file_path, file_name=file_path,
wallet=self.wallet, wallet=default_wallet,
cid=cid, cid=container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], request_node=self.cluster.cluster_nodes[0],

View file

@ -7,8 +7,6 @@ import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.cli.container import create_container
from frostfs_testlib.steps.cli.object import get_netmap_netinfo, get_object_from_random_node, head_object from frostfs_testlib.steps.cli.object import get_netmap_netinfo, get_object_from_random_node, head_object
from frostfs_testlib.steps.epoch import get_epoch, wait_for_epochs_align from frostfs_testlib.steps.epoch import get_epoch, wait_for_epochs_align
from frostfs_testlib.steps.http.http_gate import ( from frostfs_testlib.steps.http.http_gate import (
@ -18,9 +16,12 @@ from frostfs_testlib.steps.http.http_gate import (
verify_object_hash, verify_object_hash,
) )
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ....helpers.container_request import REP_2_1_2_PUBLIC, requires_container
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
EXPIRATION_TIMESTAMP_HEADER = "__SYSTEM__EXPIRATION_TIMESTAMP" EXPIRATION_TIMESTAMP_HEADER = "__SYSTEM__EXPIRATION_TIMESTAMP"
@ -38,29 +39,11 @@ SYSTEM_EXPIRATION_RFC3339 = "System-Expiration-RFC3339"
@pytest.mark.http_gate @pytest.mark.http_gate
@pytest.mark.http_put @pytest.mark.http_put
class Test_http_system_header(ClusterTestBase): class Test_http_system_header(ClusterTestBase):
PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
@pytest.fixture(scope="class", autouse=True)
@allure.title("[Class/Autouse]: Prepare wallet and deposit")
def prepare_wallet(self, default_wallet):
Test_http_system_header.wallet = default_wallet
@pytest.fixture(scope="class")
@allure.title("Create container")
def user_container(self):
return create_container(
wallet=self.wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule=self.PLACEMENT_RULE,
basic_acl=PUBLIC_ACL,
)
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
@allure.title("epoch_duration in seconds") @allure.title("epoch_duration in seconds")
def epoch_duration(self) -> int: def epoch_duration(self, default_wallet: WalletInfo) -> int:
net_info = get_netmap_netinfo( net_info = get_netmap_netinfo(
wallet=self.wallet, wallet=default_wallet,
endpoint=self.cluster.default_rpc_endpoint, endpoint=self.cluster.default_rpc_endpoint,
shell=self.shell, shell=self.shell,
) )
@ -83,7 +66,7 @@ class Test_http_system_header(ClusterTestBase):
else: else:
return str(calendar.timegm(future_datetime.timetuple())) return str(calendar.timegm(future_datetime.timetuple()))
@allure.title("Check is (header_output) Key=Value exists and equal in passed (header_to_find)") @allure.title("Check if (header_output) Key=Value exists and equal in passed (header_to_find)")
def check_key_value_presented_header(self, header_output: dict, header_to_find: dict) -> bool: def check_key_value_presented_header(self, header_output: dict, header_to_find: dict) -> bool:
header_att = header_output["header"]["attributes"] header_att = header_output["header"]["attributes"]
for key_to_check, val_to_check in header_to_find.items(): for key_to_check, val_to_check in header_to_find.items():
@ -112,25 +95,25 @@ class Test_http_system_header(ClusterTestBase):
), f"Only {EXPIRATION_EXPIRATION_RFC} can be displayed in header attributes" ), f"Only {EXPIRATION_EXPIRATION_RFC} can be displayed in header attributes"
@allure.title("Put / get / verify object and return head command result to invoker") @allure.title("Put / get / verify object and return head command result to invoker")
def oid_header_info_for_object(self, file_path: str, attributes: dict, user_container: str): def oid_header_info_for_object(self, default_wallet: WalletInfo, container: str, test_file: str, attributes: dict):
oid = upload_via_http_gate_curl( oid = upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=file_path, filepath=test_file,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=attr_into_str_header_curl(attributes), headers=attr_into_str_header_curl(attributes),
) )
verify_object_hash( verify_object_hash(
oid=oid, oid=oid,
file_name=file_path, file_name=test_file,
wallet=self.wallet, wallet=default_wallet,
cid=user_container, cid=container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,
request_node=self.cluster.cluster_nodes[0], request_node=self.cluster.cluster_nodes[0],
) )
head = head_object( head = head_object(
wallet=self.wallet, wallet=default_wallet,
cid=user_container, cid=container,
oid=oid, oid=oid,
shell=self.shell, shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint, endpoint=self.cluster.default_rpc_endpoint,
@ -138,12 +121,13 @@ class Test_http_system_header(ClusterTestBase):
return oid, head return oid, head
@allure.title("[NEGATIVE] Put object with expired epoch") @allure.title("[NEGATIVE] Put object with expired epoch")
def test_unable_put_expired_epoch(self, user_container: str, simple_object_size: ObjectSize): @requires_container(REP_2_1_2_PUBLIC)
def test_unable_put_expired_epoch(self, container: str, simple_object_size: ObjectSize):
headers = attr_into_str_header_curl({"System-Expiration-Epoch": str(get_epoch(self.shell, self.cluster) - 1)}) headers = attr_into_str_header_curl({"System-Expiration-Epoch": str(get_epoch(self.shell, self.cluster) - 1)})
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
with reporter.step("Put object using HTTP with attribute Expiration-Epoch where epoch is expired"): with reporter.step("Put object using HTTP with attribute Expiration-Epoch where epoch is expired"):
upload_via_http_gate_curl( upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=headers, headers=headers,
@ -151,12 +135,13 @@ class Test_http_system_header(ClusterTestBase):
) )
@allure.title("[NEGATIVE] Put object with negative System-Expiration-Duration") @allure.title("[NEGATIVE] Put object with negative System-Expiration-Duration")
def test_unable_put_negative_duration(self, user_container: str, simple_object_size: ObjectSize): @requires_container(REP_2_1_2_PUBLIC)
def test_unable_put_negative_duration(self, container: str, simple_object_size: ObjectSize):
headers = attr_into_str_header_curl({"System-Expiration-Duration": "-1h"}) headers = attr_into_str_header_curl({"System-Expiration-Duration": "-1h"})
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
with reporter.step("Put object using HTTP with attribute System-Expiration-Duration where duration is negative"): with reporter.step("Put object using HTTP with attribute System-Expiration-Duration where duration is negative"):
upload_via_http_gate_curl( upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=headers, headers=headers,
@ -164,12 +149,13 @@ class Test_http_system_header(ClusterTestBase):
) )
@allure.title("[NEGATIVE] Put object with System-Expiration-Timestamp value in the past") @allure.title("[NEGATIVE] Put object with System-Expiration-Timestamp value in the past")
def test_unable_put_expired_timestamp(self, user_container: str, simple_object_size: ObjectSize): @requires_container(REP_2_1_2_PUBLIC)
def test_unable_put_expired_timestamp(self, container: str, simple_object_size: ObjectSize):
headers = attr_into_str_header_curl({"System-Expiration-Timestamp": "1635075727"}) headers = attr_into_str_header_curl({"System-Expiration-Timestamp": "1635075727"})
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
with reporter.step("Put object using HTTP with attribute System-Expiration-Timestamp where duration is in the past"): with reporter.step("Put object using HTTP with attribute System-Expiration-Timestamp where duration is in the past"):
upload_via_http_gate_curl( upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=headers, headers=headers,
@ -177,11 +163,12 @@ class Test_http_system_header(ClusterTestBase):
) )
@allure.title("[NEGATIVE] Put object using HTTP with attribute System-Expiration-RFC3339 where duration is in the past") @allure.title("[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: ObjectSize): @requires_container(REP_2_1_2_PUBLIC)
def test_unable_put_expired_rfc(self, container: str, simple_object_size: ObjectSize):
headers = attr_into_str_header_curl({"System-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.value) file_path = generate_file(simple_object_size.value)
upload_via_http_gate_curl( upload_via_http_gate_curl(
cid=user_container, cid=container,
filepath=file_path, filepath=file_path,
endpoint=self.cluster.default_http_gate_endpoint, endpoint=self.cluster.default_http_gate_endpoint,
headers=headers, headers=headers,
@ -189,7 +176,10 @@ class Test_http_system_header(ClusterTestBase):
) )
@allure.title("Priority of attributes epoch>duration (obj_size={object_size})") @allure.title("Priority of attributes epoch>duration (obj_size={object_size})")
def test_http_attr_priority_epoch_duration(self, user_container: str, object_size: ObjectSize, epoch_duration: int): @requires_container(REP_2_1_2_PUBLIC)
def test_http_attr_priority_epoch_duration(
self, default_wallet: WalletInfo, container: str, object_size: ObjectSize, epoch_duration: int
):
self.tick_epoch() self.tick_epoch()
epoch_count = 1 epoch_count = 1
expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count
@ -201,7 +191,7 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step( with reporter.step(
f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr" f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr"
): ):
oid, head_info = self.oid_header_info_for_object(file_path=file_path, attributes=attributes, user_container=user_container) oid, head_info = self.oid_header_info_for_object(default_wallet, file_path, attributes, container)
self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch) self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch)
with reporter.step("Check that object becomes unavailable when epoch is expired"): with reporter.step("Check that object becomes unavailable when epoch is expired"):
for _ in range(0, epoch_count + 1): for _ in range(0, epoch_count + 1):
@ -213,17 +203,20 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step("Check object deleted because it expires-on epoch"): with reporter.step("Check object deleted because it expires-on epoch"):
wait_for_epochs_align(self.shell, self.cluster) wait_for_epochs_align(self.shell, self.cluster)
try_to_get_object_and_expect_error( try_to_get_object_and_expect_error(
cid=user_container, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern="404 Not Found", error_pattern="404 Not Found",
) )
# check that object is not available via grpc # check that object is not available via grpc
with pytest.raises(Exception, match=OBJECT_NOT_FOUND): with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_object_from_random_node(self.wallet, user_container, oid, self.shell, self.cluster) get_object_from_random_node(default_wallet, container, oid, self.shell, self.cluster)
@allure.title("Priority of attributes duration>timestamp (obj_size={object_size})") @allure.title("Priority of attributes duration>timestamp (obj_size={object_size})")
def test_http_attr_priority_dur_timestamp(self, user_container: str, object_size: ObjectSize, epoch_duration: int): @requires_container(REP_2_1_2_PUBLIC)
def test_http_attr_priority_dur_timestamp(
self, default_wallet: WalletInfo, container: str, object_size: ObjectSize, epoch_duration: int
):
self.tick_epoch() self.tick_epoch()
epoch_count = 2 epoch_count = 2
expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count
@ -238,7 +231,7 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step( with reporter.step(
f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr" f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr"
): ):
oid, head_info = self.oid_header_info_for_object(file_path=file_path, attributes=attributes, user_container=user_container) oid, head_info = self.oid_header_info_for_object(default_wallet, file_path, attributes, container)
self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch) self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch)
with reporter.step("Check that object becomes unavailable when epoch is expired"): with reporter.step("Check that object becomes unavailable when epoch is expired"):
for _ in range(0, epoch_count + 1): for _ in range(0, epoch_count + 1):
@ -250,17 +243,20 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step("Check object deleted because it expires-on epoch"): with reporter.step("Check object deleted because it expires-on epoch"):
wait_for_epochs_align(self.shell, self.cluster) wait_for_epochs_align(self.shell, self.cluster)
try_to_get_object_and_expect_error( try_to_get_object_and_expect_error(
cid=user_container, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern="404 Not Found", error_pattern="404 Not Found",
) )
# check that object is not available via grpc # check that object is not available via grpc
with pytest.raises(Exception, match=OBJECT_NOT_FOUND): with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_object_from_random_node(self.wallet, user_container, oid, self.shell, self.cluster) get_object_from_random_node(default_wallet, container, oid, self.shell, self.cluster)
@allure.title("Priority of attributes timestamp>Expiration-RFC (obj_size={object_size})") @allure.title("Priority of attributes timestamp>Expiration-RFC (obj_size={object_size})")
def test_http_attr_priority_timestamp_rfc(self, user_container: str, object_size: ObjectSize, epoch_duration: int): @requires_container(REP_2_1_2_PUBLIC)
def test_http_attr_priority_timestamp_rfc(
self, default_wallet: WalletInfo, container: str, object_size: ObjectSize, epoch_duration: int
):
self.tick_epoch() self.tick_epoch()
epoch_count = 2 epoch_count = 2
expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count
@ -275,7 +271,7 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step( with reporter.step(
f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr" f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr"
): ):
oid, head_info = self.oid_header_info_for_object(file_path=file_path, attributes=attributes, user_container=user_container) oid, head_info = self.oid_header_info_for_object(default_wallet, file_path, attributes, container)
self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch) self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch)
with reporter.step("Check that object becomes unavailable when epoch is expired"): with reporter.step("Check that object becomes unavailable when epoch is expired"):
for _ in range(0, epoch_count + 1): for _ in range(0, epoch_count + 1):
@ -287,14 +283,14 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step("Check object deleted because it expires-on epoch"): with reporter.step("Check object deleted because it expires-on epoch"):
wait_for_epochs_align(self.shell, self.cluster) wait_for_epochs_align(self.shell, self.cluster)
try_to_get_object_and_expect_error( try_to_get_object_and_expect_error(
cid=user_container, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern="404 Not Found", error_pattern="404 Not Found",
) )
# check that object is not available via grpc # check that object is not available via grpc
with pytest.raises(Exception, match=OBJECT_NOT_FOUND): with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_object_from_random_node(self.wallet, user_container, oid, self.shell, self.cluster) get_object_from_random_node(default_wallet, container, oid, self.shell, self.cluster)
@allure.title("Object should be deleted when expiration passed (obj_size={object_size})") @allure.title("Object should be deleted when expiration passed (obj_size={object_size})")
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -303,7 +299,10 @@ class Test_http_system_header(ClusterTestBase):
["simple"], ["simple"],
indirect=True, indirect=True,
) )
def test_http_rfc_object_unavailable_after_expir(self, user_container: str, object_size: ObjectSize, epoch_duration: int): @requires_container(REP_2_1_2_PUBLIC)
def test_http_rfc_object_unavailable_after_expir(
self, default_wallet: WalletInfo, container: str, object_size: ObjectSize, epoch_duration: int
):
self.tick_epoch() self.tick_epoch()
epoch_count = 2 epoch_count = 2
expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count expected_epoch = get_epoch(self.shell, self.cluster) + epoch_count
@ -315,11 +314,7 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step( with reporter.step(
f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr" f"Put objects using HTTP with attributes and head command should display {EXPIRATION_EPOCH_HEADER}: {expected_epoch} attr"
): ):
oid, head_info = self.oid_header_info_for_object( oid, head_info = self.oid_header_info_for_object(default_wallet, file_path, attributes, container)
file_path=file_path,
attributes=attributes,
user_container=user_container,
)
self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch) self.validation_for_http_header_attr(head_info=head_info, expected_epoch=expected_epoch)
with reporter.step("Check that object becomes unavailable when epoch is expired"): with reporter.step("Check that object becomes unavailable when epoch is expired"):
for _ in range(0, epoch_count + 1): for _ in range(0, epoch_count + 1):
@ -332,11 +327,11 @@ class Test_http_system_header(ClusterTestBase):
with reporter.step("Check object deleted because it expires-on epoch"): with reporter.step("Check object deleted because it expires-on epoch"):
wait_for_epochs_align(self.shell, self.cluster) wait_for_epochs_align(self.shell, self.cluster)
try_to_get_object_and_expect_error( try_to_get_object_and_expect_error(
cid=user_container, cid=container,
oid=oid, oid=oid,
node=self.cluster.cluster_nodes[0], node=self.cluster.cluster_nodes[0],
error_pattern="404 Not Found", error_pattern="404 Not Found",
) )
# check that object is not available via grpc # check that object is not available via grpc
with pytest.raises(Exception, match=OBJECT_NOT_FOUND): with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_object_from_random_node(self.wallet, user_container, oid, self.shell, self.cluster) get_object_from_random_node(default_wallet, container, oid, self.shell, self.cluster)

View file

@ -1,13 +1,13 @@
import json import json
import os
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.cli import FrostfsCli from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT
from frostfs_testlib.resources.wellknown_acl import EACL_PUBLIC_READ_WRITE from frostfs_testlib.shell.interfaces import Shell
from frostfs_testlib.steps.cli.container import create_container, delete_container from frostfs_testlib.steps.cli.object import get_object, get_object_nodes, put_object
from frostfs_testlib.steps.cli.object import delete_object, get_object, get_object_nodes, put_object
from frostfs_testlib.storage.cluster import Cluster, ClusterNode, StorageNode from frostfs_testlib.storage.cluster import Cluster, ClusterNode, StorageNode
from frostfs_testlib.storage.controllers import ClusterStateController, ShardsWatcher from frostfs_testlib.storage.controllers import ClusterStateController, ShardsWatcher
from frostfs_testlib.storage.controllers.state_managers.config_state_manager import ConfigStateManager from frostfs_testlib.storage.controllers.state_managers.config_state_manager import ConfigStateManager
@ -17,65 +17,86 @@ from frostfs_testlib.testing import parallel, wait_for_success
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from ...helpers.container_request import PUBLIC_WITH_POLICY, requires_container
def set_shard_rw_mode(node: ClusterNode):
watcher = ShardsWatcher(node)
shards = watcher.get_shards()
for shard in shards:
watcher.set_shard_mode(shard["shard_id"], mode="read-write")
watcher.await_for_all_shards_status(status="read-write")
@pytest.fixture()
@allure.title("Revert all shards mode")
def revert_all_shards_mode(cluster: Cluster) -> None:
yield
parallel(set_shard_rw_mode, cluster.cluster_nodes)
@pytest.fixture()
def object_id(client_shell: Shell, cluster: Cluster, container: str, default_wallet: WalletInfo, max_object_size: int) -> str:
with reporter.step("Create container, and put object"):
file = generate_file(round(max_object_size * 0.8))
oid = put_object(default_wallet, file, container, client_shell, cluster.default_rpc_endpoint)
return oid
@pytest.fixture()
def node_with_object(cluster: Cluster, container: str, object_id: str) -> ClusterNode:
with reporter.step("Search node with object"):
nodes = get_object_nodes(cluster, container, object_id, cluster.cluster_nodes[0])
return nodes[0]
@pytest.fixture()
@wait_for_success(180, 30, title="Search object in system")
def object_path_on_node(object_id: str, container: str, node_with_object: ClusterNode) -> str:
oid_path = f"{object_id[0]}/{object_id[1]}/{object_id[2]}/{object_id[3]}"
object_path = None
with reporter.step("Search object file"):
node_shell = node_with_object.storage_node.host.get_shell()
data_path = node_with_object.storage_node.get_data_directory()
all_datas = node_shell.exec(f"ls -la {data_path}/data | awk '{{ print $9 }}'").stdout.strip()
for data_dir in all_datas.replace(".", "").strip().split("\n"):
check_dir = node_shell.exec(f" [ -d {data_path}/data/{data_dir}/data/{oid_path} ] && echo 1 || echo 0").stdout
if "1" in check_dir:
object_path = f"{data_path}/data/{data_dir}/data/{oid_path}"
object_name = f"{object_id[4:]}.{container}"
break
assert object_path is not None, f"{object_id} object not found in directory - {data_path}/data"
return os.path.join(object_path, object_name)
@pytest.fixture()
def erroneous_object_id(object_id: str, object_path_on_node: str, node_with_object: ClusterNode):
with reporter.step("Block read file"):
node_with_object.host.get_shell().exec(f"chmod a-r {object_path_on_node}")
yield object_id
with reporter.step("Restore file access"):
node_with_object.host.get_shell().exec(f"chmod +r {object_path_on_node}")
@pytest.fixture()
def change_config_storage(cluster_state_controller: ClusterStateController):
with reporter.step("Change threshold error shards"):
cluster_state_controller.manager(ConfigStateManager).set_on_all_nodes(
service_type=StorageNode, values={"storage:shard_ro_error_threshold": "5"}
)
yield
with reporter.step("Restore threshold error shards"):
cluster_state_controller.manager(ConfigStateManager).revert_all()
@pytest.mark.nightly @pytest.mark.nightly
@pytest.mark.shard @pytest.mark.shard
class TestControlShard(ClusterTestBase): class TestControlShard(ClusterTestBase):
@staticmethod
@wait_for_success(180, 30)
def get_object_path_and_name_file(oid: str, cid: str, node: ClusterNode) -> tuple[str, str]:
oid_path = f"{oid[0]}/{oid[1]}/{oid[2]}/{oid[3]}"
object_path = None
with reporter.step("Search object file"):
node_shell = node.storage_node.host.get_shell()
data_path = node.storage_node.get_data_directory()
all_datas = node_shell.exec(f"ls -la {data_path}/data | awk '{{ print $9 }}'").stdout.strip()
for data_dir in all_datas.replace(".", "").strip().split("\n"):
check_dir = node_shell.exec(f" [ -d {data_path}/data/{data_dir}/data/{oid_path} ] && echo 1 || echo 0").stdout
if "1" in check_dir:
object_path = f"{data_path}/data/{data_dir}/data/{oid_path}"
object_name = f"{oid[4:]}.{cid}"
break
assert object_path is not None, f"{oid} object not found in directory - {data_path}/data"
return object_path, object_name
def set_shard_rw_mode(self, node: ClusterNode):
watcher = ShardsWatcher(node)
shards = watcher.get_shards()
for shard in shards:
watcher.set_shard_mode(shard["shard_id"], mode="read-write")
watcher.await_for_all_shards_status(status="read-write")
@pytest.fixture()
@allure.title("Revert all shards mode")
def revert_all_shards_mode(self) -> None:
yield
parallel(self.set_shard_rw_mode, self.cluster.cluster_nodes)
@pytest.fixture()
def oid_cid_node(self, default_wallet: WalletInfo, max_object_size: int) -> tuple[str, str, ClusterNode]:
with reporter.step("Create container, and put object"):
cid = create_container(
wallet=default_wallet,
shell=self.shell,
endpoint=self.cluster.default_rpc_endpoint,
rule="REP 1 CBF 1",
basic_acl=EACL_PUBLIC_READ_WRITE,
)
file = generate_file(round(max_object_size * 0.8))
oid = put_object(wallet=default_wallet, path=file, cid=cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
with reporter.step("Search node with object"):
nodes = get_object_nodes(cluster=self.cluster, cid=cid, oid=oid, alive_node=self.cluster.cluster_nodes[0])
yield oid, cid, nodes[0]
object_path, object_name = self.get_object_path_and_name_file(oid, cid, nodes[0])
nodes[0].host.get_shell().exec(f"chmod +r {object_path}/{object_name}")
delete_object(wallet=default_wallet, cid=cid, oid=oid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
delete_container(wallet=default_wallet, cid=cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
@staticmethod @staticmethod
def get_shards_from_cli(node: StorageNode) -> list[Shard]: def get_shards_from_cli(node: StorageNode) -> list[Shard]:
wallet_path = node.get_remote_wallet_path() wallet_path = node.get_remote_wallet_path()
@ -94,16 +115,6 @@ class TestControlShard(ClusterTestBase):
) )
return [Shard.from_object(shard) for shard in json.loads(result.stdout.split(">", 1)[1])] return [Shard.from_object(shard) for shard in json.loads(result.stdout.split(">", 1)[1])]
@pytest.fixture()
def change_config_storage(self, cluster_state_controller: ClusterStateController):
with reporter.step("Change threshold error shards"):
cluster_state_controller.manager(ConfigStateManager).set_on_all_nodes(
service_type=StorageNode, values={"storage:shard_ro_error_threshold": "5"}
)
yield
with reporter.step("Restore threshold error shards"):
cluster_state_controller.manager(ConfigStateManager).revert_all()
@allure.title("All shards are available") @allure.title("All shards are available")
def test_control_shard(self, cluster: Cluster): def test_control_shard(self, cluster: Cluster):
for storage_node in cluster.storage_nodes: for storage_node in cluster.storage_nodes:
@ -114,31 +125,25 @@ class TestControlShard(ClusterTestBase):
@allure.title("Shard become read-only when errors exceeds threshold") @allure.title("Shard become read-only when errors exceeds threshold")
@pytest.mark.failover @pytest.mark.failover
@requires_container(PUBLIC_WITH_POLICY("REP 1 CBF 1", short_name="REP 1"))
def test_shard_errors( def test_shard_errors(
self, self,
default_wallet: WalletInfo, default_wallet: WalletInfo,
oid_cid_node: tuple[str, str, ClusterNode], container: str,
node_with_object: ClusterNode,
erroneous_object_id: str,
object_path_on_node: str,
change_config_storage: None, change_config_storage: None,
revert_all_shards_mode: None, revert_all_shards_mode: None,
): ):
oid, cid, node = oid_cid_node
with reporter.step("Search object in system."):
object_path, object_name = self.get_object_path_and_name_file(*oid_cid_node)
with reporter.step("Block read file"):
node.host.get_shell().exec(f"chmod a-r {object_path}/{object_name}")
with reporter.step("Get object, expect 6 errors"): with reporter.step("Get object, expect 6 errors"):
for _ in range(6): for _ in range(6):
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
get_object( get_object(default_wallet, container, erroneous_object_id, self.shell, node_with_object.storage_node.get_rpc_endpoint())
wallet=default_wallet,
cid=cid,
oid=oid,
shell=self.shell,
endpoint=node.storage_node.get_rpc_endpoint(),
)
with reporter.step("Check shard status"): with reporter.step("Check shard status"):
for shard in ShardsWatcher(node).get_shards(): for shard in ShardsWatcher(node_with_object).get_shards():
if shard["blobstor"][1]["path"] in object_path: if shard["blobstor"][1]["path"] in object_path_on_node:
with reporter.step(f"Shard - {shard['shard_id']} to {node.host_ip}, mode - {shard['mode']}"): with reporter.step(f"Shard {shard['shard_id']} should be in read-only mode"):
assert shard["mode"] == "read-only" assert shard["mode"] == "read-only"
break break