Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
7d2c92ebc0 [#361] Move common fixture to testlib
Signed-off-by: a.berezin <a.berezin@yadro.com>
2025-03-07 17:06:14 +03:00
0c4e601840 [#359] Override represantation method for Host
Signed-off-by: Ilyas Niyazov <i.niyazov@yadro.com>
2025-03-06 08:44:34 +00:00
f1073d214c [#360] Increased timeout for IAM policy attach/detach
Signed-off-by: Yaroslava Lukoyanova <y.lukoyanova@yadro.com>
2025-03-05 16:43:55 +03:00
b00d080982 [#357] Synchronize client and CliCommand timeouts
Signed-off-by: Kirill Sosnovskikh <k.sosnovskikh@yadro.com>
2025-03-03 12:49:49 +00:00
97b9b5498a [#358] Add minor improvements for convenient work with clients
Signed-off-by: Kirill Sosnovskikh <k.sosnovskikh@yadro.com>
2025-02-25 17:07:23 +03:00
10 changed files with 70 additions and 48 deletions

View file

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

View file

@ -24,9 +24,7 @@ class CliCommand:
def __init__(self, shell: Shell, cli_exec_path: str, **base_params):
self.shell = shell
self.cli_exec_path = cli_exec_path
self.__base_params = " ".join(
[f"--{param} {value}" for param, value in base_params.items() if value]
)
self.__base_params = " ".join([f"--{param} {value}" for param, value in base_params.items() if value])
def _format_command(self, command: str, **params) -> str:
param_str = []
@ -48,9 +46,7 @@ class CliCommand:
val_str = str(value_item).replace("'", "\\'")
param_str.append(f"--{param} '{val_str}'")
elif isinstance(value, dict):
param_str.append(
f'--{param} \'{",".join(f"{key}={val}" for key, val in value.items())}\''
)
param_str.append(f'--{param} \'{",".join(f"{key}={val}" for key, val in value.items())}\'')
else:
if "'" in str(value):
value_str = str(value).replace('"', '\\"')
@ -63,12 +59,18 @@ class CliCommand:
return f"{self.cli_exec_path} {self.__base_params} {command or ''} {param_str}"
def _execute(self, command: Optional[str], **params) -> CommandResult:
return self.shell.exec(self._format_command(command, **params))
def _execute_with_password(self, command: Optional[str], password, **params) -> CommandResult:
timeout = int(params["timeout"].rstrip("s")) if params.get("timeout") else None
return self.shell.exec(
self._format_command(command, **params),
options=CommandOptions(
interactive_inputs=[InteractiveInput(prompt_pattern="assword", input=password)]
CommandOptions(timeout=timeout),
)
def _execute_with_password(self, command: Optional[str], password, **params) -> CommandResult:
timeout = int(params["timeout"].rstrip("s")) if params.get("timeout") else None
return self.shell.exec(
self._format_command(command, **params),
CommandOptions(
interactive_inputs=[InteractiveInput(prompt_pattern="assword", input=password)],
timeout=timeout,
),
)

View file

@ -0,0 +1 @@
from frostfs_testlib.clients.http.http_client import HttpClient

View file

@ -1 +1,3 @@
from frostfs_testlib.clients.s3.interfaces import BucketContainerResolver, S3ClientWrapper, VersioningStatus
from frostfs_testlib.clients.s3.aws_cli_client import AwsCliClient
from frostfs_testlib.clients.s3.boto3_client import Boto3ClientWrapper
from frostfs_testlib.clients.s3.interfaces import ACL, BucketContainerResolver, S3ClientWrapper, VersioningStatus

View file

@ -33,12 +33,14 @@ class AwsCliClient(S3ClientWrapper):
self, access_key_id: str, secret_access_key: str, s3gate_endpoint: str, profile: str = "default", region: str = "us-east-1"
) -> None:
self.s3gate_endpoint = s3gate_endpoint
self.iam_endpoint = None
self.access_key_id: str = access_key_id
self.secret_access_key: str = secret_access_key
self.profile = profile
self.local_shell = LocalShell()
self.region = region
self.iam_endpoint = None
self.local_shell = LocalShell()
try:
_configure_aws_cli(f"aws configure --profile {profile}", access_key_id, secret_access_key, region)
self.local_shell.exec(f"aws configure set max_attempts {MAX_REQUEST_ATTEMPTS} --profile {profile}")
@ -977,7 +979,7 @@ class AwsCliClient(S3ClientWrapper):
cmd += f" --profile {self.profile}"
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@ -988,7 +990,7 @@ class AwsCliClient(S3ClientWrapper):
cmd += f" --profile {self.profile}"
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@ -1120,7 +1122,7 @@ class AwsCliClient(S3ClientWrapper):
cmd += f" --profile {self.profile}"
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@ -1131,7 +1133,7 @@ class AwsCliClient(S3ClientWrapper):
cmd += f" --profile {self.profile}"
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@ -1350,7 +1352,7 @@ class AwsCliClient(S3ClientWrapper):
cmd += f" --profile {self.profile}"
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@ -1365,7 +1367,7 @@ class AwsCliClient(S3ClientWrapper):
output = self.local_shell.exec(cmd).stdout
response = self._to_json(output)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response

View file

@ -35,26 +35,20 @@ class Boto3ClientWrapper(S3ClientWrapper):
def __init__(
self, access_key_id: str, secret_access_key: str, s3gate_endpoint: str, profile: str = "default", region: str = "us-east-1"
) -> None:
self.boto3_client: S3Client = None
self.s3gate_endpoint: str = ""
self.boto3_client: S3Client = None
self.boto3_iam_client: S3Client = None
self.iam_endpoint: str = ""
self.boto3_iam_client: S3Client = None
self.boto3_sts_client: S3Client = None
self.access_key_id: str = access_key_id
self.secret_access_key: str = secret_access_key
self.access_key_id = access_key_id
self.secret_access_key = secret_access_key
self.profile = profile
self.region = region
self.session = boto3.Session()
self.config = Config(
retries={
"max_attempts": MAX_REQUEST_ATTEMPTS,
"mode": RETRY_MODE,
}
)
self.config = Config(retries={"max_attempts": MAX_REQUEST_ATTEMPTS, "mode": RETRY_MODE})
self.set_endpoint(s3gate_endpoint)
@ -90,7 +84,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint_url=self.iam_endpoint,
verify=False,
)
# since the STS does not have an enpoint, IAM is used
# since the STS does not have an endpoint, IAM is used
self.boto3_sts_client = self.session.client(
service_name="sts",
aws_access_key_id=self.access_key_id,
@ -145,6 +139,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
params = {"Bucket": bucket}
if object_lock_enabled_for_bucket is not None:
params.update({"ObjectLockEnabledForBucket": object_lock_enabled_for_bucket})
if acl is not None:
params.update({"ACL": acl})
elif grant_write or grant_read or grant_full_control:
@ -154,6 +149,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
params.update({"GrantRead": grant_read})
elif grant_full_control:
params.update({"GrantFullControl": grant_full_control})
if location_constraint:
params.update({"CreateBucketConfiguration": {"LocationConstraint": location_constraint}})
@ -840,7 +836,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint=self.iam_endpoint,
profile=self.profile,
)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@reporter.step("Attaches the specified managed policy to the specified user")
@ -852,7 +848,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint=self.iam_endpoint,
profile=self.profile,
)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@reporter.step("Creates a new AWS secret access key and access key ID for the specified user")
@ -983,7 +979,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint=self.iam_endpoint,
profile=self.profile,
)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@reporter.step("Removes the specified managed policy from the specified user")
@ -995,7 +991,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint=self.iam_endpoint,
profile=self.profile,
)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@reporter.step("Returns a list of IAM users that are in the specified IAM group")
@ -1205,7 +1201,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint=self.iam_endpoint,
profile=self.profile,
)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@reporter.step("Adds or updates an inline policy document that is embedded in the specified IAM user")
@ -1220,7 +1216,7 @@ class Boto3ClientWrapper(S3ClientWrapper):
endpoint=self.iam_endpoint,
profile=self.profile,
)
sleep(S3_SYNC_WAIT_TIME * 10)
sleep(S3_SYNC_WAIT_TIME * 14)
return response
@reporter.step("Removes the specified user from the specified group")

View file

@ -22,15 +22,15 @@ class VersioningStatus(HumanReadableEnum):
SUSPENDED = "Suspended"
ACL_COPY = [
"private",
"public-read",
"public-read-write",
"authenticated-read",
"aws-exec-read",
"bucket-owner-read",
"bucket-owner-full-control",
]
class ACL:
PRIVATE = "private"
PUBLIC_READ = "public-read"
PUBLIC_READ_WRITE = "public-read-write"
AUTHENTICATED_READ = "authenticated-read"
AWS_EXEC_READ = "aws-exec-read"
BUCKET_OWNER_READ = "bucket-owner-read"
BUCKET_OWNER_FULL_CONTROL = "bucket-owner-full-control"
LOG_DELIVERY_WRITE = "log-delivery-write"
class BucketContainerResolver(ABC):
@ -50,6 +50,14 @@ class BucketContainerResolver(ABC):
class S3ClientWrapper(HumanReadableABC):
access_key_id: str
secret_access_key: str
profile: str
region: str
s3gate_endpoint: str
iam_endpoint: str
@abstractmethod
def __init__(self, access_key_id: str, secret_access_key: str, s3gate_endpoint: str, profile: str, region: str) -> None:
pass

View file

@ -1,5 +1,6 @@
import logging
import os
from datetime import datetime
from importlib.metadata import entry_points
import pytest
@ -11,6 +12,12 @@ from frostfs_testlib.resources.common import ASSETS_DIR, HOSTING_CONFIG_FILE
from frostfs_testlib.storage import get_service_registry
@pytest.fixture(scope="session", autouse=True)
def session_start_time():
start_time = datetime.utcnow()
return start_time
@pytest.fixture(scope="session")
def configure_testlib():
reporter.get_reporter().register_handler(reporter.AllureHandler())

View file

@ -29,6 +29,9 @@ class Host(ABC):
self._service_config_by_name = {service_config.name: service_config for service_config in config.services}
self._cli_config_by_name = {cli_config.name: cli_config for cli_config in config.clis}
def __repr__(self) -> str:
return self.config.address
@property
def config(self) -> HostConfig:
"""Returns config of the host.

View file

@ -1,5 +1,6 @@
# Regex patterns of status codes of Container service
CONTAINER_NOT_FOUND = "code = 3072.*message = container not found"
SUBJECT_NOT_FOUND = "code = 1024.*message = frostfs error: chain/client.*subject not found.*"
# Regex patterns of status codes of Object service
MALFORMED_REQUEST = "code = 1024.*message = malformed request"