Compare commits

...

7 commits

Author SHA1 Message Date
b2bf6677f1 [#310] Update test marking
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-10-25 18:52:43 +03:00
3f3be83d90 [#305] Added IAM abstract method 2024-10-25 08:07:47 +00:00
5fa58a55c0 [#304] Improve logging Boto3 IAM methods
Signed-off-by: Kirill Sosnovskikh <k.sosnovskikh@yadro.com>
2024-10-18 19:24:26 +03:00
738cfacbb7 [#300] Refactor tests: use unique_name instead hex + timestamp
Signed-off-by: Kirill Sosnovskikh <k.sosnovskikh@yadro.com>
2024-10-14 10:09:13 +00:00
cf48f474eb [#303] add check if registry is on hdd
Signed-off-by: m.malygina <m.malygina@yadro.com>
2024-10-14 11:16:09 +03:00
2a41f2b0f6 [#301] Added interfaces for put/get lifecycle configuration to s3 clients 2024-10-11 13:35:33 +00:00
a04eba8aec [#302] Autoadd marks for frostfs
Signed-off-by: a.berezin <a.berezin@yadro.com>
2024-10-11 12:23:32 +03:00
15 changed files with 247 additions and 67 deletions

View file

@ -27,8 +27,8 @@ dependencies = [
"testrail-api>=1.12.0", "testrail-api>=1.12.0",
"pytest==7.1.2", "pytest==7.1.2",
"tenacity==8.0.1", "tenacity==8.0.1",
"boto3==1.16.33", "boto3==1.35.30",
"boto3-stubs[essential]==1.16.33", "boto3-stubs[essential]==1.35.30",
] ]
requires-python = ">=3.10" requires-python = ">=3.10"

View file

@ -8,8 +8,8 @@ docstring_parser==0.15
testrail-api==1.12.0 testrail-api==1.12.0
tenacity==8.0.1 tenacity==8.0.1
pytest==7.1.2 pytest==7.1.2
boto3==1.16.33 boto3==1.35.30
boto3-stubs[essential]==1.16.33 boto3-stubs[essential]==1.35.30
# Dev dependencies # Dev dependencies
black==22.8.0 black==22.8.0

View file

@ -1,3 +1,4 @@
__version__ = "2.0.1" __version__ = "2.0.1"
from .fixtures import configure_testlib, hosting, temp_directory from .fixtures import configure_testlib, hosting, temp_directory
from .hooks import pytest_collection_modifyitems

View file

@ -69,9 +69,7 @@ class FrostfsAdmMorph(CliCommand):
**{param: param_value for param, param_value in locals().items() if param not in ["self"]}, **{param: param_value for param, param_value in locals().items() if param not in ["self"]},
) )
def set_config( def set_config(self, set_key_value: str, rpc_endpoint: Optional[str] = None, alphabet_wallets: Optional[str] = None) -> CommandResult:
self, set_key_value: str, rpc_endpoint: Optional[str] = None, alphabet_wallets: Optional[str] = None
) -> CommandResult:
"""Add/update global config value in the FrostFS network. """Add/update global config value in the FrostFS network.
Args: Args:
@ -125,7 +123,7 @@ class FrostfsAdmMorph(CliCommand):
) )
def force_new_epoch( def force_new_epoch(
self, rpc_endpoint: Optional[str] = None, alphabet_wallets: Optional[str] = None self, rpc_endpoint: Optional[str] = None, alphabet_wallets: Optional[str] = None, delta: Optional[int] = None
) -> CommandResult: ) -> CommandResult:
"""Create new FrostFS epoch event in the side chain. """Create new FrostFS epoch event in the side chain.
@ -344,11 +342,7 @@ class FrostfsAdmMorph(CliCommand):
return self._execute( return self._execute(
f"morph remove-nodes {' '.join(node_netmap_keys)}", f"morph remove-nodes {' '.join(node_netmap_keys)}",
**{ **{param: param_value for param, param_value in locals().items() if param not in ["self", "node_netmap_keys"]},
param: param_value
for param, param_value in locals().items()
if param not in ["self", "node_netmap_keys"]
},
) )
def add_rule( def add_rule(

View file

@ -1,5 +1,4 @@
import re import re
from datetime import datetime
from typing import Optional from typing import Optional
from frostfs_testlib import reporter from frostfs_testlib import reporter
@ -10,6 +9,7 @@ from frostfs_testlib.shell import LocalShell
from frostfs_testlib.steps.cli.container import list_containers from frostfs_testlib.steps.cli.container import list_containers
from frostfs_testlib.storage.cluster import ClusterNode from frostfs_testlib.storage.cluster import ClusterNode
from frostfs_testlib.storage.dataclasses.frostfs_services import S3Gate from frostfs_testlib.storage.dataclasses.frostfs_services import S3Gate
from frostfs_testlib.utils import string_utils
class AuthmateS3CredentialsProvider(S3CredentialsProvider): class AuthmateS3CredentialsProvider(S3CredentialsProvider):
@ -22,7 +22,7 @@ class AuthmateS3CredentialsProvider(S3CredentialsProvider):
gate_public_keys = [node.service(S3Gate).get_wallet_public_key() for node in cluster_nodes] gate_public_keys = [node.service(S3Gate).get_wallet_public_key() for node in cluster_nodes]
# unique short bucket name # unique short bucket name
bucket = f"bucket-{hex(int(datetime.now().timestamp()*1000000))}" bucket = string_utils.unique_name("bucket-")
frostfs_authmate: FrostfsAuthmate = FrostfsAuthmate(shell, FROSTFS_AUTHMATE_EXEC) frostfs_authmate: FrostfsAuthmate = FrostfsAuthmate(shell, FROSTFS_AUTHMATE_EXEC)
issue_secret_output = frostfs_authmate.secret.issue( issue_secret_output = frostfs_authmate.secret.issue(

View file

@ -0,0 +1,13 @@
import pytest
@pytest.hookimpl
def pytest_collection_modifyitems(items: list[pytest.Item]):
# All tests which reside in frostfs nodeid are granted with frostfs marker, excluding
# nodeid = full path of the test
# 1. plugins
# 2. testlib itself
for item in items:
location = item.location[0]
if "frostfs" in location and "plugin" not in location and "testlib" not in location:
item.add_marker("frostfs")

View file

@ -1,5 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from frostfs_testlib.load.interfaces.loader import Loader
from frostfs_testlib.load.k6 import K6 from frostfs_testlib.load.k6 import K6
from frostfs_testlib.load.load_config import LoadParams from frostfs_testlib.load.load_config import LoadParams
from frostfs_testlib.storage.cluster import ClusterNode from frostfs_testlib.storage.cluster import ClusterNode
@ -48,3 +49,7 @@ class ScenarioRunner(ABC):
@abstractmethod @abstractmethod
def get_results(self) -> dict: def get_results(self) -> dict:
"""Get results from K6 run""" """Get results from K6 run"""
@abstractmethod
def get_loaders(self) -> list[Loader]:
"""Return loaders"""

View file

@ -30,6 +30,7 @@ from frostfs_testlib.utils.file_keeper import FileKeeper
class RunnerBase(ScenarioRunner): class RunnerBase(ScenarioRunner):
k6_instances: list[K6] k6_instances: list[K6]
loaders: list[Loader]
@reporter.step("Run preset on loaders") @reporter.step("Run preset on loaders")
def preset(self): def preset(self):
@ -49,9 +50,11 @@ class RunnerBase(ScenarioRunner):
def get_k6_instances(self): def get_k6_instances(self):
return self.k6_instances return self.k6_instances
def get_loaders(self) -> list[Loader]:
return self.loaders
class DefaultRunner(RunnerBase): class DefaultRunner(RunnerBase):
loaders: list[Loader]
user: User user: User
def __init__( def __init__(
@ -228,7 +231,6 @@ class DefaultRunner(RunnerBase):
class LocalRunner(RunnerBase): class LocalRunner(RunnerBase):
loaders: list[Loader]
cluster_state_controller: ClusterStateController cluster_state_controller: ClusterStateController
file_keeper: FileKeeper file_keeper: FileKeeper
user: User user: User

View file

@ -754,6 +754,36 @@ class AwsCliClient(S3ClientWrapper):
response = self._to_json(output) response = self._to_json(output)
return response.get("ObjectLockConfiguration") return response.get("ObjectLockConfiguration")
@reporter.step("Put bucket lifecycle configuration")
def put_bucket_lifecycle_configuration(self, bucket: str, lifecycle_configuration: dict, dumped_configuration: str) -> dict:
cmd = (
f"aws {self.common_flags} s3api put-bucket-lifecycle-configuration --bucket {bucket} "
f"--endpoint-url {self.s3gate_endpoint} --lifecycle-configuration file://{dumped_configuration} --profile {self.profile}"
)
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
return response
@reporter.step("Get bucket lifecycle configuration")
def get_bucket_lifecycle_configuration(self, bucket: str) -> dict:
cmd = (
f"aws {self.common_flags} s3api get-bucket-lifecycle-configuration --bucket {bucket} "
f"--endpoint-url {self.s3gate_endpoint} --profile {self.profile}"
)
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
return response
@reporter.step("Delete bucket lifecycle configuration")
def delete_bucket_lifecycle(self, bucket: str) -> dict:
cmd = (
f"aws {self.common_flags} s3api delete-bucket-lifecycle --bucket {bucket} "
f"--endpoint-url {self.s3gate_endpoint} --profile {self.profile}"
)
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
return response
@staticmethod @staticmethod
def _to_json(output: str) -> dict: def _to_json(output: str) -> dict:
json_output = {} json_output = {}

View file

@ -68,6 +68,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
self.access_key_id: str = access_key_id self.access_key_id: str = access_key_id
self.secret_access_key: str = secret_access_key self.secret_access_key: str = secret_access_key
self.s3gate_endpoint: str = "" self.s3gate_endpoint: str = ""
self.iam_endpoint: str = ""
self.boto3_iam_client: S3Client = None self.boto3_iam_client: S3Client = None
self.set_endpoint(s3gate_endpoint) self.set_endpoint(s3gate_endpoint)
@ -90,11 +91,16 @@ class Boto3ClientWrapper(S3ClientWrapper):
@reporter.step("Set endpoint IAM to {iam_endpoint}") @reporter.step("Set endpoint IAM to {iam_endpoint}")
def set_iam_endpoint(self, iam_endpoint: str): def set_iam_endpoint(self, iam_endpoint: str):
if self.iam_endpoint == iam_endpoint:
return
self.iam_endpoint = iam_endpoint
self.boto3_iam_client = self.session.client( self.boto3_iam_client = self.session.client(
service_name="iam", service_name="iam",
aws_access_key_id=self.access_key_id, aws_access_key_id=self.access_key_id,
aws_secret_access_key=self.secret_access_key, aws_secret_access_key=self.secret_access_key,
endpoint_url=iam_endpoint, endpoint_url=self.iam_endpoint,
verify=False, verify=False,
) )
@ -296,6 +302,27 @@ class Boto3ClientWrapper(S3ClientWrapper):
response = self.boto3_client.delete_bucket_cors(Bucket=bucket) response = self.boto3_client.delete_bucket_cors(Bucket=bucket)
log_command_execution(self.s3gate_endpoint, "S3 delete_bucket_cors result", response, {"Bucket": bucket}) log_command_execution(self.s3gate_endpoint, "S3 delete_bucket_cors result", response, {"Bucket": bucket})
@reporter.step("Put bucket lifecycle configuration")
@report_error
def put_bucket_lifecycle_configuration(self, bucket: str, lifecycle_configuration: dict, dumped_configuration: str) -> dict:
response = self.boto3_client.put_bucket_lifecycle_configuration(Bucket=bucket, LifecycleConfiguration=lifecycle_configuration)
log_command_execution(self.s3gate_endpoint, "S3 put_bucket_lifecycle_configuration result", response, {"Bucket": bucket})
return response
@reporter.step("Get bucket lifecycle configuration")
@report_error
def get_bucket_lifecycle_configuration(self, bucket: str) -> dict:
response = self.boto3_client.get_bucket_lifecycle_configuration(Bucket=bucket)
log_command_execution(self.s3gate_endpoint, "S3 get_bucket_lifecycle_configuration result", response, {"Bucket": bucket})
return {"Rules": response.get("Rules")}
@reporter.step("Delete bucket lifecycle configuration")
@report_error
def delete_bucket_lifecycle(self, bucket: str) -> dict:
response = self.boto3_client.delete_bucket_lifecycle(Bucket=bucket)
log_command_execution(self.s3gate_endpoint, "S3 delete_bucket_lifecycle result", response, {"Bucket": bucket})
return response
# END OF BUCKET METHODS # # END OF BUCKET METHODS #
# OBJECT METHODS # # OBJECT METHODS #
@ -666,25 +693,36 @@ class Boto3ClientWrapper(S3ClientWrapper):
# Some methods don't have checks because boto3 is silent in some cases (delete, attach, etc.) # Some methods don't have checks because boto3 is silent in some cases (delete, attach, etc.)
@reporter.step("Adds the specified user to the specified group") @reporter.step("Adds the specified user to the specified group")
@report_error
def iam_add_user_to_group(self, user_name: str, group_name: str) -> dict: def iam_add_user_to_group(self, user_name: str, group_name: str) -> dict:
response = self.boto3_iam_client.add_user_to_group(UserName=user_name, GroupName=group_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.add_user_to_group(**params)
log_command_execution(self.iam_endpoint, "IAM Add User to Group", response, params)
return response return response
@reporter.step("Attaches the specified managed policy to the specified IAM group") @reporter.step("Attaches the specified managed policy to the specified IAM group")
@report_error
def iam_attach_group_policy(self, group_name: str, policy_arn: str) -> dict: def iam_attach_group_policy(self, group_name: str, policy_arn: str) -> dict:
response = self.boto3_iam_client.attach_group_policy(GroupName=group_name, PolicyArn=policy_arn) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.attach_group_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Attach Group Policy", response, params)
sleep(S3_SYNC_WAIT_TIME * 10) sleep(S3_SYNC_WAIT_TIME * 10)
return response return response
@reporter.step("Attaches the specified managed policy to the specified user") @reporter.step("Attaches the specified managed policy to the specified user")
@report_error
def iam_attach_user_policy(self, user_name: str, policy_arn: str) -> dict: def iam_attach_user_policy(self, user_name: str, policy_arn: str) -> dict:
response = self.boto3_iam_client.attach_user_policy(UserName=user_name, PolicyArn=policy_arn) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.attach_user_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Attach User Policy", response, params)
sleep(S3_SYNC_WAIT_TIME * 10) sleep(S3_SYNC_WAIT_TIME * 10)
return response return response
@reporter.step("Creates a new AWS secret access key and access key ID for the specified user") @reporter.step("Creates a new AWS secret access key and access key ID for the specified user")
@report_error
def iam_create_access_key(self, user_name: str) -> dict: def iam_create_access_key(self, user_name: str) -> dict:
response = self.boto3_iam_client.create_access_key(UserName=user_name) response = self.boto3_iam_client.create_access_key(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM Create Access Key", response, {"UserName": user_name})
access_key_id = response["AccessKey"].get("AccessKeyId") access_key_id = response["AccessKey"].get("AccessKeyId")
secret_access_key = response["AccessKey"].get("SecretAccessKey") secret_access_key = response["AccessKey"].get("SecretAccessKey")
@ -694,138 +732,190 @@ class Boto3ClientWrapper(S3ClientWrapper):
return access_key_id, secret_access_key return access_key_id, secret_access_key
@reporter.step("Creates a new group") @reporter.step("Creates a new group")
@report_error
def iam_create_group(self, group_name: str) -> dict: def iam_create_group(self, group_name: str) -> dict:
response = self.boto3_iam_client.create_group(GroupName=group_name) response = self.boto3_iam_client.create_group(GroupName=group_name)
log_command_execution(self.iam_endpoint, "IAM Create Group", response, {"GroupName": group_name})
assert response.get("Group"), f"Expected Group in response:\n{response}" assert response.get("Group"), f"Expected Group in response:\n{response}"
assert response["Group"].get("GroupName") == group_name, f"GroupName should be equal to {group_name}" assert response["Group"].get("GroupName") == group_name, f"GroupName should be equal to {group_name}"
return response return response
@reporter.step("Creates a new managed policy for your AWS account") @reporter.step("Creates a new managed policy for your AWS account")
@report_error
def iam_create_policy(self, policy_name: str, policy_document: dict) -> dict: def iam_create_policy(self, policy_name: str, policy_document: dict) -> dict:
response = self.boto3_iam_client.create_policy(PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)) params = self._convert_to_s3_params(locals().items())
params["PolicyDocument"] = json.dumps(policy_document)
response = self.boto3_iam_client.create_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Create Policy", response, params)
assert response.get("Policy"), f"Expected Policy in response:\n{response}" assert response.get("Policy"), f"Expected Policy in response:\n{response}"
assert response["Policy"].get("PolicyName") == policy_name, f"PolicyName should be equal to {policy_name}" assert response["Policy"].get("PolicyName") == policy_name, f"PolicyName should be equal to {policy_name}"
return response return response
@reporter.step("Creates a new IAM user for your AWS account") @reporter.step("Creates a new IAM user for your AWS account")
@report_error
def iam_create_user(self, user_name: str) -> dict: def iam_create_user(self, user_name: str) -> dict:
response = self.boto3_iam_client.create_user(UserName=user_name) response = self.boto3_iam_client.create_user(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM Create User", response, {"UserName": user_name})
assert response.get("User"), f"Expected User in response:\n{response}" assert response.get("User"), f"Expected User in response:\n{response}"
assert response["User"].get("UserName") == user_name, f"UserName should be equal to {user_name}" assert response["User"].get("UserName") == user_name, f"UserName should be equal to {user_name}"
return response return response
@reporter.step("Deletes the access key pair associated with the specified IAM user") @reporter.step("Deletes the access key pair associated with the specified IAM user")
@report_error
def iam_delete_access_key(self, access_key_id: str, user_name: str) -> dict: def iam_delete_access_key(self, access_key_id: str, user_name: str) -> dict:
response = self.boto3_iam_client.delete_access_key(AccessKeyId=access_key_id, UserName=user_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.delete_access_key(**params)
log_command_execution(self.iam_endpoint, "IAM Delete Access Key", response, params)
return response return response
@reporter.step("Deletes the specified IAM group") @reporter.step("Deletes the specified IAM group")
@report_error
def iam_delete_group(self, group_name: str) -> dict: def iam_delete_group(self, group_name: str) -> dict:
response = self.boto3_iam_client.delete_group(GroupName=group_name) response = self.boto3_iam_client.delete_group(GroupName=group_name)
log_command_execution(self.iam_endpoint, "IAM Delete Group", response, {"GroupName": group_name})
return response return response
@reporter.step("Deletes the specified inline policy that is embedded in the specified IAM group") @reporter.step("Deletes the specified inline policy that is embedded in the specified IAM group")
@report_error
def iam_delete_group_policy(self, group_name: str, policy_name: str) -> dict: def iam_delete_group_policy(self, group_name: str, policy_name: str) -> dict:
response = self.boto3_iam_client.delete_group_policy(GroupName=group_name, PolicyName=policy_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.delete_group_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Delete Group Policy", response, params)
return response return response
@reporter.step("Deletes the specified managed policy") @reporter.step("Deletes the specified managed policy")
@report_error
def iam_delete_policy(self, policy_arn: str) -> dict: def iam_delete_policy(self, policy_arn: str) -> dict:
response = self.boto3_iam_client.delete_policy(PolicyArn=policy_arn) response = self.boto3_iam_client.delete_policy(PolicyArn=policy_arn)
log_command_execution(self.iam_endpoint, "IAM Delete Policy", response, {"PolicyArn": policy_arn})
return response return response
@reporter.step("Deletes the specified IAM user") @reporter.step("Deletes the specified IAM user")
@report_error
def iam_delete_user(self, user_name: str) -> dict: def iam_delete_user(self, user_name: str) -> dict:
response = self.boto3_iam_client.delete_user(UserName=user_name) response = self.boto3_iam_client.delete_user(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM Delete User", response, {"UserName": user_name})
return response return response
@reporter.step("Deletes the specified inline policy that is embedded in the specified IAM user") @reporter.step("Deletes the specified inline policy that is embedded in the specified IAM user")
@report_error
def iam_delete_user_policy(self, user_name: str, policy_name: str) -> dict: def iam_delete_user_policy(self, user_name: str, policy_name: str) -> dict:
response = self.boto3_iam_client.delete_user_policy(UserName=user_name, PolicyName=policy_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.delete_user_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Delete User Policy", response, params)
return response return response
@reporter.step("Removes the specified managed policy from the specified IAM group") @reporter.step("Removes the specified managed policy from the specified IAM group")
@report_error
def iam_detach_group_policy(self, group_name: str, policy_arn: str) -> dict: def iam_detach_group_policy(self, group_name: str, policy_arn: str) -> dict:
response = self.boto3_iam_client.detach_group_policy(GroupName=group_name, PolicyArn=policy_arn) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.detach_group_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Detach Group Policy", response, params)
sleep(S3_SYNC_WAIT_TIME * 10) sleep(S3_SYNC_WAIT_TIME * 10)
return response return response
@reporter.step("Removes the specified managed policy from the specified user") @reporter.step("Removes the specified managed policy from the specified user")
@report_error
def iam_detach_user_policy(self, user_name: str, policy_arn: str) -> dict: def iam_detach_user_policy(self, user_name: str, policy_arn: str) -> dict:
response = self.boto3_iam_client.detach_user_policy(UserName=user_name, PolicyArn=policy_arn) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.detach_user_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Detach User Policy", response, params)
sleep(S3_SYNC_WAIT_TIME * 10) sleep(S3_SYNC_WAIT_TIME * 10)
return response return response
@reporter.step("Returns a list of IAM users that are in the specified IAM group") @reporter.step("Returns a list of IAM users that are in the specified IAM group")
@report_error
def iam_get_group(self, group_name: str) -> dict: def iam_get_group(self, group_name: str) -> dict:
response = self.boto3_iam_client.get_group(GroupName=group_name) response = self.boto3_iam_client.get_group(GroupName=group_name)
log_command_execution(self.iam_endpoint, "IAM Get Group", response, {"GroupName": group_name})
assert response.get("Group").get("GroupName") == group_name, f"GroupName should be equal to {group_name}" assert response.get("Group").get("GroupName") == group_name, f"GroupName should be equal to {group_name}"
return response return response
@reporter.step("Retrieves the specified inline policy document that is embedded in the specified IAM group") @reporter.step("Retrieves the specified inline policy document that is embedded in the specified IAM group")
@report_error
def iam_get_group_policy(self, group_name: str, policy_name: str) -> dict: def iam_get_group_policy(self, group_name: str, policy_name: str) -> dict:
response = self.boto3_iam_client.get_group_policy(GroupName=group_name, PolicyName=policy_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.get_group_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Get Group Policy", response, params)
return response return response
@reporter.step("Retrieves information about the specified managed policy") @reporter.step("Retrieves information about the specified managed policy")
@report_error
def iam_get_policy(self, policy_arn: str) -> dict: def iam_get_policy(self, policy_arn: str) -> dict:
response = self.boto3_iam_client.get_policy(PolicyArn=policy_arn) response = self.boto3_iam_client.get_policy(PolicyArn=policy_arn)
log_command_execution(self.iam_endpoint, "IAM Get Policy", response, {"PolicyArn": policy_arn})
assert response.get("Policy"), f"Expected Policy in response:\n{response}" assert response.get("Policy"), f"Expected Policy in response:\n{response}"
assert response["Policy"].get("Arn") == policy_arn, f"PolicyArn should be equal to {policy_arn}" assert response["Policy"].get("Arn") == policy_arn, f"PolicyArn should be equal to {policy_arn}"
return response return response
@reporter.step("Retrieves information about the specified version of the specified managed policy") @reporter.step("Retrieves information about the specified version of the specified managed policy")
@report_error
def iam_get_policy_version(self, policy_arn: str, version_id: str) -> dict: def iam_get_policy_version(self, policy_arn: str, version_id: str) -> dict:
response = self.boto3_iam_client.get_policy_version(PolicyArn=policy_arn, VersionId=version_id) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.get_policy_version(**params)
log_command_execution(self.iam_endpoint, "IAM Get Policy Version", response, params)
assert response.get("PolicyVersion"), f"Expected PolicyVersion in response:\n{response}" assert response.get("PolicyVersion"), f"Expected PolicyVersion in response:\n{response}"
assert response["PolicyVersion"].get("VersionId") == version_id, f"VersionId should be equal to {version_id}" assert response["PolicyVersion"].get("VersionId") == version_id, f"VersionId should be equal to {version_id}"
return response return response
@reporter.step("Retrieves information about the specified IAM user") @reporter.step("Retrieves information about the specified IAM user")
@report_error
def iam_get_user(self, user_name: str) -> dict: def iam_get_user(self, user_name: str) -> dict:
response = self.boto3_iam_client.get_user(UserName=user_name) response = self.boto3_iam_client.get_user(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM Get User", response, {"UserName": user_name})
assert response.get("User"), f"Expected User in response:\n{response}" assert response.get("User"), f"Expected User in response:\n{response}"
assert response["User"].get("UserName") == user_name, f"UserName should be equal to {user_name}" assert response["User"].get("UserName") == user_name, f"UserName should be equal to {user_name}"
return response return response
@reporter.step("Retrieves the specified inline policy document that is embedded in the specified IAM user") @reporter.step("Retrieves the specified inline policy document that is embedded in the specified IAM user")
@report_error
def iam_get_user_policy(self, user_name: str, policy_name: str) -> dict: def iam_get_user_policy(self, user_name: str, policy_name: str) -> dict:
response = self.boto3_iam_client.get_user_policy(UserName=user_name, PolicyName=policy_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.get_user_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Get User Policy", response, params)
assert response.get("UserName"), f"Expected UserName in response:\n{response}" assert response.get("UserName"), f"Expected UserName in response:\n{response}"
return response return response
@reporter.step("Returns information about the access key IDs associated with the specified IAM user") @reporter.step("Returns information about the access key IDs associated with the specified IAM user")
@report_error
def iam_list_access_keys(self, user_name: str) -> dict: def iam_list_access_keys(self, user_name: str) -> dict:
response = self.boto3_iam_client.list_access_keys(UserName=user_name) response = self.boto3_iam_client.list_access_keys(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM List Access Keys", response, {"UserName": user_name})
return response return response
@reporter.step("Lists all managed policies that are attached to the specified IAM group") @reporter.step("Lists all managed policies that are attached to the specified IAM group")
@report_error
def iam_list_attached_group_policies(self, group_name: str) -> dict: def iam_list_attached_group_policies(self, group_name: str) -> dict:
response = self.boto3_iam_client.list_attached_group_policies(GroupName=group_name) response = self.boto3_iam_client.list_attached_group_policies(GroupName=group_name)
log_command_execution(self.iam_endpoint, "IAM List Attached Group Policies", response, {"GroupName": group_name})
assert response.get("AttachedPolicies"), f"Expected AttachedPolicies in response:\n{response}" assert response.get("AttachedPolicies"), f"Expected AttachedPolicies in response:\n{response}"
return response return response
@reporter.step("Lists all managed policies that are attached to the specified IAM user") @reporter.step("Lists all managed policies that are attached to the specified IAM user")
@report_error
def iam_list_attached_user_policies(self, user_name: str) -> dict: def iam_list_attached_user_policies(self, user_name: str) -> dict:
response = self.boto3_iam_client.list_attached_user_policies(UserName=user_name) response = self.boto3_iam_client.list_attached_user_policies(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM List Attached User Policies", response, {"UserName": user_name})
assert response.get("AttachedPolicies"), f"Expected AttachedPolicies in response:\n{response}" assert response.get("AttachedPolicies"), f"Expected AttachedPolicies in response:\n{response}"
return response return response
@reporter.step("Lists all IAM users, groups, and roles that the specified managed policy is attached to") @reporter.step("Lists all IAM users, groups, and roles that the specified managed policy is attached to")
@report_error
def iam_list_entities_for_policy(self, policy_arn: str) -> dict: def iam_list_entities_for_policy(self, policy_arn: str) -> dict:
response = self.boto3_iam_client.list_entities_for_policy(PolicyArn=policy_arn) response = self.boto3_iam_client.list_entities_for_policy(PolicyArn=policy_arn)
log_command_execution(self.iam_endpoint, "IAM List Entities For Policy", response, {"PolicyArn": policy_arn})
assert response.get("PolicyGroups"), f"Expected PolicyGroups in response:\n{response}" assert response.get("PolicyGroups"), f"Expected PolicyGroups in response:\n{response}"
assert response.get("PolicyUsers"), f"Expected PolicyUsers in response:\n{response}" assert response.get("PolicyUsers"), f"Expected PolicyUsers in response:\n{response}"
@ -833,98 +923,125 @@ class Boto3ClientWrapper(S3ClientWrapper):
return response return response
@reporter.step("Lists the names of the inline policies that are embedded in the specified IAM group") @reporter.step("Lists the names of the inline policies that are embedded in the specified IAM group")
@report_error
def iam_list_group_policies(self, group_name: str) -> dict: def iam_list_group_policies(self, group_name: str) -> dict:
response = self.boto3_iam_client.list_group_policies(GroupName=group_name) response = self.boto3_iam_client.list_group_policies(GroupName=group_name)
log_command_execution(self.iam_endpoint, "IAM List Group Policies", response, {"GroupName": group_name})
assert response.get("PolicyNames"), f"Expected PolicyNames in response:\n{response}" assert response.get("PolicyNames"), f"Expected PolicyNames in response:\n{response}"
return response return response
@reporter.step("Lists the IAM groups") @reporter.step("Lists the IAM groups")
@report_error
def iam_list_groups(self) -> dict: def iam_list_groups(self) -> dict:
response = self.boto3_iam_client.list_groups() response = self.boto3_iam_client.list_groups()
log_command_execution(self.iam_endpoint, "IAM List Groups", response)
assert response.get("Groups"), f"Expected Groups in response:\n{response}" assert response.get("Groups"), f"Expected Groups in response:\n{response}"
return response return response
@reporter.step("Lists the IAM groups that the specified IAM user belongs to") @reporter.step("Lists the IAM groups that the specified IAM user belongs to")
@report_error
def iam_list_groups_for_user(self, user_name: str) -> dict: def iam_list_groups_for_user(self, user_name: str) -> dict:
response = self.boto3_iam_client.list_groups_for_user(UserName=user_name) response = self.boto3_iam_client.list_groups_for_user(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM List Groups For User", response, {"UserName": user_name})
assert response.get("Groups"), f"Expected Groups in response:\n{response}" assert response.get("Groups"), f"Expected Groups in response:\n{response}"
return response return response
@reporter.step("Lists all the managed policies that are available in your AWS account") @reporter.step("Lists all the managed policies that are available in your AWS account")
@report_error
def iam_list_policies(self) -> dict: def iam_list_policies(self) -> dict:
response = self.boto3_iam_client.list_policies() response = self.boto3_iam_client.list_policies()
log_command_execution(self.iam_endpoint, "IAM List Policies", response)
assert response.get("Policies"), f"Expected Policies in response:\n{response}" assert response.get("Policies"), f"Expected Policies in response:\n{response}"
return response return response
@reporter.step("Lists information about the versions of the specified managed policy") @reporter.step("Lists information about the versions of the specified managed policy")
@report_error
def iam_list_policy_versions(self, policy_arn: str) -> dict: def iam_list_policy_versions(self, policy_arn: str) -> dict:
response = self.boto3_iam_client.list_policy_versions(PolicyArn=policy_arn) response = self.boto3_iam_client.list_policy_versions(PolicyArn=policy_arn)
log_command_execution(self.iam_endpoint, "IAM List Policy Versions", response, {"PolicyArn": policy_arn})
assert response.get("Versions"), f"Expected Versions in response:\n{response}" assert response.get("Versions"), f"Expected Versions in response:\n{response}"
return response return response
@reporter.step("Lists the names of the inline policies embedded in the specified IAM user") @reporter.step("Lists the names of the inline policies embedded in the specified IAM user")
@report_error
def iam_list_user_policies(self, user_name: str) -> dict: def iam_list_user_policies(self, user_name: str) -> dict:
response = self.boto3_iam_client.list_user_policies(UserName=user_name) response = self.boto3_iam_client.list_user_policies(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM List User Policies", response, {"UserName": user_name})
assert response.get("PolicyNames"), f"Expected PolicyNames in response:\n{response}" assert response.get("PolicyNames"), f"Expected PolicyNames in response:\n{response}"
return response return response
@reporter.step("Lists the IAM users") @reporter.step("Lists the IAM users")
@report_error
def iam_list_users(self) -> dict: def iam_list_users(self) -> dict:
response = self.boto3_iam_client.list_users() response = self.boto3_iam_client.list_users()
log_command_execution(self.iam_endpoint, "IAM List Users", response)
assert response.get("Users"), f"Expected Users in response:\n{response}" assert response.get("Users"), f"Expected Users in response:\n{response}"
return response return response
@reporter.step("Adds or updates an inline policy document that is embedded in the specified IAM group") @reporter.step("Adds or updates an inline policy document that is embedded in the specified IAM group")
@report_error
def iam_put_group_policy(self, group_name: str, policy_name: str, policy_document: dict) -> dict: def iam_put_group_policy(self, group_name: str, policy_name: str, policy_document: dict) -> dict:
response = self.boto3_iam_client.put_group_policy( params = self._convert_to_s3_params(locals().items())
GroupName=group_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document) params["PolicyDocument"] = json.dumps(policy_document)
) response = self.boto3_iam_client.put_group_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Put Group Policy", response, params)
sleep(S3_SYNC_WAIT_TIME * 10) sleep(S3_SYNC_WAIT_TIME * 10)
return response return response
@reporter.step("Adds or updates an inline policy document that is embedded in the specified IAM user") @reporter.step("Adds or updates an inline policy document that is embedded in the specified IAM user")
@report_error
def iam_put_user_policy(self, user_name: str, policy_name: str, policy_document: dict) -> dict: def iam_put_user_policy(self, user_name: str, policy_name: str, policy_document: dict) -> dict:
response = self.boto3_iam_client.put_user_policy( params = self._convert_to_s3_params(locals().items())
UserName=user_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document) params["PolicyDocument"] = json.dumps(policy_document)
) response = self.boto3_iam_client.put_user_policy(**params)
log_command_execution(self.iam_endpoint, "IAM Put User Policy", response, params)
sleep(S3_SYNC_WAIT_TIME * 10) sleep(S3_SYNC_WAIT_TIME * 10)
return response return response
@reporter.step("Removes the specified user from the specified group") @reporter.step("Removes the specified user from the specified group")
@report_error
def iam_remove_user_from_group(self, group_name: str, user_name: str) -> dict: def iam_remove_user_from_group(self, group_name: str, user_name: str) -> dict:
response = self.boto3_iam_client.remove_user_from_group(GroupName=group_name, UserName=user_name) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.remove_user_from_group(**params)
log_command_execution(self.iam_endpoint, "IAM Remove User From Group", response, params)
return response return response
@reporter.step("Updates the name and/or the path of the specified IAM group") @reporter.step("Updates the name and/or the path of the specified IAM group")
@report_error
def iam_update_group(self, group_name: str, new_name: str, new_path: Optional[str] = None) -> dict: def iam_update_group(self, group_name: str, new_name: str, new_path: Optional[str] = None) -> dict:
response = self.boto3_iam_client.update_group(GroupName=group_name, NewGroupName=new_name, NewPath="/") params = {"GroupName": group_name, "NewGroupName": new_name, "NewPath": "/"}
response = self.boto3_iam_client.update_group(**params)
log_command_execution(self.iam_endpoint, "IAM Update Group", response, params)
return response return response
@reporter.step("Updates the name and/or the path of the specified IAM user") @reporter.step("Updates the name and/or the path of the specified IAM user")
@report_error
def iam_update_user(self, user_name: str, new_name: str, new_path: Optional[str] = None) -> dict: def iam_update_user(self, user_name: str, new_name: str, new_path: Optional[str] = None) -> dict:
response = self.boto3_iam_client.update_user(UserName=user_name, NewUserName=new_name, NewPath="/") params = {"UserName": user_name, "NewUserName": new_name, "NewPath": "/"}
response = self.boto3_iam_client.update_user(**params)
log_command_execution(self.iam_endpoint, "IAM Update User", response, params)
return response return response
@reporter.step("Adds one or more tags to an IAM user") @reporter.step("Adds one or more tags to an IAM user")
@report_error
def iam_tag_user(self, user_name: str, tags: list) -> dict: def iam_tag_user(self, user_name: str, tags: list) -> dict:
tags_json = [{"Key": tag_key, "Value": tag_value} for tag_key, tag_value in tags] params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.tag_user(UserName=user_name, Tags=tags_json) params["Tags"] = [{"Key": tag_key, "Value": tag_value} for tag_key, tag_value in tags]
response = self.boto3_iam_client.tag_user(**params)
log_command_execution(self.iam_endpoint, "IAM Tag User", response, params)
return response return response
@reporter.step("List tags of IAM user") @reporter.step("List tags of IAM user")
@report_error
def iam_list_user_tags(self, user_name: str) -> dict: def iam_list_user_tags(self, user_name: str) -> dict:
response = self.boto3_iam_client.list_user_tags(UserName=user_name) response = self.boto3_iam_client.list_user_tags(UserName=user_name)
log_command_execution(self.iam_endpoint, "IAM List User Tags", response, {"UserName": user_name})
return response return response
@reporter.step("Removes the specified tags from the user") @reporter.step("Removes the specified tags from the user")
@report_error
def iam_untag_user(self, user_name: str, tag_keys: list) -> dict: def iam_untag_user(self, user_name: str, tag_keys: list) -> dict:
response = self.boto3_iam_client.untag_user(UserName=user_name, TagKeys=tag_keys) params = self._convert_to_s3_params(locals().items())
response = self.boto3_iam_client.untag_user(**params)
log_command_execution(self.iam_endpoint, "IAM Untag User", response, params)
return response return response

View file

@ -58,6 +58,10 @@ class S3ClientWrapper(HumanReadableABC):
def set_endpoint(self, s3gate_endpoint: str): def set_endpoint(self, s3gate_endpoint: str):
"""Set endpoint""" """Set endpoint"""
@abstractmethod
def set_iam_endpoint(self, iam_endpoint: str):
"""Set iam endpoint"""
@abstractmethod @abstractmethod
def create_bucket( def create_bucket(
self, self,
@ -366,6 +370,18 @@ class S3ClientWrapper(HumanReadableABC):
def delete_object_tagging(self, bucket: str, key: str) -> None: def delete_object_tagging(self, bucket: str, key: str) -> None:
"""Removes the entire tag set from the specified object.""" """Removes the entire tag set from the specified object."""
@abstractmethod
def put_bucket_lifecycle_configuration(self, bucket: str, lifecycle_configuration: dict, dumped_configuration: str) -> dict:
"""Adds or updates bucket lifecycle configuration"""
@abstractmethod
def get_bucket_lifecycle_configuration(self, bucket: str) -> dict:
"""Gets bucket lifecycle configuration"""
@abstractmethod
def delete_bucket_lifecycle(self, bucket: str) -> dict:
"""Deletes bucket lifecycle"""
@abstractmethod @abstractmethod
def get_object_attributes( def get_object_attributes(
self, self,

View file

@ -69,7 +69,7 @@ def get_epoch(shell: Shell, cluster: Cluster, alive_node: Optional[StorageNode]
@reporter.step("Tick Epoch") @reporter.step("Tick Epoch")
def tick_epoch(shell: Shell, cluster: Cluster, alive_node: Optional[StorageNode] = None): def tick_epoch(shell: Shell, cluster: Cluster, alive_node: Optional[StorageNode] = None, delta: Optional[int] = None):
""" """
Tick epoch using frostfs-adm or NeoGo if frostfs-adm is not available (DevEnv) Tick epoch using frostfs-adm or NeoGo if frostfs-adm is not available (DevEnv)
Args: Args:
@ -88,12 +88,17 @@ def tick_epoch(shell: Shell, cluster: Cluster, alive_node: Optional[StorageNode]
frostfs_adm_exec_path=FROSTFS_ADM_EXEC, frostfs_adm_exec_path=FROSTFS_ADM_EXEC,
config_file=FROSTFS_ADM_CONFIG_PATH, config_file=FROSTFS_ADM_CONFIG_PATH,
) )
frostfs_adm.morph.force_new_epoch() frostfs_adm.morph.force_new_epoch(delta=delta)
return return
# Otherwise we tick epoch using transaction # Otherwise we tick epoch using transaction
cur_epoch = get_epoch(shell, cluster) cur_epoch = get_epoch(shell, cluster)
if delta:
next_epoch = cur_epoch + delta
else:
next_epoch = cur_epoch + 1
# Use first node by default # Use first node by default
ir_node = cluster.services(InnerRing)[0] ir_node = cluster.services(InnerRing)[0]
# In case if no local_wallet_path is provided, we use wallet_path # In case if no local_wallet_path is provided, we use wallet_path
@ -110,7 +115,7 @@ def tick_epoch(shell: Shell, cluster: Cluster, alive_node: Optional[StorageNode]
wallet_password=ir_wallet_pass, wallet_password=ir_wallet_pass,
scripthash=get_contract_hash(morph_chain, "netmap.frostfs", shell=shell), scripthash=get_contract_hash(morph_chain, "netmap.frostfs", shell=shell),
method="newEpoch", method="newEpoch",
arguments=f"int:{cur_epoch + 1}", arguments=f"int:{next_epoch}",
multisig_hash=f"{ir_address}:Global", multisig_hash=f"{ir_address}:Global",
address=ir_address, address=ir_address,
rpc_endpoint=morph_endpoint, rpc_endpoint=morph_endpoint,

View file

@ -1,8 +1,8 @@
import re import re
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.testing.test_control import wait_for_success
from frostfs_testlib.storage.cluster import ClusterNode from frostfs_testlib.storage.cluster import ClusterNode
from frostfs_testlib.testing.test_control import wait_for_success
@reporter.step("Check metrics result") @reporter.step("Check metrics result")
@ -19,7 +19,7 @@ def check_metrics_counter(
counter_act += get_metrics_value(cluster_node, parse_from_command, **metrics_greps) counter_act += get_metrics_value(cluster_node, parse_from_command, **metrics_greps)
assert eval( assert eval(
f"{counter_act} {operator} {counter_exp}" f"{counter_act} {operator} {counter_exp}"
), f"Expected: {counter_exp} {operator} Actual: {counter_act} in node: {cluster_node}" ), f"Expected: {counter_exp} {operator} Actual: {counter_act} in nodes: {cluster_nodes}"
@reporter.step("Get metrics value from node: {node}") @reporter.step("Get metrics value from node: {node}")

View file

@ -25,12 +25,8 @@ class ClusterTestBase:
for _ in range(epochs_to_tick): for _ in range(epochs_to_tick):
self.tick_epoch(alive_node, wait_block) self.tick_epoch(alive_node, wait_block)
def tick_epoch( def tick_epoch(self, alive_node: Optional[StorageNode] = None, wait_block: int = None, delta: Optional[int] = None):
self, epoch.tick_epoch(self.shell, self.cluster, alive_node=alive_node, delta=delta)
alive_node: Optional[StorageNode] = None,
wait_block: int = None,
):
epoch.tick_epoch(self.shell, self.cluster, alive_node=alive_node)
if wait_block: if wait_block:
self.wait_for_blocks(wait_block) self.wait_for_blocks(wait_block)

View file

@ -8,6 +8,7 @@ ONLY_ASCII_LETTERS = string.ascii_letters
DIGITS_AND_ASCII_LETTERS = string.ascii_letters + string.digits DIGITS_AND_ASCII_LETTERS = string.ascii_letters + string.digits
NON_DIGITS_AND_LETTERS = string.punctuation NON_DIGITS_AND_LETTERS = string.punctuation
# if unique_name is called multiple times within the same microsecond, append 0-4 to the name so it surely unique
FUSE = itertools.cycle(range(5)) FUSE = itertools.cycle(range(5))