Compare commits
1 commit
master
...
new-ec-rep
Author | SHA1 | Date | |
---|---|---|---|
bbc1a20a4d |
20 changed files with 321 additions and 622 deletions
108
.devenv.hosting.yaml
Normal file
108
.devenv.hosting.yaml
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
hosts:
|
||||||
|
- address: localhost
|
||||||
|
attributes:
|
||||||
|
sudo_shell: false
|
||||||
|
plugin_name: docker
|
||||||
|
healthcheck_plugin_name: basic
|
||||||
|
attributes:
|
||||||
|
skip_readiness_check: True
|
||||||
|
force_transactions: True
|
||||||
|
services:
|
||||||
|
- name: frostfs-storage_01
|
||||||
|
attributes:
|
||||||
|
container_name: s01
|
||||||
|
config_path: /etc/frostfs/storage/config.yml
|
||||||
|
wallet_path: ../frostfs-dev-env/services/storage/wallet01.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/empty-password.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/storage/wallet01.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s01
|
||||||
|
endpoint_data0: s01.frostfs.devenv:8080
|
||||||
|
control_endpoint: s01.frostfs.devenv:8081
|
||||||
|
un_locode: "RU MOW"
|
||||||
|
- name: frostfs-storage_02
|
||||||
|
attributes:
|
||||||
|
container_name: s02
|
||||||
|
config_path: /etc/frostfs/storage/config.yml
|
||||||
|
wallet_path: ../frostfs-dev-env/services/storage/wallet02.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/empty-password.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/storage/wallet02.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s02
|
||||||
|
endpoint_data0: s02.frostfs.devenv:8080
|
||||||
|
control_endpoint: s02.frostfs.devenv:8081
|
||||||
|
un_locode: "RU LED"
|
||||||
|
- name: frostfs-storage_03
|
||||||
|
attributes:
|
||||||
|
container_name: s03
|
||||||
|
config_path: /etc/frostfs/storage/config.yml
|
||||||
|
wallet_path: ../frostfs-dev-env/services/storage/wallet03.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/empty-password.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/storage/wallet03.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s03
|
||||||
|
endpoint_data0: s03.frostfs.devenv:8080
|
||||||
|
control_endpoint: s03.frostfs.devenv:8081
|
||||||
|
un_locode: "SE STO"
|
||||||
|
- name: frostfs-storage_04
|
||||||
|
attributes:
|
||||||
|
container_name: s04
|
||||||
|
config_path: /etc/frostfs/storage/config.yml
|
||||||
|
wallet_path: ../frostfs-dev-env/services/storage/wallet04.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/empty-password.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/storage/wallet04.json
|
||||||
|
wallet_password: ""
|
||||||
|
volume_name: storage_storage_s04
|
||||||
|
endpoint_data0: s04.frostfs.devenv:8080
|
||||||
|
control_endpoint: s04.frostfs.devenv:8081
|
||||||
|
un_locode: "FI HEL"
|
||||||
|
- name: frostfs-s3_01
|
||||||
|
attributes:
|
||||||
|
container_name: s3_gate
|
||||||
|
config_path: ../frostfs-dev-env/services/s3_gate/.s3.env
|
||||||
|
wallet_path: ../frostfs-dev-env/services/s3_gate/wallet.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/password-s3.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/s3_gate/wallet.json
|
||||||
|
wallet_password: "s3"
|
||||||
|
endpoint_data0: https://s3.frostfs.devenv:8080
|
||||||
|
- name: frostfs-http_01
|
||||||
|
attributes:
|
||||||
|
container_name: http_gate
|
||||||
|
config_path: ../frostfs-dev-env/services/http_gate/.http.env
|
||||||
|
wallet_path: ../frostfs-dev-env/services/http_gate/wallet.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/password-other.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/http_gate/wallet.json
|
||||||
|
wallet_password: "one"
|
||||||
|
endpoint_data0: http://http.frostfs.devenv
|
||||||
|
- name: frostfs-ir_01
|
||||||
|
attributes:
|
||||||
|
container_name: ir01
|
||||||
|
config_path: ../frostfs-dev-env/services/ir/.ir.env
|
||||||
|
wallet_path: ../frostfs-dev-env/services/ir/az.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/password-other.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/ir/az.json
|
||||||
|
wallet_password: "one"
|
||||||
|
- name: neo-go_01
|
||||||
|
attributes:
|
||||||
|
container_name: morph_chain
|
||||||
|
config_path: ../frostfs-dev-env/services/morph_chain/protocol.privnet.yml
|
||||||
|
wallet_path: ../frostfs-dev-env/services/morph_chain/node-wallet.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/password-other.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/morph_chain/node-wallet.json
|
||||||
|
wallet_password: "one"
|
||||||
|
endpoint_internal0: http://morph-chain.frostfs.devenv:30333
|
||||||
|
- name: main-chain_01
|
||||||
|
attributes:
|
||||||
|
container_name: main_chain
|
||||||
|
config_path: ../frostfs-dev-env/services/chain/protocol.privnet.yml
|
||||||
|
wallet_path: ../frostfs-dev-env/services/chain/node-wallet.json
|
||||||
|
local_wallet_config_path: ./TemporaryDir/password-other.yml
|
||||||
|
local_wallet_path: ../frostfs-dev-env/services/chain/node-wallet.json
|
||||||
|
wallet_password: "one"
|
||||||
|
endpoint_internal0: http://main-chain.frostfs.devenv:30333
|
||||||
|
- name: coredns_01
|
||||||
|
attributes:
|
||||||
|
container_name: coredns
|
||||||
|
clis:
|
||||||
|
- name: frostfs-cli
|
||||||
|
exec_path: frostfs-cli
|
|
@ -9,12 +9,6 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
name: isort (python)
|
name: isort (python)
|
||||||
- repo: https://git.frostfs.info/TrueCloudLab/allure-validator
|
|
||||||
rev: 1.0.1
|
|
||||||
hooks:
|
|
||||||
- id: allure-validator
|
|
||||||
args: ["pytest_tests/"]
|
|
||||||
pass_filenames: false
|
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
autofix_prs: false
|
autofix_prs: false
|
||||||
|
|
|
@ -19,7 +19,6 @@ markers =
|
||||||
grpc_api: standard gRPC API tests
|
grpc_api: standard gRPC API tests
|
||||||
grpc_control: tests related to using frostfs-cli control commands
|
grpc_control: tests related to using frostfs-cli control commands
|
||||||
grpc_object_lock: gRPC lock tests
|
grpc_object_lock: gRPC lock tests
|
||||||
grpc_without_user: gRPC without user tests
|
|
||||||
http_gate: HTTP gate contract
|
http_gate: HTTP gate contract
|
||||||
http_put: HTTP gate test cases with PUT call
|
http_put: HTTP gate test cases with PUT call
|
||||||
s3_gate: All S3 gate tests
|
s3_gate: All S3 gate tests
|
||||||
|
@ -68,4 +67,3 @@ markers =
|
||||||
ec_replication: replication EC
|
ec_replication: replication EC
|
||||||
static_session_container: tests for a static session in a container
|
static_session_container: tests for a static session in a container
|
||||||
shard: shard management tests
|
shard: shard management tests
|
||||||
session_logs: check logs messages
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from typing import Optional
|
||||||
|
|
||||||
from frostfs_testlib import reporter
|
from frostfs_testlib import reporter
|
||||||
from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT
|
from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT
|
||||||
from frostfs_testlib.resources.error_patterns import OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND
|
from frostfs_testlib.resources.error_patterns import OBJECT_ACCESS_DENIED
|
||||||
from frostfs_testlib.shell import Shell
|
from frostfs_testlib.shell import Shell
|
||||||
from frostfs_testlib.steps.cli.object import (
|
from frostfs_testlib.steps.cli.object import (
|
||||||
delete_object,
|
delete_object,
|
||||||
|
@ -20,10 +20,6 @@ from frostfs_testlib.utils.file_utils import get_file_hash
|
||||||
|
|
||||||
OPERATION_ERROR_TYPE = RuntimeError
|
OPERATION_ERROR_TYPE = RuntimeError
|
||||||
|
|
||||||
# TODO: Revert to just OBJECT_ACCESS_DENIED when the issue is fixed
|
|
||||||
# https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/1297
|
|
||||||
OBJECT_NO_ACCESS = rf"(?:{OBJECT_NOT_FOUND}|{OBJECT_ACCESS_DENIED})"
|
|
||||||
|
|
||||||
|
|
||||||
def can_get_object(
|
def can_get_object(
|
||||||
wallet: WalletInfo,
|
wallet: WalletInfo,
|
||||||
|
@ -47,7 +43,9 @@ def can_get_object(
|
||||||
cluster=cluster,
|
cluster=cluster,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_NO_ACCESS), f"Expected {err} to match {OBJECT_NO_ACCESS}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
assert get_file_hash(file_name) == get_file_hash(got_file_path)
|
assert get_file_hash(file_name) == get_file_hash(got_file_path)
|
||||||
return True
|
return True
|
||||||
|
@ -76,7 +74,9 @@ def can_put_object(
|
||||||
cluster=cluster,
|
cluster=cluster,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_ACCESS_DENIED), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -102,7 +102,9 @@ def can_delete_object(
|
||||||
endpoint=endpoint,
|
endpoint=endpoint,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_NO_ACCESS), f"Expected {err} to match {OBJECT_NO_ACCESS}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -130,7 +132,9 @@ def can_get_head_object(
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_NO_ACCESS), f"Expected {err} to match {OBJECT_NO_ACCESS}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -159,7 +163,9 @@ def can_get_range_of_object(
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_NO_ACCESS), f"Expected {err} to match {OBJECT_NO_ACCESS}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -188,7 +194,9 @@ def can_get_range_hash_of_object(
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_NO_ACCESS), f"Expected {err} to match {OBJECT_NO_ACCESS}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -215,7 +223,9 @@ def can_search_object(
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except OPERATION_ERROR_TYPE as err:
|
except OPERATION_ERROR_TYPE as err:
|
||||||
assert string_utils.is_str_match_pattern(err, OBJECT_NO_ACCESS), f"Expected {err} to match {OBJECT_NO_ACCESS}"
|
assert string_utils.is_str_match_pattern(
|
||||||
|
err, OBJECT_ACCESS_DENIED
|
||||||
|
), f"Expected {err} to match {OBJECT_ACCESS_DENIED}"
|
||||||
return False
|
return False
|
||||||
if oid:
|
if oid:
|
||||||
return oid in oids
|
return oid in oids
|
||||||
|
|
|
@ -3,3 +3,4 @@ import os
|
||||||
TEST_CYCLES_COUNT = int(os.getenv("TEST_CYCLES_COUNT", "1"))
|
TEST_CYCLES_COUNT = int(os.getenv("TEST_CYCLES_COUNT", "1"))
|
||||||
|
|
||||||
DEVENV_PATH = os.getenv("DEVENV_PATH", os.path.join("..", "frostfs-dev-env"))
|
DEVENV_PATH = os.getenv("DEVENV_PATH", os.path.join("..", "frostfs-dev-env"))
|
||||||
|
HOSTING_CONFIG_FILE = os.getenv("HOSTING_CONFIG_FILE", ".devenv.hosting.yaml")
|
||||||
|
|
|
@ -21,7 +21,8 @@ from pytest_tests.helpers.container_access import (
|
||||||
assert_full_access_to_container,
|
assert_full_access_to_container,
|
||||||
assert_no_access_to_container,
|
assert_no_access_to_container,
|
||||||
)
|
)
|
||||||
from pytest_tests.helpers.object_access import OBJECT_NO_ACCESS
|
|
||||||
|
OBJECT_NO_ACCESS = f"(?:{OBJECT_NOT_FOUND}|{OBJECT_ACCESS_DENIED})"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.ape
|
@pytest.mark.ape
|
||||||
|
@ -57,7 +58,7 @@ class TestApeFilters(ClusterTestBase):
|
||||||
return cid, objects_with_header, objects_with_other_header, objects_without_header, file_path
|
return cid, objects_with_header, objects_with_other_header, objects_without_header, file_path
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def private_container(self, default_wallet: WalletInfo, frostfs_cli: FrostfsCli, cluster: Cluster):
|
def container_with_objects(self, default_wallet: WalletInfo, file_path: TestFile, frostfs_cli: FrostfsCli, cluster: Cluster):
|
||||||
with reporter.step("Create private container"):
|
with reporter.step("Create private container"):
|
||||||
cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl="0")
|
cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl="0")
|
||||||
|
|
||||||
|
@ -76,14 +77,9 @@ class TestApeFilters(ClusterTestBase):
|
||||||
with reporter.step("Wait for one block"):
|
with reporter.step("Wait for one block"):
|
||||||
self.wait_for_blocks()
|
self.wait_for_blocks()
|
||||||
|
|
||||||
return cid
|
objects_with_header, objects_with_other_header, objects_without_header = self._fill_container(default_wallet, file_path, cid)
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
return cid, objects_with_header, objects_with_other_header, objects_without_header, file_path
|
||||||
def container_with_objects(self, private_container: str, default_wallet: WalletInfo, file_path: TestFile):
|
|
||||||
objects_with_header, objects_with_other_header, objects_without_header = self._fill_container(
|
|
||||||
default_wallet, file_path, private_container
|
|
||||||
)
|
|
||||||
return private_container, objects_with_header, objects_with_other_header, objects_without_header, file_path
|
|
||||||
|
|
||||||
@reporter.step("Add objects to container")
|
@reporter.step("Add objects to container")
|
||||||
def _fill_container(self, wallet: WalletInfo, test_file: TestFile, cid: str):
|
def _fill_container(self, wallet: WalletInfo, test_file: TestFile, cid: str):
|
||||||
|
@ -106,7 +102,6 @@ class TestApeFilters(ClusterTestBase):
|
||||||
@pytest.mark.sanity
|
@pytest.mark.sanity
|
||||||
@allure.title("Operations with request filter (match_type={match_type}, obj_size={object_size})")
|
@allure.title("Operations with request filter (match_type={match_type}, obj_size={object_size})")
|
||||||
@pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL])
|
@pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL])
|
||||||
@pytest.mark.skip("https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/1243")
|
|
||||||
def test_ape_filters_request(
|
def test_ape_filters_request(
|
||||||
self,
|
self,
|
||||||
frostfs_cli: FrostfsCli,
|
frostfs_cli: FrostfsCli,
|
||||||
|
@ -125,7 +120,7 @@ class TestApeFilters(ClusterTestBase):
|
||||||
endpoint = self.cluster.default_rpc_endpoint
|
endpoint = self.cluster.default_rpc_endpoint
|
||||||
|
|
||||||
with reporter.step("Deny all operations for others via APE with request condition"):
|
with reporter.step("Deny all operations for others via APE with request condition"):
|
||||||
request_condition = ape.Condition('"frostfs:xheader/check_key"', '"check_value"', ape.ConditionType.REQUEST, match_type)
|
request_condition = ape.Condition('"check_key"', '"check_value"', ape.ConditionType.REQUEST, match_type)
|
||||||
role_condition = ape.Condition.by_role(ape.Role.OTHERS)
|
role_condition = ape.Condition.by_role(ape.Role.OTHERS)
|
||||||
deny_rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, [request_condition, role_condition])
|
deny_rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, [request_condition, role_condition])
|
||||||
|
|
||||||
|
@ -162,7 +157,6 @@ class TestApeFilters(ClusterTestBase):
|
||||||
|
|
||||||
@allure.title("Operations with deny user headers filter (match_type={match_type}, obj_size={object_size})")
|
@allure.title("Operations with deny user headers filter (match_type={match_type}, obj_size={object_size})")
|
||||||
@pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL])
|
@pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL])
|
||||||
@pytest.mark.skip("https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/1300")
|
|
||||||
def test_ape_deny_filters_object(
|
def test_ape_deny_filters_object(
|
||||||
self,
|
self,
|
||||||
frostfs_cli: FrostfsCli,
|
frostfs_cli: FrostfsCli,
|
||||||
|
@ -174,15 +168,15 @@ class TestApeFilters(ClusterTestBase):
|
||||||
# TODO: Refactor this to be fixtures, not test logic
|
# TODO: Refactor this to be fixtures, not test logic
|
||||||
(
|
(
|
||||||
cid,
|
cid,
|
||||||
objects_with_attributes,
|
objects_with_header,
|
||||||
objects_with_other_attributes,
|
objects_with_other_header,
|
||||||
objs_without_attributes,
|
objs_without_header,
|
||||||
file_path,
|
file_path,
|
||||||
) = public_container_with_objects
|
) = public_container_with_objects
|
||||||
endpoint = self.cluster.default_rpc_endpoint
|
endpoint = self.cluster.default_rpc_endpoint
|
||||||
|
|
||||||
allow_objects = objects_with_other_attributes if match_type == ape.MatchType.EQUAL else objects_with_attributes
|
allow_objects = objects_with_other_header if match_type == ape.MatchType.EQUAL else objects_with_header
|
||||||
deny_objects = objects_with_attributes if match_type == ape.MatchType.EQUAL else objects_with_other_attributes
|
deny_objects = objects_with_header if match_type == ape.MatchType.EQUAL else objects_with_other_header
|
||||||
|
|
||||||
# When there is no attribute on the object, it's the same as "", and "" is not equal to "<some_value>"
|
# When there is no attribute on the object, it's the same as "", and "" is not equal to "<some_value>"
|
||||||
# So it's the same as deny_objects
|
# So it's the same as deny_objects
|
||||||
|
@ -198,23 +192,10 @@ class TestApeFilters(ClusterTestBase):
|
||||||
ape.ObjectOperations.DELETE: False, # Denied by restricted PUT
|
ape.ObjectOperations.DELETE: False, # Denied by restricted PUT
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
allowed_access = {
|
|
||||||
ape.MatchType.EQUAL: FULL_ACCESS,
|
|
||||||
ape.MatchType.NOT_EQUAL: {
|
|
||||||
ape.ObjectOperations.PUT: False, # because currently we are put without attributes
|
|
||||||
ape.ObjectOperations.GET: True,
|
|
||||||
ape.ObjectOperations.HEAD: True,
|
|
||||||
ape.ObjectOperations.GET_RANGE: True,
|
|
||||||
ape.ObjectOperations.GET_RANGE_HASH: True,
|
|
||||||
ape.ObjectOperations.SEARCH: True,
|
|
||||||
ape.ObjectOperations.DELETE: False, # Because delete needs to put a tombstone without attributes
|
|
||||||
},
|
|
||||||
}
|
|
||||||
# End of refactor
|
# End of refactor
|
||||||
|
|
||||||
with reporter.step("Deny operations for others via APE with resource condition"):
|
with reporter.step("Deny operations for others via APE with resource condition"):
|
||||||
resource_condition = ape.Condition('"check_key"', '"check_value"', ape.ConditionType.RESOURCE, match_type)
|
resource_condition = ape.Condition('"check_key"', '"check_value"', ape.ConditionType.RESOURCE, match_type)
|
||||||
not_a_tombstone_condition = ape.Condition.by_object_type("TOMBSTONE", ape.ConditionType.RESOURCE, ape.MatchType.NOT_EQUAL)
|
|
||||||
role_condition = ape.Condition.by_role(ape.Role.OTHERS)
|
role_condition = ape.Condition.by_role(ape.Role.OTHERS)
|
||||||
deny_rule = ape.Rule(ape.Verb.DENY, self.RESOURCE_OPERATIONS, [resource_condition, role_condition])
|
deny_rule = ape.Rule(ape.Verb.DENY, self.RESOURCE_OPERATIONS, [resource_condition, role_condition])
|
||||||
|
|
||||||
|
@ -241,7 +222,7 @@ class TestApeFilters(ClusterTestBase):
|
||||||
no_attributes_access[match_type],
|
no_attributes_access[match_type],
|
||||||
other_wallet,
|
other_wallet,
|
||||||
cid,
|
cid,
|
||||||
objs_without_attributes.pop(),
|
objs_without_header.pop(),
|
||||||
file_path,
|
file_path,
|
||||||
self.shell,
|
self.shell,
|
||||||
self.cluster,
|
self.cluster,
|
||||||
|
@ -249,9 +230,7 @@ class TestApeFilters(ClusterTestBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
with reporter.step("Check others have full access to objects without deny attribute"):
|
with reporter.step("Check others have full access to objects without deny attribute"):
|
||||||
assert_access_to_container(
|
assert_full_access_to_container(other_wallet, cid, allow_objects.pop(), file_path, self.shell, self.cluster, xhdr=xhdr)
|
||||||
allowed_access[match_type], other_wallet, cid, allow_objects.pop(), file_path, self.shell, self.cluster, xhdr=xhdr
|
|
||||||
)
|
|
||||||
|
|
||||||
with reporter.step("Check others have no access to objects with deny attribute"):
|
with reporter.step("Check others have no access to objects with deny attribute"):
|
||||||
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
||||||
|
@ -289,25 +268,23 @@ class TestApeFilters(ClusterTestBase):
|
||||||
# TODO: Refactor this to be fixtures, not test logic!
|
# TODO: Refactor this to be fixtures, not test logic!
|
||||||
(
|
(
|
||||||
cid,
|
cid,
|
||||||
objects_with_attributes,
|
objects_with_header,
|
||||||
objects_with_other_attributes,
|
objects_with_other_header,
|
||||||
objects_without_attributes,
|
objects_without_header,
|
||||||
file_path,
|
file_path,
|
||||||
) = container_with_objects
|
) = container_with_objects
|
||||||
endpoint = self.cluster.default_rpc_endpoint
|
endpoint = self.cluster.default_rpc_endpoint
|
||||||
|
|
||||||
if match_type == ape.MatchType.EQUAL:
|
if match_type == ape.MatchType.EQUAL:
|
||||||
allow_objects = objects_with_attributes
|
allow_objects = objects_with_header
|
||||||
deny_objects = objects_with_other_attributes
|
deny_objects = objects_with_other_header
|
||||||
allow_attribute = self.HEADER
|
allow_attribute = self.HEADER
|
||||||
deny_attribute = self.OTHER_HEADER
|
deny_attribute = self.OTHER_HEADER
|
||||||
no_attributes_match_context = pytest.raises(Exception, match=OBJECT_NO_ACCESS)
|
|
||||||
else:
|
else:
|
||||||
allow_objects = objects_with_other_attributes
|
allow_objects = objects_with_other_header
|
||||||
deny_objects = objects_with_attributes
|
deny_objects = objects_with_header
|
||||||
allow_attribute = self.OTHER_HEADER
|
allow_attribute = self.OTHER_HEADER
|
||||||
deny_attribute = self.HEADER
|
deny_attribute = self.HEADER
|
||||||
no_attributes_match_context = expect_not_raises()
|
|
||||||
# End of refactor block
|
# End of refactor block
|
||||||
|
|
||||||
with reporter.step("Allow operations for others except few operations by resource condition via APE"):
|
with reporter.step("Allow operations for others except few operations by resource condition via APE"):
|
||||||
|
@ -320,15 +297,15 @@ class TestApeFilters(ClusterTestBase):
|
||||||
with reporter.step("Wait for one block"):
|
with reporter.step("Wait for one block"):
|
||||||
self.wait_for_blocks()
|
self.wait_for_blocks()
|
||||||
|
|
||||||
with reporter.step("Check GET, PUT and HEAD operations with objects without attributes for OTHERS role"):
|
with reporter.step("Check others cannot get and put objects without attributes"):
|
||||||
oid = objects_without_attributes.pop()
|
oid = objects_without_header.pop()
|
||||||
with no_attributes_match_context:
|
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||||
assert head_object(other_wallet, cid, oid, self.shell, endpoint)
|
assert head_object(other_wallet, cid, oid, self.shell, endpoint)
|
||||||
|
|
||||||
with no_attributes_match_context:
|
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||||
assert get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster)
|
assert get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster)
|
||||||
|
|
||||||
with no_attributes_match_context:
|
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||||
assert put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster)
|
assert put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster)
|
||||||
|
|
||||||
with reporter.step("Create bearer token with everything allowed for others role"):
|
with reporter.step("Create bearer token with everything allowed for others role"):
|
||||||
|
@ -337,7 +314,7 @@ class TestApeFilters(ClusterTestBase):
|
||||||
bearer = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint)
|
bearer = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint)
|
||||||
|
|
||||||
with reporter.step("Check others can get and put objects without attributes and using bearer token"):
|
with reporter.step("Check others can get and put objects without attributes and using bearer token"):
|
||||||
oid = objects_without_attributes[0]
|
oid = objects_without_header[0]
|
||||||
with expect_not_raises():
|
with expect_not_raises():
|
||||||
head_object(other_wallet, cid, oid, self.shell, endpoint, bearer)
|
head_object(other_wallet, cid, oid, self.shell, endpoint, bearer)
|
||||||
|
|
||||||
|
@ -360,13 +337,13 @@ class TestApeFilters(ClusterTestBase):
|
||||||
|
|
||||||
with reporter.step("Check others cannot get and put objects without attributes matching the filter"):
|
with reporter.step("Check others cannot get and put objects without attributes matching the filter"):
|
||||||
oid = deny_objects[0]
|
oid = deny_objects[0]
|
||||||
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||||
head_object(other_wallet, cid, oid, self.shell, endpoint)
|
head_object(other_wallet, cid, oid, self.shell, endpoint)
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||||
assert get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster)
|
assert get_object_from_random_node(other_wallet, cid, oid, file_path, self.shell, self.cluster)
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||||
assert put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, attributes=deny_attribute)
|
assert put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, attributes=deny_attribute)
|
||||||
|
|
||||||
with reporter.step("Check others can get and put objects without attributes matching the filter with bearer token"):
|
with reporter.step("Check others can get and put objects without attributes matching the filter with bearer token"):
|
||||||
|
@ -379,57 +356,3 @@ class TestApeFilters(ClusterTestBase):
|
||||||
|
|
||||||
with expect_not_raises():
|
with expect_not_raises():
|
||||||
put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, bearer, attributes=allow_attribute)
|
put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, bearer, attributes=allow_attribute)
|
||||||
|
|
||||||
@allure.title("PUT and GET object using bearer with objectID in filter (obj_size={object_size}, match_type=NOT_EQUAL)")
|
|
||||||
def test_ape_filter_object_id_not_equals(
|
|
||||||
self,
|
|
||||||
frostfs_cli: FrostfsCli,
|
|
||||||
default_wallet: WalletInfo,
|
|
||||||
other_wallet: WalletInfo,
|
|
||||||
private_container: str,
|
|
||||||
temp_directory: str,
|
|
||||||
file_path: TestFile,
|
|
||||||
):
|
|
||||||
with reporter.step("Put object to container"):
|
|
||||||
oid = put_object_to_random_node(default_wallet, file_path, private_container, self.shell, self.cluster)
|
|
||||||
|
|
||||||
with reporter.step("Create bearer token with objectID filter"):
|
|
||||||
role_condition = ape.Condition.by_role(ape.Role.OTHERS)
|
|
||||||
object_condition = ape.Condition.by_object_id(oid, ape.ConditionType.RESOURCE, ape.MatchType.NOT_EQUAL)
|
|
||||||
rule = ape.Rule(ape.Verb.ALLOW, ALL_OBJECT_OPERATIONS, [role_condition, object_condition])
|
|
||||||
bearer = create_bearer_token(frostfs_cli, temp_directory, private_container, rule, self.cluster.default_rpc_endpoint)
|
|
||||||
|
|
||||||
with reporter.step("Others should be able to put object using bearer token"):
|
|
||||||
with expect_not_raises():
|
|
||||||
put_object_to_random_node(other_wallet, file_path, private_container, self.shell, self.cluster, bearer)
|
|
||||||
|
|
||||||
with reporter.step("Others should not be able to get object matching the filter"):
|
|
||||||
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
|
||||||
get_object_from_random_node(other_wallet, private_container, oid, self.shell, self.cluster, bearer)
|
|
||||||
|
|
||||||
@allure.title("PUT and GET object using bearer with objectID in filter (obj_size={object_size}, match_type=EQUAL)")
|
|
||||||
def test_ape_filter_object_id_equals(
|
|
||||||
self,
|
|
||||||
frostfs_cli: FrostfsCli,
|
|
||||||
default_wallet: WalletInfo,
|
|
||||||
other_wallet: WalletInfo,
|
|
||||||
private_container: str,
|
|
||||||
temp_directory: str,
|
|
||||||
file_path: TestFile,
|
|
||||||
):
|
|
||||||
with reporter.step("Put object to container"):
|
|
||||||
oid = put_object_to_random_node(default_wallet, file_path, private_container, self.shell, self.cluster)
|
|
||||||
|
|
||||||
with reporter.step("Create bearer token with objectID filter"):
|
|
||||||
role_condition = ape.Condition.by_role(ape.Role.OTHERS)
|
|
||||||
object_condition = ape.Condition.by_object_id(oid, ape.ConditionType.RESOURCE, ape.MatchType.EQUAL)
|
|
||||||
rule = ape.Rule(ape.Verb.ALLOW, ALL_OBJECT_OPERATIONS, [role_condition, object_condition])
|
|
||||||
bearer = create_bearer_token(frostfs_cli, temp_directory, private_container, rule, self.cluster.default_rpc_endpoint)
|
|
||||||
|
|
||||||
with reporter.step("Others should not be able to put object using bearer token"):
|
|
||||||
with pytest.raises(Exception, match=OBJECT_NO_ACCESS):
|
|
||||||
put_object_to_random_node(other_wallet, file_path, private_container, self.shell, self.cluster, bearer)
|
|
||||||
|
|
||||||
with reporter.step("Others should be able to get object matching the filter"):
|
|
||||||
with expect_not_raises():
|
|
||||||
get_object_from_random_node(other_wallet, private_container, oid, self.shell, self.cluster, bearer)
|
|
||||||
|
|
|
@ -3,23 +3,26 @@ import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from importlib.metadata import entry_points
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
|
import yaml
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
from frostfs_testlib import plugins, reporter
|
from frostfs_testlib import plugins, reporter
|
||||||
from frostfs_testlib.cli import FrostfsCli
|
from frostfs_testlib.cli import FrostfsCli
|
||||||
from frostfs_testlib.credentials.interfaces import CredentialsProvider, User
|
from frostfs_testlib.credentials.interfaces import CredentialsProvider, User
|
||||||
from frostfs_testlib.healthcheck.interfaces import Healthcheck
|
from frostfs_testlib.healthcheck.interfaces import Healthcheck
|
||||||
from frostfs_testlib.hosting import Hosting
|
from frostfs_testlib.hosting import Hosting
|
||||||
from frostfs_testlib.resources import optionals
|
from frostfs_testlib.reporter import AllureHandler, StepsLogger
|
||||||
from frostfs_testlib.resources.common import ASSETS_DIR, COMPLEX_OBJECT_CHUNKS_COUNT, COMPLEX_OBJECT_TAIL_SIZE, SIMPLE_OBJECT_SIZE
|
from frostfs_testlib.resources.common import ASSETS_DIR, COMPLEX_OBJECT_CHUNKS_COUNT, COMPLEX_OBJECT_TAIL_SIZE, SIMPLE_OBJECT_SIZE
|
||||||
from frostfs_testlib.s3 import AwsCliClient, Boto3ClientWrapper, S3ClientWrapper, VersioningStatus
|
from frostfs_testlib.s3 import AwsCliClient, Boto3ClientWrapper, S3ClientWrapper, VersioningStatus
|
||||||
from frostfs_testlib.shell import LocalShell, Shell
|
from frostfs_testlib.shell import LocalShell, Shell
|
||||||
from frostfs_testlib.steps.cli.container import DEFAULT_EC_PLACEMENT_RULE, DEFAULT_PLACEMENT_RULE, FROSTFS_CLI_EXEC
|
from frostfs_testlib.steps.cli.container import DEFAULT_EC_PLACEMENT_RULE, DEFAULT_PLACEMENT_RULE, FROSTFS_CLI_EXEC
|
||||||
from frostfs_testlib.steps.cli.object import get_netmap_netinfo
|
from frostfs_testlib.steps.cli.object import get_netmap_netinfo
|
||||||
from frostfs_testlib.steps.s3 import s3_helper
|
from frostfs_testlib.steps.s3 import s3_helper
|
||||||
|
from frostfs_testlib.storage import get_service_registry
|
||||||
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.frostfs_services import StorageNode
|
from frostfs_testlib.storage.dataclasses.frostfs_services import StorageNode
|
||||||
|
@ -28,17 +31,18 @@ from frostfs_testlib.storage.dataclasses.policy import PlacementPolicy
|
||||||
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.testing.test_control import run_optionally, wait_for_success
|
from frostfs_testlib.testing.test_control import wait_for_success
|
||||||
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 pytest_tests.resources.common import TEST_CYCLES_COUNT
|
from pytest_tests.resources.common import HOSTING_CONFIG_FILE, TEST_CYCLES_COUNT
|
||||||
|
|
||||||
logger = logging.getLogger("NeoLogger")
|
logger = logging.getLogger("NeoLogger")
|
||||||
|
|
||||||
SERVICE_ACTIVE_TIME = 20
|
SERVICE_ACTIVE_TIME = 20
|
||||||
WALLTETS_IN_POOL = 2
|
WALLTETS_IN_POOL = 2
|
||||||
|
|
||||||
|
|
||||||
# Add logs check test even if it's not fit to mark selectors
|
# Add logs check test even if it's not fit to mark selectors
|
||||||
def pytest_configure(config: pytest.Config):
|
def pytest_configure(config: pytest.Config):
|
||||||
markers = config.option.markexpr
|
markers = config.option.markexpr
|
||||||
|
@ -49,6 +53,8 @@ def pytest_configure(config: pytest.Config):
|
||||||
number_key = pytest.StashKey[str]()
|
number_key = pytest.StashKey[str]()
|
||||||
start_time = pytest.StashKey[int]()
|
start_time = pytest.StashKey[int]()
|
||||||
test_outcome = pytest.StashKey[str]()
|
test_outcome = pytest.StashKey[str]()
|
||||||
|
|
||||||
|
|
||||||
# pytest hook. Do not rename
|
# pytest hook. Do not rename
|
||||||
def pytest_collection_modifyitems(items: list[pytest.Item]):
|
def pytest_collection_modifyitems(items: list[pytest.Item]):
|
||||||
# Change order of tests based on @pytest.mark.order(<int>) marker
|
# Change order of tests based on @pytest.mark.order(<int>) marker
|
||||||
|
@ -113,11 +119,38 @@ def pytest_generate_tests(metafunc: pytest.Metafunc):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def configure_testlib():
|
||||||
|
reporter.get_reporter().register_handler(AllureHandler())
|
||||||
|
reporter.get_reporter().register_handler(StepsLogger())
|
||||||
|
|
||||||
|
logging.getLogger("paramiko").setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Register Services for cluster
|
||||||
|
registry = get_service_registry()
|
||||||
|
services = entry_points(group="frostfs.testlib.services")
|
||||||
|
for svc in services:
|
||||||
|
registry.register_service(svc.name, svc.load())
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def client_shell(configure_testlib) -> Shell:
|
def client_shell(configure_testlib) -> Shell:
|
||||||
yield LocalShell()
|
yield LocalShell()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def hosting(configure_testlib) -> Hosting:
|
||||||
|
with open(HOSTING_CONFIG_FILE, "r") as file:
|
||||||
|
hosting_config = yaml.full_load(file)
|
||||||
|
|
||||||
|
hosting_instance = Hosting()
|
||||||
|
hosting_instance.configure(hosting_config)
|
||||||
|
|
||||||
|
yield hosting_instance
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def require_multiple_hosts(hosting: Hosting):
|
def require_multiple_hosts(hosting: Hosting):
|
||||||
"""Designates tests that require environment with multiple hosts.
|
"""Designates tests that require environment with multiple hosts.
|
||||||
|
@ -348,7 +381,6 @@ def two_buckets(buckets_pool: list[str], s3_client: S3ClientWrapper) -> list[str
|
||||||
|
|
||||||
@allure.title("[Autouse/Session] Collect binary versions")
|
@allure.title("[Autouse/Session] Collect binary versions")
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@run_optionally(optionals.OPTIONAL_AUTOUSE_FIXTURES_ENABLED)
|
|
||||||
def collect_binary_versions(hosting: Hosting, client_shell: Shell, request: pytest.FixtureRequest):
|
def collect_binary_versions(hosting: Hosting, client_shell: Shell, request: pytest.FixtureRequest):
|
||||||
environment_dir = request.config.getoption("--alluredir")
|
environment_dir = request.config.getoption("--alluredir")
|
||||||
if not environment_dir:
|
if not environment_dir:
|
||||||
|
@ -394,7 +426,6 @@ def session_start_time(configure_testlib):
|
||||||
|
|
||||||
@allure.title("[Autouse/Session] After deploy healthcheck")
|
@allure.title("[Autouse/Session] After deploy healthcheck")
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@run_optionally(optionals.OPTIONAL_AUTOUSE_FIXTURES_ENABLED)
|
|
||||||
def after_deploy_healthcheck(cluster: Cluster):
|
def after_deploy_healthcheck(cluster: Cluster):
|
||||||
with reporter.step("Wait for cluster readiness after deploy"):
|
with reporter.step("Wait for cluster readiness after deploy"):
|
||||||
parallel(readiness_on_node, cluster.cluster_nodes)
|
parallel(readiness_on_node, cluster.cluster_nodes)
|
||||||
|
|
|
@ -19,10 +19,12 @@ from pytest_tests.helpers.utility import placement_policy_from_container
|
||||||
@pytest.mark.container
|
@pytest.mark.container
|
||||||
@pytest.mark.sanity
|
@pytest.mark.sanity
|
||||||
class TestContainer(ClusterTestBase):
|
class TestContainer(ClusterTestBase):
|
||||||
@allure.title("Create container (name={name})")
|
|
||||||
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
|
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
|
||||||
@pytest.mark.smoke
|
@pytest.mark.smoke
|
||||||
def test_container_creation(self, default_wallet: WalletInfo, name: str):
|
def test_container_creation(self, default_wallet: WalletInfo, name: str):
|
||||||
|
scenario_title = "with name" if name else "without name"
|
||||||
|
allure.dynamic.title(f"Create container {scenario_title}")
|
||||||
|
|
||||||
wallet = default_wallet
|
wallet = default_wallet
|
||||||
|
|
||||||
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"
|
||||||
|
@ -57,7 +59,9 @@ class TestContainer(ClusterTestBase):
|
||||||
with reporter.step("Check container has correct information"):
|
with reporter.step("Check container has correct information"):
|
||||||
expected_policy = placement_rule.casefold()
|
expected_policy = placement_rule.casefold()
|
||||||
actual_policy = placement_policy_from_container(container_info)
|
actual_policy = placement_policy_from_container(container_info)
|
||||||
assert actual_policy == expected_policy, f"Expected policy\n{expected_policy} but got policy\n{actual_policy}"
|
assert (
|
||||||
|
actual_policy == expected_policy
|
||||||
|
), f"Expected policy\n{expected_policy} but got policy\n{actual_policy}"
|
||||||
|
|
||||||
for info in info_to_check:
|
for info in info_to_check:
|
||||||
expected_info = info.casefold()
|
expected_info = info.casefold()
|
||||||
|
@ -108,6 +112,10 @@ class TestContainer(ClusterTestBase):
|
||||||
|
|
||||||
with reporter.step("Delete containers and check they were deleted"):
|
with reporter.step("Delete containers and check they were deleted"):
|
||||||
for cid in cids:
|
for cid in cids:
|
||||||
delete_container(wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, await_mode=True)
|
delete_container(
|
||||||
containers_list = list_containers(wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
|
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, await_mode=True
|
||||||
|
)
|
||||||
|
containers_list = list_containers(
|
||||||
|
wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||||
|
)
|
||||||
assert cid not in containers_list, "Container not deleted"
|
assert cid not in containers_list, "Container not deleted"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import allure
|
import allure
|
||||||
|
@ -16,7 +15,7 @@ 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.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, parallel_workers_limit
|
from frostfs_testlib.testing.parallel import parallel
|
||||||
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 get_file_hash
|
from frostfs_testlib.utils.file_utils import get_file_hash
|
||||||
from pytest import FixtureRequest
|
from pytest import FixtureRequest
|
||||||
|
@ -56,7 +55,8 @@ class TestFailoverServer(ClusterTestBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
containers = [
|
containers = [
|
||||||
StorageContainer(StorageContainerInfo(result.result(), default_wallet), self.shell, self.cluster) for result in results
|
StorageContainer(StorageContainerInfo(result.result(), default_wallet), self.shell, self.cluster)
|
||||||
|
for result in results
|
||||||
]
|
]
|
||||||
|
|
||||||
return containers
|
return containers
|
||||||
|
@ -102,7 +102,9 @@ class TestFailoverServer(ClusterTestBase):
|
||||||
|
|
||||||
@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]]:
|
||||||
object_info = container.generate_object(simple_object_size.value)
|
object_info = container.generate_object(simple_object_size.value)
|
||||||
object_nodes = get_object_nodes(self.cluster, object_info.cid, object_info.oid, self.cluster.cluster_nodes[0])
|
object_nodes = get_object_nodes(self.cluster, object_info.cid, object_info.oid, self.cluster.cluster_nodes[0])
|
||||||
return object_info, object_nodes
|
return object_info, object_nodes
|
||||||
|
@ -122,9 +124,7 @@ class TestFailoverServer(ClusterTestBase):
|
||||||
|
|
||||||
@reporter.step("Verify objects")
|
@reporter.step("Verify objects")
|
||||||
def verify_objects(self, nodes: list[StorageNode], storage_objects: list[StorageObjectInfo]) -> None:
|
def verify_objects(self, nodes: list[StorageNode], storage_objects: list[StorageObjectInfo]) -> None:
|
||||||
workers_count = os.environ.get("PARALLEL_CUSTOM_LIMIT", 50)
|
parallel(self._verify_object, storage_objects * len(nodes), node=itertools.cycle(nodes))
|
||||||
with parallel_workers_limit(int(workers_count)):
|
|
||||||
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", [(5, 10)], indirect=True)
|
||||||
|
@ -200,7 +200,9 @@ class TestFailoverServer(ClusterTestBase):
|
||||||
simple_file: str,
|
simple_file: str,
|
||||||
):
|
):
|
||||||
object_info, object_nodes = object_and_nodes
|
object_info, object_nodes = object_and_nodes
|
||||||
endpoint_without_object = list(set(self.cluster.cluster_nodes) - set(object_nodes))[0].storage_node.get_rpc_endpoint()
|
endpoint_without_object = list(set(self.cluster.cluster_nodes) - set(object_nodes))[
|
||||||
|
0
|
||||||
|
].storage_node.get_rpc_endpoint()
|
||||||
endpoint_with_object = object_nodes[0].storage_node.get_rpc_endpoint()
|
endpoint_with_object = object_nodes[0].storage_node.get_rpc_endpoint()
|
||||||
|
|
||||||
with reporter.step("Stop all nodes with object except first one"):
|
with reporter.step("Stop all nodes with object except first one"):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
@ -8,7 +9,13 @@ 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.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.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
|
||||||
|
@ -25,6 +32,7 @@ STORAGE_NODE_COMMUNICATION_PORT = "8080"
|
||||||
STORAGE_NODE_COMMUNICATION_PORT_TLS = "8082"
|
STORAGE_NODE_COMMUNICATION_PORT_TLS = "8082"
|
||||||
PORTS_TO_BLOCK = [STORAGE_NODE_COMMUNICATION_PORT, STORAGE_NODE_COMMUNICATION_PORT_TLS]
|
PORTS_TO_BLOCK = [STORAGE_NODE_COMMUNICATION_PORT, STORAGE_NODE_COMMUNICATION_PORT_TLS]
|
||||||
blocked_nodes: list[ClusterNode] = []
|
blocked_nodes: list[ClusterNode] = []
|
||||||
|
file_wait_delete = []
|
||||||
|
|
||||||
OBJECT_ATTRIBUTES = [
|
OBJECT_ATTRIBUTES = [
|
||||||
None,
|
None,
|
||||||
|
@ -55,6 +63,14 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
yield
|
yield
|
||||||
cluster_state_controller.restore_interfaces()
|
cluster_state_controller.restore_interfaces()
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
@allure.title("Delete file after test")
|
||||||
|
def delete_file_after_test(self) -> None:
|
||||||
|
yield
|
||||||
|
for path in file_wait_delete:
|
||||||
|
os.remove(path)
|
||||||
|
file_wait_delete.clear()
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def storage_objects(
|
def storage_objects(
|
||||||
self,
|
self,
|
||||||
|
@ -97,7 +113,9 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
|
|
||||||
storage_objects.append(storage_object)
|
storage_objects.append(storage_object)
|
||||||
|
|
||||||
return storage_objects
|
yield storage_objects
|
||||||
|
|
||||||
|
delete_objects(storage_objects, self.shell, self.cluster)
|
||||||
|
|
||||||
@allure.title("Block Storage node traffic")
|
@allure.title("Block Storage node traffic")
|
||||||
def test_block_storage_node_traffic(
|
def test_block_storage_node_traffic(
|
||||||
|
@ -156,7 +174,9 @@ 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, cid, 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"):
|
||||||
|
@ -164,7 +184,9 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
with reporter.step(f"Unblock at host {node}"):
|
with reporter.step(f"Unblock at host {node}"):
|
||||||
cluster_state_controller.restore_traffic(node=node)
|
cluster_state_controller.restore_traffic(node=node)
|
||||||
block_node = [
|
block_node = [
|
||||||
cluster_node for cluster_node in self.cluster.cluster_nodes if cluster_node.storage_node == node.storage_node
|
cluster_node
|
||||||
|
for cluster_node in self.cluster.cluster_nodes
|
||||||
|
if cluster_node.storage_node == node.storage_node
|
||||||
]
|
]
|
||||||
blocked_nodes.remove(*block_node)
|
blocked_nodes.remove(*block_node)
|
||||||
sleep(wakeup_node_timeout)
|
sleep(wakeup_node_timeout)
|
||||||
|
@ -182,6 +204,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
cluster_state_controller: ClusterStateController,
|
cluster_state_controller: ClusterStateController,
|
||||||
default_wallet: WalletInfo,
|
default_wallet: WalletInfo,
|
||||||
restore_down_interfaces: None,
|
restore_down_interfaces: None,
|
||||||
|
delete_file_after_test: None,
|
||||||
storage_objects: list[StorageObjectInfo],
|
storage_objects: list[StorageObjectInfo],
|
||||||
):
|
):
|
||||||
storage_object = storage_objects[0]
|
storage_object = storage_objects[0]
|
||||||
|
@ -208,7 +231,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
self.tick_epochs(1, alive_node=nodes_without_an_object[0].storage_node, wait_block=2)
|
self.tick_epochs(1, alive_node=nodes_without_an_object[0].storage_node, wait_block=2)
|
||||||
|
|
||||||
with reporter.step("Get object for target nodes to data interfaces, expect false"):
|
with reporter.step("Get object for target nodes to data interfaces, expect false"):
|
||||||
with pytest.raises(RuntimeError, match="can't create API client: can't init SDK client: gRPC dial: context deadline exceeded"):
|
with pytest.raises(RuntimeError, match="return code: 1"):
|
||||||
get_object(
|
get_object(
|
||||||
wallet=default_wallet,
|
wallet=default_wallet,
|
||||||
cid=storage_object.cid,
|
cid=storage_object.cid,
|
||||||
|
@ -225,6 +248,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
shell=self.shell,
|
shell=self.shell,
|
||||||
endpoint=nodes_without_an_object[0].storage_node.get_rpc_endpoint(),
|
endpoint=nodes_without_an_object[0].storage_node.get_rpc_endpoint(),
|
||||||
)
|
)
|
||||||
|
file_wait_delete.append(input_file)
|
||||||
|
|
||||||
with reporter.step("Restore interface and tick 1 epoch, wait 2 block"):
|
with reporter.step("Restore interface and tick 1 epoch, wait 2 block"):
|
||||||
cluster_state_controller.restore_interfaces()
|
cluster_state_controller.restore_interfaces()
|
||||||
|
@ -237,6 +261,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
cluster_state_controller: ClusterStateController,
|
cluster_state_controller: ClusterStateController,
|
||||||
default_wallet: WalletInfo,
|
default_wallet: WalletInfo,
|
||||||
restore_down_interfaces: None,
|
restore_down_interfaces: None,
|
||||||
|
delete_file_after_test: None,
|
||||||
storage_objects: list[StorageObjectInfo],
|
storage_objects: list[StorageObjectInfo],
|
||||||
simple_object_size: ObjectSize,
|
simple_object_size: ObjectSize,
|
||||||
):
|
):
|
||||||
|
@ -264,7 +289,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
self.tick_epochs(1, alive_node=nodes_without_an_object[0].storage_node, wait_block=2)
|
self.tick_epochs(1, alive_node=nodes_without_an_object[0].storage_node, wait_block=2)
|
||||||
|
|
||||||
with reporter.step("Get object others node, expect false"):
|
with reporter.step("Get object others node, expect false"):
|
||||||
with pytest.raises(RuntimeError, match="rpc error"):
|
with pytest.raises(RuntimeError, match="return code: 1"):
|
||||||
get_object(
|
get_object(
|
||||||
wallet=default_wallet,
|
wallet=default_wallet,
|
||||||
cid=storage_object.cid,
|
cid=storage_object.cid,
|
||||||
|
@ -274,7 +299,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
with reporter.step("Put object, others node, expect false"):
|
with reporter.step("Put object, others node, expect false"):
|
||||||
with pytest.raises(RuntimeError, match="rpc error"):
|
with pytest.raises(RuntimeError, match="return code: 1"):
|
||||||
put_object(
|
put_object(
|
||||||
wallet=default_wallet,
|
wallet=default_wallet,
|
||||||
path=storage_object.file_path,
|
path=storage_object.file_path,
|
||||||
|
@ -291,6 +316,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
shell=self.shell,
|
shell=self.shell,
|
||||||
endpoint=nodes_with_object[0].storage_node.get_rpc_endpoint(),
|
endpoint=nodes_with_object[0].storage_node.get_rpc_endpoint(),
|
||||||
)
|
)
|
||||||
|
file_wait_delete.append(input_file)
|
||||||
|
|
||||||
with reporter.step(f"Put object nodes with object, expect true"):
|
with reporter.step(f"Put object nodes with object, expect true"):
|
||||||
temp_file_path = generate_file(simple_object_size.value)
|
temp_file_path = generate_file(simple_object_size.value)
|
||||||
|
@ -301,7 +327,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
shell=self.shell,
|
shell=self.shell,
|
||||||
endpoint=nodes_with_object[0].storage_node.get_rpc_endpoint(),
|
endpoint=nodes_with_object[0].storage_node.get_rpc_endpoint(),
|
||||||
)
|
)
|
||||||
|
file_wait_delete.append(temp_file_path)
|
||||||
with reporter.step("Restore interface and tick 1 epoch, wait 2 block"):
|
with reporter.step("Restore interface and tick 1 epoch, wait 2 block"):
|
||||||
cluster_state_controller.restore_interfaces()
|
cluster_state_controller.restore_interfaces()
|
||||||
self.tick_epochs(1, alive_node=nodes_without_an_object[0].storage_node, wait_block=2)
|
self.tick_epochs(1, alive_node=nodes_without_an_object[0].storage_node, wait_block=2)
|
||||||
|
@ -319,6 +345,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
cluster_state_controller: ClusterStateController,
|
cluster_state_controller: ClusterStateController,
|
||||||
default_wallet: WalletInfo,
|
default_wallet: WalletInfo,
|
||||||
simple_object_size: ObjectSize,
|
simple_object_size: ObjectSize,
|
||||||
|
delete_file_after_test: None,
|
||||||
restore_down_interfaces: None,
|
restore_down_interfaces: None,
|
||||||
block_interface: Interfaces,
|
block_interface: Interfaces,
|
||||||
other_interface: Interfaces,
|
other_interface: Interfaces,
|
||||||
|
@ -340,6 +367,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
|
|
||||||
with reporter.step("Put object"):
|
with reporter.step("Put object"):
|
||||||
file_path = generate_file(simple_object_size.value)
|
file_path = generate_file(simple_object_size.value)
|
||||||
|
file_wait_delete.append(file_path)
|
||||||
|
|
||||||
oid = put_object(
|
oid = put_object(
|
||||||
wallet=default_wallet,
|
wallet=default_wallet,
|
||||||
|
@ -357,6 +385,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
shell=self.shell,
|
shell=self.shell,
|
||||||
endpoint=f"{cluster_nodes[0].get_data_interface(other_interface.value)[0]}:8080",
|
endpoint=f"{cluster_nodes[0].get_data_interface(other_interface.value)[0]}:8080",
|
||||||
)
|
)
|
||||||
|
file_wait_delete.append(file_get_path)
|
||||||
|
|
||||||
with reporter.step("Restore interfaces all nodes"):
|
with reporter.step("Restore interfaces all nodes"):
|
||||||
cluster_state_controller.restore_interfaces()
|
cluster_state_controller.restore_interfaces()
|
||||||
|
@ -372,6 +401,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
cluster_state_controller: ClusterStateController,
|
cluster_state_controller: ClusterStateController,
|
||||||
default_wallet: WalletInfo,
|
default_wallet: WalletInfo,
|
||||||
simple_object_size: ObjectSize,
|
simple_object_size: ObjectSize,
|
||||||
|
delete_file_after_test: None,
|
||||||
restore_down_interfaces: None,
|
restore_down_interfaces: None,
|
||||||
interface: Interfaces,
|
interface: Interfaces,
|
||||||
):
|
):
|
||||||
|
@ -400,6 +430,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
|
|
||||||
with reporter.step(f"Put object, after down {interface}"):
|
with reporter.step(f"Put object, after down {interface}"):
|
||||||
file_path = generate_file(simple_object_size.value)
|
file_path = generate_file(simple_object_size.value)
|
||||||
|
file_wait_delete.append(file_path)
|
||||||
|
|
||||||
oid = put_object(
|
oid = put_object(
|
||||||
wallet=default_wallet,
|
wallet=default_wallet,
|
||||||
|
@ -417,6 +448,7 @@ class TestFailoverNetwork(ClusterTestBase):
|
||||||
shell=self.shell,
|
shell=self.shell,
|
||||||
endpoint=self.cluster.default_rpc_endpoint,
|
endpoint=self.cluster.default_rpc_endpoint,
|
||||||
)
|
)
|
||||||
|
file_wait_delete.append(file_get_path)
|
||||||
|
|
||||||
now_block = {}
|
now_block = {}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,9 @@ 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):
|
def test_garbage_collector_metrics_expire_at_object(
|
||||||
|
self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster
|
||||||
|
):
|
||||||
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"
|
placement_policy = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||||
metrics_step = 1
|
metrics_step = 1
|
||||||
|
@ -41,7 +43,9 @@ class TestGarbageCollectorMetrics(ClusterTestBase):
|
||||||
with reporter.step("Get current garbage collector metrics for each nodes"):
|
with reporter.step("Get current garbage collector metrics for each nodes"):
|
||||||
metrics_counter = {}
|
metrics_counter = {}
|
||||||
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}"):
|
with reporter.step(f"Create container with policy {placement_policy}"):
|
||||||
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, placement_policy)
|
cid = create_container(default_wallet, self.shell, cluster.default_rpc_endpoint, placement_policy)
|
||||||
|
@ -59,12 +63,18 @@ class TestGarbageCollectorMetrics(ClusterTestBase):
|
||||||
|
|
||||||
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(cid, 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"):
|
||||||
self.tick_epochs(epochs_to_tick=2, wait_block=2)
|
self.tick_epochs(epochs_to_tick=2, wait_block=2)
|
||||||
|
|
||||||
with reporter.step(f"Check garbage collector metrics 'the counter should increase by {metrics_step}' in object nodes"):
|
with reporter.step(
|
||||||
|
f"Check garbage collector metrics 'the counter should increase by {metrics_step}' in object nodes"
|
||||||
|
):
|
||||||
for node in object_nodes:
|
for node in object_nodes:
|
||||||
metrics_counter[node] += metrics_step
|
metrics_counter[node] += metrics_step
|
||||||
|
|
||||||
|
@ -76,38 +86,30 @@ 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):
|
def test_garbage_collector_metrics_deleted_objects(
|
||||||
|
self, simple_object_size: ObjectSize, default_wallet: WalletInfo, cluster: Cluster
|
||||||
|
):
|
||||||
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"
|
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("Select random node"):
|
||||||
metrics_counter = {}
|
node = random.choice(cluster.cluster_nodes)
|
||||||
for node in cluster.cluster_nodes:
|
|
||||||
metrics_counter[node] = get_metrics_value(node, command="frostfs_node_garbage_collector_deleted_objects_total")
|
with reporter.step("Get current garbage collector metrics for selected node"):
|
||||||
|
metrics_counter = get_metrics_value(node, command="frostfs_node_garbage_collector_deleted_objects_total")
|
||||||
|
|
||||||
with reporter.step(f"Create container with policy {placement_policy}"):
|
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)
|
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 selected node"):
|
||||||
oid = put_object_to_random_node(
|
oid = put_object(default_wallet, file_path, cid, self.shell, node.storage_node.get_rpc_endpoint())
|
||||||
default_wallet,
|
|
||||||
file_path,
|
|
||||||
cid,
|
|
||||||
self.shell,
|
|
||||||
cluster,
|
|
||||||
)
|
|
||||||
|
|
||||||
with reporter.step("Get object nodes"):
|
|
||||||
object_storage_nodes = get_nodes_with_object(cid, 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]
|
|
||||||
|
|
||||||
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, cid, 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:
|
metrics_counter += metrics_step
|
||||||
exp_metrics_counter = metrics_counter[node] + metrics_step
|
check_metrics_counter(
|
||||||
check_metrics_counter(
|
[node], counter_exp=metrics_counter, command="frostfs_node_garbage_collector_deleted_objects_total"
|
||||||
[node], counter_exp=exp_metrics_counter, command="frostfs_node_garbage_collector_deleted_objects_total"
|
)
|
||||||
)
|
|
||||||
|
|
|
@ -16,42 +16,37 @@ from frostfs_testlib.testing.test_control import wait_for_success
|
||||||
|
|
||||||
class TestLogsMetrics(ClusterTestBase):
|
class TestLogsMetrics(ClusterTestBase):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def revert_all(self, cluster_state_controller: ClusterStateController):
|
|
||||||
yield
|
|
||||||
cluster_state_controller.manager(ConfigStateManager).revert_all()
|
|
||||||
|
|
||||||
def restart_storage_service(self, cluster_state_controller: ClusterStateController) -> datetime:
|
def restart_storage_service(self, cluster_state_controller: ClusterStateController) -> datetime:
|
||||||
config_manager = cluster_state_controller.manager(ConfigStateManager)
|
config_manager = cluster_state_controller.manager(ConfigStateManager)
|
||||||
config_manager.csc.stop_services_of_type(StorageNode)
|
config_manager.csc.stop_services_of_type(StorageNode)
|
||||||
restart_time = datetime.now(timezone.utc)
|
restart_time = datetime.now(timezone.utc)
|
||||||
config_manager.csc.start_services_of_type(StorageNode)
|
config_manager.csc.start_services_of_type(StorageNode)
|
||||||
return restart_time
|
yield restart_time
|
||||||
|
|
||||||
|
cluster_state_controller.manager(ConfigStateManager).revert_all()
|
||||||
|
|
||||||
@wait_for_success(interval=10)
|
@wait_for_success(interval=10)
|
||||||
def check_metrics_in_node(self, cluster_node: ClusterNode, restart_time: datetime, log_priority: str = None, **metrics_greps):
|
def check_metrics_in_node(self, cluster_node: ClusterNode, restart_time: datetime, log_priority: str = None, **metrics_greps):
|
||||||
current_time = datetime.now(timezone.utc)
|
counter_logs = self.get_count_logs_by_level(cluster_node, metrics_greps.get("level"), restart_time, log_priority)
|
||||||
counter_metrics = get_metrics_value(cluster_node, **metrics_greps)
|
counter_metrics = get_metrics_value(cluster_node, **metrics_greps)
|
||||||
counter_logs = self.get_count_logs_by_level(cluster_node, metrics_greps.get("level"), restart_time, current_time, log_priority)
|
|
||||||
assert counter_logs == counter_metrics, f"counter_logs: {counter_logs}, counter_metrics: {counter_metrics} in node: {cluster_node}"
|
assert counter_logs == counter_metrics, f"counter_logs: {counter_logs}, counter_metrics: {counter_metrics} in node: {cluster_node}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_count_logs_by_level(cluster_node: ClusterNode, log_level: str, after_time: datetime, until_time: datetime, log_priority: str):
|
def get_count_logs_by_level(cluster_node: ClusterNode, log_level: str, after_time: datetime, log_priority: str):
|
||||||
count_logs = 0
|
count_logs = 0
|
||||||
try:
|
try:
|
||||||
logs = cluster_node.host.get_filtered_logs(
|
logs = cluster_node.host.get_filtered_logs(log_level, unit="frostfs-storage", since=after_time, priority=log_priority)
|
||||||
log_level, unit="frostfs-storage", since=after_time, until=until_time, priority=log_priority
|
result = re.findall(rf"\s+{log_level}\s+", logs)
|
||||||
)
|
|
||||||
result = re.findall(rf"Z\s+{log_level}\s+", logs)
|
|
||||||
count_logs += len(result)
|
count_logs += len(result)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
...
|
...
|
||||||
return count_logs
|
return count_logs
|
||||||
|
|
||||||
@allure.title("Metrics for the log counter")
|
@allure.title("Metrics for the log counter")
|
||||||
def test_log_counter_metrics(self, cluster_state_controller: ClusterStateController, revert_all):
|
def test_log_counter_metrics(self, cluster: Cluster, restart_storage_service: datetime):
|
||||||
restart_time = self.restart_storage_service(cluster_state_controller)
|
restart_time = restart_storage_service
|
||||||
with reporter.step("Select random node"):
|
with reporter.step("Select random node"):
|
||||||
node = random.choice(self.cluster.cluster_nodes)
|
node = random.choice(cluster.cluster_nodes)
|
||||||
|
|
||||||
with reporter.step(f"Check metrics count logs with level 'info'"):
|
with reporter.step(f"Check metrics count logs with level 'info'"):
|
||||||
self.check_metrics_in_node(
|
self.check_metrics_in_node(
|
||||||
|
|
|
@ -1,414 +0,0 @@
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
|
|
||||||
import allure
|
|
||||||
import pytest
|
|
||||||
from frostfs_testlib import reporter
|
|
||||||
from frostfs_testlib.cli import FrostfsCli
|
|
||||||
from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT, FROSTFS_CLI_EXEC
|
|
||||||
from frostfs_testlib.resources.error_patterns import OBJECT_IS_LOCKED
|
|
||||||
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL_F
|
|
||||||
from frostfs_testlib.shell import Shell
|
|
||||||
from frostfs_testlib.steps.cli.container import create_container
|
|
||||||
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 TestFile, get_file_hash
|
|
||||||
|
|
||||||
logger = logging.getLogger("NeoLogger")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.grpc_without_user
|
|
||||||
class TestObjectApiWithoutUser(ClusterTestBase):
|
|
||||||
def _parse_oid(self, stdout: str) -> str:
|
|
||||||
id_str = stdout.strip().split("\n")[-2]
|
|
||||||
oid = id_str.split(":")[1]
|
|
||||||
return oid.strip()
|
|
||||||
|
|
||||||
def _parse_tombstone_oid(self, stdout: str) -> str:
|
|
||||||
id_str = stdout.split("\n")[1]
|
|
||||||
tombstone = id_str.split(":")[1]
|
|
||||||
return tombstone.strip()
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
|
||||||
def public_container(self, default_wallet: WalletInfo) -> str:
|
|
||||||
with reporter.step("Create public container"):
|
|
||||||
cid_public = create_container(
|
|
||||||
default_wallet,
|
|
||||||
self.shell,
|
|
||||||
self.cluster.default_rpc_endpoint,
|
|
||||||
basic_acl=PUBLIC_ACL_F,
|
|
||||||
)
|
|
||||||
|
|
||||||
return cid_public
|
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
|
||||||
def frostfs_cli(self, client_shell: Shell) -> FrostfsCli:
|
|
||||||
return FrostfsCli(client_shell, FROSTFS_CLI_EXEC)
|
|
||||||
|
|
||||||
@allure.title("Get public container by native API with generate private key")
|
|
||||||
def test_get_container_with_generated_key(self, frostfs_cli: FrostfsCli, public_container: str):
|
|
||||||
"""
|
|
||||||
Validate `container get` native API with flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Get container with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.container.get(rpc_endpoint, cid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
@allure.title("Get list containers by native API with generate private key")
|
|
||||||
def test_list_containers_with_generated_key(self, frostfs_cli: FrostfsCli, default_wallet: WalletInfo, public_container: str):
|
|
||||||
"""
|
|
||||||
Validate `container list` native API with flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
owner = default_wallet.get_address_from_json(0)
|
|
||||||
|
|
||||||
with reporter.step("List containers with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
result = frostfs_cli.container.list(rpc_endpoint, owner=owner, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
with reporter.step("Expect container in received containers list"):
|
|
||||||
containers = result.stdout.split()
|
|
||||||
assert public_container in containers
|
|
||||||
|
|
||||||
@allure.title("Get list of public container objects by native API with generate private key")
|
|
||||||
def test_list_objects_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str):
|
|
||||||
"""
|
|
||||||
Validate `container list_objects` native API with flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("List objects with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
result = frostfs_cli.container.list_objects(rpc_endpoint, cid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
with reporter.step("Expect empty objects list"):
|
|
||||||
objects = result.stdout.split()
|
|
||||||
assert len(objects) == 0, objects
|
|
||||||
|
|
||||||
@allure.title("Search public container nodes by native API with generate private key")
|
|
||||||
def test_search_nodes_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str):
|
|
||||||
"""
|
|
||||||
Validate `container search_node` native API with flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Search nodes with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.container.search_node(rpc_endpoint, cid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
@allure.title("Put object into public container by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_put_object_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object put` into container with public ACL and flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("List objects with generate key"):
|
|
||||||
result = frostfs_cli.container.list_objects(rpc_endpoint, cid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
with reporter.step("Expect object in received objects list"):
|
|
||||||
objects = result.stdout.split()
|
|
||||||
assert oid in objects, objects
|
|
||||||
|
|
||||||
@allure.title("Get public container object by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_get_object_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object get` for container with public ACL and flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
expected_hash = get_file_hash(file_path)
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Get object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.object.get(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid,
|
|
||||||
file=file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
downloaded_hash = get_file_hash(file_path)
|
|
||||||
|
|
||||||
with reporter.step("Validate downloaded file"):
|
|
||||||
assert expected_hash == downloaded_hash
|
|
||||||
|
|
||||||
@allure.title("Head public container object by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_head_object_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object head` for container with public ACL and flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Head object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.object.head(rpc_endpoint, cid, oid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
@allure.title("Delete public container object by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_delete_object_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object delete` for container with public ACL and flag `--generate key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Delete object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
result = frostfs_cli.object.delete(rpc_endpoint, cid, oid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
oid = self._parse_tombstone_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Head object with generate key"):
|
|
||||||
result = frostfs_cli.object.head(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid,
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
with reporter.step("Expect object type TOMBSTONE"):
|
|
||||||
object_type = re.search(r"(?<=type: )tombstone", result.stdout, re.IGNORECASE).group()
|
|
||||||
assert object_type == "TOMBSTONE", object_type
|
|
||||||
|
|
||||||
@allure.title("Lock public container object by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_lock_object_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object lock` for container with public ACL and flag `--generate-key`.
|
|
||||||
Attempt to delete the locked object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Lock object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.object.lock(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid,
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
lifetime=5,
|
|
||||||
)
|
|
||||||
|
|
||||||
with reporter.step("Delete locked object with generate key and expect error"):
|
|
||||||
with pytest.raises(Exception, match=OBJECT_IS_LOCKED):
|
|
||||||
frostfs_cli.object.delete(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid,
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
@allure.title("Search public container objects by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_search_object_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object search` for container with public ACL and flag `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Object search with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
result = frostfs_cli.object.search(rpc_endpoint, cid, generate_key=True, timeout=CLI_DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
with reporter.step("Expect object in received objects list of container"):
|
|
||||||
object_ids = re.findall(r"(\w{43,44})", result.stdout)
|
|
||||||
assert oid in object_ids
|
|
||||||
|
|
||||||
@allure.title("Get range of public container object by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_range_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object range` for container with public ACL and `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Get range of object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.object.range(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid,
|
|
||||||
"0:10",
|
|
||||||
file=file_path,
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
@allure.title("Get hash of public container object by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_hash_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object hash` for container with public ACL and `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
generate_key=True,
|
|
||||||
no_progress=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Get range hash of object with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
frostfs_cli.object.hash(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid,
|
|
||||||
range="0:10",
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
@allure.title("Get public container object nodes by native API with generate private key (obj_size={object_size})")
|
|
||||||
def test_nodes_with_generate_key(self, frostfs_cli: FrostfsCli, public_container: str, file_path: TestFile):
|
|
||||||
"""
|
|
||||||
Validate `object nodes` for container with public ACL and `--generate-key`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cid = public_container
|
|
||||||
rpc_endpoint = self.cluster.default_rpc_endpoint
|
|
||||||
|
|
||||||
with reporter.step("Put object with generate key"):
|
|
||||||
result = frostfs_cli.object.put(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
file_path,
|
|
||||||
no_progress=True,
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
oid = self._parse_oid(result.stdout)
|
|
||||||
|
|
||||||
with reporter.step("Configure frostfs-cli for alive remote node"):
|
|
||||||
alive_node = self.cluster.cluster_nodes[0]
|
|
||||||
node_shell = alive_node.host.get_shell()
|
|
||||||
rpc_endpoint = alive_node.storage_node.get_rpc_endpoint()
|
|
||||||
node_frostfs_cli = FrostfsCli(node_shell, FROSTFS_CLI_EXEC)
|
|
||||||
|
|
||||||
with reporter.step("Get object nodes with generate key"):
|
|
||||||
with expect_not_raises():
|
|
||||||
node_frostfs_cli.object.nodes(
|
|
||||||
rpc_endpoint,
|
|
||||||
cid,
|
|
||||||
oid=oid,
|
|
||||||
generate_key=True,
|
|
||||||
timeout=CLI_DEFAULT_TIMEOUT,
|
|
||||||
)
|
|
|
@ -4,10 +4,11 @@ from dataclasses import dataclass
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
from frostfs_testlib import plugins, reporter
|
from frostfs_testlib import reporter
|
||||||
from frostfs_testlib.cli import FrostfsAdm, FrostfsCli
|
from frostfs_testlib.cli import FrostfsAdm, FrostfsCli
|
||||||
from frostfs_testlib.cli.netmap_parser import NetmapParser
|
from frostfs_testlib.cli.netmap_parser import NetmapParser
|
||||||
from frostfs_testlib.credentials.interfaces import User
|
from frostfs_testlib.credentials.interfaces import User
|
||||||
|
from frostfs_testlib.plugins import load_plugin
|
||||||
from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT, FROSTFS_ADM_CONFIG_PATH, FROSTFS_ADM_EXEC, FROSTFS_CLI_EXEC
|
from frostfs_testlib.resources.cli import CLI_DEFAULT_TIMEOUT, FROSTFS_ADM_CONFIG_PATH, FROSTFS_ADM_EXEC, FROSTFS_CLI_EXEC
|
||||||
from frostfs_testlib.resources.common import COMPLEX_OBJECT_CHUNKS_COUNT, COMPLEX_OBJECT_TAIL_SIZE, HOSTING_CONFIG_FILE
|
from frostfs_testlib.resources.common import COMPLEX_OBJECT_CHUNKS_COUNT, COMPLEX_OBJECT_TAIL_SIZE, HOSTING_CONFIG_FILE
|
||||||
from frostfs_testlib.s3 import AwsCliClient, S3ClientWrapper
|
from frostfs_testlib.s3 import AwsCliClient, S3ClientWrapper
|
||||||
|
@ -59,7 +60,7 @@ class Chunk:
|
||||||
@allure.title("Init bucket container resolver")
|
@allure.title("Init bucket container resolver")
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def bucket_container_resolver(node_under_test: ClusterNode) -> BucketContainerResolver:
|
def bucket_container_resolver(node_under_test: ClusterNode) -> BucketContainerResolver:
|
||||||
resolver_cls = plugins.load_plugin("frostfs.testlib.bucket_cid_resolver", node_under_test.host.config.product)
|
resolver_cls = load_plugin("frostfs.testlib.bucket_cid_resolver", node_under_test.host.config.product)
|
||||||
resolver: BucketContainerResolver = resolver_cls()
|
resolver: BucketContainerResolver = resolver_cls()
|
||||||
return resolver
|
return resolver
|
||||||
|
|
||||||
|
@ -846,6 +847,15 @@ class TestECReplication(ClusterTestBase):
|
||||||
).stdout
|
).stdout
|
||||||
assert container
|
assert container
|
||||||
|
|
||||||
|
@allure.title("[NEGATIVE] Don`t create more 1 EC policy")
|
||||||
|
def test_more_one_ec_policy(
|
||||||
|
self,
|
||||||
|
frostfs_cli: FrostfsCli,
|
||||||
|
) -> None:
|
||||||
|
with reporter.step("Create container with policy - 'EC 2.1 EC 1.1'"):
|
||||||
|
with pytest.raises(RuntimeError, match="can't parse placement policy"):
|
||||||
|
self.create_container(frostfs_cli, self.cluster.default_rpc_endpoint, "EC 2.1 EC 1.1 CBF 1 SELECT 4 FROM *")
|
||||||
|
|
||||||
@allure.title("Bucket object count chunks (s3_client={s3_client}, size={object_size})")
|
@allure.title("Bucket object count chunks (s3_client={s3_client}, size={object_size})")
|
||||||
@pytest.mark.parametrize("s3_policy, s3_client", [("pytest_tests/resources/files/policy.json", AwsCliClient)], indirect=True)
|
@pytest.mark.parametrize("s3_policy, s3_client", [("pytest_tests/resources/files/policy.json", AwsCliClient)], indirect=True)
|
||||||
def test_count_chunks_bucket_with_ec_location(
|
def test_count_chunks_bucket_with_ec_location(
|
||||||
|
|
|
@ -60,7 +60,6 @@ class Test_http_bearer(ClusterTestBase):
|
||||||
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})")
|
|
||||||
def test_put_with_bearer_when_eacl_restrict(
|
def test_put_with_bearer_when_eacl_restrict(
|
||||||
self,
|
self,
|
||||||
object_size: ObjectSize,
|
object_size: ObjectSize,
|
||||||
|
|
|
@ -125,7 +125,7 @@ class Test_http_object(ClusterTestBase):
|
||||||
http_request_path=request,
|
http_request_path=request,
|
||||||
)
|
)
|
||||||
|
|
||||||
@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")
|
||||||
@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, object_size: ObjectSize, s3_client: S3ClientWrapper):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import allure
|
|
||||||
import pytest
|
import pytest
|
||||||
from frostfs_testlib import reporter
|
from frostfs_testlib import reporter
|
||||||
from frostfs_testlib.shell import Shell
|
from frostfs_testlib.shell import Shell
|
||||||
|
@ -23,7 +22,6 @@ class TestSessionTokenContainer(ClusterTestBase):
|
||||||
"""
|
"""
|
||||||
return {verb: get_container_signed_token(owner_wallet, user_wallet, verb, client_shell, temp_directory) for verb in ContainerVerb}
|
return {verb: get_container_signed_token(owner_wallet, user_wallet, verb, client_shell, temp_directory) for verb in ContainerVerb}
|
||||||
|
|
||||||
@allure.title("Static session with create operation")
|
|
||||||
def test_static_session_token_container_create(
|
def test_static_session_token_container_create(
|
||||||
self,
|
self,
|
||||||
owner_wallet: WalletInfo,
|
owner_wallet: WalletInfo,
|
||||||
|
@ -48,7 +46,6 @@ class TestSessionTokenContainer(ClusterTestBase):
|
||||||
assert cid not in list_containers(user_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
|
assert cid not in list_containers(user_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
|
||||||
assert cid in list_containers(owner_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
|
assert cid in list_containers(owner_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
|
||||||
|
|
||||||
@allure.title("[NEGATIVE] Static session without create operation")
|
|
||||||
def test_static_session_token_container_create_with_other_verb(
|
def test_static_session_token_container_create_with_other_verb(
|
||||||
self,
|
self,
|
||||||
user_wallet: WalletInfo,
|
user_wallet: WalletInfo,
|
||||||
|
@ -68,7 +65,6 @@ class TestSessionTokenContainer(ClusterTestBase):
|
||||||
wait_for_creation=False,
|
wait_for_creation=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@allure.title("[NEGATIVE] Static session with create operation for other wallet")
|
|
||||||
def test_static_session_token_container_create_with_other_wallet(
|
def test_static_session_token_container_create_with_other_wallet(
|
||||||
self,
|
self,
|
||||||
stranger_wallet: WalletInfo,
|
stranger_wallet: WalletInfo,
|
||||||
|
@ -87,7 +83,6 @@ class TestSessionTokenContainer(ClusterTestBase):
|
||||||
wait_for_creation=False,
|
wait_for_creation=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@allure.title("Static session with delete operation")
|
|
||||||
def test_static_session_token_container_delete(
|
def test_static_session_token_container_delete(
|
||||||
self,
|
self,
|
||||||
owner_wallet: WalletInfo,
|
owner_wallet: WalletInfo,
|
||||||
|
|
|
@ -31,7 +31,9 @@ class TestControlShard(ClusterTestBase):
|
||||||
data_path = node.storage_node.get_data_directory()
|
data_path = node.storage_node.get_data_directory()
|
||||||
all_datas = node_shell.exec(f"ls -la {data_path}/data | awk '{{ print $9 }}'").stdout.strip()
|
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"):
|
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
|
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:
|
if "1" in check_dir:
|
||||||
object_path = f"{data_path}/data/{data_dir}/data/{oid_path}"
|
object_path = f"{data_path}/data/{data_dir}/data/{oid_path}"
|
||||||
object_name = f"{oid[4:]}.{cid}"
|
object_name = f"{oid[4:]}.{cid}"
|
||||||
|
@ -64,7 +66,9 @@ class TestControlShard(ClusterTestBase):
|
||||||
basic_acl=EACL_PUBLIC_READ_WRITE,
|
basic_acl=EACL_PUBLIC_READ_WRITE,
|
||||||
)
|
)
|
||||||
file = generate_file(round(max_object_size * 0.8))
|
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)
|
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"):
|
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])
|
nodes = get_object_nodes(cluster=self.cluster, cid=cid, oid=oid, alive_node=self.cluster.cluster_nodes[0])
|
||||||
|
|
||||||
|
@ -72,7 +76,9 @@ class TestControlShard(ClusterTestBase):
|
||||||
|
|
||||||
object_path, object_name = self.get_object_path_and_name_file(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}")
|
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_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)
|
delete_container(wallet=default_wallet, cid=cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -111,7 +117,6 @@ class TestControlShard(ClusterTestBase):
|
||||||
|
|
||||||
assert set(shards_from_config) == set(shards_from_cli)
|
assert set(shards_from_config) == set(shards_from_cli)
|
||||||
|
|
||||||
@allure.title("Shard become read-only when errors exceeds threshold")
|
|
||||||
@pytest.mark.failover
|
@pytest.mark.failover
|
||||||
def test_shard_errors(
|
def test_shard_errors(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -20,9 +20,8 @@ def pytest_generate_tests(metafunc: pytest.Metafunc):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.session_logs
|
@pytest.mark.logs_after_session
|
||||||
class TestLogs:
|
class TestLogs:
|
||||||
@pytest.mark.logs_after_session
|
|
||||||
@pytest.mark.order(1000)
|
@pytest.mark.order(1000)
|
||||||
@allure.title("Check logs from frostfs-testcases with marks '{request.config.option.markexpr}' - search errors")
|
@allure.title("Check logs from frostfs-testcases with marks '{request.config.option.markexpr}' - search errors")
|
||||||
def test_logs_search_errors(self, temp_directory: str, cluster: Cluster, session_start_time: datetime, request: pytest.FixtureRequest):
|
def test_logs_search_errors(self, temp_directory: str, cluster: Cluster, session_start_time: datetime, request: pytest.FixtureRequest):
|
||||||
|
|
|
@ -4,6 +4,7 @@ base58==2.1.0
|
||||||
boto3==1.16.33
|
boto3==1.16.33
|
||||||
botocore==1.19.33
|
botocore==1.19.33
|
||||||
configobj==5.0.6
|
configobj==5.0.6
|
||||||
|
frostfs-testlib>=2.0.1
|
||||||
neo-mamba==1.0.0
|
neo-mamba==1.0.0
|
||||||
pexpect==4.8.0
|
pexpect==4.8.0
|
||||||
pyyaml==6.0.1
|
pyyaml==6.0.1
|
||||||
|
|
Loading…
Reference in a new issue