Refactor for cluster usage
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
This commit is contained in:
parent
d9e881001e
commit
bd05aae585
46 changed files with 3859 additions and 2703 deletions
|
@ -6,7 +6,7 @@ from typing import Optional
|
|||
|
||||
import allure
|
||||
from cli_helpers import _cmd_run
|
||||
from common import ASSETS_DIR, S3_GATE
|
||||
from common import ASSETS_DIR
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
REGULAR_TIMEOUT = 90
|
||||
|
@ -17,6 +17,10 @@ class AwsCliClient:
|
|||
# Flags that we use for all S3 commands: disable SSL verification (as we use self-signed
|
||||
# certificate in devenv) and disable automatic pagination in CLI output
|
||||
common_flags = "--no-verify-ssl --no-paginate"
|
||||
s3gate_endpoint: str
|
||||
|
||||
def __init__(self, s3gate_endpoint) -> None:
|
||||
self.s3gate_endpoint = s3gate_endpoint
|
||||
|
||||
def create_bucket(
|
||||
self,
|
||||
|
@ -36,7 +40,7 @@ class AwsCliClient:
|
|||
object_lock = " --no-object-lock-enabled-for-bucket"
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api create-bucket --bucket {Bucket} "
|
||||
f"{object_lock} --endpoint {S3_GATE}"
|
||||
f"{object_lock} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if ACL:
|
||||
cmd += f" --acl {ACL}"
|
||||
|
@ -51,14 +55,14 @@ class AwsCliClient:
|
|||
_cmd_run(cmd, REGULAR_TIMEOUT)
|
||||
|
||||
def list_buckets(self) -> dict:
|
||||
cmd = f"aws {self.common_flags} s3api list-buckets --endpoint {S3_GATE}"
|
||||
cmd = f"aws {self.common_flags} s3api list-buckets --endpoint {self.s3gate_endpoint}"
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
||||
def get_bucket_acl(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-bucket-acl --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, REGULAR_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -66,7 +70,7 @@ class AwsCliClient:
|
|||
def get_bucket_versioning(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-bucket-versioning --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, REGULAR_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -74,7 +78,7 @@ class AwsCliClient:
|
|||
def get_bucket_location(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-bucket-location --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, REGULAR_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -83,14 +87,15 @@ class AwsCliClient:
|
|||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-bucket-versioning --bucket {Bucket} "
|
||||
f'--versioning-configuration Status={VersioningConfiguration.get("Status")} '
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
||||
def list_objects(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api list-objects --bucket {Bucket} " f"--endpoint {S3_GATE}"
|
||||
f"aws {self.common_flags} s3api list-objects --bucket {Bucket} "
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -98,7 +103,7 @@ class AwsCliClient:
|
|||
def list_objects_v2(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api list-objects-v2 --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -106,7 +111,7 @@ class AwsCliClient:
|
|||
def list_object_versions(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api list-object-versions --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -124,7 +129,7 @@ class AwsCliClient:
|
|||
) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api copy-object --copy-source {CopySource} "
|
||||
f"--bucket {Bucket} --key {Key} --endpoint {S3_GATE}"
|
||||
f"--bucket {Bucket} --key {Key} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if ACL:
|
||||
cmd += f" --acl {ACL}"
|
||||
|
@ -142,7 +147,7 @@ class AwsCliClient:
|
|||
return self._to_json(output)
|
||||
|
||||
def head_bucket(self, Bucket: str) -> dict:
|
||||
cmd = f"aws {self.common_flags} s3api head-bucket --bucket {Bucket} --endpoint {S3_GATE}"
|
||||
cmd = f"aws {self.common_flags} s3api head-bucket --bucket {Bucket} --endpoint {self.s3gate_endpoint}"
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
||||
|
@ -162,7 +167,7 @@ class AwsCliClient:
|
|||
) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object --bucket {Bucket} --key {Key} "
|
||||
f"--body {Body} --endpoint {S3_GATE}"
|
||||
f"--body {Body} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if Metadata:
|
||||
cmd += f" --metadata"
|
||||
|
@ -189,7 +194,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api head-object --bucket {Bucket} --key {Key} "
|
||||
f"{version} --endpoint {S3_GATE}"
|
||||
f"{version} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -205,7 +210,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-object --bucket {Bucket} --key {Key} "
|
||||
f"{version} {file_path} --endpoint {S3_GATE}"
|
||||
f"{version} {file_path} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if Range:
|
||||
cmd += f" --range {Range}"
|
||||
|
@ -216,7 +221,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-object-acl --bucket {Bucket} --key {Key} "
|
||||
f"{version} --endpoint {S3_GATE}"
|
||||
f"{version} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, REGULAR_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -231,7 +236,7 @@ class AwsCliClient:
|
|||
) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-acl --bucket {Bucket} --key {Key} "
|
||||
f" --endpoint {S3_GATE}"
|
||||
f" --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if ACL:
|
||||
cmd += f" --acl {ACL}"
|
||||
|
@ -251,7 +256,7 @@ class AwsCliClient:
|
|||
) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-bucket-acl --bucket {Bucket} "
|
||||
f" --endpoint {S3_GATE}"
|
||||
f" --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if ACL:
|
||||
cmd += f" --acl {ACL}"
|
||||
|
@ -270,7 +275,7 @@ class AwsCliClient:
|
|||
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api delete-objects --bucket {Bucket} "
|
||||
f"--delete file://{file_path} --endpoint {S3_GATE}"
|
||||
f"--delete file://{file_path} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, LONG_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -279,7 +284,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api delete-object --bucket {Bucket} "
|
||||
f"--key {Key} {version} --endpoint {S3_GATE}"
|
||||
f"--key {Key} {version} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, LONG_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -300,20 +305,20 @@ class AwsCliClient:
|
|||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-object-attributes --bucket {bucket} "
|
||||
f"--key {key} {version} {parts} {part_number} --object-attributes {attrs} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
||||
def delete_bucket(self, Bucket: str) -> dict:
|
||||
cmd = f"aws {self.common_flags} s3api delete-bucket --bucket {Bucket} --endpoint {S3_GATE}"
|
||||
cmd = f"aws {self.common_flags} s3api delete-bucket --bucket {Bucket} --endpoint {self.s3gate_endpoint}"
|
||||
output = _cmd_run(cmd, LONG_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
||||
def get_bucket_tagging(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-bucket-tagging --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -321,7 +326,7 @@ class AwsCliClient:
|
|||
def get_bucket_policy(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-bucket-policy --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -329,7 +334,7 @@ class AwsCliClient:
|
|||
def put_bucket_policy(self, Bucket: str, Policy: dict) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-bucket-policy --bucket {Bucket} "
|
||||
f"--policy {json.dumps(Policy)} --endpoint {S3_GATE}"
|
||||
f"--policy {json.dumps(Policy)} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -337,7 +342,7 @@ class AwsCliClient:
|
|||
def get_bucket_cors(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-bucket-cors --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -345,7 +350,7 @@ class AwsCliClient:
|
|||
def put_bucket_cors(self, Bucket: str, CORSConfiguration: dict) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-bucket-cors --bucket {Bucket} "
|
||||
f"--cors-configuration '{json.dumps(CORSConfiguration)}' --endpoint {S3_GATE}"
|
||||
f"--cors-configuration '{json.dumps(CORSConfiguration)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -353,7 +358,7 @@ class AwsCliClient:
|
|||
def delete_bucket_cors(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api delete-bucket-cors --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -361,7 +366,7 @@ class AwsCliClient:
|
|||
def put_bucket_tagging(self, Bucket: str, Tagging: dict) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-bucket-tagging --bucket {Bucket} "
|
||||
f"--tagging '{json.dumps(Tagging)}' --endpoint {S3_GATE}"
|
||||
f"--tagging '{json.dumps(Tagging)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -369,7 +374,7 @@ class AwsCliClient:
|
|||
def delete_bucket_tagging(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api delete-bucket-tagging --bucket {Bucket} "
|
||||
f"--endpoint {S3_GATE}"
|
||||
f"--endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -380,7 +385,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-retention --bucket {Bucket} --key {Key} "
|
||||
f"{version} --retention '{json.dumps(Retention, indent=4, sort_keys=True, default=str)}' --endpoint {S3_GATE}"
|
||||
f"{version} --retention '{json.dumps(Retention, indent=4, sort_keys=True, default=str)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -391,7 +396,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-legal-hold --bucket {Bucket} --key {Key} "
|
||||
f"{version} --legal-hold '{json.dumps(LegalHold)}' --endpoint {S3_GATE}"
|
||||
f"{version} --legal-hold '{json.dumps(LegalHold)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -407,7 +412,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-retention --bucket {Bucket} --key {Key} "
|
||||
f"{version} --retention '{json.dumps(Retention, indent=4, sort_keys=True, default=str)}' --endpoint {S3_GATE}"
|
||||
f"{version} --retention '{json.dumps(Retention, indent=4, sort_keys=True, default=str)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
if not BypassGovernanceRetention is None:
|
||||
cmd += " --bypass-governance-retention"
|
||||
|
@ -420,7 +425,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-legal-hold --bucket {Bucket} --key {Key} "
|
||||
f"{version} --legal-hold '{json.dumps(LegalHold)}' --endpoint {S3_GATE}"
|
||||
f"{version} --legal-hold '{json.dumps(LegalHold)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -428,7 +433,7 @@ class AwsCliClient:
|
|||
def put_object_tagging(self, Bucket: str, Key: str, Tagging: dict) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-tagging --bucket {Bucket} --key {Key} "
|
||||
f"--tagging '{json.dumps(Tagging)}' --endpoint {S3_GATE}"
|
||||
f"--tagging '{json.dumps(Tagging)}' --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -437,7 +442,7 @@ class AwsCliClient:
|
|||
version = f" --version-id {VersionId}" if VersionId else ""
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-object-tagging --bucket {Bucket} --key {Key} "
|
||||
f"{version} --endpoint {S3_GATE}"
|
||||
f"{version} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, REGULAR_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -445,7 +450,7 @@ class AwsCliClient:
|
|||
def delete_object_tagging(self, Bucket: str, Key: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api delete-object-tagging --bucket {Bucket} "
|
||||
f"--key {Key} --endpoint {S3_GATE}"
|
||||
f"--key {Key} --endpoint {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -460,7 +465,7 @@ class AwsCliClient:
|
|||
) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3 sync {dir_path} s3://{bucket_name} "
|
||||
f"--endpoint-url {S3_GATE}"
|
||||
f"--endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
if Metadata:
|
||||
cmd += f" --metadata"
|
||||
|
@ -481,7 +486,7 @@ class AwsCliClient:
|
|||
) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3 cp {dir_path} s3://{bucket_name} "
|
||||
f"--endpoint-url {S3_GATE} --recursive"
|
||||
f"--endpoint-url {self.s3gate_endpoint} --recursive"
|
||||
)
|
||||
if Metadata:
|
||||
cmd += f" --metadata"
|
||||
|
@ -495,7 +500,7 @@ class AwsCliClient:
|
|||
def create_multipart_upload(self, Bucket: str, Key: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api create-multipart-upload --bucket {Bucket} "
|
||||
f"--key {Key} --endpoint-url {S3_GATE}"
|
||||
f"--key {Key} --endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -503,7 +508,7 @@ class AwsCliClient:
|
|||
def list_multipart_uploads(self, Bucket: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api list-multipart-uploads --bucket {Bucket} "
|
||||
f"--endpoint-url {S3_GATE}"
|
||||
f"--endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -511,7 +516,7 @@ class AwsCliClient:
|
|||
def abort_multipart_upload(self, Bucket: str, Key: str, UploadId: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api abort-multipart-upload --bucket {Bucket} "
|
||||
f"--key {Key} --upload-id {UploadId} --endpoint-url {S3_GATE}"
|
||||
f"--key {Key} --upload-id {UploadId} --endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -520,7 +525,7 @@ class AwsCliClient:
|
|||
cmd = (
|
||||
f"aws {self.common_flags} s3api upload-part --bucket {Bucket} --key {Key} "
|
||||
f"--upload-id {UploadId} --part-number {PartNumber} --body {Body} "
|
||||
f"--endpoint-url {S3_GATE}"
|
||||
f"--endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, LONG_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -531,7 +536,7 @@ class AwsCliClient:
|
|||
cmd = (
|
||||
f"aws {self.common_flags} s3api upload-part-copy --bucket {Bucket} --key {Key} "
|
||||
f"--upload-id {UploadId} --part-number {PartNumber} --copy-source {CopySource} "
|
||||
f"--endpoint-url {S3_GATE}"
|
||||
f"--endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd, LONG_TIMEOUT)
|
||||
return self._to_json(output)
|
||||
|
@ -539,7 +544,7 @@ class AwsCliClient:
|
|||
def list_parts(self, UploadId: str, Bucket: str, Key: str) -> dict:
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api list-parts --bucket {Bucket} --key {Key} "
|
||||
f"--upload-id {UploadId} --endpoint-url {S3_GATE}"
|
||||
f"--upload-id {UploadId} --endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -555,7 +560,7 @@ class AwsCliClient:
|
|||
cmd = (
|
||||
f"aws {self.common_flags} s3api complete-multipart-upload --bucket {Bucket} "
|
||||
f"--key {Key} --upload-id {UploadId} --multipart-upload file://{file_path} "
|
||||
f"--endpoint-url {S3_GATE}"
|
||||
f"--endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -563,7 +568,7 @@ class AwsCliClient:
|
|||
def put_object_lock_configuration(self, Bucket, ObjectLockConfiguration):
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api put-object-lock-configuration --bucket {Bucket} "
|
||||
f"--object-lock-configuration '{json.dumps(ObjectLockConfiguration)}' --endpoint-url {S3_GATE}"
|
||||
f"--object-lock-configuration '{json.dumps(ObjectLockConfiguration)}' --endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
||||
|
@ -571,7 +576,7 @@ class AwsCliClient:
|
|||
def get_object_lock_configuration(self, Bucket):
|
||||
cmd = (
|
||||
f"aws {self.common_flags} s3api get-object-lock-configuration --bucket {Bucket} "
|
||||
f"--endpoint-url {S3_GATE}"
|
||||
f"--endpoint-url {self.s3gate_endpoint}"
|
||||
)
|
||||
output = _cmd_run(cmd)
|
||||
return self._to_json(output)
|
324
pytest_tests/helpers/cluster.py
Normal file
324
pytest_tests/helpers/cluster.py
Normal file
|
@ -0,0 +1,324 @@
|
|||
import random
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import data_formatters
|
||||
from neofs_testlib.blockchain import RPCClient
|
||||
from neofs_testlib.hosting import Host, Hosting
|
||||
from neofs_testlib.hosting.config import ServiceConfig
|
||||
from test_control import wait_for_success
|
||||
|
||||
|
||||
@dataclass
|
||||
class NodeBase:
|
||||
"""
|
||||
Represents a node of some underlying service
|
||||
"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
host: Host
|
||||
|
||||
def __init__(self, id, name, host) -> None:
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.host = host
|
||||
self.construct()
|
||||
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name
|
||||
|
||||
def __hash__(self):
|
||||
return id(self.name)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.label
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return self.name
|
||||
|
||||
@wait_for_success(60, 1)
|
||||
def start_service(self):
|
||||
self.host.start_service(self.name)
|
||||
|
||||
@wait_for_success(60, 1)
|
||||
def stop_service(self):
|
||||
self.host.stop_service(self.name)
|
||||
|
||||
def get_wallet_password(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.WALLET_PASSWORD)
|
||||
|
||||
def get_wallet_path(self) -> str:
|
||||
return self._get_attribute(
|
||||
_ConfigAttributes.LOCAL_WALLET_PATH,
|
||||
_ConfigAttributes.WALLET_PATH,
|
||||
)
|
||||
|
||||
def get_wallet_config_path(self):
|
||||
return self._get_attribute(
|
||||
_ConfigAttributes.LOCAL_WALLET_CONFIG,
|
||||
_ConfigAttributes.WALLET_CONFIG,
|
||||
)
|
||||
|
||||
def get_wallet_public_key(self):
|
||||
storage_wallet_path = self.get_wallet_path()
|
||||
storage_wallet_pass = self.get_wallet_password()
|
||||
return data_formatters.get_wallet_public_key(storage_wallet_path, storage_wallet_pass)
|
||||
|
||||
def _get_attribute(self, attribute_name: str, default_attribute_name: str = None) -> list[str]:
|
||||
config = self.host.get_service_config(self.name)
|
||||
if default_attribute_name:
|
||||
return config.attributes.get(
|
||||
attribute_name, config.attributes.get(default_attribute_name)
|
||||
)
|
||||
else:
|
||||
return config.attributes.get(attribute_name)
|
||||
|
||||
def _get_service_config(self) -> ServiceConfig:
|
||||
return self.host.get_service_config(self.name)
|
||||
|
||||
|
||||
class InnerRingNode(NodeBase):
|
||||
"""
|
||||
Class represents inner ring node in a cluster
|
||||
|
||||
Inner ring node is not always the same as physical host (or physical node, if you will):
|
||||
It can be service running in a container or on physical host
|
||||
For testing perspective, it's not relevant how it is actually running,
|
||||
since neofs network will still treat it as "node"
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class S3Gate(NodeBase):
|
||||
"""
|
||||
Class represents S3 gateway in a cluster
|
||||
"""
|
||||
|
||||
def get_endpoint(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.ENDPOINT)
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return f"{self.name}: {self.get_endpoint()}"
|
||||
|
||||
|
||||
class HTTPGate(NodeBase):
|
||||
"""
|
||||
Class represents HTTP gateway in a cluster
|
||||
"""
|
||||
|
||||
def get_endpoint(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.ENDPOINT)
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return f"{self.name}: {self.get_endpoint()}"
|
||||
|
||||
|
||||
class MorphChain(NodeBase):
|
||||
"""
|
||||
Class represents side-chain aka morph-chain consensus node in a cluster
|
||||
|
||||
Consensus node is not always the same as physical host (or physical node, if you will):
|
||||
It can be service running in a container or on physical host
|
||||
For testing perspective, it's not relevant how it is actually running,
|
||||
since neofs network will still treat it as "node"
|
||||
"""
|
||||
|
||||
rpc_client: RPCClient = None
|
||||
|
||||
def construct(self):
|
||||
self.rpc_client = RPCClient(self.get_endpoint())
|
||||
|
||||
def get_endpoint(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.ENDPOINT)
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return f"{self.name}: {self.get_endpoint()}"
|
||||
|
||||
|
||||
class MainChain(NodeBase):
|
||||
"""
|
||||
Class represents main-chain consensus node in a cluster
|
||||
|
||||
Consensus node is not always the same as physical host:
|
||||
It can be service running in a container or on physical host (or physical node, if you will):
|
||||
For testing perspective, it's not relevant how it is actually running,
|
||||
since neofs network will still treat it as "node"
|
||||
"""
|
||||
|
||||
rpc_client: RPCClient = None
|
||||
|
||||
def construct(self):
|
||||
self.rpc_client = RPCClient(self.get_endpoint())
|
||||
|
||||
def get_endpoint(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.ENDPOINT)
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return f"{self.name}: {self.get_endpoint()}"
|
||||
|
||||
|
||||
class StorageNode(NodeBase):
|
||||
"""
|
||||
Class represents storage node in a storage cluster
|
||||
|
||||
Storage node is not always the same as physical host:
|
||||
It can be service running in a container or on physical host (or physical node, if you will):
|
||||
For testing perspective, it's not relevant how it is actually running,
|
||||
since neofs network will still treat it as "node"
|
||||
"""
|
||||
|
||||
def get_rpc_endpoint(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.RPC_ENDPOINT)
|
||||
|
||||
def get_control_endpoint(self) -> str:
|
||||
return self._get_attribute(_ConfigAttributes.CONTROL_ENDPOINT)
|
||||
|
||||
def get_un_locode(self):
|
||||
return self._get_attribute(_ConfigAttributes.UN_LOCODE)
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return f"{self.name}: {self.get_rpc_endpoint()}"
|
||||
|
||||
|
||||
class Cluster:
|
||||
"""
|
||||
This class represents a Cluster object for the whole storage based on provided hosting
|
||||
"""
|
||||
|
||||
default_rpc_endpoint: str
|
||||
default_s3_gate_endpoint: str
|
||||
|
||||
def __init__(self, hosting: Hosting) -> None:
|
||||
self._hosting = hosting
|
||||
self.default_rpc_endpoint = self.storage_nodes[0].get_rpc_endpoint()
|
||||
self.default_s3_gate_endpoint = self.s3gates[0].get_endpoint()
|
||||
self.default_http_gate_endpoint = self.http_gates[0].get_endpoint()
|
||||
|
||||
@property
|
||||
def hosts(self) -> list[Host]:
|
||||
"""
|
||||
Returns list of Hosts
|
||||
"""
|
||||
return self._hosting.hosts
|
||||
|
||||
@property
|
||||
def hosting(self) -> Hosting:
|
||||
return self._hosting
|
||||
|
||||
@property
|
||||
def storage_nodes(self) -> list[StorageNode]:
|
||||
"""
|
||||
Returns list of Storage Nodes (not physical nodes)
|
||||
"""
|
||||
return self._get_nodes(_ServicesNames.STORAGE)
|
||||
|
||||
@property
|
||||
def s3gates(self) -> list[S3Gate]:
|
||||
"""
|
||||
Returns list of S3 gates
|
||||
"""
|
||||
return self._get_nodes(_ServicesNames.S3_GATE)
|
||||
|
||||
@property
|
||||
def http_gates(self) -> list[S3Gate]:
|
||||
"""
|
||||
Returns list of HTTP gates
|
||||
"""
|
||||
return self._get_nodes(_ServicesNames.HTTP_GATE)
|
||||
|
||||
@property
|
||||
def morph_chain_nodes(self) -> list[MorphChain]:
|
||||
"""
|
||||
Returns list of morph-chain consensus nodes (not physical nodes)
|
||||
"""
|
||||
return self._get_nodes(_ServicesNames.MORPH_CHAIN)
|
||||
|
||||
@property
|
||||
def main_chain_nodes(self) -> list[MainChain]:
|
||||
"""
|
||||
Returns list of main-chain consensus nodes (not physical nodes)
|
||||
"""
|
||||
return self._get_nodes(_ServicesNames.MAIN_CHAIN)
|
||||
|
||||
@property
|
||||
def ir_nodes(self) -> list[InnerRingNode]:
|
||||
"""
|
||||
Returns list of inner-ring nodes (not physical nodes)
|
||||
"""
|
||||
return self._get_nodes(_ServicesNames.INNER_RING)
|
||||
|
||||
def _get_nodes(self, service_name) -> list[StorageNode]:
|
||||
configs = self.hosting.find_service_configs(f"{service_name}\d*$")
|
||||
|
||||
class_mapping: dict[str, Any] = {
|
||||
_ServicesNames.STORAGE: StorageNode,
|
||||
_ServicesNames.INNER_RING: InnerRingNode,
|
||||
_ServicesNames.MORPH_CHAIN: MorphChain,
|
||||
_ServicesNames.S3_GATE: S3Gate,
|
||||
_ServicesNames.HTTP_GATE: HTTPGate,
|
||||
_ServicesNames.MAIN_CHAIN: MainChain,
|
||||
}
|
||||
|
||||
cls = class_mapping.get(service_name)
|
||||
return [
|
||||
cls(
|
||||
self._get_id(config.name),
|
||||
config.name,
|
||||
self.hosting.get_host_by_service(config.name),
|
||||
)
|
||||
for config in configs
|
||||
]
|
||||
|
||||
def _get_id(self, node_name) -> str:
|
||||
pattern = "\d*$"
|
||||
|
||||
matches = re.search(pattern, node_name)
|
||||
if matches:
|
||||
return int(matches.group())
|
||||
|
||||
def get_random_storage_rpc_endpoint(self) -> str:
|
||||
return random.choice(self.get_storage_rpc_endpoints())
|
||||
|
||||
def get_storage_rpc_endpoints(self) -> list[str]:
|
||||
nodes = self.storage_nodes
|
||||
return [node.get_rpc_endpoint() for node in nodes]
|
||||
|
||||
def get_morph_endpoints(self) -> list[str]:
|
||||
nodes = self.morph_chain_nodes
|
||||
return [node.get_endpoint() for node in nodes]
|
||||
|
||||
|
||||
class _ServicesNames:
|
||||
STORAGE = "s"
|
||||
S3_GATE = "s3-gate"
|
||||
HTTP_GATE = "http-gate"
|
||||
MORPH_CHAIN = "morph-chain"
|
||||
INNER_RING = "ir"
|
||||
MAIN_CHAIN = "main-chain"
|
||||
|
||||
|
||||
class _ConfigAttributes:
|
||||
WALLET_PASSWORD = "wallet_password"
|
||||
WALLET_PATH = "wallet_path"
|
||||
WALLET_CONFIG = "wallet_config"
|
||||
LOCAL_WALLET_PATH = "local_wallet_path"
|
||||
LOCAL_WALLET_CONFIG = "local_config_path"
|
||||
RPC_ENDPOINT = "rpc_endpoint"
|
||||
ENDPOINT = "endpoint"
|
||||
CONTROL_ENDPOINT = "control_endpoint"
|
||||
UN_LOCODE = "un_locode"
|
|
@ -2,9 +2,10 @@ from dataclasses import dataclass
|
|||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from cluster import Cluster
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_verbs import put_object
|
||||
from neofs_verbs import put_object_to_random_node
|
||||
from storage_object import StorageObjectInfo
|
||||
from wallet import WalletFile
|
||||
|
||||
|
@ -16,9 +17,15 @@ class StorageContainerInfo:
|
|||
|
||||
|
||||
class StorageContainer:
|
||||
def __init__(self, storage_container_info: StorageContainerInfo, shell: Shell) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
storage_container_info: StorageContainerInfo,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
) -> None:
|
||||
self.shell = shell
|
||||
self.storage_container_info = storage_container_info
|
||||
self.cluster = cluster
|
||||
|
||||
def get_id(self) -> str:
|
||||
return self.storage_container_info.id
|
||||
|
@ -36,12 +43,13 @@ class StorageContainer:
|
|||
wallet_path = self.get_wallet_path()
|
||||
|
||||
with allure.step(f"Put object with size {size} to container {container_id}"):
|
||||
object_id = put_object(
|
||||
object_id = put_object_to_random_node(
|
||||
wallet=wallet_path,
|
||||
path=file_path,
|
||||
cid=container_id,
|
||||
expire_at=expire_at,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
storage_object = StorageObjectInfo(
|
||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from cluster import Cluster
|
||||
from common import FREE_STORAGE, WALLET_PASS
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet, init_wallet
|
||||
|
@ -25,9 +26,10 @@ class WalletFile:
|
|||
|
||||
|
||||
class WalletFactory:
|
||||
def __init__(self, wallets_dir: str, shell: Shell) -> None:
|
||||
def __init__(self, wallets_dir: str, shell: Shell, cluster: Cluster) -> None:
|
||||
self.shell = shell
|
||||
self.wallets_dir = wallets_dir
|
||||
self.cluster = cluster
|
||||
|
||||
def create_wallet(self, password: str = WALLET_PASS) -> WalletFile:
|
||||
"""
|
||||
|
@ -40,17 +42,21 @@ class WalletFactory:
|
|||
"""
|
||||
wallet_path = os.path.join(self.wallets_dir, f"{str(uuid.uuid4())}.json")
|
||||
init_wallet(wallet_path, password)
|
||||
|
||||
if not FREE_STORAGE:
|
||||
main_chain = self.cluster.main_chain_nodes[0]
|
||||
deposit = 30
|
||||
transfer_gas(
|
||||
shell=self.shell,
|
||||
amount=deposit + 1,
|
||||
main_chain=main_chain,
|
||||
wallet_to_path=wallet_path,
|
||||
wallet_to_password=password,
|
||||
)
|
||||
deposit_gas(
|
||||
shell=self.shell,
|
||||
amount=deposit,
|
||||
main_chain=main_chain,
|
||||
wallet_from_path=wallet_path,
|
||||
wallet_from_password=password,
|
||||
)
|
||||
|
|
25
pytest_tests/steps/cluster_test_base.py
Normal file
25
pytest_tests/steps/cluster_test_base.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import epoch
|
||||
import pytest
|
||||
from cluster import Cluster
|
||||
from neofs_testlib.shell import Shell
|
||||
|
||||
|
||||
# To skip adding every mandatory singleton dependency to EACH test function
|
||||
class ClusterTestBase:
|
||||
shell: Shell
|
||||
cluster: Cluster
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def fill_mandatory_dependencies(self, cluster: Cluster, client_shell: Shell):
|
||||
ClusterTestBase.shell = client_shell
|
||||
ClusterTestBase.cluster = cluster
|
||||
yield
|
||||
|
||||
def tick_epoch(self):
|
||||
epoch.tick_epoch(self.shell, self.cluster)
|
||||
|
||||
def get_epoch(self):
|
||||
return epoch.get_epoch(self.shell, self.cluster)
|
||||
|
||||
def ensure_fresh_epoch(self):
|
||||
return epoch.ensure_fresh_epoch(self.shell, self.cluster)
|
|
@ -3,24 +3,25 @@ import logging
|
|||
import os
|
||||
import re
|
||||
import uuid
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
import allure
|
||||
import boto3
|
||||
import pytest
|
||||
import s3_gate_bucket
|
||||
import s3_gate_object
|
||||
import urllib3
|
||||
from aws_cli_client import AwsCliClient
|
||||
from botocore.config import Config
|
||||
from botocore.exceptions import ClientError
|
||||
from cli_helpers import _cmd_run, _configure_aws_cli, _run_with_passwd
|
||||
from common import NEOFS_AUTHMATE_EXEC, NEOFS_ENDPOINT, S3_GATE, S3_GATE_WALLET_PATH
|
||||
from data_formatters import get_wallet_public_key
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from cluster import Cluster
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from common import NEOFS_AUTHMATE_EXEC
|
||||
from neofs_testlib.shell import Shell
|
||||
from pytest import FixtureRequest
|
||||
from python_keywords.container import list_containers
|
||||
|
||||
from steps import s3_gate_bucket, s3_gate_object
|
||||
from steps.aws_cli_client import AwsCliClient
|
||||
|
||||
# Disable warnings on self-signed certificate which the
|
||||
# boto library produces on requests to S3-gate in dev-env
|
||||
urllib3.disable_warnings()
|
||||
|
@ -34,13 +35,15 @@ MAX_REQUEST_ATTEMPTS = 1
|
|||
RETRY_MODE = "standard"
|
||||
|
||||
|
||||
class TestS3GateBase:
|
||||
s3_client = None
|
||||
class TestS3GateBase(ClusterTestBase):
|
||||
s3_client: Any = None
|
||||
|
||||
@pytest.fixture(scope="class", autouse=True)
|
||||
@allure.title("[Class/Autouse]: Create S3 client")
|
||||
def s3_client(self, prepare_wallet_and_deposit, client_shell: Shell, request, hosting: Hosting):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
def s3_client(
|
||||
self, default_wallet, client_shell: Shell, request: FixtureRequest, cluster: Cluster
|
||||
) -> Any:
|
||||
wallet = default_wallet
|
||||
s3_bearer_rules_file = f"{os.getcwd()}/robot/resources/files/s3_bearer_rules.json"
|
||||
policy = None if isinstance(request.param, str) else request.param[1]
|
||||
(
|
||||
|
@ -49,14 +52,20 @@ class TestS3GateBase:
|
|||
access_key_id,
|
||||
secret_access_key,
|
||||
owner_private_key,
|
||||
) = init_s3_credentials(wallet, hosting, s3_bearer_rules_file=s3_bearer_rules_file)
|
||||
containers_list = list_containers(wallet, shell=client_shell)
|
||||
) = init_s3_credentials(wallet, cluster, s3_bearer_rules_file=s3_bearer_rules_file)
|
||||
containers_list = list_containers(
|
||||
wallet, shell=client_shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
assert cid in containers_list, f"Expected cid {cid} in {containers_list}"
|
||||
|
||||
if "aws cli" in request.param:
|
||||
client = configure_cli_client(access_key_id, secret_access_key)
|
||||
client = configure_cli_client(
|
||||
access_key_id, secret_access_key, cluster.default_s3_gate_endpoint
|
||||
)
|
||||
else:
|
||||
client = configure_boto3_client(access_key_id, secret_access_key)
|
||||
client = configure_boto3_client(
|
||||
access_key_id, secret_access_key, cluster.default_s3_gate_endpoint
|
||||
)
|
||||
TestS3GateBase.s3_client = client
|
||||
TestS3GateBase.wallet = wallet
|
||||
|
||||
|
@ -93,27 +102,22 @@ class TestS3GateBase:
|
|||
s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket)
|
||||
|
||||
|
||||
def get_wallet_password(hosting: Hosting, s3_service_name: str) -> str:
|
||||
service_config = hosting.get_service_config(s3_service_name)
|
||||
return service_config.attributes.get("wallet_password")
|
||||
|
||||
|
||||
@allure.step("Init S3 Credentials")
|
||||
def init_s3_credentials(
|
||||
wallet_path: str,
|
||||
hosting: Hosting,
|
||||
cluster: Cluster,
|
||||
s3_bearer_rules_file: Optional[str] = None,
|
||||
policy: Optional[dict] = None,
|
||||
s3_service_name: str = "s3-gate01",
|
||||
):
|
||||
bucket = str(uuid.uuid4())
|
||||
s3_bearer_rules = s3_bearer_rules_file or "robot/resources/files/s3_bearer_rules.json"
|
||||
s3_password = get_wallet_password(hosting, s3_service_name)
|
||||
gate_public_key = get_wallet_public_key(S3_GATE_WALLET_PATH, s3_password)
|
||||
|
||||
s3gate_node = cluster.s3gates[0]
|
||||
gate_public_key = s3gate_node.get_wallet_public_key()
|
||||
cmd = (
|
||||
f"{NEOFS_AUTHMATE_EXEC} --debug --with-log --timeout {CREDENTIALS_CREATE_TIMEOUT} "
|
||||
f"issue-secret --wallet {wallet_path} --gate-public-key={gate_public_key} "
|
||||
f"--peer {NEOFS_ENDPOINT} --container-friendly-name {bucket} "
|
||||
f"--peer {cluster.default_rpc_endpoint} --container-friendly-name {bucket} "
|
||||
f"--bearer-rules {s3_bearer_rules}"
|
||||
)
|
||||
if policy:
|
||||
|
@ -148,9 +152,9 @@ def init_s3_credentials(
|
|||
|
||||
|
||||
@allure.step("Configure S3 client (boto3)")
|
||||
def configure_boto3_client(access_key_id: str, secret_access_key: str):
|
||||
def configure_boto3_client(access_key_id: str, secret_access_key: str, s3gate_endpoint: str):
|
||||
try:
|
||||
session = boto3.session.Session()
|
||||
session = boto3.Session()
|
||||
config = Config(
|
||||
retries={
|
||||
"max_attempts": MAX_REQUEST_ATTEMPTS,
|
||||
|
@ -163,7 +167,7 @@ def configure_boto3_client(access_key_id: str, secret_access_key: str):
|
|||
aws_access_key_id=access_key_id,
|
||||
aws_secret_access_key=secret_access_key,
|
||||
config=config,
|
||||
endpoint_url=S3_GATE,
|
||||
endpoint_url=s3gate_endpoint,
|
||||
verify=False,
|
||||
)
|
||||
return s3_client
|
||||
|
@ -175,9 +179,9 @@ def configure_boto3_client(access_key_id: str, secret_access_key: str):
|
|||
|
||||
|
||||
@allure.step("Configure S3 client (aws cli)")
|
||||
def configure_cli_client(access_key_id: str, secret_access_key: str):
|
||||
def configure_cli_client(access_key_id: str, secret_access_key: str, s3gate_endpoint: str):
|
||||
try:
|
||||
client = AwsCliClient()
|
||||
client = AwsCliClient(s3gate_endpoint)
|
||||
_configure_aws_cli("aws configure", access_key_id, secret_access_key)
|
||||
_cmd_run(f"aws configure set max_attempts {MAX_REQUEST_ATTEMPTS}")
|
||||
_cmd_run(f"aws configure set retry_mode {RETRY_MODE}")
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
import urllib3
|
||||
from aws_cli_client import AwsCliClient
|
||||
from botocore.exceptions import ClientError
|
||||
from cli_helpers import log_command_execution
|
||||
|
||||
from steps.aws_cli_client import AwsCliClient
|
||||
from steps.s3_gate_bucket import S3_SYNC_WAIT_TIME
|
||||
from s3_gate_bucket import S3_SYNC_WAIT_TIME
|
||||
|
||||
##########################################################
|
||||
# Disabling warnings on self-signed certificate which the
|
||||
|
|
|
@ -9,7 +9,7 @@ from typing import Any, Optional
|
|||
|
||||
import allure
|
||||
import json_transformers
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, WALLET_CONFIG
|
||||
from data_formatters import get_wallet_public_key
|
||||
from json_transformers import encode_for_json
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
|
@ -180,7 +180,7 @@ def generate_object_session_token(
|
|||
)
|
||||
|
||||
|
||||
@allure.step("Get signed token for object session")
|
||||
@allure.step("Get signed token for container session")
|
||||
def get_container_signed_token(
|
||||
owner_wallet: WalletFile,
|
||||
user_wallet: WalletFile,
|
||||
|
@ -190,7 +190,7 @@ def get_container_signed_token(
|
|||
lifetime: Optional[Lifetime] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Returns signed token file path for static object session
|
||||
Returns signed token file path for static container session
|
||||
"""
|
||||
session_token_file = generate_container_session_token(
|
||||
owner_wallet=owner_wallet,
|
||||
|
@ -235,7 +235,7 @@ def create_session_token(
|
|||
owner: str,
|
||||
wallet_path: str,
|
||||
wallet_password: str,
|
||||
rpc_endpoint: str = NEOFS_ENDPOINT,
|
||||
rpc_endpoint: str,
|
||||
) -> str:
|
||||
"""
|
||||
Create session token for an object.
|
||||
|
|
|
@ -3,6 +3,7 @@ from time import sleep
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from cluster import Cluster
|
||||
from epoch import tick_epoch
|
||||
from grpc_responses import OBJECT_ALREADY_REMOVED
|
||||
from neofs_testlib.shell import Shell
|
||||
|
@ -16,7 +17,9 @@ CLEANUP_TIMEOUT = 10
|
|||
|
||||
|
||||
@allure.step("Delete Objects")
|
||||
def delete_objects(storage_objects: list[StorageObjectInfo], shell: Shell) -> None:
|
||||
def delete_objects(
|
||||
storage_objects: list[StorageObjectInfo], shell: Shell, cluster: Cluster
|
||||
) -> None:
|
||||
"""
|
||||
Deletes given storage objects.
|
||||
|
||||
|
@ -28,7 +31,11 @@ def delete_objects(storage_objects: list[StorageObjectInfo], shell: Shell) -> No
|
|||
with allure.step("Delete objects"):
|
||||
for storage_object in storage_objects:
|
||||
storage_object.tombstone = delete_object(
|
||||
storage_object.wallet_file_path, storage_object.cid, storage_object.oid, shell
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell=shell,
|
||||
endpoint=cluster.default_rpc_endpoint,
|
||||
)
|
||||
verify_head_tombstone(
|
||||
wallet_path=storage_object.wallet_file_path,
|
||||
|
@ -36,9 +43,10 @@ def delete_objects(storage_objects: list[StorageObjectInfo], shell: Shell) -> No
|
|||
oid_ts=storage_object.tombstone,
|
||||
oid=storage_object.oid,
|
||||
shell=shell,
|
||||
endpoint=cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
tick_epoch(shell=shell)
|
||||
tick_epoch(shell, cluster)
|
||||
sleep(CLEANUP_TIMEOUT)
|
||||
|
||||
with allure.step("Get objects and check errors"):
|
||||
|
@ -49,4 +57,5 @@ def delete_objects(storage_objects: list[StorageObjectInfo], shell: Shell) -> No
|
|||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell=shell,
|
||||
endpoint=cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
import os
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Optional
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
from common import (
|
||||
ASSETS_DIR,
|
||||
IR_WALLET_CONFIG,
|
||||
IR_WALLET_PATH,
|
||||
STORAGE_WALLET_CONFIG,
|
||||
STORAGE_WALLET_PATH,
|
||||
WALLET_CONFIG,
|
||||
WALLET_PASS,
|
||||
)
|
||||
from cluster import Cluster
|
||||
from common import WALLET_CONFIG, WALLET_PASS
|
||||
from file_helper import generate_file
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from python_keywords.acl import EACLRole
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.neofs_verbs import put_object
|
||||
from python_keywords.neofs_verbs import put_object_to_random_node
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
OBJECT_COUNT = 5
|
||||
|
@ -32,35 +26,42 @@ class Wallet:
|
|||
|
||||
@dataclass
|
||||
class Wallets:
|
||||
wallets: Dict[EACLRole, List[Wallet]]
|
||||
wallets: dict[EACLRole, list[Wallet]]
|
||||
|
||||
def get_wallet(self, role: EACLRole = EACLRole.USER) -> Wallet:
|
||||
return self.wallets[role][0]
|
||||
|
||||
def get_wallets_list(self, role: EACLRole = EACLRole.USER) -> List[Wallet]:
|
||||
def get_wallets_list(self, role: EACLRole = EACLRole.USER) -> list[Wallet]:
|
||||
return self.wallets[role]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wallets(prepare_wallet_and_deposit):
|
||||
def wallets(default_wallet, temp_directory, cluster: Cluster) -> Wallets:
|
||||
other_wallets_paths = [
|
||||
os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json") for _ in range(2)
|
||||
os.path.join(temp_directory, f"{str(uuid.uuid4())}.json") for _ in range(2)
|
||||
]
|
||||
for other_wallet_path in other_wallets_paths:
|
||||
init_wallet(other_wallet_path, WALLET_PASS)
|
||||
|
||||
ir_node = cluster.ir_nodes[0]
|
||||
storage_node = cluster.storage_nodes[0]
|
||||
|
||||
ir_wallet_path = ir_node.get_wallet_path()
|
||||
ir_wallet_config = ir_node.get_wallet_config_path()
|
||||
|
||||
storage_wallet_path = storage_node.get_wallet_path()
|
||||
storage_wallet_config = storage_node.get_wallet_config_path()
|
||||
|
||||
yield Wallets(
|
||||
wallets={
|
||||
EACLRole.USER: [
|
||||
Wallet(wallet_path=prepare_wallet_and_deposit, config_path=WALLET_CONFIG)
|
||||
],
|
||||
EACLRole.USER: [Wallet(wallet_path=default_wallet, config_path=WALLET_CONFIG)],
|
||||
EACLRole.OTHERS: [
|
||||
Wallet(wallet_path=other_wallet_path, config_path=WALLET_CONFIG)
|
||||
for other_wallet_path in other_wallets_paths
|
||||
],
|
||||
EACLRole.SYSTEM: [
|
||||
Wallet(wallet_path=IR_WALLET_PATH, config_path=IR_WALLET_CONFIG),
|
||||
Wallet(wallet_path=STORAGE_WALLET_PATH, config_path=STORAGE_WALLET_CONFIG),
|
||||
Wallet(wallet_path=ir_wallet_path, config_path=ir_wallet_config),
|
||||
Wallet(wallet_path=storage_wallet_path, config_path=storage_wallet_config),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
@ -72,19 +73,27 @@ def file_path():
|
|||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def eacl_container_with_objects(wallets, client_shell, file_path):
|
||||
def eacl_container_with_objects(
|
||||
wallets: Wallets, client_shell: Shell, cluster: Cluster, file_path: str
|
||||
):
|
||||
user_wallet = wallets.get_wallet()
|
||||
with allure.step("Create eACL public container"):
|
||||
cid = create_container(user_wallet.wallet_path, basic_acl=PUBLIC_ACL, shell=client_shell)
|
||||
cid = create_container(
|
||||
user_wallet.wallet_path,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
shell=client_shell,
|
||||
endpoint=cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step("Add test objects to container"):
|
||||
objects_oids = [
|
||||
put_object(
|
||||
put_object_to_random_node(
|
||||
user_wallet.wallet_path,
|
||||
file_path,
|
||||
cid,
|
||||
attributes={"key1": "val1", "key": val, "key2": "abc"},
|
||||
shell=client_shell,
|
||||
cluster=cluster,
|
||||
)
|
||||
for val in range(OBJECT_COUNT)
|
||||
]
|
||||
|
|
|
@ -5,20 +5,10 @@ from typing import Optional
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from common import (
|
||||
ASSETS_DIR,
|
||||
COMPLEX_OBJ_SIZE,
|
||||
FREE_STORAGE,
|
||||
IR_WALLET_CONFIG,
|
||||
IR_WALLET_PASS,
|
||||
IR_WALLET_PATH,
|
||||
SIMPLE_OBJ_SIZE,
|
||||
WALLET_PASS,
|
||||
)
|
||||
from epoch import tick_epoch
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from common import ASSETS_DIR, COMPLEX_OBJ_SIZE, FREE_STORAGE, SIMPLE_OBJ_SIZE, WALLET_PASS
|
||||
from file_helper import generate_file
|
||||
from grpc_responses import OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from python_keywords.acl import (
|
||||
EACLAccess,
|
||||
|
@ -30,7 +20,7 @@ from python_keywords.acl import (
|
|||
set_eacl,
|
||||
)
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.neofs_verbs import put_object
|
||||
from python_keywords.neofs_verbs import put_object_to_random_node
|
||||
from python_keywords.payment_neogo import deposit_gas, transfer_gas
|
||||
from python_keywords.storage_group import (
|
||||
delete_storagegroup,
|
||||
|
@ -53,53 +43,59 @@ deposit = 30
|
|||
@pytest.mark.sanity
|
||||
@pytest.mark.acl
|
||||
@pytest.mark.storage_group
|
||||
class TestStorageGroup:
|
||||
class TestStorageGroup(ClusterTestBase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def prepare_two_wallets(self, prepare_wallet_and_deposit, client_shell):
|
||||
self.main_wallet = prepare_wallet_and_deposit
|
||||
def prepare_two_wallets(self, default_wallet):
|
||||
self.main_wallet = default_wallet
|
||||
self.other_wallet = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
|
||||
init_wallet(self.other_wallet, WALLET_PASS)
|
||||
if not FREE_STORAGE:
|
||||
main_chain = self.cluster.main_chain_nodes[0]
|
||||
deposit = 30
|
||||
transfer_gas(
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
amount=deposit + 1,
|
||||
main_chain=main_chain,
|
||||
wallet_to_path=self.other_wallet,
|
||||
wallet_to_password=WALLET_PASS,
|
||||
)
|
||||
deposit_gas(
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
amount=deposit,
|
||||
main_chain=main_chain,
|
||||
wallet_from_path=self.other_wallet,
|
||||
wallet_from_password=WALLET_PASS,
|
||||
)
|
||||
|
||||
@allure.title("Test Storage Group in Private Container")
|
||||
def test_storagegroup_basic_private_container(self, client_shell, object_size):
|
||||
cid = create_container(self.main_wallet, shell=client_shell)
|
||||
def test_storagegroup_basic_private_container(self, object_size):
|
||||
cid = create_container(
|
||||
self.main_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
oid = put_object_to_random_node(self.main_wallet, file_path, cid, self.shell, self.cluster)
|
||||
objects = [oid]
|
||||
storage_group = put_storagegroup(
|
||||
shell=client_shell, wallet=self.main_wallet, cid=cid, objects=objects
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
objects=objects,
|
||||
)
|
||||
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
)
|
||||
self.expect_failure_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.other_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
gid=storage_group,
|
||||
)
|
||||
self.storagegroup_operations_by_system_ro_container(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
|
@ -107,27 +103,31 @@ class TestStorageGroup:
|
|||
)
|
||||
|
||||
@allure.title("Test Storage Group in Public Container")
|
||||
def test_storagegroup_basic_public_container(self, client_shell, object_size):
|
||||
cid = create_container(self.main_wallet, basic_acl="public-read-write", shell=client_shell)
|
||||
def test_storagegroup_basic_public_container(self, object_size):
|
||||
cid = create_container(
|
||||
self.main_wallet,
|
||||
basic_acl="public-read-write",
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
oid = put_object_to_random_node(
|
||||
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
objects = [oid]
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
)
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.other_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
)
|
||||
self.storagegroup_operations_by_system_ro_container(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
|
@ -135,20 +135,25 @@ class TestStorageGroup:
|
|||
)
|
||||
|
||||
@allure.title("Test Storage Group in Read-Only Container")
|
||||
def test_storagegroup_basic_ro_container(self, client_shell, object_size):
|
||||
cid = create_container(self.main_wallet, basic_acl="public-read", shell=client_shell)
|
||||
def test_storagegroup_basic_ro_container(self, object_size):
|
||||
cid = create_container(
|
||||
self.main_wallet,
|
||||
basic_acl="public-read",
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
oid = put_object_to_random_node(
|
||||
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
objects = [oid]
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
)
|
||||
self.storagegroup_operations_by_other_ro_container(
|
||||
shell=client_shell,
|
||||
owner_wallet=self.main_wallet,
|
||||
other_wallet=self.other_wallet,
|
||||
cid=cid,
|
||||
|
@ -156,7 +161,6 @@ class TestStorageGroup:
|
|||
object_size=object_size,
|
||||
)
|
||||
self.storagegroup_operations_by_system_ro_container(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
|
@ -164,21 +168,27 @@ class TestStorageGroup:
|
|||
)
|
||||
|
||||
@allure.title("Test Storage Group with Bearer Allow")
|
||||
def test_storagegroup_bearer_allow(self, client_shell, object_size):
|
||||
def test_storagegroup_bearer_allow(self, object_size):
|
||||
cid = create_container(
|
||||
self.main_wallet, basic_acl="eacl-public-read-write", shell=client_shell
|
||||
self.main_wallet,
|
||||
basic_acl="eacl-public-read-write",
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
oid = put_object_to_random_node(
|
||||
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
objects = [oid]
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
)
|
||||
storage_group = put_storagegroup(client_shell, self.main_wallet, cid, objects)
|
||||
storage_group = put_storagegroup(
|
||||
self.shell, self.cluster.default_rpc_endpoint, self.main_wallet, cid, objects
|
||||
)
|
||||
eacl_deny = [
|
||||
EACLRule(access=EACLAccess.DENY, role=role, operation=op)
|
||||
for op in EACLOperation
|
||||
|
@ -187,11 +197,12 @@ class TestStorageGroup:
|
|||
set_eacl(
|
||||
self.main_wallet,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
self.expect_failure_for_storagegroup_operations(
|
||||
client_shell, self.main_wallet, cid, objects, storage_group
|
||||
self.main_wallet, cid, objects, storage_group
|
||||
)
|
||||
bearer_file = form_bearertoken_file(
|
||||
self.main_wallet,
|
||||
|
@ -200,10 +211,10 @@ class TestStorageGroup:
|
|||
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.USER)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
obj_list=objects,
|
||||
|
@ -212,24 +223,38 @@ class TestStorageGroup:
|
|||
)
|
||||
|
||||
@allure.title("Test to check Storage Group lifetime")
|
||||
def test_storagegroup_lifetime(self, client_shell, object_size):
|
||||
cid = create_container(self.main_wallet, shell=client_shell)
|
||||
def test_storagegroup_lifetime(self, object_size):
|
||||
cid = create_container(
|
||||
self.main_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
oid = put_object_to_random_node(
|
||||
self.main_wallet, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
objects = [oid]
|
||||
storage_group = put_storagegroup(client_shell, self.main_wallet, cid, objects, lifetime=1)
|
||||
storage_group = put_storagegroup(
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
self.main_wallet,
|
||||
cid,
|
||||
objects,
|
||||
lifetime=1,
|
||||
)
|
||||
with allure.step("Tick two epochs"):
|
||||
for _ in range(2):
|
||||
tick_epoch(shell=client_shell)
|
||||
self.tick_epoch()
|
||||
with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
|
||||
get_storagegroup(
|
||||
shell=client_shell, wallet=self.main_wallet, cid=cid, gid=storage_group
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Run Storage Group Operations And Expect Success")
|
||||
def expect_success_for_storagegroup_operations(
|
||||
shell: Shell,
|
||||
self,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
obj_list: list,
|
||||
|
@ -241,12 +266,20 @@ class TestStorageGroup:
|
|||
Put, List, Get and Delete the Storage Group which contains
|
||||
the Object.
|
||||
"""
|
||||
storage_group = put_storagegroup(shell, wallet, cid, obj_list, bearer)
|
||||
storage_group = put_storagegroup(
|
||||
self.shell, self.cluster.default_rpc_endpoint, wallet, cid, obj_list, bearer
|
||||
)
|
||||
verify_list_storage_group(
|
||||
shell=shell, wallet=wallet, cid=cid, gid=storage_group, bearer=bearer
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
bearer=bearer,
|
||||
)
|
||||
verify_get_storage_group(
|
||||
shell=shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
|
@ -254,12 +287,18 @@ class TestStorageGroup:
|
|||
object_size=object_size,
|
||||
bearer=bearer,
|
||||
)
|
||||
delete_storagegroup(shell=shell, wallet=wallet, cid=cid, gid=storage_group, bearer=bearer)
|
||||
delete_storagegroup(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
bearer=bearer,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Run Storage Group Operations And Expect Failure")
|
||||
def expect_failure_for_storagegroup_operations(
|
||||
shell: Shell, wallet: str, cid: str, obj_list: list, gid: str
|
||||
self, wallet: str, cid: str, obj_list: list, gid: str
|
||||
):
|
||||
"""
|
||||
This func verifies if the Object's owner isn't allowed to
|
||||
|
@ -267,30 +306,64 @@ class TestStorageGroup:
|
|||
the Object.
|
||||
"""
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
put_storagegroup(shell=shell, wallet=wallet, cid=cid, objects=obj_list)
|
||||
put_storagegroup(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
objects=obj_list,
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
list_storagegroup(shell=shell, wallet=wallet, cid=cid)
|
||||
list_storagegroup(
|
||||
shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, wallet=wallet, cid=cid
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
get_storagegroup(shell=shell, wallet=wallet, cid=cid, gid=gid)
|
||||
get_storagegroup(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
gid=gid,
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
delete_storagegroup(shell=shell, wallet=wallet, cid=cid, gid=gid)
|
||||
delete_storagegroup(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
gid=gid,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Run Storage Group Operations On Other's Behalf In RO Container")
|
||||
def storagegroup_operations_by_other_ro_container(
|
||||
shell: Shell,
|
||||
self,
|
||||
owner_wallet: str,
|
||||
other_wallet: str,
|
||||
cid: str,
|
||||
obj_list: list,
|
||||
object_size: int,
|
||||
):
|
||||
storage_group = put_storagegroup(shell, owner_wallet, cid, obj_list)
|
||||
storage_group = put_storagegroup(
|
||||
self.shell, self.cluster.default_rpc_endpoint, owner_wallet, cid, obj_list
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
put_storagegroup(shell=shell, wallet=other_wallet, cid=cid, objects=obj_list)
|
||||
verify_list_storage_group(shell=shell, wallet=other_wallet, cid=cid, gid=storage_group)
|
||||
put_storagegroup(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=other_wallet,
|
||||
cid=cid,
|
||||
objects=obj_list,
|
||||
)
|
||||
verify_list_storage_group(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=other_wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
)
|
||||
verify_get_storage_group(
|
||||
shell=shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet=other_wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
|
@ -298,56 +371,81 @@ class TestStorageGroup:
|
|||
object_size=object_size,
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
delete_storagegroup(shell=shell, wallet=other_wallet, cid=cid, gid=storage_group)
|
||||
delete_storagegroup(
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=other_wallet,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Run Storage Group Operations On Systems's Behalf In RO Container")
|
||||
def storagegroup_operations_by_system_ro_container(
|
||||
shell: Shell, wallet: str, cid: str, obj_list: list, object_size: int
|
||||
self, wallet: str, cid: str, obj_list: list, object_size: int
|
||||
):
|
||||
"""
|
||||
In this func we create a Storage Group on Inner Ring's key behalf
|
||||
and include an Object created on behalf of some user. We expect
|
||||
that System key is granted to make all operations except PUT and DELETE.
|
||||
"""
|
||||
ir_node = self.cluster.ir_nodes[0]
|
||||
ir_wallet_path = ir_node.get_wallet_path()
|
||||
ir_wallet_password = ir_node.get_wallet_password()
|
||||
ir_wallet_config = ir_node.get_wallet_config_path()
|
||||
|
||||
if not FREE_STORAGE:
|
||||
main_chain = self.cluster.main_chain_nodes[0]
|
||||
deposit = 30
|
||||
transfer_gas(
|
||||
shell=shell,
|
||||
shell=self.shell,
|
||||
amount=deposit + 1,
|
||||
wallet_to_path=IR_WALLET_PATH,
|
||||
wallet_to_password=IR_WALLET_PASS,
|
||||
main_chain=main_chain,
|
||||
wallet_to_path=ir_wallet_path,
|
||||
wallet_to_password=ir_wallet_password,
|
||||
)
|
||||
deposit_gas(
|
||||
shell=shell,
|
||||
shell=self.shell,
|
||||
amount=deposit,
|
||||
wallet_from_path=IR_WALLET_PATH,
|
||||
wallet_from_password=IR_WALLET_PASS,
|
||||
main_chain=main_chain,
|
||||
wallet_from_path=ir_wallet_path,
|
||||
wallet_from_password=ir_wallet_password,
|
||||
)
|
||||
storage_group = put_storagegroup(shell, wallet, cid, obj_list)
|
||||
storage_group = put_storagegroup(
|
||||
self.shell, self.cluster.default_rpc_endpoint, wallet, cid, obj_list
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
put_storagegroup(shell, IR_WALLET_PATH, cid, obj_list, wallet_config=IR_WALLET_CONFIG)
|
||||
put_storagegroup(
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
ir_wallet_path,
|
||||
cid,
|
||||
obj_list,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
verify_list_storage_group(
|
||||
shell=shell,
|
||||
wallet=IR_WALLET_PATH,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=ir_wallet_path,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
wallet_config=IR_WALLET_CONFIG,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
verify_get_storage_group(
|
||||
shell=shell,
|
||||
wallet=IR_WALLET_PATH,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet=ir_wallet_path,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
obj_list=obj_list,
|
||||
object_size=object_size,
|
||||
wallet_config=IR_WALLET_CONFIG,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
delete_storagegroup(
|
||||
shell=shell,
|
||||
wallet=IR_WALLET_PATH,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=ir_wallet_path,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
wallet_config=IR_WALLET_CONFIG,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import allure
|
||||
import pytest
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from python_keywords.acl import EACLRole
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.container_access import (
|
||||
|
@ -7,7 +8,7 @@ from python_keywords.container_access import (
|
|||
check_no_access_to_container,
|
||||
check_read_only_container,
|
||||
)
|
||||
from python_keywords.neofs_verbs import put_object
|
||||
from python_keywords.neofs_verbs import put_object_to_random_node
|
||||
from wellknown_acl import PRIVATE_ACL_F, PUBLIC_ACL_F, READONLY_ACL_F
|
||||
|
||||
|
||||
|
@ -15,13 +16,16 @@ from wellknown_acl import PRIVATE_ACL_F, PUBLIC_ACL_F, READONLY_ACL_F
|
|||
@pytest.mark.smoke
|
||||
@pytest.mark.acl
|
||||
@pytest.mark.acl_basic
|
||||
class TestACLBasic:
|
||||
class TestACLBasic(ClusterTestBase):
|
||||
@pytest.fixture(scope="function")
|
||||
def public_container(self, client_shell, wallets):
|
||||
def public_container(self, wallets):
|
||||
user_wallet = wallets.get_wallet()
|
||||
with allure.step("Create public container"):
|
||||
cid_public = create_container(
|
||||
user_wallet.wallet_path, basic_acl=PUBLIC_ACL_F, shell=client_shell
|
||||
user_wallet.wallet_path,
|
||||
basic_acl=PUBLIC_ACL_F,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
yield cid_public
|
||||
|
@ -30,11 +34,14 @@ class TestACLBasic:
|
|||
# delete_container(user_wallet.wallet_path, cid_public)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def private_container(self, client_shell, wallets):
|
||||
def private_container(self, wallets):
|
||||
user_wallet = wallets.get_wallet()
|
||||
with allure.step("Create private container"):
|
||||
cid_private = create_container(
|
||||
user_wallet.wallet_path, basic_acl=PRIVATE_ACL_F, shell=client_shell
|
||||
user_wallet.wallet_path,
|
||||
basic_acl=PRIVATE_ACL_F,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
yield cid_private
|
||||
|
@ -43,11 +50,14 @@ class TestACLBasic:
|
|||
# delete_container(user_wallet.wallet_path, cid_private)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def read_only_container(self, client_shell, wallets):
|
||||
def read_only_container(self, wallets):
|
||||
user_wallet = wallets.get_wallet()
|
||||
with allure.step("Create public readonly container"):
|
||||
cid_read_only = create_container(
|
||||
user_wallet.wallet_path, basic_acl=READONLY_ACL_F, shell=client_shell
|
||||
user_wallet.wallet_path,
|
||||
basic_acl=READONLY_ACL_F,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
yield cid_read_only
|
||||
|
@ -56,7 +66,7 @@ class TestACLBasic:
|
|||
# delete_container(user_wallet.wallet_path, cid_read_only)
|
||||
|
||||
@allure.title("Test basic ACL on public container")
|
||||
def test_basic_acl_public(self, wallets, client_shell, public_container, file_path):
|
||||
def test_basic_acl_public(self, wallets, public_container, file_path):
|
||||
"""
|
||||
Test basic ACL set during public container creation.
|
||||
"""
|
||||
|
@ -67,30 +77,42 @@ class TestACLBasic:
|
|||
with allure.step("Add test objects to container"):
|
||||
# We create new objects for each wallet because check_full_access_to_container
|
||||
# deletes the object
|
||||
owner_object_oid = put_object(
|
||||
owner_object_oid = put_object_to_random_node(
|
||||
user_wallet.wallet_path,
|
||||
file_path,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes={"created": "owner"},
|
||||
)
|
||||
other_object_oid = put_object(
|
||||
other_object_oid = put_object_to_random_node(
|
||||
other_wallet.wallet_path,
|
||||
file_path,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes={"created": "other"},
|
||||
)
|
||||
with allure.step(f"Check {desc} has full access to public container"):
|
||||
check_full_access_to_container(
|
||||
wallet.wallet_path, cid, owner_object_oid, file_path, shell=client_shell
|
||||
wallet.wallet_path,
|
||||
cid,
|
||||
owner_object_oid,
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
check_full_access_to_container(
|
||||
wallet.wallet_path, cid, other_object_oid, file_path, shell=client_shell
|
||||
wallet.wallet_path,
|
||||
cid,
|
||||
other_object_oid,
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
@allure.title("Test basic ACL on private container")
|
||||
def test_basic_acl_private(self, wallets, client_shell, private_container, file_path):
|
||||
def test_basic_acl_private(self, wallets, private_container, file_path):
|
||||
"""
|
||||
Test basic ACL set during private container creation.
|
||||
"""
|
||||
|
@ -98,19 +120,29 @@ class TestACLBasic:
|
|||
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
|
||||
cid = private_container
|
||||
with allure.step("Add test objects to container"):
|
||||
owner_object_oid = put_object(
|
||||
user_wallet.wallet_path, file_path, cid, shell=client_shell
|
||||
owner_object_oid = put_object_to_random_node(
|
||||
user_wallet.wallet_path, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
with allure.step("Check only owner has full access to private container"):
|
||||
with allure.step("Check no one except owner has access to operations with container"):
|
||||
check_no_access_to_container(
|
||||
other_wallet.wallet_path, cid, owner_object_oid, file_path, shell=client_shell
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
owner_object_oid,
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step("Check owner has full access to private container"):
|
||||
check_full_access_to_container(
|
||||
user_wallet.wallet_path, cid, owner_object_oid, file_path, shell=client_shell
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
owner_object_oid,
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
@allure.title("Test basic ACL on readonly container")
|
||||
|
@ -123,14 +155,26 @@ class TestACLBasic:
|
|||
cid = read_only_container
|
||||
|
||||
with allure.step("Add test objects to container"):
|
||||
object_oid = put_object(user_wallet.wallet_path, file_path, cid, shell=client_shell)
|
||||
object_oid = put_object_to_random_node(
|
||||
user_wallet.wallet_path, file_path, cid, shell=client_shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
with allure.step("Check other has read-only access to operations with container"):
|
||||
check_read_only_container(
|
||||
other_wallet.wallet_path, cid, object_oid, file_path, shell=client_shell
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
object_oid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step("Check owner has full access to public container"):
|
||||
check_full_access_to_container(
|
||||
user_wallet.wallet_path, cid, object_oid, file_path, shell=client_shell
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
object_oid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import allure
|
||||
import pytest
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from python_keywords.acl import (
|
||||
EACLAccess,
|
||||
EACLOperation,
|
||||
|
@ -20,15 +21,14 @@ from python_keywords.container_access import (
|
|||
@pytest.mark.sanity
|
||||
@pytest.mark.acl
|
||||
@pytest.mark.acl_bearer
|
||||
class TestACLBearer:
|
||||
class TestACLBearer(ClusterTestBase):
|
||||
@pytest.mark.parametrize("role", [EACLRole.USER, EACLRole.OTHERS])
|
||||
def test_bearer_token_operations(
|
||||
self, wallets, client_shell, eacl_container_with_objects, role
|
||||
):
|
||||
def test_bearer_token_operations(self, wallets, eacl_container_with_objects, role):
|
||||
allure.dynamic.title(f"Testcase to validate NeoFS operations with {role.value} BearerToken")
|
||||
cid, objects_oids, file_path = eacl_container_with_objects
|
||||
user_wallet = wallets.get_wallet()
|
||||
deny_wallet = wallets.get_wallet(role)
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
|
||||
with allure.step(f"Check {role.value} has full access to container without bearer token"):
|
||||
check_full_access_to_container(
|
||||
|
@ -37,15 +37,16 @@ class TestACLBearer:
|
|||
objects_oids.pop(),
|
||||
file_path,
|
||||
wallet_config=deny_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(f"Set deny all operations for {role.value} via eACL"):
|
||||
eacl = [
|
||||
EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in EACLOperation
|
||||
]
|
||||
eacl_file = create_eacl(cid, eacl, shell=client_shell)
|
||||
set_eacl(user_wallet.wallet_path, cid, eacl_file, shell=client_shell)
|
||||
eacl_file = create_eacl(cid, eacl, shell=self.shell)
|
||||
set_eacl(user_wallet.wallet_path, cid, eacl_file, shell=self.shell, endpoint=endpoint)
|
||||
wait_for_cache_expired()
|
||||
|
||||
with allure.step(f"Create bearer token for {role.value} with all operations allowed"):
|
||||
|
@ -56,7 +57,8 @@ class TestACLBearer:
|
|||
EACLRule(operation=op, access=EACLAccess.ALLOW, role=role)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step(
|
||||
|
@ -68,7 +70,8 @@ class TestACLBearer:
|
|||
objects_oids.pop(),
|
||||
file_path,
|
||||
wallet_config=deny_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(
|
||||
|
@ -81,15 +84,16 @@ class TestACLBearer:
|
|||
file_path,
|
||||
bearer=bearer,
|
||||
wallet_config=deny_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(f"Set allow all operations for {role.value} via eACL"):
|
||||
eacl = [
|
||||
EACLRule(access=EACLAccess.ALLOW, role=role, operation=op) for op in EACLOperation
|
||||
]
|
||||
eacl_file = create_eacl(cid, eacl, shell=client_shell)
|
||||
set_eacl(user_wallet.wallet_path, cid, eacl_file, shell=client_shell)
|
||||
eacl_file = create_eacl(cid, eacl, shell=self.shell)
|
||||
set_eacl(user_wallet.wallet_path, cid, eacl_file, shell=self.shell, endpoint=endpoint)
|
||||
wait_for_cache_expired()
|
||||
|
||||
with allure.step(
|
||||
|
@ -101,13 +105,13 @@ class TestACLBearer:
|
|||
objects_oids.pop(),
|
||||
file_path,
|
||||
wallet_config=deny_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
@allure.title("BearerToken Operations for compound Operations")
|
||||
def test_bearer_token_compound_operations(
|
||||
self, wallets, client_shell, eacl_container_with_objects
|
||||
):
|
||||
def test_bearer_token_compound_operations(self, wallets, eacl_container_with_objects):
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
cid, objects_oids, file_path = eacl_container_with_objects
|
||||
user_wallet = wallets.get_wallet()
|
||||
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
|
||||
|
@ -153,8 +157,9 @@ class TestACLBearer:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
eacl_table_path=create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
eacl_table_path=create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -166,7 +171,8 @@ class TestACLBearer:
|
|||
file_path,
|
||||
deny_operations=deny_map[EACLRole.USER],
|
||||
wallet_config=user_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
check_custom_access_to_container(
|
||||
other_wallet.wallet_path,
|
||||
|
@ -175,7 +181,8 @@ class TestACLBearer:
|
|||
file_path,
|
||||
deny_operations=deny_map[EACLRole.OTHERS],
|
||||
wallet_config=other_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step("Check rule consistency using bearer token"):
|
||||
|
@ -186,7 +193,8 @@ class TestACLBearer:
|
|||
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.USER)
|
||||
for op in bearer_map[EACLRole.USER]
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
bearer_other = form_bearertoken_file(
|
||||
|
@ -196,7 +204,8 @@ class TestACLBearer:
|
|||
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS)
|
||||
for op in bearer_map[EACLRole.OTHERS]
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
check_custom_access_to_container(
|
||||
|
@ -207,7 +216,8 @@ class TestACLBearer:
|
|||
deny_operations=deny_map_with_bearer[EACLRole.USER],
|
||||
bearer=bearer_user,
|
||||
wallet_config=user_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
check_custom_access_to_container(
|
||||
other_wallet.wallet_path,
|
||||
|
@ -217,5 +227,6 @@ class TestACLBearer:
|
|||
deny_operations=deny_map_with_bearer[EACLRole.OTHERS],
|
||||
bearer=bearer_other,
|
||||
wallet_config=other_wallet.config_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import allure
|
||||
import pytest
|
||||
from common import NEOFS_NETMAP_DICT
|
||||
from failover_utils import wait_object_replication_on_nodes
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from failover_utils import wait_object_replication
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.acl import (
|
||||
EACLAccess,
|
||||
|
@ -18,7 +17,7 @@ from python_keywords.container_access import (
|
|||
check_full_access_to_container,
|
||||
check_no_access_to_container,
|
||||
)
|
||||
from python_keywords.neofs_verbs import put_object
|
||||
from python_keywords.neofs_verbs import put_object_to_random_node
|
||||
from python_keywords.node_management import drop_object
|
||||
from python_keywords.object_access import (
|
||||
can_delete_object,
|
||||
|
@ -35,36 +34,39 @@ from wellknown_acl import PUBLIC_ACL
|
|||
@pytest.mark.sanity
|
||||
@pytest.mark.acl
|
||||
@pytest.mark.acl_extended
|
||||
class TestEACLContainer:
|
||||
NODE_COUNT = len(NEOFS_NETMAP_DICT.keys())
|
||||
|
||||
class TestEACLContainer(ClusterTestBase):
|
||||
@pytest.fixture(scope="function")
|
||||
def eacl_full_placement_container_with_object(
|
||||
self, wallets, file_path, client_shell: Shell
|
||||
) -> str:
|
||||
def eacl_full_placement_container_with_object(self, wallets, file_path) -> str:
|
||||
user_wallet = wallets.get_wallet()
|
||||
storage_nodes = self.cluster.storage_nodes
|
||||
node_count = len(storage_nodes)
|
||||
with allure.step("Create eACL public container with full placement rule"):
|
||||
full_placement_rule = (
|
||||
f"REP {self.NODE_COUNT} IN X CBF 1 SELECT {self.NODE_COUNT} FROM * AS X"
|
||||
)
|
||||
full_placement_rule = f"REP {node_count} IN X CBF 1 SELECT {node_count} FROM * AS X"
|
||||
cid = create_container(
|
||||
wallet=user_wallet.wallet_path,
|
||||
rule=full_placement_rule,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step("Add test object to container"):
|
||||
oid = put_object(user_wallet.wallet_path, file_path, cid, shell=client_shell)
|
||||
wait_object_replication_on_nodes(
|
||||
user_wallet.wallet_path, cid, oid, self.NODE_COUNT, shell=client_shell
|
||||
oid = put_object_to_random_node(
|
||||
user_wallet.wallet_path, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
node_count,
|
||||
shell=self.shell,
|
||||
nodes=storage_nodes,
|
||||
)
|
||||
|
||||
yield cid, oid, file_path
|
||||
|
||||
@pytest.mark.parametrize("deny_role", [EACLRole.USER, EACLRole.OTHERS])
|
||||
def test_extended_acl_deny_all_operations(
|
||||
self, wallets, client_shell, eacl_container_with_objects, deny_role
|
||||
self, wallets, eacl_container_with_objects, deny_role
|
||||
):
|
||||
user_wallet = wallets.get_wallet()
|
||||
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
|
||||
|
@ -83,8 +85,9 @@ class TestEACLContainer:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -97,7 +100,8 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids[0],
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(
|
||||
|
@ -108,7 +112,8 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(f"Allow all operations for {deny_role_str} via eACL"):
|
||||
|
@ -119,30 +124,33 @@ class TestEACLContainer:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
with allure.step(f"Check all have full access to eACL public container"):
|
||||
with allure.step("Check all have full access to eACL public container"):
|
||||
check_full_access_to_container(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
object_oids.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
check_full_access_to_container(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
object_oids.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
@allure.title("Testcase to allow NeoFS operations for only one other pubkey.")
|
||||
def test_extended_acl_deny_all_operations_exclude_pubkey(
|
||||
self, wallets, client_shell, eacl_container_with_objects
|
||||
self, wallets, eacl_container_with_objects
|
||||
):
|
||||
user_wallet = wallets.get_wallet()
|
||||
other_wallet, other_wallet_allow = wallets.get_wallets_list(EACLRole.OTHERS)[0:2]
|
||||
|
@ -164,8 +172,9 @@ class TestEACLContainer:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -176,7 +185,8 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids[0],
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step("Check owner has full access to public container"):
|
||||
|
@ -185,7 +195,8 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step("Check allowed other has full access to public container"):
|
||||
|
@ -194,20 +205,20 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
@allure.title("Testcase to validate NeoFS replication with eACL deny rules.")
|
||||
def test_extended_acl_deny_replication(
|
||||
self,
|
||||
wallets,
|
||||
client_shell,
|
||||
hosting: Hosting,
|
||||
eacl_full_placement_container_with_object,
|
||||
file_path,
|
||||
):
|
||||
user_wallet = wallets.get_wallet()
|
||||
cid, oid, file_path = eacl_full_placement_container_with_object
|
||||
storage_nodes = self.cluster.storage_nodes
|
||||
storage_node = self.cluster.storage_nodes[0]
|
||||
|
||||
with allure.step("Deny all operations for user via eACL"):
|
||||
eacl_deny = [
|
||||
|
@ -221,40 +232,48 @@ class TestEACLContainer:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
with allure.step("Drop object to check replication"):
|
||||
drop_object(hosting, node_name=[*NEOFS_NETMAP_DICT][0], cid=cid, oid=oid)
|
||||
drop_object(storage_node, cid=cid, oid=oid)
|
||||
|
||||
storage_wallet_path = NEOFS_NETMAP_DICT[[*NEOFS_NETMAP_DICT][0]]["wallet_path"]
|
||||
storage_wallet_path = storage_node.get_wallet_path()
|
||||
with allure.step("Wait for dropped object replicated"):
|
||||
wait_object_replication_on_nodes(
|
||||
storage_wallet_path, cid, oid, self.NODE_COUNT, shell=client_shell
|
||||
wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
len(storage_nodes),
|
||||
self.shell,
|
||||
storage_nodes,
|
||||
)
|
||||
|
||||
@allure.title("Testcase to validate NeoFS system operations with extended ACL")
|
||||
def test_extended_actions_system(self, wallets, client_shell, eacl_container_with_objects):
|
||||
def test_extended_actions_system(self, wallets, eacl_container_with_objects):
|
||||
user_wallet = wallets.get_wallet()
|
||||
ir_wallet, storage_wallet = wallets.get_wallets_list(role=EACLRole.SYSTEM)[:2]
|
||||
|
||||
cid, object_oids, file_path = eacl_container_with_objects
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
|
||||
with allure.step("Check IR and STORAGE rules compliance"):
|
||||
assert not can_put_object(
|
||||
ir_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_put_object(
|
||||
storage_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -263,7 +282,8 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids[0],
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_get_object(
|
||||
|
@ -271,7 +291,8 @@ class TestEACLContainer:
|
|||
cid,
|
||||
object_oids[0],
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -279,28 +300,32 @@ class TestEACLContainer:
|
|||
ir_wallet.wallet_path,
|
||||
cid,
|
||||
object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_get_head_object(
|
||||
storage_wallet.wallet_path,
|
||||
cid,
|
||||
object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
assert can_search_object(
|
||||
ir_wallet.wallet_path,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
oid=object_oids[0],
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_search_object(
|
||||
storage_wallet.wallet_path,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
oid=object_oids[0],
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
@ -310,7 +335,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -318,7 +344,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -326,7 +353,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -334,7 +362,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -343,7 +372,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -351,7 +381,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -365,9 +396,10 @@ class TestEACLContainer:
|
|||
EACLRule(access=EACLAccess.DENY, role=EACLRole.SYSTEM, operation=op)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
),
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -376,14 +408,16 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert not can_put_object(
|
||||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -393,7 +427,8 @@ class TestEACLContainer:
|
|||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -402,7 +437,8 @@ class TestEACLContainer:
|
|||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -411,7 +447,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -419,7 +456,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -427,7 +465,8 @@ class TestEACLContainer:
|
|||
assert can_search_object(
|
||||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
oid=object_oids[0],
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
|
@ -435,7 +474,8 @@ class TestEACLContainer:
|
|||
assert can_search_object(
|
||||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
oid=object_oids[0],
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
@ -445,7 +485,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -453,7 +494,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -462,7 +504,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -470,7 +513,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -479,7 +523,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -487,7 +532,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -501,9 +547,10 @@ class TestEACLContainer:
|
|||
EACLRule(access=EACLAccess.ALLOW, role=EACLRole.SYSTEM, operation=op)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
),
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -512,14 +559,16 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_put_object(
|
||||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -528,7 +577,8 @@ class TestEACLContainer:
|
|||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_get_object(
|
||||
|
@ -536,7 +586,8 @@ class TestEACLContainer:
|
|||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
file_name=file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -544,29 +595,33 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_get_head_object(
|
||||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
assert can_search_object(
|
||||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
oid=object_oids[0],
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
assert can_search_object(
|
||||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
oid=object_oids[0],
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -575,7 +630,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -583,7 +639,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -591,7 +648,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -599,7 +657,8 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
||||
|
@ -608,7 +667,8 @@ class TestEACLContainer:
|
|||
wallet=ir_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=ir_wallet.config_path,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -616,6 +676,7 @@ class TestEACLContainer:
|
|||
wallet=storage_wallet.wallet_path,
|
||||
cid=cid,
|
||||
oid=object_oids[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
wallet_config=storage_wallet.config_path,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import allure
|
||||
import pytest
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from python_keywords.acl import (
|
||||
EACLAccess,
|
||||
EACLFilter,
|
||||
|
@ -19,7 +20,7 @@ from python_keywords.container_access import (
|
|||
check_full_access_to_container,
|
||||
check_no_access_to_container,
|
||||
)
|
||||
from python_keywords.neofs_verbs import put_object
|
||||
from python_keywords.neofs_verbs import put_object_to_random_node
|
||||
from python_keywords.object_access import can_get_head_object, can_get_object, can_put_object
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
|
@ -27,7 +28,7 @@ from wellknown_acl import PUBLIC_ACL
|
|||
@pytest.mark.sanity
|
||||
@pytest.mark.acl
|
||||
@pytest.mark.acl_filters
|
||||
class TestEACLFilters:
|
||||
class TestEACLFilters(ClusterTestBase):
|
||||
# SPEC: https://github.com/nspcc-dev/neofs-spec/blob/master/01-arch/07-acl.md
|
||||
ATTRIBUTE = {"check_key": "check_value"}
|
||||
OTHER_ATTRIBUTE = {"check_key": "other_value"}
|
||||
|
@ -67,52 +68,66 @@ class TestEACLFilters:
|
|||
]
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def eacl_container_with_objects(self, wallets, client_shell, file_path):
|
||||
def eacl_container_with_objects(self, wallets, file_path):
|
||||
user_wallet = wallets.get_wallet()
|
||||
with allure.step("Create eACL public container"):
|
||||
cid = create_container(
|
||||
user_wallet.wallet_path, basic_acl=PUBLIC_ACL, shell=client_shell
|
||||
user_wallet.wallet_path,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step("Add test objects to container"):
|
||||
objects_with_header = [
|
||||
put_object(
|
||||
put_object_to_random_node(
|
||||
user_wallet.wallet_path,
|
||||
file_path,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes={**self.SET_HEADERS, "key": val},
|
||||
)
|
||||
for val in range(self.OBJECT_COUNT)
|
||||
]
|
||||
|
||||
objects_with_other_header = [
|
||||
put_object(
|
||||
put_object_to_random_node(
|
||||
user_wallet.wallet_path,
|
||||
file_path,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes={**self.OTHER_HEADERS, "key": val},
|
||||
)
|
||||
for val in range(self.OBJECT_COUNT)
|
||||
]
|
||||
|
||||
objects_without_header = [
|
||||
put_object(user_wallet.wallet_path, file_path, cid, shell=client_shell)
|
||||
put_object_to_random_node(
|
||||
user_wallet.wallet_path,
|
||||
file_path,
|
||||
cid,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
for _ in range(self.OBJECT_COUNT)
|
||||
]
|
||||
|
||||
yield cid, objects_with_header, objects_with_other_header, objects_without_header, file_path
|
||||
|
||||
with allure.step("Delete eACL public container"):
|
||||
delete_container(user_wallet.wallet_path, cid, shell=client_shell)
|
||||
delete_container(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]
|
||||
)
|
||||
def test_extended_acl_filters_request(
|
||||
self, wallets, client_shell, eacl_container_with_objects, match_type
|
||||
):
|
||||
def test_extended_acl_filters_request(self, wallets, eacl_container_with_objects, match_type):
|
||||
allure.dynamic.title(f"Validate NeoFS operations with request filter: {match_type.name}")
|
||||
user_wallet = wallets.get_wallet()
|
||||
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
|
||||
|
@ -139,8 +154,9 @@ class TestEACLFilters:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -163,7 +179,12 @@ class TestEACLFilters:
|
|||
):
|
||||
with allure.step("Check other has full access when sending request without headers"):
|
||||
check_full_access_to_container(
|
||||
other_wallet.wallet_path, cid, oid.pop(), file_path, shell=client_shell
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid.pop(),
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(
|
||||
|
@ -174,7 +195,8 @@ class TestEACLFilters:
|
|||
cid,
|
||||
oid.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=allow_headers,
|
||||
)
|
||||
|
||||
|
@ -184,7 +206,8 @@ class TestEACLFilters:
|
|||
cid,
|
||||
oid.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=deny_headers,
|
||||
)
|
||||
|
||||
|
@ -199,14 +222,16 @@ class TestEACLFilters:
|
|||
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
check_full_access_to_container(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=deny_headers,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
|
@ -215,7 +240,7 @@ class TestEACLFilters:
|
|||
"match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]
|
||||
)
|
||||
def test_extended_acl_deny_filters_object(
|
||||
self, wallets, client_shell, eacl_container_with_objects, match_type
|
||||
self, wallets, eacl_container_with_objects, match_type
|
||||
):
|
||||
allure.dynamic.title(
|
||||
f"Validate NeoFS operations with deny user headers filter: {match_type.name}"
|
||||
|
@ -245,8 +270,9 @@ class TestEACLFilters:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -271,7 +297,8 @@ class TestEACLFilters:
|
|||
cid,
|
||||
objs_without_header.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=xhdr,
|
||||
)
|
||||
|
||||
|
@ -281,7 +308,8 @@ class TestEACLFilters:
|
|||
cid,
|
||||
allow_objects.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=xhdr,
|
||||
)
|
||||
|
||||
|
@ -291,7 +319,8 @@ class TestEACLFilters:
|
|||
other_wallet.wallet_path,
|
||||
cid,
|
||||
deny_objects[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
xhdr=xhdr,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
|
@ -300,7 +329,8 @@ class TestEACLFilters:
|
|||
cid,
|
||||
deny_objects[0],
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=xhdr,
|
||||
)
|
||||
|
||||
|
@ -318,14 +348,16 @@ class TestEACLFilters:
|
|||
)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
check_full_access_to_container(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
deny_objects.pop(),
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
xhdr=xhdr,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
|
@ -338,10 +370,13 @@ class TestEACLFilters:
|
|||
other_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes=allow_attribute,
|
||||
)
|
||||
assert can_put_object(other_wallet.wallet_path, cid, file_path, shell=client_shell)
|
||||
assert can_put_object(
|
||||
other_wallet.wallet_path, cid, file_path, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
deny_attribute = (
|
||||
self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE
|
||||
|
@ -352,7 +387,8 @@ class TestEACLFilters:
|
|||
other_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes=deny_attribute,
|
||||
)
|
||||
|
||||
|
@ -369,13 +405,15 @@ class TestEACLFilters:
|
|||
role=EACLRole.OTHERS,
|
||||
)
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
assert can_put_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes=deny_attribute,
|
||||
bearer=bearer_other_for_put,
|
||||
)
|
||||
|
@ -384,7 +422,7 @@ class TestEACLFilters:
|
|||
"match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]
|
||||
)
|
||||
def test_extended_acl_allow_filters_object(
|
||||
self, wallets, client_shell, eacl_container_with_objects, match_type
|
||||
self, wallets, eacl_container_with_objects, match_type
|
||||
):
|
||||
allure.dynamic.title(
|
||||
"Testcase to validate NeoFS operation with allow eACL user headers filters:"
|
||||
|
@ -420,8 +458,9 @@ class TestEACLFilters:
|
|||
set_eacl(
|
||||
user_wallet.wallet_path,
|
||||
cid,
|
||||
create_eacl(cid, eacl, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
|
@ -439,13 +478,26 @@ class TestEACLFilters:
|
|||
with allure.step(f"Check other cannot get and put objects without attributes"):
|
||||
oid = objects_without_header.pop()
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_get_head_object(other_wallet.wallet_path, cid, oid, shell=client_shell)
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_get_object(
|
||||
other_wallet.wallet_path, cid, oid, file_path, shell=client_shell
|
||||
assert can_get_head_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_put_object(other_wallet.wallet_path, cid, file_path, shell=client_shell)
|
||||
assert can_get_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_put_object(
|
||||
other_wallet.wallet_path, cid, file_path, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
with allure.step(
|
||||
"Check other can get and put objects without attributes and using bearer token"
|
||||
|
@ -461,13 +513,15 @@ class TestEACLFilters:
|
|||
)
|
||||
for op in EACLOperation
|
||||
],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
assert can_get_head_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
objects_without_header[0],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
assert can_get_object(
|
||||
|
@ -475,37 +529,62 @@ class TestEACLFilters:
|
|||
cid,
|
||||
objects_without_header[0],
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
assert can_put_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
|
||||
with allure.step(f"Check other can get objects with attributes matching the filter"):
|
||||
oid = allow_objects.pop()
|
||||
assert can_get_head_object(other_wallet.wallet_path, cid, oid, shell=client_shell)
|
||||
assert can_get_object(other_wallet.wallet_path, cid, oid, file_path, shell=client_shell)
|
||||
assert can_get_head_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
assert can_get_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
assert can_put_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes=allow_attribute,
|
||||
)
|
||||
|
||||
with allure.step("Check other cannot get objects without attributes matching the filter"):
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_get_head_object(
|
||||
other_wallet.wallet_path, cid, deny_objects[0], shell=client_shell
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
deny_objects[0],
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_get_object(
|
||||
other_wallet.wallet_path, cid, deny_objects[0], file_path, shell=client_shell
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
deny_objects[0],
|
||||
file_path,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
with pytest.raises(AssertionError):
|
||||
assert can_put_object(
|
||||
|
@ -513,7 +592,8 @@ class TestEACLFilters:
|
|||
cid,
|
||||
file_path,
|
||||
attributes=deny_attribute,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step(
|
||||
|
@ -522,21 +602,28 @@ class TestEACLFilters:
|
|||
):
|
||||
oid = deny_objects.pop()
|
||||
assert can_get_head_object(
|
||||
other_wallet.wallet_path, cid, oid, shell=client_shell, bearer=bearer_other
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
assert can_get_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
assert can_put_object(
|
||||
other_wallet.wallet_path,
|
||||
cid,
|
||||
file_path,
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
attributes=deny_attribute,
|
||||
bearer=bearer_other,
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ import allure
|
|||
import pytest
|
||||
import yaml
|
||||
from binary_version_helper import get_local_binaries_versions, get_remote_binaries_versions
|
||||
from cluster import Cluster
|
||||
from common import (
|
||||
ASSETS_DIR,
|
||||
BACKGROUND_LOAD_MAX_TIME,
|
||||
|
@ -20,7 +21,6 @@ from common import (
|
|||
LOAD_NODE_SSH_PRIVATE_KEY_PATH,
|
||||
LOAD_NODE_SSH_USER,
|
||||
LOAD_NODES,
|
||||
NEOFS_NETMAP_DICT,
|
||||
STORAGE_NODE_SERVICE_NAME_REGEX,
|
||||
WALLET_PASS,
|
||||
)
|
||||
|
@ -33,8 +33,9 @@ from neofs_testlib.shell import LocalShell, Shell
|
|||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from payment_neogo import deposit_gas, transfer_gas
|
||||
from pytest import FixtureRequest
|
||||
from python_keywords.node_management import node_healthcheck
|
||||
from wallet import WalletFactory
|
||||
from python_keywords.node_management import storage_node_healthcheck
|
||||
|
||||
from helpers.wallet import WalletFactory
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
@ -66,6 +67,7 @@ def hosting(configure_testlib) -> Hosting:
|
|||
|
||||
hosting_instance = Hosting()
|
||||
hosting_instance.configure(hosting_config)
|
||||
|
||||
yield hosting_instance
|
||||
|
||||
|
||||
|
@ -81,8 +83,13 @@ def require_multiple_hosts(hosting: Hosting):
|
|||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def wallet_factory(prepare_tmp_dir: str, client_shell: Shell) -> WalletFactory:
|
||||
return WalletFactory(prepare_tmp_dir, client_shell)
|
||||
def wallet_factory(temp_directory: str, client_shell: Shell, cluster: Cluster) -> WalletFactory:
|
||||
return WalletFactory(temp_directory, client_shell, cluster)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def cluster(hosting: Hosting) -> Cluster:
|
||||
yield Cluster(hosting)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
|
@ -97,7 +104,7 @@ def check_binary_versions(request, hosting: Hosting, client_shell: Shell):
|
|||
|
||||
@pytest.fixture(scope="session")
|
||||
@allure.title("Prepare tmp directory")
|
||||
def prepare_tmp_dir():
|
||||
def temp_directory():
|
||||
with allure.step("Prepare tmp directory"):
|
||||
full_path = os.path.join(os.getcwd(), ASSETS_DIR)
|
||||
shutil.rmtree(full_path, ignore_errors=True)
|
||||
|
@ -111,7 +118,7 @@ def prepare_tmp_dir():
|
|||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
@allure.title("Analyze logs")
|
||||
def analyze_logs(prepare_tmp_dir: str, hosting: Hosting, request: FixtureRequest):
|
||||
def analyze_logs(temp_directory: str, hosting: Hosting, request: FixtureRequest):
|
||||
start_time = datetime.utcnow()
|
||||
yield
|
||||
end_time = datetime.utcnow()
|
||||
|
@ -123,39 +130,39 @@ def analyze_logs(prepare_tmp_dir: str, hosting: Hosting, request: FixtureRequest
|
|||
|
||||
# Test name may exceed os NAME_MAX (255 bytes), so we use test start datetime instead
|
||||
start_time_str = start_time.strftime("%Y_%m_%d_%H_%M_%S_%f")
|
||||
logs_dir = os.path.join(prepare_tmp_dir, f"logs_{start_time_str}")
|
||||
logs_dir = os.path.join(temp_directory, f"logs_{start_time_str}")
|
||||
dump_logs(hosting, logs_dir, start_time, end_time)
|
||||
check_logs(logs_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
@allure.title("Collect logs")
|
||||
def collect_logs(prepare_tmp_dir, hosting: Hosting):
|
||||
def collect_logs(temp_directory, hosting: Hosting):
|
||||
start_time = datetime.utcnow()
|
||||
yield
|
||||
end_time = datetime.utcnow()
|
||||
|
||||
# Dump logs to temp directory (because they might be too large to keep in RAM)
|
||||
logs_dir = os.path.join(prepare_tmp_dir, "logs")
|
||||
logs_dir = os.path.join(temp_directory, "logs")
|
||||
dump_logs(hosting, logs_dir, start_time, end_time)
|
||||
attach_logs(logs_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
@allure.title("Run health check for all storage nodes")
|
||||
def run_health_check(collect_logs, hosting: Hosting):
|
||||
def run_health_check(collect_logs, cluster: Cluster):
|
||||
failed_nodes = []
|
||||
for node_name in NEOFS_NETMAP_DICT.keys():
|
||||
health_check = node_healthcheck(hosting, node_name)
|
||||
for node in cluster.storage_nodes:
|
||||
health_check = storage_node_healthcheck(node)
|
||||
if health_check.health_status != "READY" or health_check.network_status != "ONLINE":
|
||||
failed_nodes.append(node_name)
|
||||
failed_nodes.append(node)
|
||||
|
||||
if failed_nodes:
|
||||
raise AssertionError(f"Nodes {failed_nodes} are not healthy")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def background_grpc_load(client_shell, prepare_wallet_and_deposit):
|
||||
def background_grpc_load(client_shell, default_wallet):
|
||||
registry_file = os.path.join("/tmp/", f"{str(uuid.uuid4())}.bolt")
|
||||
prepare_file = os.path.join("/tmp/", f"{str(uuid.uuid4())}.json")
|
||||
allure.dynamic.title(
|
||||
|
@ -221,21 +228,24 @@ def background_grpc_load(client_shell, prepare_wallet_and_deposit):
|
|||
|
||||
@pytest.fixture(scope="session")
|
||||
@allure.title("Prepare wallet and deposit")
|
||||
def prepare_wallet_and_deposit(client_shell, prepare_tmp_dir):
|
||||
def default_wallet(client_shell: Shell, temp_directory: str, cluster: Cluster):
|
||||
wallet_path = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
|
||||
init_wallet(wallet_path, WALLET_PASS)
|
||||
allure.attach.file(wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON)
|
||||
|
||||
if not FREE_STORAGE:
|
||||
main_chain = cluster.main_chain_nodes[0]
|
||||
deposit = 30
|
||||
transfer_gas(
|
||||
shell=client_shell,
|
||||
amount=deposit + 1,
|
||||
main_chain=main_chain,
|
||||
wallet_to_path=wallet_path,
|
||||
wallet_to_password=WALLET_PASS,
|
||||
)
|
||||
deposit_gas(
|
||||
shell=client_shell,
|
||||
main_chain=main_chain,
|
||||
amount=deposit,
|
||||
wallet_from_path=wallet_path,
|
||||
wallet_from_password=WALLET_PASS,
|
||||
|
|
|
@ -14,84 +14,114 @@ from python_keywords.container import (
|
|||
from utility import placement_policy_from_container
|
||||
from wellknown_acl import PRIVATE_ACL_F
|
||||
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.smoke
|
||||
@pytest.mark.container
|
||||
def test_container_creation(client_shell, prepare_wallet_and_deposit, name):
|
||||
scenario_title = f"with name {name}" if name else "without name"
|
||||
allure.dynamic.title(f"User can create container {scenario_title}")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.container
|
||||
class TestContainer(ClusterTestBase):
|
||||
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
|
||||
@pytest.mark.smoke
|
||||
def test_container_creation(self, default_wallet, name):
|
||||
scenario_title = f"with name {name}" if name else "without name"
|
||||
allure.dynamic.title(f"User can create container {scenario_title}")
|
||||
|
||||
wallet = prepare_wallet_and_deposit
|
||||
with open(wallet) as file:
|
||||
json_wallet = json.load(file)
|
||||
wallet = default_wallet
|
||||
with open(wallet) as file:
|
||||
json_wallet = json.load(file)
|
||||
|
||||
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||
cid = create_container(wallet, rule=placement_rule, name=name, shell=client_shell)
|
||||
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||
cid = create_container(
|
||||
wallet,
|
||||
rule=placement_rule,
|
||||
name=name,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
containers = list_containers(wallet, shell=client_shell)
|
||||
assert cid in containers, f"Expected container {cid} in containers: {containers}"
|
||||
containers = list_containers(
|
||||
wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
assert cid in containers, f"Expected container {cid} in containers: {containers}"
|
||||
|
||||
container_info: str = get_container(wallet, cid, json_mode=False, shell=client_shell)
|
||||
container_info = container_info.casefold() # To ignore case when comparing with expected values
|
||||
container_info: str = get_container(
|
||||
wallet,
|
||||
cid,
|
||||
json_mode=False,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
container_info = (
|
||||
container_info.casefold()
|
||||
) # To ignore case when comparing with expected values
|
||||
|
||||
info_to_check = {
|
||||
f"basic ACL: {PRIVATE_ACL_F} (private)",
|
||||
f"owner ID: {json_wallet.get('accounts')[0].get('address')}",
|
||||
f"container ID: {cid}",
|
||||
}
|
||||
if name:
|
||||
info_to_check.add(f"Name={name}")
|
||||
info_to_check = {
|
||||
f"basic ACL: {PRIVATE_ACL_F} (private)",
|
||||
f"owner ID: {json_wallet.get('accounts')[0].get('address')}",
|
||||
f"container ID: {cid}",
|
||||
}
|
||||
if name:
|
||||
info_to_check.add(f"Name={name}")
|
||||
|
||||
with allure.step("Check container has correct information"):
|
||||
expected_policy = placement_rule.casefold()
|
||||
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}"
|
||||
|
||||
for info in info_to_check:
|
||||
expected_info = info.casefold()
|
||||
with allure.step("Check container has correct information"):
|
||||
expected_policy = placement_rule.casefold()
|
||||
actual_policy = placement_policy_from_container(container_info)
|
||||
assert (
|
||||
expected_info in container_info
|
||||
), f"Expected {expected_info} in container info:\n{container_info}"
|
||||
actual_policy == expected_policy
|
||||
), f"Expected policy\n{expected_policy} but got policy\n{actual_policy}"
|
||||
|
||||
with allure.step("Delete container and check it was deleted"):
|
||||
delete_container(wallet, cid, shell=client_shell)
|
||||
tick_epoch(shell=client_shell)
|
||||
wait_for_container_deletion(wallet, cid, shell=client_shell)
|
||||
for info in info_to_check:
|
||||
expected_info = info.casefold()
|
||||
assert (
|
||||
expected_info in container_info
|
||||
), f"Expected {expected_info} in container info:\n{container_info}"
|
||||
|
||||
with allure.step("Delete container and check it was deleted"):
|
||||
delete_container(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
wait_for_container_deletion(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
|
||||
@allure.title("Parallel container creation and deletion")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.container
|
||||
def test_container_creation_deletion_parallel(client_shell, prepare_wallet_and_deposit):
|
||||
containers_count = 3
|
||||
wallet = prepare_wallet_and_deposit
|
||||
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||
@allure.title("Parallel container creation and deletion")
|
||||
def test_container_creation_deletion_parallel(self, default_wallet):
|
||||
containers_count = 3
|
||||
wallet = default_wallet
|
||||
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||
|
||||
cids: list[str] = []
|
||||
with allure.step(f"Create {containers_count} containers"):
|
||||
for _ in range(containers_count):
|
||||
cids.append(
|
||||
create_container(
|
||||
wallet,
|
||||
rule=placement_rule,
|
||||
await_mode=False,
|
||||
shell=client_shell,
|
||||
wait_for_creation=False,
|
||||
cids: list[str] = []
|
||||
with allure.step(f"Create {containers_count} containers"):
|
||||
for _ in range(containers_count):
|
||||
cids.append(
|
||||
create_container(
|
||||
wallet,
|
||||
rule=placement_rule,
|
||||
await_mode=False,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wait_for_creation=False,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
with allure.step(f"Wait for containers occur in container list"):
|
||||
for cid in cids:
|
||||
wait_for_container_creation(
|
||||
wallet, cid, sleep_interval=containers_count, shell=client_shell
|
||||
)
|
||||
with allure.step(f"Wait for containers occur in container list"):
|
||||
for cid in cids:
|
||||
wait_for_container_creation(
|
||||
wallet,
|
||||
cid,
|
||||
sleep_interval=containers_count,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step("Delete containers and check they were deleted"):
|
||||
for cid in cids:
|
||||
delete_container(wallet, cid, shell=client_shell)
|
||||
tick_epoch(shell=client_shell)
|
||||
wait_for_container_deletion(wallet, cid, shell=client_shell)
|
||||
with allure.step("Delete containers and check they were deleted"):
|
||||
for cid in cids:
|
||||
delete_container(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
wait_for_container_deletion(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
|
|
|
@ -4,98 +4,107 @@ from time import sleep
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
|
||||
from cluster import StorageNode
|
||||
from failover_utils import wait_all_storage_nodes_returned, wait_object_replication
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from iptables_helper import IpTablesHelper
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.neofs_verbs import get_object, put_object
|
||||
from python_keywords.neofs_verbs import get_object, put_object_to_random_node
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
STORAGE_NODE_COMMUNICATION_PORT = "8080"
|
||||
STORAGE_NODE_COMMUNICATION_PORT_TLS = "8082"
|
||||
PORTS_TO_BLOCK = [STORAGE_NODE_COMMUNICATION_PORT, STORAGE_NODE_COMMUNICATION_PORT_TLS]
|
||||
blocked_hosts = []
|
||||
blocked_nodes: list[StorageNode] = []
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@allure.step("Restore network")
|
||||
def restore_network(hosting: Hosting):
|
||||
yield
|
||||
|
||||
not_empty = len(blocked_hosts) != 0
|
||||
for host_address in list(blocked_hosts):
|
||||
with allure.step(f"Restore network at host {host_address}"):
|
||||
host = hosting.get_host_by_address(host_address)
|
||||
IpTablesHelper.restore_input_traffic_to_port(host.get_shell(), PORTS_TO_BLOCK)
|
||||
blocked_hosts.remove(host)
|
||||
if not_empty:
|
||||
wait_all_storage_node_returned(hosting)
|
||||
|
||||
|
||||
@allure.title("Block Storage node traffic")
|
||||
@pytest.mark.failover
|
||||
@pytest.mark.failover_network
|
||||
def test_block_storage_node_traffic(
|
||||
prepare_wallet_and_deposit, client_shell, require_multiple_hosts, hosting: Hosting
|
||||
):
|
||||
"""
|
||||
Block storage nodes traffic using iptables and wait for replication for objects.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
wakeup_node_timeout = 10 # timeout to let nodes detect that traffic has blocked
|
||||
nodes_to_block_count = 2
|
||||
class TestFailoverNetwork(ClusterTestBase):
|
||||
@pytest.fixture(autouse=True)
|
||||
@allure.step("Restore network")
|
||||
def restore_network(self):
|
||||
yield
|
||||
|
||||
source_file_path = generate_file()
|
||||
cid = create_container(wallet, shell=client_shell, rule=placement_rule, basic_acl=PUBLIC_ACL)
|
||||
oid = put_object(wallet, source_file_path, cid, shell=client_shell)
|
||||
not_empty = len(blocked_nodes) != 0
|
||||
for node in list(blocked_nodes):
|
||||
with allure.step(f"Restore network at host for {node.label}"):
|
||||
IpTablesHelper.restore_input_traffic_to_port(node.host.get_shell(), PORTS_TO_BLOCK)
|
||||
blocked_nodes.remove(node)
|
||||
if not_empty:
|
||||
wait_all_storage_nodes_returned(self.cluster)
|
||||
|
||||
# TODO: we need to refactor wait_object_replication_on_nodes so that it returns
|
||||
# storage node names rather than endpoints
|
||||
node_endpoints = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||
@allure.title("Block Storage node traffic")
|
||||
def test_block_storage_node_traffic(self, default_wallet, require_multiple_hosts):
|
||||
"""
|
||||
Block storage nodes traffic using iptables and wait for replication for objects.
|
||||
"""
|
||||
wallet = default_wallet
|
||||
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
wakeup_node_timeout = 10 # timeout to let nodes detect that traffic has blocked
|
||||
nodes_to_block_count = 2
|
||||
|
||||
logger.info(f"Nodes are {node_endpoints}")
|
||||
node_endpoints_to_block = node_endpoints
|
||||
if nodes_to_block_count > len(node_endpoints):
|
||||
# TODO: the intent of this logic is not clear, need to revisit
|
||||
node_endpoints_to_block = choices(node_endpoints, k=2)
|
||||
source_file_path = generate_file()
|
||||
cid = create_container(
|
||||
wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=placement_rule,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
oid = put_object_to_random_node(
|
||||
wallet, source_file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
excluded_nodes = []
|
||||
for node_endpoint in node_endpoints_to_block:
|
||||
host_address = node_endpoint.split(":")[0]
|
||||
host = hosting.get_host_by_address(host_address)
|
||||
nodes = wait_object_replication(
|
||||
cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
|
||||
with allure.step(f"Block incoming traffic at host {host_address} on port {PORTS_TO_BLOCK}"):
|
||||
blocked_hosts.append(host_address)
|
||||
excluded_nodes.append(node_endpoint)
|
||||
IpTablesHelper.drop_input_traffic_to_port(host.get_shell(), PORTS_TO_BLOCK)
|
||||
sleep(wakeup_node_timeout)
|
||||
logger.info(f"Nodes are {nodes}")
|
||||
nodes_to_block = nodes
|
||||
if nodes_to_block_count > len(nodes):
|
||||
# TODO: the intent of this logic is not clear, need to revisit
|
||||
nodes_to_block = choices(nodes, k=2)
|
||||
|
||||
with allure.step(f"Check object is not stored on node {node_endpoint}"):
|
||||
new_nodes = wait_object_replication_on_nodes(
|
||||
wallet, cid, oid, 2, shell=client_shell, excluded_nodes=excluded_nodes
|
||||
)
|
||||
assert node_endpoint not in new_nodes
|
||||
excluded_nodes = []
|
||||
for node in nodes_to_block:
|
||||
with allure.step(f"Block incoming traffic at node {node} on port {PORTS_TO_BLOCK}"):
|
||||
blocked_nodes.append(node)
|
||||
excluded_nodes.append(node)
|
||||
IpTablesHelper.drop_input_traffic_to_port(node.host.get_shell(), PORTS_TO_BLOCK)
|
||||
sleep(wakeup_node_timeout)
|
||||
|
||||
with allure.step(f"Check object is not stored on node {node}"):
|
||||
new_nodes = wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
2,
|
||||
shell=self.shell,
|
||||
nodes=list(set(self.cluster.storage_nodes) - set(excluded_nodes)),
|
||||
)
|
||||
assert node not in new_nodes
|
||||
|
||||
with allure.step(f"Check object data is not corrupted"):
|
||||
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)
|
||||
|
||||
for node in nodes_to_block:
|
||||
with allure.step(f"Unblock incoming traffic at host {node} on port {PORTS_TO_BLOCK}"):
|
||||
IpTablesHelper.restore_input_traffic_to_port(node.host.get_shell(), PORTS_TO_BLOCK)
|
||||
blocked_nodes.remove(node)
|
||||
sleep(wakeup_node_timeout)
|
||||
|
||||
with allure.step(f"Check object data is not corrupted"):
|
||||
got_file_path = get_object(wallet, cid, oid, endpoint=new_nodes[0], shell=client_shell)
|
||||
new_nodes = wait_object_replication(
|
||||
cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
|
||||
got_file_path = get_object(
|
||||
wallet, cid, oid, shell=self.shell, endpoint=new_nodes[0].get_rpc_endpoint()
|
||||
)
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
|
||||
for node_endpoint in node_endpoints_to_block:
|
||||
host_address = node_endpoint.split(":")[0]
|
||||
host = hosting.get_host_by_address(host_address)
|
||||
|
||||
with allure.step(
|
||||
f"Unblock incoming traffic at host {host_address} on port {PORTS_TO_BLOCK}"
|
||||
):
|
||||
IpTablesHelper.restore_input_traffic_to_port(host.get_shell(), PORTS_TO_BLOCK)
|
||||
blocked_hosts.remove(host_address)
|
||||
sleep(wakeup_node_timeout)
|
||||
|
||||
with allure.step(f"Check object data is not corrupted"):
|
||||
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||
|
||||
got_file_path = get_object(wallet, cid, oid, shell=client_shell, endpoint=new_nodes[0])
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
|
|
|
@ -2,23 +2,26 @@ import logging
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from failover_utils import wait_all_storage_node_returned, wait_object_replication_on_nodes
|
||||
from cluster import Cluster, StorageNode
|
||||
from failover_utils import wait_all_storage_nodes_returned, wait_object_replication
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from neofs_testlib.hosting import Host, Hosting
|
||||
from neofs_testlib.hosting import Host
|
||||
from neofs_testlib.shell import CommandOptions
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.neofs_verbs import get_object, put_object
|
||||
from python_keywords.neofs_verbs import get_object, put_object_to_random_node
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
stopped_hosts = []
|
||||
stopped_nodes: list[StorageNode] = []
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
@allure.step("Return all stopped hosts")
|
||||
def after_run_return_all_stopped_hosts(hosting: Hosting):
|
||||
def after_run_return_all_stopped_hosts(cluster: Cluster):
|
||||
yield
|
||||
return_stopped_hosts(hosting)
|
||||
return_stopped_hosts(cluster)
|
||||
|
||||
|
||||
def panic_reboot_host(host: Host) -> None:
|
||||
|
@ -29,112 +32,147 @@ def panic_reboot_host(host: Host) -> None:
|
|||
shell.exec('sudo sh -c "echo b > /proc/sysrq-trigger"', options)
|
||||
|
||||
|
||||
def return_stopped_hosts(hosting: Hosting) -> None:
|
||||
for host_address in list(stopped_hosts):
|
||||
with allure.step(f"Start host {host_address}"):
|
||||
host = hosting.get_host_by_address(host_address)
|
||||
host.start_host()
|
||||
stopped_hosts.remove(host_address)
|
||||
def return_stopped_hosts(cluster: Cluster) -> None:
|
||||
for node in list(stopped_nodes):
|
||||
with allure.step(f"Start host {node}"):
|
||||
node.host.start_host()
|
||||
stopped_nodes.remove(node)
|
||||
|
||||
wait_all_storage_node_returned(hosting)
|
||||
wait_all_storage_nodes_returned(cluster)
|
||||
|
||||
|
||||
@allure.title("Lose and return storage node's host")
|
||||
@pytest.mark.parametrize("hard_reboot", [True, False])
|
||||
@pytest.mark.failover
|
||||
@pytest.mark.failover_reboot
|
||||
def test_lose_storage_node_host(
|
||||
prepare_wallet_and_deposit,
|
||||
client_shell,
|
||||
hosting: Hosting,
|
||||
hard_reboot: bool,
|
||||
require_multiple_hosts,
|
||||
):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
source_file_path = generate_file()
|
||||
cid = create_container(wallet, shell=client_shell, rule=placement_rule, basic_acl=PUBLIC_ACL)
|
||||
oid = put_object(wallet, source_file_path, cid, shell=client_shell)
|
||||
node_endpoints = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||
|
||||
for node_endpoint in node_endpoints:
|
||||
host_address = node_endpoint.split(":")[0]
|
||||
host = hosting.get_host_by_address(host_address)
|
||||
stopped_hosts.append(host.config.address)
|
||||
|
||||
with allure.step(f"Stop host {host_address}"):
|
||||
host.stop_host("hard" if hard_reboot else "soft")
|
||||
|
||||
new_nodes = wait_object_replication_on_nodes(
|
||||
wallet, cid, oid, 2, shell=client_shell, excluded_nodes=[node_endpoint]
|
||||
class TestFailoverStorage(ClusterTestBase):
|
||||
@allure.title("Lose and return storage node's host")
|
||||
@pytest.mark.parametrize("hard_reboot", [True, False])
|
||||
@pytest.mark.failover_reboot
|
||||
def test_lose_storage_node_host(
|
||||
self,
|
||||
default_wallet,
|
||||
hard_reboot: bool,
|
||||
require_multiple_hosts,
|
||||
):
|
||||
wallet = default_wallet
|
||||
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
source_file_path = generate_file()
|
||||
cid = create_container(
|
||||
wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=placement_rule,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
oid = put_object_to_random_node(
|
||||
wallet, source_file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
nodes = wait_object_replication(
|
||||
cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
assert all(old_node not in new_nodes for old_node in node_endpoints)
|
||||
|
||||
with allure.step("Check object data is not corrupted"):
|
||||
got_file_path = get_object(wallet, cid, oid, endpoint=new_nodes[0], shell=client_shell)
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
for node in nodes:
|
||||
stopped_nodes.append(node)
|
||||
|
||||
with allure.step(f"Return all hosts"):
|
||||
return_stopped_hosts(hosting)
|
||||
with allure.step(f"Stop host {node}"):
|
||||
node.host.stop_host("hard" if hard_reboot else "soft")
|
||||
|
||||
with allure.step("Check object data is not corrupted"):
|
||||
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||
got_file_path = get_object(wallet, cid, oid, shell=client_shell, endpoint=new_nodes[0])
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
new_nodes = wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
2,
|
||||
shell=self.shell,
|
||||
nodes=list(set(self.cluster.storage_nodes) - {node}),
|
||||
)
|
||||
assert all(old_node not in new_nodes for old_node in nodes)
|
||||
|
||||
with allure.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
|
||||
)
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
|
||||
@allure.title("Panic storage node's host")
|
||||
@pytest.mark.parametrize("sequence", [True, False])
|
||||
@pytest.mark.failover
|
||||
@pytest.mark.failover_panic
|
||||
def test_panic_storage_node_host(
|
||||
prepare_wallet_and_deposit,
|
||||
client_shell,
|
||||
hosting: Hosting,
|
||||
require_multiple_hosts,
|
||||
sequence: bool,
|
||||
):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
source_file_path = generate_file()
|
||||
cid = create_container(wallet, shell=client_shell, rule=placement_rule, basic_acl=PUBLIC_ACL)
|
||||
oid = put_object(wallet, source_file_path, cid, shell=client_shell)
|
||||
with allure.step(f"Return all hosts"):
|
||||
return_stopped_hosts(self.cluster)
|
||||
|
||||
node_endpoints = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||
allure.attach(
|
||||
"\n".join(node_endpoints),
|
||||
"Current nodes with object",
|
||||
allure.attachment_type.TEXT,
|
||||
)
|
||||
with allure.step("Check object data is not corrupted"):
|
||||
new_nodes = wait_object_replication(
|
||||
cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
got_file_path = get_object(
|
||||
wallet, cid, oid, shell=self.shell, endpoint=new_nodes[0].get_rpc_endpoint()
|
||||
)
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
|
||||
new_nodes: list[str] = []
|
||||
for node_endpoint in node_endpoints:
|
||||
host_address = node_endpoint.split(":")[0]
|
||||
@allure.title("Panic storage node's host")
|
||||
@pytest.mark.parametrize("sequence", [True, False])
|
||||
@pytest.mark.failover_panic
|
||||
def test_panic_storage_node_host(
|
||||
self,
|
||||
default_wallet,
|
||||
require_multiple_hosts,
|
||||
sequence: bool,
|
||||
):
|
||||
wallet = default_wallet
|
||||
placement_rule = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
source_file_path = generate_file()
|
||||
cid = create_container(
|
||||
wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=placement_rule,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
oid = put_object_to_random_node(
|
||||
wallet, source_file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
with allure.step(f"Hard reboot host {node_endpoint} via magic SysRq option"):
|
||||
host = hosting.get_host_by_address(host_address)
|
||||
panic_reboot_host(host)
|
||||
if sequence:
|
||||
try:
|
||||
new_nodes = wait_object_replication_on_nodes(
|
||||
wallet, cid, oid, 2, shell=client_shell, excluded_nodes=[node_endpoint]
|
||||
)
|
||||
except AssertionError:
|
||||
new_nodes = wait_object_replication_on_nodes(
|
||||
wallet, cid, oid, 2, shell=client_shell
|
||||
)
|
||||
|
||||
allure.attach(
|
||||
"\n".join(new_nodes),
|
||||
f"Nodes with object after {node_endpoint} fail",
|
||||
allure.attachment_type.TEXT,
|
||||
)
|
||||
|
||||
if not sequence:
|
||||
new_nodes = wait_object_replication_on_nodes(wallet, cid, oid, 2, shell=client_shell)
|
||||
nodes = wait_object_replication(
|
||||
cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
allure.attach(
|
||||
"\n".join(new_nodes), "Nodes with object after nodes fail", allure.attachment_type.TEXT
|
||||
"\n".join(nodes),
|
||||
"Current nodes with object",
|
||||
allure.attachment_type.TEXT,
|
||||
)
|
||||
|
||||
got_file_path = get_object(wallet, cid, oid, shell=client_shell, endpoint=new_nodes[0])
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
new_nodes: list[StorageNode] = []
|
||||
for node in nodes:
|
||||
with allure.step(f"Hard reboot host {node} via magic SysRq option"):
|
||||
panic_reboot_host(node.host)
|
||||
if sequence:
|
||||
try:
|
||||
new_nodes = wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
2,
|
||||
shell=self.shell,
|
||||
nodes=list(set(self.cluster.storage_nodes) - {node}),
|
||||
)
|
||||
except AssertionError:
|
||||
new_nodes = wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
2,
|
||||
shell=self.shell,
|
||||
nodes=self.cluster.storage_nodes,
|
||||
)
|
||||
|
||||
allure.attach(
|
||||
"\n".join(new_nodes),
|
||||
f"Nodes with object after {node} fail",
|
||||
allure.attachment_type.TEXT,
|
||||
)
|
||||
|
||||
if not sequence:
|
||||
new_nodes = wait_object_replication(
|
||||
cid, oid, 2, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
allure.attach(
|
||||
"\n".join(new_nodes),
|
||||
"Nodes with object after nodes fail",
|
||||
allure.attachment_type.TEXT,
|
||||
)
|
||||
|
||||
got_file_path = get_object(
|
||||
wallet, cid, oid, shell=self.shell, endpoint=new_nodes[0].get_rpc_endpoint()
|
||||
)
|
||||
assert get_file_hash(source_file_path) == get_file_hash(got_file_path)
|
||||
|
|
|
@ -1,541 +1,531 @@
|
|||
import logging
|
||||
from random import choice
|
||||
import random
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
from common import (
|
||||
COMPLEX_OBJ_SIZE,
|
||||
MORPH_BLOCK_TIME,
|
||||
NEOFS_CONTRACT_CACHE_TIMEOUT,
|
||||
NEOFS_NETMAP_DICT,
|
||||
STORAGE_RPC_ENDPOINT_1,
|
||||
STORAGE_WALLET_PASS,
|
||||
)
|
||||
from data_formatters import get_wallet_public_key
|
||||
from cluster import StorageNode
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from common import COMPLEX_OBJ_SIZE, MORPH_BLOCK_TIME, NEOFS_CONTRACT_CACHE_TIMEOUT
|
||||
from epoch import tick_epoch
|
||||
from file_helper import generate_file
|
||||
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.container import create_container, get_container
|
||||
from python_keywords.failover_utils import wait_object_replication_on_nodes
|
||||
from python_keywords.neofs_verbs import delete_object, get_object, head_object, put_object
|
||||
from python_keywords.failover_utils import wait_object_replication
|
||||
from python_keywords.neofs_verbs import (
|
||||
delete_object,
|
||||
get_object,
|
||||
get_object_from_random_node,
|
||||
head_object,
|
||||
put_object,
|
||||
put_object_to_random_node,
|
||||
)
|
||||
from python_keywords.node_management import (
|
||||
check_node_in_map,
|
||||
delete_node_data,
|
||||
drop_object,
|
||||
exclude_node_from_network_map,
|
||||
get_locode,
|
||||
get_locode_from_random_node,
|
||||
get_netmap_snapshot,
|
||||
include_node_to_network_map,
|
||||
node_healthcheck,
|
||||
node_set_status,
|
||||
node_shard_list,
|
||||
node_shard_set_mode,
|
||||
start_nodes,
|
||||
stop_nodes,
|
||||
start_storage_nodes,
|
||||
storage_node_healthcheck,
|
||||
storage_node_set_status,
|
||||
)
|
||||
from storage_policy import get_nodes_with_object, get_simple_object_copies
|
||||
from utility import parse_time, placement_policy_from_container, wait_for_gc_pass_on_storage_nodes
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
check_nodes = []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@allure.title("Create container and pick the node with data")
|
||||
def create_container_and_pick_node(prepare_wallet_and_deposit, client_shell, hosting: Hosting):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path = generate_file()
|
||||
placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
|
||||
|
||||
cid = create_container(wallet, shell=client_shell, rule=placement_rule, basic_acl=PUBLIC_ACL)
|
||||
oid = put_object(wallet, file_path, cid, shell=client_shell)
|
||||
|
||||
nodes = get_nodes_with_object(wallet, cid, oid, shell=client_shell)
|
||||
assert len(nodes) == 1
|
||||
node = nodes[0]
|
||||
|
||||
node_name = choice(
|
||||
[node_name for node_name, params in NEOFS_NETMAP_DICT.items() if params.get("rpc") == node]
|
||||
)
|
||||
|
||||
yield cid, node_name
|
||||
|
||||
shards = node_shard_list(hosting, node_name)
|
||||
assert shards
|
||||
|
||||
for shard in shards:
|
||||
node_shard_set_mode(hosting, node_name, shard, "read-write")
|
||||
|
||||
node_shard_list(hosting, node_name)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def after_run_start_all_nodes(hosting: Hosting):
|
||||
yield
|
||||
try:
|
||||
start_nodes(hosting, list(NEOFS_NETMAP_DICT.keys()))
|
||||
except Exception as err:
|
||||
logger.error(f"Node start fails with error:\n{err}")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def return_nodes_after_test_run(client_shell: Shell, hosting: Hosting):
|
||||
yield
|
||||
return_nodes(client_shell, hosting)
|
||||
|
||||
|
||||
@allure.step("Tick epoch with retries")
|
||||
def tick_epoch_with_retries(shell: Shell, attempts: int = 3, timeout: int = 3):
|
||||
for __attempt in range(attempts):
|
||||
try:
|
||||
tick_epoch(shell=shell)
|
||||
except RuntimeError:
|
||||
sleep(timeout)
|
||||
continue
|
||||
return
|
||||
raise
|
||||
|
||||
|
||||
@allure.step("Return node to cluster")
|
||||
def return_nodes(shell: Shell, hosting: Hosting, alive_node: Optional[str] = None) -> None:
|
||||
for node in list(check_nodes):
|
||||
with allure.step(f"Start node {node}"):
|
||||
host = hosting.get_host_by_service(node)
|
||||
host.start_service(node)
|
||||
with allure.step(f"Waiting status ready for node {node}"):
|
||||
wait_for_node_to_be_ready(hosting, node)
|
||||
|
||||
# We need to wait for node to establish notifications from morph-chain
|
||||
# Otherwise it will hang up when we will try to set status
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
|
||||
with allure.step(f"Move node {node} to online state"):
|
||||
node_set_status(hosting, node, status="online", retries=2)
|
||||
|
||||
check_nodes.remove(node)
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch_with_retries(attempts=3)
|
||||
check_node_in_map(node, shell=shell, alive_node=alive_node)
|
||||
check_nodes: list[StorageNode] = []
|
||||
|
||||
|
||||
@allure.title("Add one node to cluster")
|
||||
@pytest.mark.add_nodes
|
||||
@pytest.mark.node_mgmt
|
||||
def test_add_nodes(
|
||||
prepare_tmp_dir,
|
||||
client_shell,
|
||||
prepare_wallet_and_deposit,
|
||||
return_nodes_after_test_run,
|
||||
hosting: Hosting,
|
||||
):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
placement_rule_3 = "REP 3 IN X CBF 1 SELECT 3 FROM * AS X"
|
||||
placement_rule_4 = "REP 4 IN X CBF 1 SELECT 4 FROM * AS X"
|
||||
source_file_path = generate_file()
|
||||
class TestNodeManagement(ClusterTestBase):
|
||||
@pytest.fixture
|
||||
@allure.title("Create container and pick the node with data")
|
||||
def create_container_and_pick_node(self, default_wallet: str) -> Tuple[str, StorageNode]:
|
||||
default_wallet
|
||||
file_path = generate_file()
|
||||
placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
|
||||
additional_node = choice(
|
||||
[
|
||||
node
|
||||
for node, node_config in NEOFS_NETMAP_DICT.items()
|
||||
if node_config.get("rpc") != STORAGE_RPC_ENDPOINT_1
|
||||
]
|
||||
)
|
||||
alive_node = choice([node for node in NEOFS_NETMAP_DICT if node != additional_node])
|
||||
|
||||
check_node_in_map(additional_node, shell=client_shell, alive_node=alive_node)
|
||||
|
||||
# Add node to recovery list before messing with it
|
||||
check_nodes.append(additional_node)
|
||||
exclude_node_from_network_map(hosting, additional_node, alive_node, shell=client_shell)
|
||||
delete_node_data(hosting, additional_node)
|
||||
|
||||
cid = create_container(wallet, rule=placement_rule_3, basic_acl=PUBLIC_ACL, shell=client_shell)
|
||||
oid = put_object(
|
||||
wallet,
|
||||
source_file_path,
|
||||
cid,
|
||||
endpoint=NEOFS_NETMAP_DICT[alive_node].get("rpc"),
|
||||
shell=client_shell,
|
||||
)
|
||||
wait_object_replication_on_nodes(wallet, cid, oid, 3, shell=client_shell)
|
||||
|
||||
return_nodes(shell=client_shell, hosting=hosting, alive_node=alive_node)
|
||||
|
||||
with allure.step("Check data could be replicated to new node"):
|
||||
random_node = choice(
|
||||
[node for node in NEOFS_NETMAP_DICT if node not in (additional_node, alive_node)]
|
||||
)
|
||||
exclude_node_from_network_map(hosting, random_node, alive_node, shell=client_shell)
|
||||
|
||||
wait_object_replication_on_nodes(
|
||||
wallet, cid, oid, 3, excluded_nodes=[random_node], shell=client_shell
|
||||
)
|
||||
include_node_to_network_map(hosting, random_node, alive_node, shell=client_shell)
|
||||
wait_object_replication_on_nodes(wallet, cid, oid, 3, shell=client_shell)
|
||||
|
||||
with allure.step("Check container could be created with new node"):
|
||||
cid = create_container(
|
||||
wallet, rule=placement_rule_4, basic_acl=PUBLIC_ACL, shell=client_shell
|
||||
default_wallet,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
rule=placement_rule,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
oid = put_object_to_random_node(default_wallet, file_path, cid, self.shell, self.cluster)
|
||||
|
||||
nodes = get_nodes_with_object(cid, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
|
||||
assert len(nodes) == 1
|
||||
node = nodes[0]
|
||||
|
||||
yield cid, node
|
||||
|
||||
shards = node_shard_list(node)
|
||||
assert shards
|
||||
|
||||
for shard in shards:
|
||||
node_shard_set_mode(node, shard, "read-write")
|
||||
|
||||
node_shard_list(node)
|
||||
|
||||
@allure.step("Tick epoch with retries")
|
||||
def tick_epoch_with_retries(self, attempts: int = 3, timeout: int = 3):
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
self.tick_epoch()
|
||||
except RuntimeError:
|
||||
sleep(timeout)
|
||||
if attempt >= attempts - 1:
|
||||
raise
|
||||
continue
|
||||
return
|
||||
|
||||
@pytest.fixture
|
||||
def after_run_start_all_nodes(self):
|
||||
yield
|
||||
self.return_nodes()
|
||||
|
||||
@pytest.fixture
|
||||
def return_nodes_after_test_run(self):
|
||||
yield
|
||||
self.return_nodes()
|
||||
|
||||
@allure.step("Return node to cluster")
|
||||
def return_nodes(self, alive_node: Optional[StorageNode] = None) -> None:
|
||||
for node in list(check_nodes):
|
||||
with allure.step(f"Start node {node}"):
|
||||
node.start_service()
|
||||
with allure.step(f"Waiting status ready for node {node}"):
|
||||
self.wait_for_node_to_be_ready(node)
|
||||
|
||||
# We need to wait for node to establish notifications from morph-chain
|
||||
# Otherwise it will hang up when we will try to set status
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
|
||||
with allure.step(f"Move node {node} to online state"):
|
||||
storage_node_set_status(node, status="online", retries=2)
|
||||
|
||||
check_nodes.remove(node)
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
self.tick_epoch_with_retries(3)
|
||||
check_node_in_map(node, shell=self.shell, alive_node=alive_node)
|
||||
|
||||
@allure.title("Add one node to cluster")
|
||||
@pytest.mark.add_nodes
|
||||
def test_add_nodes(
|
||||
self,
|
||||
default_wallet,
|
||||
return_nodes_after_test_run,
|
||||
):
|
||||
wallet = default_wallet
|
||||
placement_rule_3 = "REP 3 IN X CBF 1 SELECT 3 FROM * AS X"
|
||||
placement_rule_4 = "REP 4 IN X CBF 1 SELECT 4 FROM * AS X"
|
||||
source_file_path = generate_file()
|
||||
|
||||
storage_nodes = self.cluster.storage_nodes
|
||||
random_node = random.choice(storage_nodes[1:])
|
||||
alive_node = random.choice(
|
||||
[storage_node for storage_node in storage_nodes if storage_node.id != random_node.id]
|
||||
)
|
||||
|
||||
check_node_in_map(random_node, shell=self.shell, alive_node=alive_node)
|
||||
|
||||
# Add node to recovery list before messing with it
|
||||
check_nodes.append(random_node)
|
||||
exclude_node_from_network_map(
|
||||
random_node, alive_node, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
delete_node_data(random_node)
|
||||
|
||||
cid = create_container(
|
||||
wallet,
|
||||
rule=placement_rule_3,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
shell=self.shell,
|
||||
endpoint=alive_node.get_rpc_endpoint(),
|
||||
)
|
||||
oid = put_object(
|
||||
wallet,
|
||||
source_file_path,
|
||||
cid,
|
||||
endpoint=NEOFS_NETMAP_DICT[alive_node].get("rpc"),
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=alive_node.get_rpc_endpoint(),
|
||||
)
|
||||
wait_object_replication_on_nodes(wallet, cid, oid, 4, shell=client_shell)
|
||||
wait_object_replication(cid, oid, 3, shell=self.shell, nodes=storage_nodes)
|
||||
|
||||
self.return_nodes(alive_node)
|
||||
|
||||
@allure.title("Control Operations with storage nodes")
|
||||
@pytest.mark.node_mgmt
|
||||
def test_nodes_management(prepare_tmp_dir, client_shell, hosting: Hosting):
|
||||
"""
|
||||
This test checks base control operations with storage nodes (healthcheck, netmap-snapshot, set-status).
|
||||
"""
|
||||
random_node = choice(list(NEOFS_NETMAP_DICT))
|
||||
alive_node = choice([node for node in NEOFS_NETMAP_DICT if node != random_node])
|
||||
with allure.step("Check data could be replicated to new node"):
|
||||
random_node = random.choice(list(set(storage_nodes) - {random_node, alive_node}))
|
||||
# Add node to recovery list before messing with it
|
||||
check_nodes.append(random_node)
|
||||
exclude_node_from_network_map(
|
||||
random_node, alive_node, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
# Calculate public key that identifies node in netmap
|
||||
random_node_wallet_path = NEOFS_NETMAP_DICT[random_node]["wallet_path"]
|
||||
random_node_netmap_key = get_wallet_public_key(random_node_wallet_path, STORAGE_WALLET_PASS)
|
||||
wait_object_replication(
|
||||
cid,
|
||||
oid,
|
||||
3,
|
||||
shell=self.shell,
|
||||
nodes=list(set(storage_nodes) - {random_node}),
|
||||
)
|
||||
include_node_to_network_map(
|
||||
random_node, alive_node, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
wait_object_replication(cid, oid, 3, shell=self.shell, nodes=storage_nodes)
|
||||
|
||||
with allure.step("Check node {random_node} is in netmap"):
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
||||
with allure.step("Check container could be created with new node"):
|
||||
cid = create_container(
|
||||
wallet,
|
||||
rule=placement_rule_4,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
shell=self.shell,
|
||||
endpoint=alive_node.get_rpc_endpoint(),
|
||||
)
|
||||
oid = put_object(
|
||||
wallet,
|
||||
source_file_path,
|
||||
cid,
|
||||
shell=self.shell,
|
||||
endpoint=alive_node.get_rpc_endpoint(),
|
||||
)
|
||||
wait_object_replication(cid, oid, 4, shell=self.shell, nodes=storage_nodes)
|
||||
|
||||
with allure.step("Run health check for all storage nodes"):
|
||||
for node_name in NEOFS_NETMAP_DICT.keys():
|
||||
health_check = node_healthcheck(hosting, node_name)
|
||||
@allure.title("Control Operations with storage nodes")
|
||||
@pytest.mark.node_mgmt
|
||||
def test_nodes_management(self, temp_directory):
|
||||
"""
|
||||
This test checks base control operations with storage nodes (healthcheck, netmap-snapshot, set-status).
|
||||
"""
|
||||
|
||||
storage_nodes = self.cluster.storage_nodes
|
||||
random_node = random.choice(storage_nodes)
|
||||
alive_node = random.choice(list(set(storage_nodes) - {random_node}))
|
||||
|
||||
# Calculate public key that identifies node in netmap
|
||||
random_node_netmap_key = random_node.get_wallet_public_key()
|
||||
|
||||
with allure.step(f"Check node ({random_node}) is in netmap"):
|
||||
snapshot = get_netmap_snapshot(node=alive_node, shell=self.shell)
|
||||
assert (
|
||||
random_node_netmap_key in snapshot
|
||||
), f"Expected node {random_node} to be in netmap"
|
||||
|
||||
with allure.step("Run health check for all storage nodes"):
|
||||
for node in self.cluster.storage_nodes:
|
||||
health_check = storage_node_healthcheck(node)
|
||||
assert (
|
||||
health_check.health_status == "READY"
|
||||
and health_check.network_status == "ONLINE"
|
||||
)
|
||||
|
||||
with allure.step(f"Move node ({random_node}) to offline state"):
|
||||
storage_node_set_status(random_node, status="offline")
|
||||
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
|
||||
with allure.step(f"Check node {random_node} went to offline"):
|
||||
health_check = storage_node_healthcheck(random_node)
|
||||
assert (
|
||||
health_check.health_status == "READY" and health_check.network_status == "OFFLINE"
|
||||
)
|
||||
snapshot = get_netmap_snapshot(node=alive_node, shell=self.shell)
|
||||
assert (
|
||||
random_node_netmap_key not in snapshot
|
||||
), f"Expected node {random_node} not in netmap"
|
||||
|
||||
with allure.step(f"Check node {random_node} went to online"):
|
||||
storage_node_set_status(random_node, status="online")
|
||||
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
|
||||
with allure.step(f"Check node {random_node} went to online"):
|
||||
health_check = storage_node_healthcheck(random_node)
|
||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||
snapshot = get_netmap_snapshot(node=alive_node, shell=self.shell)
|
||||
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
||||
|
||||
with allure.step(f"Move node {random_node} to offline state"):
|
||||
node_set_status(hosting, random_node, status="offline")
|
||||
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=client_shell)
|
||||
|
||||
with allure.step(f"Check node {random_node} went to offline"):
|
||||
health_check = node_healthcheck(hosting, random_node)
|
||||
assert health_check.health_status == "READY" and health_check.network_status == "OFFLINE"
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||
assert random_node_netmap_key not in snapshot, f"Expected node {random_node} not in netmap"
|
||||
|
||||
with allure.step(f"Check node {random_node} went to online"):
|
||||
node_set_status(hosting, random_node, status="online")
|
||||
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=client_shell)
|
||||
|
||||
with allure.step(f"Check node {random_node} went to online"):
|
||||
health_check = node_healthcheck(hosting, random_node)
|
||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"placement_rule,expected_copies",
|
||||
[
|
||||
("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", 2),
|
||||
("REP 2 IN X CBF 1 SELECT 2 FROM * AS X", 2),
|
||||
("REP 3 IN X CBF 1 SELECT 3 FROM * AS X", 3),
|
||||
("REP 1 IN X CBF 1 SELECT 1 FROM * AS X", 1),
|
||||
("REP 1 IN X CBF 2 SELECT 1 FROM * AS X", 1),
|
||||
("REP 4 IN X CBF 1 SELECT 4 FROM * AS X", 4),
|
||||
("REP 2 IN X CBF 1 SELECT 4 FROM * AS X", 2),
|
||||
],
|
||||
)
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("Test object copies based on placement policy")
|
||||
def test_placement_policy(
|
||||
prepare_wallet_and_deposit, placement_rule, expected_copies, client_shell: Shell
|
||||
):
|
||||
"""
|
||||
This test checks object's copies based on container's placement policy.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path = generate_file()
|
||||
validate_object_copies(wallet, placement_rule, file_path, expected_copies, shell=client_shell)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"placement_rule,expected_copies,nodes",
|
||||
[
|
||||
("REP 4 IN X CBF 1 SELECT 4 FROM * AS X", 4, ["s01", "s02", "s03", "s04"]),
|
||||
(
|
||||
"REP 1 IN LOC_PLACE CBF 1 SELECT 1 FROM LOC_SW AS LOC_PLACE FILTER Country EQ Sweden AS LOC_SW",
|
||||
1,
|
||||
["s03"],
|
||||
),
|
||||
("REP 1 CBF 1 SELECT 1 FROM LOC_SPB FILTER 'UN-LOCODE' EQ 'RU LED' AS LOC_SPB", 1, ["s02"]),
|
||||
(
|
||||
"REP 1 IN LOC_SPB_PLACE REP 1 IN LOC_MSK_PLACE CBF 1 SELECT 1 FROM LOC_SPB AS LOC_SPB_PLACE "
|
||||
"SELECT 1 FROM LOC_MSK AS LOC_MSK_PLACE "
|
||||
"FILTER 'UN-LOCODE' EQ 'RU LED' AS LOC_SPB FILTER 'UN-LOCODE' EQ 'RU MOW' AS LOC_MSK",
|
||||
2,
|
||||
["s01", "s02"],
|
||||
),
|
||||
(
|
||||
"REP 4 CBF 1 SELECT 4 FROM LOC_EU FILTER Continent EQ Europe AS LOC_EU",
|
||||
4,
|
||||
["s01", "s02", "s03", "s04"],
|
||||
),
|
||||
(
|
||||
"REP 1 CBF 1 SELECT 1 FROM LOC_SPB "
|
||||
"FILTER 'UN-LOCODE' NE 'RU MOW' AND 'UN-LOCODE' NE 'SE STO' AND 'UN-LOCODE' NE 'FI HEL' AS LOC_SPB",
|
||||
1,
|
||||
["s02"],
|
||||
),
|
||||
(
|
||||
"REP 2 CBF 1 SELECT 2 FROM LOC_RU FILTER SubDivCode NE 'AB' AND SubDivCode NE '18' AS LOC_RU",
|
||||
2,
|
||||
["s01", "s02"],
|
||||
),
|
||||
(
|
||||
"REP 2 CBF 1 SELECT 2 FROM LOC_RU FILTER Country EQ 'Russia' AS LOC_RU",
|
||||
2,
|
||||
["s01", "s02"],
|
||||
),
|
||||
(
|
||||
"REP 2 CBF 1 SELECT 2 FROM LOC_EU FILTER Country NE 'Russia' AS LOC_EU",
|
||||
2,
|
||||
["s03", "s04"],
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("Test object copies and storage nodes based on placement policy")
|
||||
def test_placement_policy_with_nodes(
|
||||
prepare_wallet_and_deposit, placement_rule, expected_copies, nodes, client_shell: Shell
|
||||
):
|
||||
"""
|
||||
Based on container's placement policy check that storage nodes are piked correctly and object has
|
||||
correct copies amount.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path = generate_file()
|
||||
cid, oid, found_nodes = validate_object_copies(
|
||||
wallet, placement_rule, file_path, expected_copies, shell=client_shell
|
||||
@pytest.mark.parametrize(
|
||||
"placement_rule,expected_copies",
|
||||
[
|
||||
("REP 2 IN X CBF 2 SELECT 2 FROM * AS X", 2),
|
||||
("REP 2 IN X CBF 1 SELECT 2 FROM * AS X", 2),
|
||||
("REP 3 IN X CBF 1 SELECT 3 FROM * AS X", 3),
|
||||
("REP 1 IN X CBF 1 SELECT 1 FROM * AS X", 1),
|
||||
("REP 1 IN X CBF 2 SELECT 1 FROM * AS X", 1),
|
||||
("REP 4 IN X CBF 1 SELECT 4 FROM * AS X", 4),
|
||||
("REP 2 IN X CBF 1 SELECT 4 FROM * AS X", 2),
|
||||
],
|
||||
)
|
||||
expected_nodes = [NEOFS_NETMAP_DICT[node_name].get("rpc") for node_name in nodes]
|
||||
assert set(found_nodes) == set(
|
||||
expected_nodes
|
||||
), f"Expected nodes {expected_nodes}, got {found_nodes}"
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("Test object copies based on placement policy")
|
||||
def test_placement_policy(self, default_wallet, placement_rule, expected_copies):
|
||||
"""
|
||||
This test checks object's copies based on container's placement policy.
|
||||
"""
|
||||
wallet = default_wallet
|
||||
file_path = generate_file()
|
||||
self.validate_object_copies(wallet, placement_rule, file_path, expected_copies)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"placement_rule,expected_copies",
|
||||
[
|
||||
("REP 2 IN X CBF 2 SELECT 6 FROM * AS X", 2),
|
||||
],
|
||||
)
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("Negative cases for placement policy")
|
||||
def test_placement_policy_negative(
|
||||
prepare_wallet_and_deposit, placement_rule, expected_copies, client_shell: Shell
|
||||
):
|
||||
"""
|
||||
Negative test for placement policy.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path = generate_file()
|
||||
with pytest.raises(RuntimeError, match=".*not enough nodes to SELECT from.*"):
|
||||
validate_object_copies(
|
||||
wallet, placement_rule, file_path, expected_copies, shell=client_shell
|
||||
@pytest.mark.parametrize(
|
||||
"placement_rule,expected_copies,expected_nodes_id",
|
||||
[
|
||||
("REP 4 IN X CBF 1 SELECT 4 FROM * AS X", 4, {1, 2, 3, 4}),
|
||||
(
|
||||
"REP 1 IN LOC_PLACE CBF 1 SELECT 1 FROM LOC_SW AS LOC_PLACE FILTER Country EQ Sweden AS LOC_SW",
|
||||
1,
|
||||
{3},
|
||||
),
|
||||
(
|
||||
"REP 1 CBF 1 SELECT 1 FROM LOC_SPB FILTER 'UN-LOCODE' EQ 'RU LED' AS LOC_SPB",
|
||||
1,
|
||||
{2},
|
||||
),
|
||||
(
|
||||
"REP 1 IN LOC_SPB_PLACE REP 1 IN LOC_MSK_PLACE CBF 1 SELECT 1 FROM LOC_SPB AS LOC_SPB_PLACE "
|
||||
"SELECT 1 FROM LOC_MSK AS LOC_MSK_PLACE "
|
||||
"FILTER 'UN-LOCODE' EQ 'RU LED' AS LOC_SPB FILTER 'UN-LOCODE' EQ 'RU MOW' AS LOC_MSK",
|
||||
2,
|
||||
{1, 2},
|
||||
),
|
||||
(
|
||||
"REP 4 CBF 1 SELECT 4 FROM LOC_EU FILTER Continent EQ Europe AS LOC_EU",
|
||||
4,
|
||||
{1, 2, 3, 4},
|
||||
),
|
||||
(
|
||||
"REP 1 CBF 1 SELECT 1 FROM LOC_SPB "
|
||||
"FILTER 'UN-LOCODE' NE 'RU MOW' AND 'UN-LOCODE' NE 'SE STO' AND 'UN-LOCODE' NE 'FI HEL' AS LOC_SPB",
|
||||
1,
|
||||
{2},
|
||||
),
|
||||
(
|
||||
"REP 2 CBF 1 SELECT 2 FROM LOC_RU FILTER SubDivCode NE 'AB' AND SubDivCode NE '18' AS LOC_RU",
|
||||
2,
|
||||
{1, 2},
|
||||
),
|
||||
(
|
||||
"REP 2 CBF 1 SELECT 2 FROM LOC_RU FILTER Country EQ 'Russia' AS LOC_RU",
|
||||
2,
|
||||
{1, 2},
|
||||
),
|
||||
(
|
||||
"REP 2 CBF 1 SELECT 2 FROM LOC_EU FILTER Country NE 'Russia' AS LOC_EU",
|
||||
2,
|
||||
{3, 4},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("Test object copies and storage nodes based on placement policy")
|
||||
def test_placement_policy_with_nodes(
|
||||
self, default_wallet, placement_rule, expected_copies, expected_nodes_id: set[int]
|
||||
):
|
||||
"""
|
||||
Based on container's placement policy check that storage nodes are piked correctly and object has
|
||||
correct copies amount.
|
||||
"""
|
||||
wallet = default_wallet
|
||||
file_path = generate_file()
|
||||
cid, oid, found_nodes = self.validate_object_copies(
|
||||
wallet, placement_rule, file_path, expected_copies
|
||||
)
|
||||
|
||||
assert (
|
||||
found_nodes == expected_nodes_id
|
||||
), f"Expected nodes {expected_nodes_id}, got {found_nodes}"
|
||||
|
||||
@pytest.mark.skip(reason="We cover this scenario in failover tests")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("NeoFS object replication on node failover")
|
||||
def test_replication(
|
||||
prepare_wallet_and_deposit, client_shell: Shell, after_run_start_all_nodes, hosting: Hosting
|
||||
):
|
||||
"""
|
||||
Test checks object replication on storage not failover and come back.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path = generate_file()
|
||||
expected_nodes_count = 2
|
||||
|
||||
cid = create_container(wallet, basic_acl=PUBLIC_ACL)
|
||||
oid = put_object(wallet, file_path, cid)
|
||||
|
||||
nodes = get_nodes_with_object(wallet, cid, oid)
|
||||
assert (
|
||||
len(nodes) == expected_nodes_count
|
||||
), f"Expected {expected_nodes_count} copies, got {len(nodes)}"
|
||||
|
||||
node_names = [name for name, config in NEOFS_NETMAP_DICT.items() if config.get("rpc") in nodes]
|
||||
stopped_nodes = stop_nodes(hosting, 1, node_names)
|
||||
|
||||
wait_for_expected_object_copies(client_shell, wallet, cid, oid)
|
||||
|
||||
start_nodes(hosting, stopped_nodes)
|
||||
tick_epoch(shell=client_shell)
|
||||
|
||||
for node_name in node_names:
|
||||
wait_for_node_go_online(hosting, node_name)
|
||||
|
||||
wait_for_expected_object_copies(client_shell, wallet, cid, oid)
|
||||
|
||||
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("NeoFS object could be dropped using control command")
|
||||
def test_drop_object(prepare_wallet_and_deposit, client_shell: Shell, hosting: Hosting):
|
||||
"""
|
||||
Test checks object could be dropped using `neofs-cli control drop-objects` command.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path_simple, file_path_complex = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
|
||||
|
||||
locode = get_locode()
|
||||
rule = f"REP 1 CBF 1 SELECT 1 FROM * FILTER 'UN-LOCODE' EQ '{locode}' AS LOC"
|
||||
cid = create_container(wallet, rule=rule, shell=client_shell)
|
||||
oid_simple = put_object(wallet, file_path_simple, cid, shell=client_shell)
|
||||
oid_complex = put_object(wallet, file_path_complex, cid, shell=client_shell)
|
||||
|
||||
for oid in (oid_simple, oid_complex):
|
||||
get_object(wallet, cid, oid, client_shell)
|
||||
head_object(wallet, cid, oid, client_shell)
|
||||
|
||||
nodes = get_nodes_with_object(wallet, cid, oid_simple, shell=client_shell)
|
||||
node_name = choice(
|
||||
[name for name, config in NEOFS_NETMAP_DICT.items() if config.get("rpc") in nodes]
|
||||
@pytest.mark.parametrize(
|
||||
"placement_rule,expected_copies",
|
||||
[
|
||||
("REP 2 IN X CBF 2 SELECT 6 FROM * AS X", 2),
|
||||
],
|
||||
)
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("Negative cases for placement policy")
|
||||
def test_placement_policy_negative(self, default_wallet, placement_rule, expected_copies):
|
||||
"""
|
||||
Negative test for placement policy.
|
||||
"""
|
||||
wallet = default_wallet
|
||||
file_path = generate_file()
|
||||
with pytest.raises(RuntimeError, match=".*not enough nodes to SELECT from.*"):
|
||||
self.validate_object_copies(wallet, placement_rule, file_path, expected_copies)
|
||||
|
||||
for oid in (oid_simple, oid_complex):
|
||||
with allure.step(f"Drop object {oid}"):
|
||||
get_object(wallet, cid, oid, shell=client_shell)
|
||||
head_object(wallet, cid, oid, shell=client_shell)
|
||||
drop_object(hosting, node_name, cid, oid)
|
||||
wait_for_obj_dropped(wallet, cid, oid, client_shell, get_object)
|
||||
wait_for_obj_dropped(wallet, cid, oid, client_shell, head_object)
|
||||
@pytest.mark.node_mgmt
|
||||
@allure.title("NeoFS object could be dropped using control command")
|
||||
def test_drop_object(self, default_wallet):
|
||||
"""
|
||||
Test checks object could be dropped using `neofs-cli control drop-objects` command.
|
||||
"""
|
||||
wallet = default_wallet
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
file_path_simple, file_path_complex = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
|
||||
|
||||
locode = get_locode_from_random_node(self.cluster)
|
||||
rule = f"REP 1 CBF 1 SELECT 1 FROM * FILTER 'UN-LOCODE' EQ '{locode}' AS LOC"
|
||||
cid = create_container(wallet, rule=rule, shell=self.shell, endpoint=endpoint)
|
||||
oid_simple = put_object_to_random_node(
|
||||
wallet, file_path_simple, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
oid_complex = put_object_to_random_node(
|
||||
wallet, file_path_complex, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
|
||||
@pytest.mark.node_mgmt
|
||||
@pytest.mark.skip(reason="Need to clarify scenario")
|
||||
@allure.title("Control Operations with storage nodes")
|
||||
def test_shards(
|
||||
prepare_wallet_and_deposit,
|
||||
create_container_and_pick_node,
|
||||
client_shell: Shell,
|
||||
hosting: Hosting,
|
||||
):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
file_path = generate_file()
|
||||
for oid in (oid_simple, oid_complex):
|
||||
get_object_from_random_node(wallet, cid, oid, shell=self.shell, cluster=self.cluster)
|
||||
head_object(wallet, cid, oid, shell=self.shell, endpoint=endpoint)
|
||||
|
||||
cid, node_name = create_container_and_pick_node
|
||||
original_oid = put_object(wallet, file_path, cid, shell=client_shell)
|
||||
nodes_with_object = get_nodes_with_object(
|
||||
cid, oid_simple, shell=self.shell, nodes=self.cluster.storage_nodes
|
||||
)
|
||||
random_node = random.choice(nodes_with_object)
|
||||
|
||||
# for mode in ('read-only', 'degraded'):
|
||||
for mode in ("degraded",):
|
||||
shards = node_shard_list(hosting, node_name)
|
||||
assert shards
|
||||
for oid in (oid_simple, oid_complex):
|
||||
with allure.step(f"Drop object {oid}"):
|
||||
get_object_from_random_node(
|
||||
wallet, cid, oid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
head_object(wallet, cid, oid, shell=self.shell, endpoint=endpoint)
|
||||
drop_object(random_node, cid, oid)
|
||||
self.wait_for_obj_dropped(wallet, cid, oid, endpoint, get_object)
|
||||
self.wait_for_obj_dropped(wallet, cid, oid, endpoint, head_object)
|
||||
|
||||
for shard in shards:
|
||||
node_shard_set_mode(hosting, node_name, shard, mode)
|
||||
@pytest.mark.node_mgmt
|
||||
@pytest.mark.skip(reason="Need to clarify scenario")
|
||||
@allure.title("Control Operations with storage nodes")
|
||||
def test_shards(
|
||||
self,
|
||||
default_wallet,
|
||||
create_container_and_pick_node,
|
||||
):
|
||||
wallet = default_wallet
|
||||
file_path = generate_file()
|
||||
|
||||
shards = node_shard_list(hosting, node_name)
|
||||
assert shards
|
||||
cid, node = create_container_and_pick_node
|
||||
original_oid = put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
put_object(wallet, file_path, cid, shell=client_shell)
|
||||
# for mode in ('read-only', 'degraded'):
|
||||
for mode in ("degraded",):
|
||||
shards = node_shard_list(node)
|
||||
assert shards
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
delete_object(wallet, cid, original_oid, shell=client_shell)
|
||||
for shard in shards:
|
||||
node_shard_set_mode(node, shard, mode)
|
||||
|
||||
get_object(wallet, cid, original_oid, shell=client_shell)
|
||||
shards = node_shard_list(node)
|
||||
assert shards
|
||||
|
||||
for shard in shards:
|
||||
node_shard_set_mode(hosting, node_name, shard, "read-write")
|
||||
with pytest.raises(RuntimeError):
|
||||
put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster)
|
||||
|
||||
shards = node_shard_list(hosting, node_name)
|
||||
assert shards
|
||||
with pytest.raises(RuntimeError):
|
||||
delete_object(
|
||||
wallet, cid, original_oid, self.shell, self.cluster.default_rpc_endpoint
|
||||
)
|
||||
|
||||
oid = put_object(wallet, file_path, cid, shell=client_shell)
|
||||
delete_object(wallet, cid, oid, shell=client_shell)
|
||||
get_object_from_random_node(wallet, cid, original_oid, self.shell, self.cluster)
|
||||
|
||||
for shard in shards:
|
||||
node_shard_set_mode(node, shard, "read-write")
|
||||
|
||||
@allure.step("Validate object has {expected_copies} copies")
|
||||
def validate_object_copies(
|
||||
wallet: str, placement_rule: str, file_path: str, expected_copies: int, shell: Shell
|
||||
):
|
||||
cid = create_container(wallet, rule=placement_rule, basic_acl=PUBLIC_ACL, shell=shell)
|
||||
got_policy = placement_policy_from_container(
|
||||
get_container(wallet, cid, json_mode=False, shell=shell)
|
||||
)
|
||||
assert got_policy == placement_rule.replace(
|
||||
"'", ""
|
||||
), f"Expected \n{placement_rule} and got policy \n{got_policy} are the same"
|
||||
oid = put_object(wallet, file_path, cid, shell=shell)
|
||||
nodes = get_nodes_with_object(wallet, cid, oid, shell=shell)
|
||||
assert len(nodes) == expected_copies, f"Expected {expected_copies} copies, got {len(nodes)}"
|
||||
return cid, oid, nodes
|
||||
shards = node_shard_list(node)
|
||||
assert shards
|
||||
|
||||
oid = put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster)
|
||||
delete_object(wallet, cid, oid, self.shell, self.cluster.default_rpc_endpoint)
|
||||
|
||||
@allure.step("Wait for node {node_name} goes online")
|
||||
def wait_for_node_go_online(hosting: Hosting, node_name: str) -> None:
|
||||
timeout, attempts = 5, 20
|
||||
for _ in range(attempts):
|
||||
try:
|
||||
health_check = node_healthcheck(hosting, node_name)
|
||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||
return
|
||||
except Exception as err:
|
||||
logger.warning(f"Node {node_name} is not online:\n{err}")
|
||||
sleep(timeout)
|
||||
raise AssertionError(
|
||||
f"Node {node_name} hasn't gone to the READY and ONLINE state after {timeout * attempts} second"
|
||||
)
|
||||
@allure.step("Validate object has {expected_copies} copies")
|
||||
def validate_object_copies(
|
||||
self, wallet: str, placement_rule: str, file_path: str, expected_copies: int
|
||||
) -> set[int]:
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
cid = create_container(
|
||||
wallet, rule=placement_rule, basic_acl=PUBLIC_ACL, shell=self.shell, endpoint=endpoint
|
||||
)
|
||||
got_policy = placement_policy_from_container(
|
||||
get_container(wallet, cid, json_mode=False, shell=self.shell, endpoint=endpoint)
|
||||
)
|
||||
assert got_policy == placement_rule.replace(
|
||||
"'", ""
|
||||
), f"Expected \n{placement_rule} and got policy \n{got_policy} are the same"
|
||||
oid = put_object_to_random_node(
|
||||
wallet, file_path, cid, shell=self.shell, cluster=self.cluster
|
||||
)
|
||||
nodes = get_nodes_with_object(cid, oid, shell=self.shell, nodes=self.cluster.storage_nodes)
|
||||
nodes_id = {node.id for node in nodes}
|
||||
assert len(nodes) == expected_copies, f"Expected {expected_copies} copies, got {len(nodes)}"
|
||||
return cid, oid, nodes_id
|
||||
|
||||
|
||||
@allure.step("Wait for node {node_name} is ready")
|
||||
def wait_for_node_to_be_ready(hosting: Hosting, node_name: str) -> None:
|
||||
timeout, attempts = 30, 6
|
||||
for _ in range(attempts):
|
||||
try:
|
||||
health_check = node_healthcheck(hosting, node_name)
|
||||
if health_check.health_status == "READY":
|
||||
@allure.step("Wait for node {node} goes online")
|
||||
def wait_for_node_go_online(self, node: StorageNode) -> None:
|
||||
timeout, attempts = 5, 20
|
||||
for _ in range(attempts):
|
||||
try:
|
||||
health_check = storage_node_healthcheck(node)
|
||||
assert (
|
||||
health_check.health_status == "READY"
|
||||
and health_check.network_status == "ONLINE"
|
||||
)
|
||||
return
|
||||
except Exception as err:
|
||||
logger.warning(f"Node {node_name} is not ready:\n{err}")
|
||||
sleep(timeout)
|
||||
raise AssertionError(
|
||||
f"Node {node_name} hasn't gone to the READY state after {timeout * attempts} seconds"
|
||||
)
|
||||
except Exception as err:
|
||||
logger.warning(f"Node {node} is not online:\n{err}")
|
||||
sleep(timeout)
|
||||
raise AssertionError(
|
||||
f"Node {node} hasn't gone to the READY and ONLINE state after {timeout * attempts} second"
|
||||
)
|
||||
|
||||
@allure.step("Wait for node {node} is ready")
|
||||
def wait_for_node_to_be_ready(self, node: StorageNode) -> None:
|
||||
timeout, attempts = 30, 6
|
||||
for _ in range(attempts):
|
||||
try:
|
||||
health_check = storage_node_healthcheck(node)
|
||||
if health_check.health_status == "READY":
|
||||
return
|
||||
except Exception as err:
|
||||
logger.warning(f"Node {node} is not ready:\n{err}")
|
||||
sleep(timeout)
|
||||
raise AssertionError(
|
||||
f"Node {node} hasn't gone to the READY state after {timeout * attempts} seconds"
|
||||
)
|
||||
|
||||
@allure.step("Wait for {expected_copies} object copies in the wallet")
|
||||
def wait_for_expected_object_copies(
|
||||
shell: Shell, wallet: str, cid: str, oid: str, expected_copies: int = 2
|
||||
) -> None:
|
||||
for i in range(2):
|
||||
copies = get_simple_object_copies(wallet, cid, oid)
|
||||
if copies == expected_copies:
|
||||
break
|
||||
tick_epoch(shell=shell)
|
||||
sleep(parse_time(NEOFS_CONTRACT_CACHE_TIMEOUT))
|
||||
else:
|
||||
raise AssertionError(f"There are no {expected_copies} copies during time")
|
||||
@allure.step("Wait for {expected_copies} object copies in the wallet")
|
||||
def wait_for_expected_object_copies(
|
||||
self, wallet: str, cid: str, oid: str, expected_copies: int = 2
|
||||
) -> None:
|
||||
nodes = self.cluster.storage_nodes
|
||||
for _ in range(2):
|
||||
copies = get_simple_object_copies(wallet, cid, oid, self.shell, nodes)
|
||||
if copies == expected_copies:
|
||||
break
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
sleep(parse_time(NEOFS_CONTRACT_CACHE_TIMEOUT))
|
||||
else:
|
||||
raise AssertionError(f"There are no {expected_copies} copies during time")
|
||||
|
||||
@allure.step("Wait for object to be dropped")
|
||||
def wait_for_obj_dropped(self, wallet: str, cid: str, oid: str, endpoint: str, checker) -> None:
|
||||
for _ in range(3):
|
||||
try:
|
||||
checker(wallet, cid, oid, shell=self.shell, endpoint=endpoint)
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
except Exception as err:
|
||||
if error_matches_status(err, OBJECT_NOT_FOUND):
|
||||
return
|
||||
raise AssertionError(f'Expected "{OBJECT_NOT_FOUND}" error, got\n{err}')
|
||||
|
||||
@allure.step("Wait for object to be dropped")
|
||||
def wait_for_obj_dropped(wallet: str, cid: str, oid: str, shell: Shell, checker) -> None:
|
||||
for _ in range(3):
|
||||
try:
|
||||
checker(wallet, cid, oid, shell=shell)
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
except Exception as err:
|
||||
if error_matches_status(err, OBJECT_NOT_FOUND):
|
||||
return
|
||||
raise AssertionError(f'Expected "{OBJECT_NOT_FOUND}" error, got\n{err}')
|
||||
|
||||
raise AssertionError(f"Object {oid} was not dropped from node")
|
||||
raise AssertionError(f"Object {oid} was not dropped from node")
|
||||
|
|
|
@ -4,6 +4,7 @@ import sys
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from cluster import Cluster
|
||||
from common import COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE
|
||||
from container import create_container
|
||||
from file_helper import generate_file, get_file_content, get_file_hash
|
||||
|
@ -12,16 +13,17 @@ from neofs_testlib.shell import Shell
|
|||
from pytest import FixtureRequest
|
||||
from python_keywords.neofs_verbs import (
|
||||
get_netmap_netinfo,
|
||||
get_object,
|
||||
get_object_from_random_node,
|
||||
get_range,
|
||||
get_range_hash,
|
||||
head_object,
|
||||
put_object,
|
||||
put_object_to_random_node,
|
||||
search_object,
|
||||
)
|
||||
from python_keywords.storage_policy import get_complex_object_copies, get_simple_object_copies
|
||||
|
||||
from helpers.storage_object_info import StorageObjectInfo
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
from steps.storage_object import delete_objects
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
@ -87,11 +89,11 @@ def generate_ranges(file_size: int, max_object_size: int) -> list[(int, int)]:
|
|||
scope="module",
|
||||
)
|
||||
def storage_objects(
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest
|
||||
default_wallet: str, client_shell: Shell, cluster: Cluster, request: FixtureRequest
|
||||
) -> list[StorageObjectInfo]:
|
||||
wallet = prepare_wallet_and_deposit
|
||||
wallet = default_wallet
|
||||
# Separate containers for complex/simple objects to avoid side-effects
|
||||
cid = create_container(wallet, shell=client_shell)
|
||||
cid = create_container(wallet, shell=client_shell, endpoint=cluster.default_rpc_endpoint)
|
||||
|
||||
file_path = generate_file(request.param)
|
||||
file_hash = get_file_hash(file_path)
|
||||
|
@ -101,11 +103,12 @@ def storage_objects(
|
|||
with allure.step("Put objects"):
|
||||
# We need to upload objects multiple times with different attributes
|
||||
for attributes in OBJECT_ATTRIBUTES:
|
||||
storage_object_id = put_object(
|
||||
storage_object_id = put_object_to_random_node(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
cluster=cluster,
|
||||
attributes=attributes,
|
||||
)
|
||||
|
||||
|
@ -121,357 +124,390 @@ def storage_objects(
|
|||
yield storage_objects
|
||||
|
||||
# Teardown after all tests done with current param
|
||||
delete_objects(storage_objects, client_shell)
|
||||
delete_objects(storage_objects, client_shell, cluster)
|
||||
|
||||
|
||||
@allure.title("Validate object storage policy by native API")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_storage_policies(
|
||||
client_shell: Shell, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate object storage policy
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate object storage policy by native API for {request.node.callspec.id}"
|
||||
)
|
||||
class TestObjectApi(ClusterTestBase):
|
||||
@allure.title("Validate object storage policy by native API")
|
||||
def test_object_storage_policies(
|
||||
self, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate object storage policy
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate object storage policy by native API for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
with allure.step("Validate storage policy for objects"):
|
||||
for storage_object in storage_objects:
|
||||
if storage_object.size == SIMPLE_OBJ_SIZE:
|
||||
copies = get_simple_object_copies(
|
||||
with allure.step("Validate storage policy for objects"):
|
||||
for storage_object in storage_objects:
|
||||
if storage_object.size == SIMPLE_OBJ_SIZE:
|
||||
copies = get_simple_object_copies(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell=self.shell,
|
||||
nodes=self.cluster.storage_nodes,
|
||||
)
|
||||
else:
|
||||
copies = get_complex_object_copies(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell=self.shell,
|
||||
nodes=self.cluster.storage_nodes,
|
||||
)
|
||||
assert copies == 2, "Expected 2 copies"
|
||||
|
||||
@allure.title("Validate get object native API")
|
||||
def test_get_object_api(
|
||||
self, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate get object native API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate get object native API for {request.node.callspec.id}")
|
||||
|
||||
with allure.step("Get objects and compare hashes"):
|
||||
for storage_object in storage_objects:
|
||||
file_path = get_object_from_random_node(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell=client_shell,
|
||||
self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
else:
|
||||
copies = get_complex_object_copies(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell=client_shell,
|
||||
)
|
||||
assert copies == 2, "Expected 2 copies"
|
||||
file_hash = get_file_hash(file_path)
|
||||
assert storage_object.file_hash == file_hash
|
||||
|
||||
@allure.title("Validate head object native API")
|
||||
def test_head_object_api(
|
||||
self, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate head object native API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate head object by native API for {request.node.callspec.id}")
|
||||
|
||||
@allure.title("Validate get object native API")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_get_object_api(
|
||||
client_shell: Shell, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate get object native API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate get object native API for {request.node.callspec.id}")
|
||||
storage_object_1 = storage_objects[0]
|
||||
storage_object_2 = storage_objects[1]
|
||||
|
||||
with allure.step("Get objects and compare hashes"):
|
||||
for storage_object in storage_objects:
|
||||
file_path = get_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
with allure.step("Head object and validate"):
|
||||
head_object(
|
||||
storage_object_1.wallet_file_path,
|
||||
storage_object_1.cid,
|
||||
storage_object_1.oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
file_hash = get_file_hash(file_path)
|
||||
assert storage_object.file_hash == file_hash
|
||||
head_info = head_object(
|
||||
storage_object_2.wallet_file_path,
|
||||
storage_object_2.cid,
|
||||
storage_object_2.oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
self.check_header_is_presented(head_info, storage_object_2.attributes)
|
||||
|
||||
@allure.title("Validate object search by native API")
|
||||
def test_search_object_api(
|
||||
self, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate object search by native API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate object search by native API for {request.node.callspec.id}")
|
||||
|
||||
@allure.title("Validate head object native API")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_head_object_api(
|
||||
client_shell: Shell, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate head object native API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate head object by native API for {request.node.callspec.id}")
|
||||
oids = [storage_object.oid for storage_object in storage_objects]
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
|
||||
storage_object_1 = storage_objects[0]
|
||||
storage_object_2 = storage_objects[1]
|
||||
test_table = [
|
||||
(OBJECT_ATTRIBUTES[1], oids[1:2]),
|
||||
(OBJECT_ATTRIBUTES[2], oids[2:3]),
|
||||
(COMMON_ATTRIBUTE, oids[1:3]),
|
||||
]
|
||||
|
||||
with allure.step("Head object and validate"):
|
||||
head_object(
|
||||
storage_object_1.wallet_file_path,
|
||||
storage_object_1.cid,
|
||||
storage_object_1.oid,
|
||||
shell=client_shell,
|
||||
)
|
||||
head_info = head_object(
|
||||
storage_object_2.wallet_file_path,
|
||||
storage_object_2.cid,
|
||||
storage_object_2.oid,
|
||||
shell=client_shell,
|
||||
)
|
||||
check_header_is_presented(head_info, storage_object_2.attributes)
|
||||
|
||||
|
||||
@allure.title("Validate object search by native API")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_search_object_api(
|
||||
client_shell: Shell, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate object search by native API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate object search by native API for {request.node.callspec.id}")
|
||||
|
||||
oids = [storage_object.oid for storage_object in storage_objects]
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
|
||||
test_table = [
|
||||
(OBJECT_ATTRIBUTES[1], oids[1:2]),
|
||||
(OBJECT_ATTRIBUTES[2], oids[2:3]),
|
||||
(COMMON_ATTRIBUTE, oids[1:3]),
|
||||
]
|
||||
|
||||
with allure.step("Search objects"):
|
||||
# Search with no attributes
|
||||
result = search_object(
|
||||
wallet, cid, shell=client_shell, expected_objects_list=oids, root=True
|
||||
)
|
||||
assert sorted(oids) == sorted(result)
|
||||
|
||||
# search by test table
|
||||
for filter, expected_oids in test_table:
|
||||
with allure.step("Search objects"):
|
||||
# Search with no attributes
|
||||
result = search_object(
|
||||
wallet,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
filters=filter,
|
||||
expected_objects_list=expected_oids,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
expected_objects_list=oids,
|
||||
root=True,
|
||||
)
|
||||
assert sorted(expected_oids) == sorted(result)
|
||||
assert sorted(oids) == sorted(result)
|
||||
|
||||
# search by test table
|
||||
for filter, expected_oids in test_table:
|
||||
result = search_object(
|
||||
wallet,
|
||||
cid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
filters=filter,
|
||||
expected_objects_list=expected_oids,
|
||||
root=True,
|
||||
)
|
||||
assert sorted(expected_oids) == sorted(result)
|
||||
|
||||
@allure.title("Validate object search with removed items")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
@pytest.mark.parametrize(
|
||||
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
|
||||
)
|
||||
def test_object_search_should_return_tombstone_items(
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest, object_size: int
|
||||
):
|
||||
"""
|
||||
Validate object search with removed items
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate object search with removed items for {request.node.callspec.id}"
|
||||
@allure.title("Validate object search with removed items")
|
||||
@pytest.mark.parametrize(
|
||||
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
|
||||
)
|
||||
|
||||
wallet = prepare_wallet_and_deposit
|
||||
cid = create_container(wallet, shell=client_shell)
|
||||
|
||||
with allure.step("Upload file"):
|
||||
file_path = generate_file(object_size)
|
||||
file_hash = get_file_hash(file_path)
|
||||
|
||||
storage_object = StorageObjectInfo(
|
||||
cid=cid,
|
||||
oid=put_object(wallet, file_path, cid, shell=client_shell),
|
||||
size=object_size,
|
||||
wallet_file_path=wallet,
|
||||
file_path=file_path,
|
||||
file_hash=file_hash,
|
||||
def test_object_search_should_return_tombstone_items(
|
||||
self, default_wallet: str, request: FixtureRequest, object_size: int
|
||||
):
|
||||
"""
|
||||
Validate object search with removed items
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate object search with removed items for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
with allure.step("Search object"):
|
||||
# Root Search object should return root object oid
|
||||
result = search_object(wallet, cid, shell=client_shell, root=True)
|
||||
assert result == [storage_object.oid]
|
||||
wallet = default_wallet
|
||||
cid = create_container(wallet, self.shell, self.cluster.default_rpc_endpoint)
|
||||
|
||||
with allure.step("Delete file"):
|
||||
delete_objects([storage_object], client_shell)
|
||||
with allure.step("Upload file"):
|
||||
file_path = generate_file(object_size)
|
||||
file_hash = get_file_hash(file_path)
|
||||
|
||||
with allure.step("Search deleted object with --root"):
|
||||
# Root Search object should return nothing
|
||||
result = search_object(wallet, cid, shell=client_shell, root=True)
|
||||
assert len(result) == 0
|
||||
storage_object = StorageObjectInfo(
|
||||
cid=cid,
|
||||
oid=put_object_to_random_node(wallet, file_path, cid, self.shell, self.cluster),
|
||||
size=object_size,
|
||||
wallet_file_path=wallet,
|
||||
file_path=file_path,
|
||||
file_hash=file_hash,
|
||||
)
|
||||
|
||||
with allure.step("Search deleted object with --phy should return only tombstones"):
|
||||
# Physical Search object should return only tombstones
|
||||
result = search_object(wallet, cid, shell=client_shell, phy=True)
|
||||
assert (
|
||||
storage_object.tombstone in result
|
||||
), f"Search result should contain tombstone of removed object"
|
||||
assert (
|
||||
storage_object.oid not in result
|
||||
), f"Search result should not contain ObjectId of removed object"
|
||||
for tombstone_oid in result:
|
||||
header = head_object(wallet, cid, tombstone_oid, shell=client_shell)["header"]
|
||||
object_type = header["objectType"]
|
||||
with allure.step("Search object"):
|
||||
# Root Search object should return root object oid
|
||||
result = search_object(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, root=True
|
||||
)
|
||||
assert result == [storage_object.oid]
|
||||
|
||||
with allure.step("Delete file"):
|
||||
delete_objects([storage_object], self.shell, self.cluster)
|
||||
|
||||
with allure.step("Search deleted object with --root"):
|
||||
# Root Search object should return nothing
|
||||
result = search_object(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, root=True
|
||||
)
|
||||
assert len(result) == 0
|
||||
|
||||
with allure.step("Search deleted object with --phy should return only tombstones"):
|
||||
# Physical Search object should return only tombstones
|
||||
result = search_object(
|
||||
wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint, phy=True
|
||||
)
|
||||
assert (
|
||||
object_type == "TOMBSTONE"
|
||||
), f"Object wasn't deleted properly. Found object {tombstone_oid} with type {object_type}"
|
||||
|
||||
|
||||
@allure.title("Validate native object API get_range_hash")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range_hash(
|
||||
client_shell: Shell, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate get_range_hash for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate native get_range_hash object API for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_path = storage_objects[0].file_path
|
||||
net_info = get_netmap_netinfo(wallet, client_shell)
|
||||
max_object_size = net_info["maximum_object_size"]
|
||||
|
||||
file_ranges_to_test = generate_ranges(storage_objects[0].size, max_object_size)
|
||||
logging.info(f"Ranges used in test {file_ranges_to_test}")
|
||||
|
||||
for range_start, range_end in file_ranges_to_test:
|
||||
range_len = range_end - range_start
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range hash ({range_cut})"):
|
||||
for oid in oids:
|
||||
range_hash = get_range_hash(
|
||||
wallet, cid, oid, shell=client_shell, range_cut=range_cut
|
||||
)
|
||||
storage_object.tombstone in result
|
||||
), "Search result should contain tombstone of removed object"
|
||||
assert (
|
||||
storage_object.oid not in result
|
||||
), "Search result should not contain ObjectId of removed object"
|
||||
for tombstone_oid in result:
|
||||
header = head_object(
|
||||
wallet,
|
||||
cid,
|
||||
tombstone_oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)["header"]
|
||||
object_type = header["objectType"]
|
||||
assert (
|
||||
get_file_hash(file_path, range_len, range_start) == range_hash
|
||||
), f"Expected range hash to match {range_cut} slice of file payload"
|
||||
object_type == "TOMBSTONE"
|
||||
), f"Object wasn't deleted properly. Found object {tombstone_oid} with type {object_type}"
|
||||
|
||||
@allure.title("Validate native object API get_range_hash")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range_hash(
|
||||
self, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate get_range_hash for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate native get_range_hash object API for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
@allure.title("Validate native object API get_range")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range(
|
||||
client_shell: Shell, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate get_range for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate native get_range object API for {request.node.callspec.id}")
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_path = storage_objects[0].file_path
|
||||
net_info = get_netmap_netinfo(
|
||||
wallet, self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
max_object_size = net_info["maximum_object_size"]
|
||||
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_path = storage_objects[0].file_path
|
||||
net_info = get_netmap_netinfo(wallet, client_shell)
|
||||
max_object_size = net_info["maximum_object_size"]
|
||||
file_ranges_to_test = generate_ranges(storage_objects[0].size, max_object_size)
|
||||
logging.info(f"Ranges used in test {file_ranges_to_test}")
|
||||
|
||||
file_ranges_to_test = generate_ranges(storage_objects[0].size, max_object_size)
|
||||
logging.info(f"Ranges used in test {file_ranges_to_test}")
|
||||
|
||||
for range_start, range_end in file_ranges_to_test:
|
||||
range_len = range_end - range_start
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range ({range_cut})"):
|
||||
for oid in oids:
|
||||
_, range_content = get_range(
|
||||
wallet, cid, oid, shell=client_shell, range_cut=range_cut
|
||||
)
|
||||
assert (
|
||||
get_file_content(
|
||||
file_path, content_len=range_len, mode="rb", offset=range_start
|
||||
for range_start, range_end in file_ranges_to_test:
|
||||
range_len = range_end - range_start
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range hash ({range_cut})"):
|
||||
for oid in oids:
|
||||
range_hash = get_range_hash(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
range_cut=range_cut,
|
||||
)
|
||||
== range_content
|
||||
), f"Expected range content to match {range_cut} slice of file payload"
|
||||
assert (
|
||||
get_file_hash(file_path, range_len, range_start) == range_hash
|
||||
), f"Expected range hash to match {range_cut} slice of file payload"
|
||||
|
||||
@allure.title("Validate native object API get_range")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range(
|
||||
self, request: FixtureRequest, storage_objects: list[StorageObjectInfo]
|
||||
):
|
||||
"""
|
||||
Validate get_range for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(f"Validate native get_range object API for {request.node.callspec.id}")
|
||||
|
||||
@allure.title("Validate native object API get_range negative cases")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range_negatives(
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
storage_objects: list[StorageObjectInfo],
|
||||
):
|
||||
"""
|
||||
Validate get_range negative for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate native get_range negative object API for {request.node.callspec.id}"
|
||||
)
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_path = storage_objects[0].file_path
|
||||
net_info = get_netmap_netinfo(
|
||||
wallet, self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
max_object_size = net_info["maximum_object_size"]
|
||||
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_size = storage_objects[0].size
|
||||
file_ranges_to_test = generate_ranges(storage_objects[0].size, max_object_size)
|
||||
logging.info(f"Ranges used in test {file_ranges_to_test}")
|
||||
|
||||
assert (
|
||||
RANGE_MIN_LEN < file_size
|
||||
), f"Incorrect test setup. File size ({file_size}) is less than RANGE_MIN_LEN ({RANGE_MIN_LEN})"
|
||||
for range_start, range_end in file_ranges_to_test:
|
||||
range_len = range_end - range_start
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range ({range_cut})"):
|
||||
for oid in oids:
|
||||
_, range_content = get_range(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
range_cut=range_cut,
|
||||
)
|
||||
assert (
|
||||
get_file_content(
|
||||
file_path, content_len=range_len, mode="rb", offset=range_start
|
||||
)
|
||||
== range_content
|
||||
), f"Expected range content to match {range_cut} slice of file payload"
|
||||
|
||||
file_ranges_to_test = [
|
||||
# Offset is bigger than the file size, the length is small.
|
||||
(file_size + 1, RANGE_MIN_LEN),
|
||||
# Offset is ok, but offset+length is too big.
|
||||
(file_size - RANGE_MIN_LEN, RANGE_MIN_LEN * 2),
|
||||
# Offset is ok, and length is very-very big (e.g. MaxUint64) so that offset+length is wrapped and still "valid".
|
||||
(RANGE_MIN_LEN, sys.maxsize * 2 + 1),
|
||||
]
|
||||
@allure.title("Validate native object API get_range negative cases")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range_negatives(
|
||||
self,
|
||||
request: FixtureRequest,
|
||||
storage_objects: list[StorageObjectInfo],
|
||||
):
|
||||
"""
|
||||
Validate get_range negative for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate native get_range negative object API for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
for range_start, range_len in file_ranges_to_test:
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range ({range_cut})"):
|
||||
for oid in oids:
|
||||
with pytest.raises(Exception, match=OUT_OF_RANGE):
|
||||
get_range(wallet, cid, oid, shell=client_shell, range_cut=range_cut)
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_size = storage_objects[0].size
|
||||
|
||||
|
||||
@allure.title("Validate native object API get_range_hash negative cases")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
def test_object_get_range_hash_negatives(
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
storage_objects: list[StorageObjectInfo],
|
||||
):
|
||||
"""
|
||||
Validate get_range_hash negative for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate native get_range_hash negative object API for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_size = storage_objects[0].size
|
||||
|
||||
assert (
|
||||
RANGE_MIN_LEN < file_size
|
||||
), f"Incorrect test setup. File size ({file_size}) is less than RANGE_MIN_LEN ({RANGE_MIN_LEN})"
|
||||
|
||||
file_ranges_to_test = [
|
||||
# Offset is bigger than the file size, the length is small.
|
||||
(file_size + 1, RANGE_MIN_LEN),
|
||||
# Offset is ok, but offset+length is too big.
|
||||
(file_size - RANGE_MIN_LEN, RANGE_MIN_LEN * 2),
|
||||
# Offset is ok, and length is very-very big (e.g. MaxUint64) so that offset+length is wrapped and still "valid".
|
||||
(RANGE_MIN_LEN, sys.maxsize * 2 + 1),
|
||||
]
|
||||
|
||||
for range_start, range_len in file_ranges_to_test:
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range ({range_cut})"):
|
||||
for oid in oids:
|
||||
with pytest.raises(Exception, match=OUT_OF_RANGE):
|
||||
get_range_hash(wallet, cid, oid, shell=client_shell, range_cut=range_cut)
|
||||
|
||||
|
||||
def check_header_is_presented(head_info: dict, object_header: dict) -> None:
|
||||
for key_to_check, val_to_check in object_header.items():
|
||||
assert (
|
||||
key_to_check in head_info["header"]["attributes"]
|
||||
), f"Key {key_to_check} is found in {head_object}"
|
||||
assert head_info["header"]["attributes"].get(key_to_check) == str(
|
||||
val_to_check
|
||||
), f"Value {val_to_check} is equal"
|
||||
RANGE_MIN_LEN < file_size
|
||||
), f"Incorrect test setup. File size ({file_size}) is less than RANGE_MIN_LEN ({RANGE_MIN_LEN})"
|
||||
|
||||
file_ranges_to_test = [
|
||||
# Offset is bigger than the file size, the length is small.
|
||||
(file_size + 1, RANGE_MIN_LEN),
|
||||
# Offset is ok, but offset+length is too big.
|
||||
(file_size - RANGE_MIN_LEN, RANGE_MIN_LEN * 2),
|
||||
# Offset is ok, and length is very-very big (e.g. MaxUint64) so that offset+length is wrapped and still "valid".
|
||||
(RANGE_MIN_LEN, sys.maxsize * 2 + 1),
|
||||
]
|
||||
|
||||
for range_start, range_len in file_ranges_to_test:
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range ({range_cut})"):
|
||||
for oid in oids:
|
||||
with pytest.raises(Exception, match=OUT_OF_RANGE):
|
||||
get_range(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
range_cut=range_cut,
|
||||
)
|
||||
|
||||
@allure.title("Validate native object API get_range_hash negative cases")
|
||||
def test_object_get_range_hash_negatives(
|
||||
self,
|
||||
request: FixtureRequest,
|
||||
storage_objects: list[StorageObjectInfo],
|
||||
):
|
||||
"""
|
||||
Validate get_range_hash negative for object by common gRPC API
|
||||
"""
|
||||
allure.dynamic.title(
|
||||
f"Validate native get_range_hash negative object API for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
wallet = storage_objects[0].wallet_file_path
|
||||
cid = storage_objects[0].cid
|
||||
oids = [storage_object.oid for storage_object in storage_objects[:2]]
|
||||
file_size = storage_objects[0].size
|
||||
|
||||
assert (
|
||||
RANGE_MIN_LEN < file_size
|
||||
), f"Incorrect test setup. File size ({file_size}) is less than RANGE_MIN_LEN ({RANGE_MIN_LEN})"
|
||||
|
||||
file_ranges_to_test = [
|
||||
# Offset is bigger than the file size, the length is small.
|
||||
(file_size + 1, RANGE_MIN_LEN),
|
||||
# Offset is ok, but offset+length is too big.
|
||||
(file_size - RANGE_MIN_LEN, RANGE_MIN_LEN * 2),
|
||||
# Offset is ok, and length is very-very big (e.g. MaxUint64) so that offset+length is wrapped and still "valid".
|
||||
(RANGE_MIN_LEN, sys.maxsize * 2 + 1),
|
||||
]
|
||||
|
||||
for range_start, range_len in file_ranges_to_test:
|
||||
range_cut = f"{range_start}:{range_len}"
|
||||
with allure.step(f"Get range ({range_cut})"):
|
||||
for oid in oids:
|
||||
with pytest.raises(Exception, match=OUT_OF_RANGE):
|
||||
get_range_hash(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
range_cut=range_cut,
|
||||
)
|
||||
|
||||
def check_header_is_presented(self, head_info: dict, object_header: dict) -> None:
|
||||
for key_to_check, val_to_check in object_header.items():
|
||||
assert (
|
||||
key_to_check in head_info["header"]["attributes"]
|
||||
), f"Key {key_to_check} is found in {head_object}"
|
||||
assert head_info["header"]["attributes"].get(key_to_check) == str(
|
||||
val_to_check
|
||||
), f"Value {val_to_check} is equal"
|
||||
|
|
|
@ -7,46 +7,52 @@ from container import create_container
|
|||
from epoch import get_epoch, tick_epoch
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from grpc_responses import OBJECT_NOT_FOUND
|
||||
from neofs_testlib.shell import Shell
|
||||
from pytest import FixtureRequest
|
||||
from python_keywords.neofs_verbs import get_object, put_object
|
||||
from python_keywords.neofs_verbs import get_object_from_random_node, put_object_to_random_node
|
||||
from utility import wait_for_gc_pass_on_storage_nodes
|
||||
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
||||
@allure.title("Test object life time")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_api
|
||||
@pytest.mark.parametrize(
|
||||
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
|
||||
)
|
||||
def test_object_api_lifetime(
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest, object_size: int
|
||||
):
|
||||
"""
|
||||
Test object deleted after expiration epoch.
|
||||
"""
|
||||
wallet = prepare_wallet_and_deposit
|
||||
cid = create_container(wallet, shell=client_shell)
|
||||
class ObjectApiLifetimeTest(ClusterTestBase):
|
||||
@allure.title("Test object life time")
|
||||
@pytest.mark.parametrize(
|
||||
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
|
||||
)
|
||||
def test_object_api_lifetime(
|
||||
self, default_wallet: str, request: FixtureRequest, object_size: int
|
||||
):
|
||||
"""
|
||||
Test object deleted after expiration epoch.
|
||||
"""
|
||||
|
||||
allure.dynamic.title(f"Test object life time for {request.node.callspec.id}")
|
||||
allure.dynamic.title(f"Test object life time for {request.node.callspec.id}")
|
||||
|
||||
file_path = generate_file(object_size)
|
||||
file_hash = get_file_hash(file_path)
|
||||
epoch = get_epoch(shell=client_shell)
|
||||
wallet = default_wallet
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
cid = create_container(wallet, self.shell, endpoint)
|
||||
|
||||
oid = put_object(wallet, file_path, cid, shell=client_shell, expire_at=epoch + 1)
|
||||
got_file = get_object(wallet, cid, oid, shell=client_shell)
|
||||
assert get_file_hash(got_file) == file_hash
|
||||
file_path = generate_file(object_size)
|
||||
file_hash = get_file_hash(file_path)
|
||||
epoch = get_epoch(self.shell, self.cluster)
|
||||
|
||||
with allure.step("Tick two epochs"):
|
||||
for _ in range(2):
|
||||
tick_epoch(shell=client_shell)
|
||||
oid = put_object_to_random_node(
|
||||
wallet, file_path, cid, self.shell, self.cluster, expire_at=epoch + 1
|
||||
)
|
||||
got_file = get_object_from_random_node(wallet, cid, oid, self.shell, self.cluster)
|
||||
assert get_file_hash(got_file) == file_hash
|
||||
|
||||
# Wait for GC, because object with expiration is counted as alive until GC removes it
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
with allure.step("Tick two epochs"):
|
||||
for _ in range(2):
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
|
||||
with allure.step("Check object deleted because it expires-on epoch"):
|
||||
with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
|
||||
get_object(wallet, cid, oid, shell=client_shell)
|
||||
# Wait for GC, because object with expiration is counted as alive until GC removes it
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
|
||||
with allure.step("Check object deleted because it expires-on epoch"):
|
||||
with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
|
||||
get_object_from_random_node(wallet, cid, oid, self.shell, self.cluster)
|
||||
|
|
|
@ -3,6 +3,8 @@ import re
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from cluster import Cluster
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from common import COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE, STORAGE_GC_TIME
|
||||
from complex_object_actions import get_link_object
|
||||
from container import create_container
|
||||
|
@ -22,9 +24,11 @@ from python_keywords.neofs_verbs import delete_object, head_object, lock_object
|
|||
from test_control import expect_not_raises, wait_for_success
|
||||
from utility import parse_time, wait_for_gc_pass_on_storage_nodes
|
||||
|
||||
import steps
|
||||
from helpers.container import StorageContainer, StorageContainerInfo
|
||||
from helpers.storage_object_info import LockObjectInfo, StorageObjectInfo
|
||||
from helpers.wallet import WalletFactory, WalletFile
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
from steps.storage_object import delete_objects
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
@ -33,25 +37,6 @@ FIXTURE_LOCK_LIFETIME = 5
|
|||
FIXTURE_OBJECT_LIFETIME = 10
|
||||
|
||||
|
||||
def get_storage_object_chunks(storage_object: StorageObjectInfo, shell: Shell):
|
||||
with allure.step(f"Get complex object chunks (f{storage_object.oid})"):
|
||||
split_object_id = get_link_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
shell,
|
||||
is_direct=False,
|
||||
)
|
||||
head = head_object(
|
||||
storage_object.wallet_file_path, storage_object.cid, split_object_id, shell
|
||||
)
|
||||
|
||||
chunks_object_ids = []
|
||||
if "split" in head["header"] and "children" in head["header"]["split"]:
|
||||
chunks_object_ids = head["header"]["split"]["children"]
|
||||
return chunks_object_ids
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
scope="module",
|
||||
)
|
||||
|
@ -64,9 +49,11 @@ def user_wallet(wallet_factory: WalletFactory):
|
|||
@pytest.fixture(
|
||||
scope="module",
|
||||
)
|
||||
def user_container(user_wallet: WalletFile, client_shell: Shell):
|
||||
container_id = create_container(user_wallet.path, shell=client_shell)
|
||||
return StorageContainer(StorageContainerInfo(container_id, user_wallet), client_shell)
|
||||
def user_container(user_wallet: WalletFile, client_shell: Shell, cluster: Cluster):
|
||||
container_id = create_container(
|
||||
user_wallet.path, shell=client_shell, endpoint=cluster.default_rpc_endpoint
|
||||
)
|
||||
return StorageContainer(StorageContainerInfo(container_id, user_wallet), client_shell, cluster)
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
|
@ -75,10 +62,11 @@ def user_container(user_wallet: WalletFile, client_shell: Shell):
|
|||
def locked_storage_object(
|
||||
user_container: StorageContainer,
|
||||
client_shell: Shell,
|
||||
cluster: Cluster,
|
||||
request: FixtureRequest,
|
||||
):
|
||||
with allure.step(f"Creating locked object"):
|
||||
current_epoch = ensure_fresh_epoch(client_shell)
|
||||
with allure.step("Creating locked object"):
|
||||
current_epoch = ensure_fresh_epoch(client_shell, cluster)
|
||||
expiration_epoch = current_epoch + FIXTURE_LOCK_LIFETIME
|
||||
|
||||
storage_object = user_container.generate_object(
|
||||
|
@ -89,6 +77,7 @@ def locked_storage_object(
|
|||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
cluster.default_rpc_endpoint,
|
||||
lifetime=FIXTURE_LOCK_LIFETIME,
|
||||
)
|
||||
storage_object.locks = [
|
||||
|
@ -99,20 +88,21 @@ def locked_storage_object(
|
|||
|
||||
yield storage_object
|
||||
|
||||
with allure.step(f"Delete created locked object"):
|
||||
current_epoch = get_epoch(client_shell)
|
||||
with allure.step("Delete created locked object"):
|
||||
current_epoch = get_epoch(client_shell, cluster)
|
||||
epoch_diff = expiration_epoch - current_epoch + 1
|
||||
|
||||
if epoch_diff > 0:
|
||||
with allure.step(f"Tick {epoch_diff} epochs"):
|
||||
for _ in range(epoch_diff):
|
||||
tick_epoch(client_shell)
|
||||
tick_epoch(client_shell, cluster)
|
||||
try:
|
||||
delete_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
cluster.default_rpc_endpoint,
|
||||
)
|
||||
except Exception as ex:
|
||||
ex_message = str(ex)
|
||||
|
@ -126,7 +116,30 @@ def locked_storage_object(
|
|||
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.grpc_object_lock
|
||||
class TestObjectLockWithGrpc:
|
||||
class TestObjectLockWithGrpc(ClusterTestBase):
|
||||
def get_storage_object_chunks(self, storage_object: StorageObjectInfo):
|
||||
with allure.step(f"Get complex object chunks (f{storage_object.oid})"):
|
||||
split_object_id = get_link_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
self.shell,
|
||||
self.cluster.storage_nodes,
|
||||
is_direct=False,
|
||||
)
|
||||
head = head_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
split_object_id,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
chunks_object_ids = []
|
||||
if "split" in head["header"] and "children" in head["header"]["split"]:
|
||||
chunks_object_ids = head["header"]["split"]["children"]
|
||||
return chunks_object_ids
|
||||
|
||||
@allure.title("Locked object should be protected from deletion")
|
||||
@pytest.mark.parametrize(
|
||||
"locked_storage_object",
|
||||
|
@ -136,7 +149,6 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_locked_object_cannot_be_deleted(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
locked_storage_object: StorageObjectInfo,
|
||||
):
|
||||
|
@ -152,7 +164,8 @@ class TestObjectLockWithGrpc:
|
|||
locked_storage_object.wallet_file_path,
|
||||
locked_storage_object.cid,
|
||||
locked_storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@allure.title("Lock object itself should be protected from deletion")
|
||||
|
@ -160,7 +173,6 @@ class TestObjectLockWithGrpc:
|
|||
@pytest.mark.parametrize("locked_storage_object", [SIMPLE_OBJ_SIZE], indirect=True)
|
||||
def test_lock_object_itself_cannot_be_deleted(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
locked_storage_object: StorageObjectInfo,
|
||||
):
|
||||
"""
|
||||
|
@ -171,14 +183,19 @@ class TestObjectLockWithGrpc:
|
|||
wallet_path = locked_storage_object.wallet_file_path
|
||||
|
||||
with pytest.raises(Exception, match=LOCK_OBJECT_REMOVAL):
|
||||
delete_object(wallet_path, lock_object.cid, lock_object.oid, client_shell)
|
||||
delete_object(
|
||||
wallet_path,
|
||||
lock_object.cid,
|
||||
lock_object.oid,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@allure.title("Lock object itself cannot be locked")
|
||||
# We operate with only lock object here so no complex object needed in this test
|
||||
@pytest.mark.parametrize("locked_storage_object", [SIMPLE_OBJ_SIZE], indirect=True)
|
||||
def test_lock_object_cannot_be_locked(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
locked_storage_object: StorageObjectInfo,
|
||||
):
|
||||
"""
|
||||
|
@ -189,7 +206,14 @@ class TestObjectLockWithGrpc:
|
|||
wallet_path = locked_storage_object.wallet_file_path
|
||||
|
||||
with pytest.raises(Exception, match=LOCK_NON_REGULAR_OBJECT):
|
||||
lock_object(wallet_path, lock_object_info.cid, lock_object_info.oid, client_shell, 1)
|
||||
lock_object(
|
||||
wallet_path,
|
||||
lock_object_info.cid,
|
||||
lock_object_info.oid,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
1,
|
||||
)
|
||||
|
||||
@allure.title("Cannot lock object without lifetime and expire_at fields")
|
||||
# We operate with only lock object here so no complex object needed in this test
|
||||
|
@ -207,7 +231,6 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_cannot_lock_object_without_lifetime(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
locked_storage_object: StorageObjectInfo,
|
||||
wrong_lifetime: int,
|
||||
wrong_expire_at: int,
|
||||
|
@ -228,7 +251,8 @@ class TestObjectLockWithGrpc:
|
|||
wallet_path,
|
||||
lock_object_info.cid,
|
||||
lock_object_info.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
lifetime=wrong_lifetime,
|
||||
expire_at=wrong_expire_at,
|
||||
)
|
||||
|
@ -239,7 +263,6 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_expired_object_should_be_deleted_after_locks_are_expired(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
user_container: StorageContainer,
|
||||
object_size: int,
|
||||
|
@ -251,7 +274,7 @@ class TestObjectLockWithGrpc:
|
|||
f"Expired object should be deleted after locks are expired for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
current_epoch = ensure_fresh_epoch(client_shell)
|
||||
current_epoch = self.ensure_fresh_epoch()
|
||||
storage_object = user_container.generate_object(object_size, expire_at=current_epoch + 1)
|
||||
|
||||
with allure.step("Lock object for couple epochs"):
|
||||
|
@ -259,20 +282,22 @@ class TestObjectLockWithGrpc:
|
|||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
lifetime=3,
|
||||
)
|
||||
lock_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
expire_at=current_epoch + 3,
|
||||
)
|
||||
|
||||
with allure.step("Check object is not deleted at expiration time"):
|
||||
tick_epoch(client_shell)
|
||||
tick_epoch(client_shell)
|
||||
self.tick_epoch()
|
||||
self.tick_epoch()
|
||||
# Must wait to ensure object is not deleted
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
with expect_not_raises():
|
||||
|
@ -280,7 +305,8 @@ class TestObjectLockWithGrpc:
|
|||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@wait_for_success(parse_time(STORAGE_GC_TIME))
|
||||
|
@ -290,11 +316,12 @@ class TestObjectLockWithGrpc:
|
|||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step("Wait for object to be deleted after third epoch"):
|
||||
tick_epoch(client_shell)
|
||||
self.tick_epoch()
|
||||
check_object_not_found()
|
||||
|
||||
@allure.title("Should be possible to lock multiple objects at once")
|
||||
|
@ -305,7 +332,6 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_should_be_possible_to_lock_multiple_objects_at_once(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
user_container: StorageContainer,
|
||||
object_size: int,
|
||||
|
@ -317,7 +343,7 @@ class TestObjectLockWithGrpc:
|
|||
f"Should be possible to lock multiple objects at once for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
current_epoch = ensure_fresh_epoch(client_shell)
|
||||
current_epoch = ensure_fresh_epoch(self.shell, self.cluster)
|
||||
storage_objects: list[StorageObjectInfo] = []
|
||||
|
||||
with allure.step("Generate three objects"):
|
||||
|
@ -330,7 +356,8 @@ class TestObjectLockWithGrpc:
|
|||
storage_objects[0].wallet_file_path,
|
||||
storage_objects[0].cid,
|
||||
",".join([storage_object.oid for storage_object in storage_objects]),
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
expire_at=current_epoch + 1,
|
||||
)
|
||||
|
||||
|
@ -341,15 +368,16 @@ class TestObjectLockWithGrpc:
|
|||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
with allure.step("Tick two epochs"):
|
||||
tick_epoch(client_shell)
|
||||
tick_epoch(client_shell)
|
||||
self.tick_epoch()
|
||||
self.tick_epoch()
|
||||
|
||||
with expect_not_raises():
|
||||
delete_objects(storage_objects, client_shell)
|
||||
delete_objects(storage_objects, self.shell, self.cluster)
|
||||
|
||||
@allure.title("Already outdated lock should not be applied")
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -359,7 +387,6 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_already_outdated_lock_should_not_be_applied(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
user_container: StorageContainer,
|
||||
object_size: int,
|
||||
|
@ -371,7 +398,7 @@ class TestObjectLockWithGrpc:
|
|||
f"Already outdated lock should not be applied for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
current_epoch = ensure_fresh_epoch(client_shell)
|
||||
current_epoch = self.ensure_fresh_epoch()
|
||||
|
||||
storage_object = user_container.generate_object(object_size, expire_at=current_epoch + 1)
|
||||
|
||||
|
@ -386,7 +413,8 @@ class TestObjectLockWithGrpc:
|
|||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
expire_at=expiration_epoch,
|
||||
)
|
||||
|
||||
|
@ -399,7 +427,6 @@ class TestObjectLockWithGrpc:
|
|||
@expect_not_raises()
|
||||
def test_after_lock_expiration_with_lifetime_user_should_be_able_to_delete_object(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
user_container: StorageContainer,
|
||||
object_size: int,
|
||||
|
@ -411,22 +438,27 @@ class TestObjectLockWithGrpc:
|
|||
f"After lock expiration with lifetime user should be able to delete object for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
current_epoch = ensure_fresh_epoch(client_shell)
|
||||
current_epoch = self.ensure_fresh_epoch()
|
||||
storage_object = user_container.generate_object(object_size, expire_at=current_epoch + 1)
|
||||
|
||||
lock_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
lifetime=1,
|
||||
)
|
||||
|
||||
tick_epoch(client_shell)
|
||||
|
||||
delete_object(
|
||||
storage_object.wallet_file_path, storage_object.cid, storage_object.oid, client_shell
|
||||
)
|
||||
self.tick_epoch()
|
||||
with expect_not_raises():
|
||||
delete_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@allure.title("After lock expiration with expire_at user should be able to delete object")
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -437,7 +469,6 @@ class TestObjectLockWithGrpc:
|
|||
@expect_not_raises()
|
||||
def test_after_lock_expiration_with_expire_at_user_should_be_able_to_delete_object(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
user_container: StorageContainer,
|
||||
object_size: int,
|
||||
|
@ -449,7 +480,7 @@ class TestObjectLockWithGrpc:
|
|||
f"After lock expiration with expire_at user should be able to delete object for {request.node.callspec.id}"
|
||||
)
|
||||
|
||||
current_epoch = ensure_fresh_epoch(client_shell)
|
||||
current_epoch = self.ensure_fresh_epoch()
|
||||
|
||||
storage_object = user_container.generate_object(object_size, expire_at=current_epoch + 5)
|
||||
|
||||
|
@ -457,15 +488,21 @@ class TestObjectLockWithGrpc:
|
|||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
expire_at=current_epoch + 1,
|
||||
)
|
||||
|
||||
tick_epoch(client_shell)
|
||||
self.tick_epoch()
|
||||
|
||||
delete_object(
|
||||
storage_object.wallet_file_path, storage_object.cid, storage_object.oid, client_shell
|
||||
)
|
||||
with expect_not_raises():
|
||||
delete_object(
|
||||
storage_object.wallet_file_path,
|
||||
storage_object.cid,
|
||||
storage_object.oid,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@allure.title("Complex object chunks should also be protected from deletion")
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -476,14 +513,13 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_complex_object_chunks_should_also_be_protected_from_deletion(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
locked_storage_object: StorageObjectInfo,
|
||||
):
|
||||
"""
|
||||
Complex object chunks should also be protected from deletion
|
||||
"""
|
||||
|
||||
chunk_object_ids = get_storage_object_chunks(locked_storage_object, client_shell)
|
||||
chunk_object_ids = self.get_storage_object_chunks(locked_storage_object)
|
||||
for chunk_object_id in chunk_object_ids:
|
||||
with allure.step(f"Try to delete chunk object {chunk_object_id}"):
|
||||
with pytest.raises(Exception, match=OBJECT_IS_LOCKED):
|
||||
|
@ -491,7 +527,8 @@ class TestObjectLockWithGrpc:
|
|||
locked_storage_object.wallet_file_path,
|
||||
locked_storage_object.cid,
|
||||
chunk_object_id,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
@allure.title("Link object of complex object should also be protected from deletion")
|
||||
|
@ -503,7 +540,6 @@ class TestObjectLockWithGrpc:
|
|||
)
|
||||
def test_link_object_of_complex_object_should_also_be_protected_from_deletion(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
locked_storage_object: StorageObjectInfo,
|
||||
):
|
||||
"""
|
||||
|
@ -514,7 +550,8 @@ class TestObjectLockWithGrpc:
|
|||
locked_storage_object.wallet_file_path,
|
||||
locked_storage_object.cid,
|
||||
locked_storage_object.oid,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.storage_nodes,
|
||||
is_direct=False,
|
||||
)
|
||||
with allure.step(f"Try to delete link object {link_object_id}"):
|
||||
|
@ -523,5 +560,6 @@ class TestObjectLockWithGrpc:
|
|||
locked_storage_object.wallet_file_path,
|
||||
locked_storage_object.cid,
|
||||
link_object_id,
|
||||
client_shell,
|
||||
self.shell,
|
||||
self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
|
|
@ -4,7 +4,8 @@ import os
|
|||
import allure
|
||||
import pytest
|
||||
import yaml
|
||||
from common import FREE_STORAGE, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from common import FREE_STORAGE, NEOFS_CLI_EXEC, WALLET_CONFIG
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.shell import CommandResult, Shell
|
||||
from wallet import WalletFactory, WalletFile
|
||||
|
@ -16,7 +17,7 @@ DEPOSIT_AMOUNT = 30
|
|||
@pytest.mark.sanity
|
||||
@pytest.mark.payments
|
||||
@pytest.mark.skipif(FREE_STORAGE, reason="Test only works on public network with paid storage")
|
||||
class TestBalanceAccounting:
|
||||
class TestBalanceAccounting(ClusterTestBase):
|
||||
@pytest.fixture(scope="class")
|
||||
def main_wallet(self, wallet_factory: WalletFactory) -> WalletFile:
|
||||
return wallet_factory.create_wallet()
|
||||
|
@ -61,7 +62,7 @@ class TestBalanceAccounting:
|
|||
def test_balance_wallet_address(self, main_wallet: WalletFile, cli: NeofsCli):
|
||||
result = cli.accounting.balance(
|
||||
wallet=main_wallet.path,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=self.cluster.default_rpc_endpoint,
|
||||
address=main_wallet.get_address(),
|
||||
)
|
||||
|
||||
|
@ -69,7 +70,9 @@ class TestBalanceAccounting:
|
|||
|
||||
@allure.title("Test balance request with wallet only")
|
||||
def test_balance_wallet(self, main_wallet: WalletFile, cli: NeofsCli):
|
||||
result = cli.accounting.balance(wallet=main_wallet.path, rpc_endpoint=NEOFS_ENDPOINT)
|
||||
result = cli.accounting.balance(
|
||||
wallet=main_wallet.path, rpc_endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
self.check_amount(result)
|
||||
|
||||
@allure.title("Test balance request with wallet and wrong address")
|
||||
|
@ -79,14 +82,16 @@ class TestBalanceAccounting:
|
|||
with pytest.raises(Exception, match="address option must be specified and valid"):
|
||||
cli.accounting.balance(
|
||||
wallet=main_wallet.path,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=self.cluster.default_rpc_endpoint,
|
||||
address=other_wallet.get_address(),
|
||||
)
|
||||
|
||||
@allure.title("Test balance request with config file")
|
||||
def test_balance_api(self, prepare_tmp_dir: str, main_wallet: WalletFile, client_shell: Shell):
|
||||
def test_balance_api(self, temp_directory: str, main_wallet: WalletFile, client_shell: Shell):
|
||||
config_file = self.write_api_config(
|
||||
config_dir=prepare_tmp_dir, endpoint=NEOFS_ENDPOINT, wallet=main_wallet.path
|
||||
config_dir=temp_directory,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wallet=main_wallet.path,
|
||||
)
|
||||
logger.info(f"Config with API endpoint: {config_file}")
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from random import choice, choices
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from aws_cli_client import AwsCliClient
|
||||
from common import ASSETS_DIR, COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE
|
||||
from epoch import tick_epoch
|
||||
from file_helper import (
|
||||
|
@ -22,7 +23,6 @@ from s3_helper import (
|
|||
)
|
||||
|
||||
from steps import s3_gate_bucket, s3_gate_object
|
||||
from steps.aws_cli_client import AwsCliClient
|
||||
from steps.s3_gate_base import TestS3GateBase
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
@ -39,7 +39,7 @@ def pytest_generate_tests(metafunc):
|
|||
@pytest.mark.s3_gate_base
|
||||
class TestS3Gate(TestS3GateBase):
|
||||
@allure.title("Test S3 Bucket API")
|
||||
def test_s3_buckets(self, client_shell):
|
||||
def test_s3_buckets(self):
|
||||
"""
|
||||
Test base S3 Bucket API (Create/List/Head/Delete).
|
||||
"""
|
||||
|
@ -83,7 +83,7 @@ class TestS3Gate(TestS3GateBase):
|
|||
|
||||
with allure.step(f"Delete empty bucket {bucket_2}"):
|
||||
s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket_2)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
|
||||
with allure.step(f"Check bucket {bucket_2} deleted"):
|
||||
with pytest.raises(Exception, match=r".*Not Found.*"):
|
||||
|
@ -99,7 +99,7 @@ class TestS3Gate(TestS3GateBase):
|
|||
|
||||
with allure.step(f"Delete bucket {bucket_1}"):
|
||||
s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket_1)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
|
||||
with allure.step(f"Check bucket {bucket_1} deleted"):
|
||||
with pytest.raises(Exception, match=r".*Not Found.*"):
|
||||
|
|
|
@ -6,6 +6,7 @@ from random import choices, sample
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from aws_cli_client import AwsCliClient
|
||||
from common import ASSETS_DIR, COMPLEX_OBJ_SIZE, FREE_STORAGE, SIMPLE_OBJ_SIZE, WALLET_PASS
|
||||
from data_formatters import get_wallet_public_key
|
||||
from file_helper import concat_files, generate_file, generate_file_with_content, get_file_hash
|
||||
|
@ -14,7 +15,6 @@ from python_keywords.payment_neogo import deposit_gas, transfer_gas
|
|||
from s3_helper import assert_object_lock_mode, check_objects_in_bucket, set_bucket_versioning
|
||||
|
||||
from steps import s3_gate_bucket, s3_gate_object
|
||||
from steps.aws_cli_client import AwsCliClient
|
||||
from steps.s3_gate_base import TestS3GateBase
|
||||
|
||||
|
||||
|
@ -653,23 +653,26 @@ class TestS3GateObject(TestS3GateBase):
|
|||
], "Tags must be the same"
|
||||
|
||||
@pytest.fixture
|
||||
def prepare_two_wallets(self, prepare_wallet_and_deposit, client_shell):
|
||||
self.main_wallet = prepare_wallet_and_deposit
|
||||
def prepare_two_wallets(self, default_wallet, client_shell):
|
||||
self.main_wallet = default_wallet
|
||||
self.main_public_key = get_wallet_public_key(self.main_wallet, WALLET_PASS)
|
||||
self.other_wallet = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
|
||||
init_wallet(self.other_wallet, WALLET_PASS)
|
||||
self.other_public_key = get_wallet_public_key(self.other_wallet, WALLET_PASS)
|
||||
|
||||
if not FREE_STORAGE:
|
||||
main_chain = self.cluster.main_chain_nodes[0]
|
||||
deposit = 30
|
||||
transfer_gas(
|
||||
shell=client_shell,
|
||||
amount=deposit + 1,
|
||||
main_chain=main_chain,
|
||||
wallet_to_path=self.other_wallet,
|
||||
wallet_to_password=WALLET_PASS,
|
||||
)
|
||||
deposit_gas(
|
||||
shell=client_shell,
|
||||
main_chain=main_chain,
|
||||
amount=deposit,
|
||||
wallet_from_path=self.other_wallet,
|
||||
wallet_from_password=WALLET_PASS,
|
||||
|
@ -906,9 +909,9 @@ class TestS3GateObject(TestS3GateBase):
|
|||
# ], "Permission for all groups is FULL_CONTROL"
|
||||
|
||||
@allure.title("Test S3 Put 10 nested level object")
|
||||
def test_s3_put_10_folder(self, bucket, prepare_tmp_dir):
|
||||
def test_s3_put_10_folder(self, bucket, temp_directory):
|
||||
path = "/".join(["".join(choices(string.ascii_letters, k=3)) for _ in range(10)])
|
||||
file_path_1 = os.path.join(prepare_tmp_dir, path, "test_file_1")
|
||||
file_path_1 = os.path.join(temp_directory, path, "test_file_1")
|
||||
generate_file_with_content(file_path=file_path_1)
|
||||
file_name = self.object_key_from_file_path(file_path_1)
|
||||
objects_list = s3_gate_object.list_objects_s3(self.s3_client, bucket)
|
||||
|
|
|
@ -35,7 +35,7 @@ def pytest_generate_tests(metafunc):
|
|||
@pytest.mark.s3_gate
|
||||
class TestS3GatePolicy(TestS3GateBase):
|
||||
@allure.title("Test S3: Verify bucket creation with retention policy applied")
|
||||
def test_s3_bucket_location(self, client_shell):
|
||||
def test_s3_bucket_location(self):
|
||||
file_path_1 = generate_file()
|
||||
file_name_1 = object_key_from_file_path(file_path_1)
|
||||
file_path_2 = generate_file()
|
||||
|
@ -72,14 +72,26 @@ class TestS3GatePolicy(TestS3GateBase):
|
|||
assert bucket_loc_2 == "rep-3"
|
||||
|
||||
with allure.step("Check object policy"):
|
||||
cid_1 = search_container_by_name(self.wallet, bucket_1, shell=client_shell)
|
||||
cid_1 = search_container_by_name(
|
||||
self.wallet, bucket_1, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
copies_1 = get_simple_object_copies(
|
||||
wallet=self.wallet, cid=cid_1, oid=version_id_1, shell=client_shell
|
||||
wallet=self.wallet,
|
||||
cid=cid_1,
|
||||
oid=version_id_1,
|
||||
shell=self.shell,
|
||||
nodes=self.cluster.storage_nodes,
|
||||
)
|
||||
assert copies_1 == 1
|
||||
cid_2 = search_container_by_name(self.wallet, bucket_2, shell=client_shell)
|
||||
cid_2 = search_container_by_name(
|
||||
self.wallet, bucket_2, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
copies_2 = get_simple_object_copies(
|
||||
wallet=self.wallet, cid=cid_2, oid=version_id_2, shell=client_shell
|
||||
wallet=self.wallet,
|
||||
cid=cid_2,
|
||||
oid=version_id_2,
|
||||
shell=self.shell,
|
||||
nodes=self.cluster.storage_nodes,
|
||||
)
|
||||
assert copies_2 == 3
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
import os
|
||||
from random import choice
|
||||
import random
|
||||
from time import sleep
|
||||
|
||||
import allure
|
||||
|
@ -9,7 +9,6 @@ from common import COMPLEX_OBJ_SIZE
|
|||
from container import create_container
|
||||
from epoch import get_epoch, tick_epoch
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.http_gate import (
|
||||
get_via_http_curl,
|
||||
get_via_http_gate,
|
||||
|
@ -18,11 +17,13 @@ from python_keywords.http_gate import (
|
|||
upload_via_http_gate,
|
||||
upload_via_http_gate_curl,
|
||||
)
|
||||
from python_keywords.neofs_verbs import get_object, put_object
|
||||
from python_keywords.neofs_verbs import get_object, put_object_to_random_node
|
||||
from python_keywords.storage_policy import get_nodes_without_object
|
||||
from utility import wait_for_gc_pass_on_storage_nodes
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
OBJECT_NOT_FOUND_ERROR = "not found"
|
||||
|
||||
|
@ -39,17 +40,17 @@ OBJECT_UPLOAD_DELAY = 10
|
|||
@allure.link("https://github.com/nspcc-dev/neofs-http-gw#downloading", name="downloading")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.http_gate
|
||||
class TestHttpGate:
|
||||
class TestHttpGate(ClusterTestBase):
|
||||
PLACEMENT_RULE_1 = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
|
||||
PLACEMENT_RULE_2 = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X"
|
||||
|
||||
@pytest.fixture(scope="class", autouse=True)
|
||||
@allure.title("[Class/Autouse]: Prepare wallet and deposit")
|
||||
def prepare_wallet(self, prepare_wallet_and_deposit):
|
||||
TestHttpGate.wallet = prepare_wallet_and_deposit
|
||||
def prepare_wallet(self, default_wallet):
|
||||
TestHttpGate.wallet = default_wallet
|
||||
|
||||
@allure.title("Test Put over gRPC, Get over HTTP")
|
||||
def test_put_grpc_get_http(self, client_shell):
|
||||
def test_put_grpc_get_http(self):
|
||||
"""
|
||||
Test that object can be put using gRPC interface and get using HTTP.
|
||||
|
||||
|
@ -65,26 +66,38 @@ class TestHttpGate:
|
|||
Hashes must be the same.
|
||||
"""
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_1, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=self.PLACEMENT_RULE_1,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
file_path_simple, file_path_large = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
|
||||
|
||||
with allure.step("Put objects using gRPC"):
|
||||
oid_simple = put_object(
|
||||
wallet=self.wallet, path=file_path_simple, cid=cid, shell=client_shell
|
||||
oid_simple = put_object_to_random_node(
|
||||
wallet=self.wallet,
|
||||
path=file_path_simple,
|
||||
cid=cid,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
oid_large = put_object(
|
||||
wallet=self.wallet, path=file_path_large, cid=cid, shell=client_shell
|
||||
oid_large = put_object_to_random_node(
|
||||
wallet=self.wallet,
|
||||
path=file_path_large,
|
||||
cid=cid,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)):
|
||||
self.get_object_and_verify_hashes(oid, file_path, self.wallet, cid, shell=client_shell)
|
||||
self.get_object_and_verify_hashes(oid, file_path, self.wallet, cid)
|
||||
|
||||
@allure.link("https://github.com/nspcc-dev/neofs-http-gw#uploading", name="uploading")
|
||||
@allure.link("https://github.com/nspcc-dev/neofs-http-gw#downloading", name="downloading")
|
||||
@allure.title("Test Put over HTTP, Get over HTTP")
|
||||
@pytest.mark.smoke
|
||||
def test_put_http_get_http(self, client_shell):
|
||||
def test_put_http_get_http(self):
|
||||
"""
|
||||
Test that object can be put and get using HTTP interface.
|
||||
|
||||
|
@ -98,16 +111,24 @@ class TestHttpGate:
|
|||
Hashes must be the same.
|
||||
"""
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_2, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=self.PLACEMENT_RULE_2,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
file_path_simple, file_path_large = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
|
||||
|
||||
with allure.step("Put objects using HTTP"):
|
||||
oid_simple = upload_via_http_gate(cid=cid, path=file_path_simple)
|
||||
oid_large = upload_via_http_gate(cid=cid, path=file_path_large)
|
||||
oid_simple = upload_via_http_gate(
|
||||
cid=cid, path=file_path_simple, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
oid_large = upload_via_http_gate(
|
||||
cid=cid, path=file_path_large, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
|
||||
for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)):
|
||||
self.get_object_and_verify_hashes(oid, file_path, self.wallet, cid, shell=client_shell)
|
||||
self.get_object_and_verify_hashes(oid, file_path, self.wallet, cid)
|
||||
|
||||
@allure.link(
|
||||
"https://github.com/nspcc-dev/neofs-http-gw#by-attributes", name="download by attributes"
|
||||
|
@ -122,7 +143,7 @@ class TestHttpGate:
|
|||
],
|
||||
ids=["simple", "hyphen", "percent"],
|
||||
)
|
||||
def test_put_http_get_http_with_headers(self, client_shell, attributes: dict):
|
||||
def test_put_http_get_http_with_headers(self, attributes: dict):
|
||||
"""
|
||||
Test that object can be downloaded using different attributes in HTTP header.
|
||||
|
||||
|
@ -136,43 +157,63 @@ class TestHttpGate:
|
|||
Hashes must be the same.
|
||||
"""
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_2, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=self.PLACEMENT_RULE_2,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
file_path = generate_file()
|
||||
|
||||
with allure.step("Put objects using HTTP with attribute"):
|
||||
headers = self._attr_into_header(attributes)
|
||||
oid = upload_via_http_gate(cid=cid, path=file_path, headers=headers)
|
||||
oid = upload_via_http_gate(
|
||||
cid=cid,
|
||||
path=file_path,
|
||||
headers=headers,
|
||||
endpoint=self.cluster.default_http_gate_endpoint,
|
||||
)
|
||||
|
||||
sleep(OBJECT_UPLOAD_DELAY)
|
||||
|
||||
self.get_object_by_attr_and_verify_hashes(oid, file_path, cid, attributes)
|
||||
|
||||
@allure.title("Test Expiration-Epoch in HTTP header")
|
||||
def test_expiration_epoch_in_http(self, client_shell):
|
||||
def test_expiration_epoch_in_http(self):
|
||||
endpoint = self.cluster.default_rpc_endpoint
|
||||
http_endpoint = self.cluster.default_http_gate_endpoint
|
||||
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_2, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=endpoint,
|
||||
rule=self.PLACEMENT_RULE_2,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
file_path = generate_file()
|
||||
oids = []
|
||||
|
||||
curr_epoch = get_epoch(client_shell)
|
||||
curr_epoch = get_epoch(self.shell, self.cluster)
|
||||
epochs = (curr_epoch, curr_epoch + 1, curr_epoch + 2, curr_epoch + 100)
|
||||
|
||||
for epoch in epochs:
|
||||
headers = {"X-Attribute-Neofs-Expiration-Epoch": str(epoch)}
|
||||
|
||||
with allure.step("Put objects using HTTP with attribute Expiration-Epoch"):
|
||||
oids.append(upload_via_http_gate(cid=cid, path=file_path, headers=headers))
|
||||
oids.append(
|
||||
upload_via_http_gate(
|
||||
cid=cid, path=file_path, headers=headers, endpoint=http_endpoint
|
||||
)
|
||||
)
|
||||
|
||||
assert len(oids) == len(epochs), "Expected all objects have been put successfully"
|
||||
|
||||
with allure.step("All objects can be get"):
|
||||
for oid in oids:
|
||||
get_via_http_gate(cid=cid, oid=oid)
|
||||
get_via_http_gate(cid=cid, oid=oid, endpoint=http_endpoint)
|
||||
|
||||
for expired_objects, not_expired_objects in [(oids[:1], oids[1:]), (oids[:2], oids[2:])]:
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(self.shell, self.cluster)
|
||||
|
||||
# Wait for GC, because object with expiration is counted as alive until GC removes it
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
|
@ -184,12 +225,16 @@ class TestHttpGate:
|
|||
|
||||
with allure.step("Other objects can be get"):
|
||||
for oid in not_expired_objects:
|
||||
get_via_http_gate(cid=cid, oid=oid)
|
||||
get_via_http_gate(cid=cid, oid=oid, endpoint=http_endpoint)
|
||||
|
||||
@allure.title("Test Zip in HTTP header")
|
||||
def test_zip_in_http(self, client_shell):
|
||||
def test_zip_in_http(self):
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_2, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=self.PLACEMENT_RULE_2,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
file_path_simple, file_path_large = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
|
||||
common_prefix = "my_files"
|
||||
|
@ -197,12 +242,24 @@ class TestHttpGate:
|
|||
headers1 = {"X-Attribute-FilePath": f"{common_prefix}/file1"}
|
||||
headers2 = {"X-Attribute-FilePath": f"{common_prefix}/file2"}
|
||||
|
||||
upload_via_http_gate(cid=cid, path=file_path_simple, headers=headers1)
|
||||
upload_via_http_gate(cid=cid, path=file_path_large, headers=headers2)
|
||||
upload_via_http_gate(
|
||||
cid=cid,
|
||||
path=file_path_simple,
|
||||
headers=headers1,
|
||||
endpoint=self.cluster.default_http_gate_endpoint,
|
||||
)
|
||||
upload_via_http_gate(
|
||||
cid=cid,
|
||||
path=file_path_large,
|
||||
headers=headers2,
|
||||
endpoint=self.cluster.default_http_gate_endpoint,
|
||||
)
|
||||
|
||||
sleep(OBJECT_UPLOAD_DELAY)
|
||||
|
||||
dir_path = get_via_zip_http_gate(cid=cid, prefix=common_prefix)
|
||||
dir_path = get_via_zip_http_gate(
|
||||
cid=cid, prefix=common_prefix, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
|
||||
with allure.step("Verify hashes"):
|
||||
assert get_file_hash(f"{dir_path}/file1") == get_file_hash(file_path_simple)
|
||||
|
@ -210,45 +267,63 @@ class TestHttpGate:
|
|||
|
||||
@pytest.mark.long
|
||||
@allure.title("Test Put over HTTP/Curl, Get over HTTP/Curl for large object")
|
||||
def test_put_http_get_http_large_file(self, client_shell):
|
||||
def test_put_http_get_http_large_file(self):
|
||||
"""
|
||||
This test checks upload and download using curl with 'large' object.
|
||||
Large is object with size up to 20Mb.
|
||||
"""
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_2, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=self.PLACEMENT_RULE_2,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
|
||||
obj_size = int(os.getenv("BIG_OBJ_SIZE", COMPLEX_OBJ_SIZE))
|
||||
file_path = generate_file(obj_size)
|
||||
|
||||
with allure.step("Put objects using HTTP"):
|
||||
oid_gate = upload_via_http_gate(cid=cid, path=file_path)
|
||||
oid_curl = upload_via_http_gate_curl(cid=cid, filepath=file_path, large_object=True)
|
||||
oid_gate = upload_via_http_gate(
|
||||
cid=cid, path=file_path, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
oid_curl = upload_via_http_gate_curl(
|
||||
cid=cid,
|
||||
filepath=file_path,
|
||||
large_object=True,
|
||||
endpoint=self.cluster.default_http_gate_endpoint,
|
||||
)
|
||||
|
||||
self.get_object_and_verify_hashes(oid_gate, file_path, self.wallet, cid, shell=client_shell)
|
||||
self.get_object_and_verify_hashes(oid_gate, file_path, self.wallet, cid)
|
||||
self.get_object_and_verify_hashes(
|
||||
oid_curl,
|
||||
file_path,
|
||||
self.wallet,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
object_getter=get_via_http_curl,
|
||||
)
|
||||
|
||||
@allure.title("Test Put/Get over HTTP using Curl utility")
|
||||
def test_put_http_get_http_curl(self, client_shell):
|
||||
def test_put_http_get_http_curl(self):
|
||||
"""
|
||||
Test checks upload and download over HTTP using curl utility.
|
||||
"""
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE_2, basic_acl=PUBLIC_ACL
|
||||
self.wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=self.PLACEMENT_RULE_2,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
)
|
||||
file_path_simple, file_path_large = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
|
||||
|
||||
with allure.step("Put objects using curl utility"):
|
||||
oid_simple = upload_via_http_gate_curl(cid=cid, filepath=file_path_simple)
|
||||
oid_large = upload_via_http_gate_curl(cid=cid, filepath=file_path_large)
|
||||
oid_simple = upload_via_http_gate_curl(
|
||||
cid=cid, filepath=file_path_simple, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
oid_large = upload_via_http_gate_curl(
|
||||
cid=cid, filepath=file_path_large, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
|
||||
for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)):
|
||||
self.get_object_and_verify_hashes(
|
||||
|
@ -256,45 +331,57 @@ class TestHttpGate:
|
|||
file_path,
|
||||
self.wallet,
|
||||
cid,
|
||||
shell=client_shell,
|
||||
object_getter=get_via_http_curl,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Try to get object and expect error")
|
||||
def try_to_get_object_and_expect_error(cid: str, oid: str, error_pattern: str) -> None:
|
||||
def try_to_get_object_and_expect_error(self, cid: str, oid: str, error_pattern: str) -> None:
|
||||
try:
|
||||
get_via_http_gate(cid=cid, oid=oid)
|
||||
get_via_http_gate(cid=cid, oid=oid, endpoint=self.cluster.default_http_gate_endpoint)
|
||||
raise AssertionError(f"Expected error on getting object with cid: {cid}")
|
||||
except Exception as err:
|
||||
match = error_pattern.casefold() in str(err).casefold()
|
||||
assert match, f"Expected {err} to match {error_pattern}"
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Verify object can be get using HTTP header attribute")
|
||||
def get_object_by_attr_and_verify_hashes(
|
||||
oid: str, file_name: str, cid: str, attrs: dict
|
||||
self, oid: str, file_name: str, cid: str, attrs: dict
|
||||
) -> None:
|
||||
got_file_path_http = get_via_http_gate(cid=cid, oid=oid)
|
||||
got_file_path_http_attr = get_via_http_gate_by_attribute(cid=cid, attribute=attrs)
|
||||
got_file_path_http = get_via_http_gate(
|
||||
cid=cid, oid=oid, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
got_file_path_http_attr = get_via_http_gate_by_attribute(
|
||||
cid=cid, attribute=attrs, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
|
||||
TestHttpGate._assert_hashes_are_equal(
|
||||
file_name, got_file_path_http, got_file_path_http_attr
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Verify object can be get using HTTP")
|
||||
def get_object_and_verify_hashes(
|
||||
oid: str, file_name: str, wallet: str, cid: str, shell: Shell, object_getter=None
|
||||
self, oid: str, file_name: str, wallet: str, cid: str, object_getter=None
|
||||
) -> None:
|
||||
nodes = get_nodes_without_object(wallet=wallet, cid=cid, oid=oid, shell=shell)
|
||||
random_node = choice(nodes)
|
||||
nodes = get_nodes_without_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
shell=self.shell,
|
||||
nodes=self.cluster.storage_nodes,
|
||||
)
|
||||
random_node = random.choice(nodes)
|
||||
object_getter = object_getter or get_via_http_gate
|
||||
|
||||
got_file_path = get_object(
|
||||
wallet=wallet, cid=cid, oid=oid, shell=shell, endpoint=random_node
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
shell=self.shell,
|
||||
endpoint=random_node.get_rpc_endpoint(),
|
||||
)
|
||||
got_file_path_http = object_getter(
|
||||
cid=cid, oid=oid, endpoint=self.cluster.default_http_gate_endpoint
|
||||
)
|
||||
got_file_path_http = object_getter(cid=cid, oid=oid)
|
||||
|
||||
TestHttpGate._assert_hashes_are_equal(file_name, got_file_path, got_file_path_http)
|
||||
|
||||
|
|
|
@ -2,131 +2,146 @@ import random
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from common import COMPLEX_OBJ_SIZE, NEOFS_NETMAP_DICT, SIMPLE_OBJ_SIZE, WALLET_PASS
|
||||
from cluster_test_base import ClusterTestBase
|
||||
from common import COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE, WALLET_PASS
|
||||
from file_helper import generate_file
|
||||
from grpc_responses import SESSION_NOT_FOUND
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.neofs_verbs import delete_object, put_object
|
||||
from python_keywords.neofs_verbs import delete_object, put_object, put_object_to_random_node
|
||||
|
||||
from steps.session_token import create_session_token
|
||||
|
||||
|
||||
@allure.title("Test Object Operations with Session Token")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.session_token
|
||||
@pytest.mark.parametrize(
|
||||
"object_size",
|
||||
[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE],
|
||||
ids=["simple object", "complex object"],
|
||||
)
|
||||
def test_object_session_token(prepare_wallet_and_deposit, client_shell: Shell, object_size):
|
||||
"""
|
||||
Test how operations over objects are executed with a session token
|
||||
class TestDynamicObjectSession(ClusterTestBase):
|
||||
@allure.title("Test Object Operations with Session Token")
|
||||
@pytest.mark.parametrize(
|
||||
"object_size",
|
||||
[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE],
|
||||
ids=["simple object", "complex object"],
|
||||
)
|
||||
def test_object_session_token(self, default_wallet, object_size):
|
||||
"""
|
||||
Test how operations over objects are executed with a session token
|
||||
|
||||
Steps:
|
||||
1. Create a private container
|
||||
2. Obj operation requests to the node which IS NOT in the container but granted
|
||||
with a session token
|
||||
3. Obj operation requests to the node which IS in the container and NOT granted
|
||||
with a session token
|
||||
4. Obj operation requests to the node which IS NOT in the container and NOT granted
|
||||
with a session token
|
||||
"""
|
||||
Steps:
|
||||
1. Create a private container
|
||||
2. Obj operation requests to the node which IS NOT in the container but granted
|
||||
with a session token
|
||||
3. Obj operation requests to the node which IS in the container and NOT granted
|
||||
with a session token
|
||||
4. Obj operation requests to the node which IS NOT in the container and NOT granted
|
||||
with a session token
|
||||
"""
|
||||
|
||||
with allure.step("Init wallet"):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
address = get_last_address_from_wallet(wallet, "")
|
||||
with allure.step("Init wallet"):
|
||||
wallet = default_wallet
|
||||
address = get_last_address_from_wallet(wallet, "")
|
||||
|
||||
with allure.step("Nodes Settlements"):
|
||||
(
|
||||
session_token_node_name,
|
||||
container_node_name,
|
||||
noncontainer_node_name,
|
||||
) = random.sample(list(NEOFS_NETMAP_DICT.keys()), 3)
|
||||
session_token_node = NEOFS_NETMAP_DICT[session_token_node_name]["rpc"]
|
||||
container_node = NEOFS_NETMAP_DICT[container_node_name]["rpc"]
|
||||
noncontainer_node = NEOFS_NETMAP_DICT[noncontainer_node_name]["rpc"]
|
||||
with allure.step("Nodes Settlements"):
|
||||
(
|
||||
session_token_node,
|
||||
container_node,
|
||||
non_container_node,
|
||||
) = random.sample(self.cluster.storage_nodes, 3)
|
||||
|
||||
with allure.step("Create Session Token"):
|
||||
session_token = create_session_token(
|
||||
shell=client_shell,
|
||||
owner=address,
|
||||
wallet_path=wallet,
|
||||
wallet_password=WALLET_PASS,
|
||||
rpc_endpoint=session_token_node,
|
||||
)
|
||||
with allure.step("Create Session Token"):
|
||||
session_token = create_session_token(
|
||||
shell=self.shell,
|
||||
owner=address,
|
||||
wallet_path=wallet,
|
||||
wallet_password=WALLET_PASS,
|
||||
rpc_endpoint=session_token_node.get_rpc_endpoint(),
|
||||
)
|
||||
|
||||
with allure.step("Create Private Container"):
|
||||
un_locode = NEOFS_NETMAP_DICT[container_node_name]["UN-LOCODE"]
|
||||
locode = "SPB" if un_locode == "RU LED" else un_locode.split()[1]
|
||||
placement_policy = (
|
||||
f"REP 1 IN LOC_{locode}_PLACE CBF 1 SELECT 1 FROM LOC_{locode} "
|
||||
f'AS LOC_{locode}_PLACE FILTER "UN-LOCODE" '
|
||||
f'EQ "{un_locode}" AS LOC_{locode}'
|
||||
)
|
||||
cid = create_container(wallet, shell=client_shell, rule=placement_policy)
|
||||
with allure.step("Create Private Container"):
|
||||
un_locode = container_node.get_un_locode()
|
||||
locode = "SPB" if un_locode == "RU LED" else un_locode.split()[1]
|
||||
placement_policy = (
|
||||
f"REP 1 IN LOC_{locode}_PLACE CBF 1 SELECT 1 FROM LOC_{locode} "
|
||||
f'AS LOC_{locode}_PLACE FILTER "UN-LOCODE" '
|
||||
f'EQ "{un_locode}" AS LOC_{locode}'
|
||||
)
|
||||
cid = create_container(
|
||||
wallet,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
rule=placement_policy,
|
||||
)
|
||||
|
||||
with allure.step("Put Objects"):
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(wallet=wallet, path=file_path, cid=cid, shell=client_shell)
|
||||
oid_delete = put_object(wallet=wallet, path=file_path, cid=cid, shell=client_shell)
|
||||
with allure.step("Put Objects"):
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object_to_random_node(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
oid_delete = put_object_to_random_node(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=self.shell,
|
||||
cluster=self.cluster,
|
||||
)
|
||||
|
||||
with allure.step("Node not in container but granted a session token"):
|
||||
put_object(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
endpoint=session_token_node,
|
||||
session=session_token,
|
||||
)
|
||||
delete_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid_delete,
|
||||
shell=client_shell,
|
||||
endpoint=session_token_node,
|
||||
session=session_token,
|
||||
)
|
||||
|
||||
with allure.step("Node in container and not granted a session token"):
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
with allure.step("Node not in container but granted a session token"):
|
||||
put_object(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
endpoint=container_node,
|
||||
shell=self.shell,
|
||||
endpoint=session_token_node.get_rpc_endpoint(),
|
||||
session=session_token,
|
||||
)
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
delete_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
shell=client_shell,
|
||||
endpoint=container_node,
|
||||
oid=oid_delete,
|
||||
shell=self.shell,
|
||||
endpoint=session_token_node.get_rpc_endpoint(),
|
||||
session=session_token,
|
||||
)
|
||||
|
||||
with allure.step("Node not in container and not granted a session token"):
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
put_object(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=client_shell,
|
||||
endpoint=noncontainer_node,
|
||||
session=session_token,
|
||||
)
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
delete_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
shell=client_shell,
|
||||
endpoint=noncontainer_node,
|
||||
session=session_token,
|
||||
)
|
||||
with allure.step("Node in container and not granted a session token"):
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
put_object(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=self.shell,
|
||||
endpoint=container_node.get_rpc_endpoint(),
|
||||
session=session_token,
|
||||
)
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
delete_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
shell=self.shell,
|
||||
endpoint=container_node.get_rpc_endpoint(),
|
||||
session=session_token,
|
||||
)
|
||||
|
||||
with allure.step("Node not in container and not granted a session token"):
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
put_object(
|
||||
wallet=wallet,
|
||||
path=file_path,
|
||||
cid=cid,
|
||||
shell=self.shell,
|
||||
endpoint=non_container_node.get_rpc_endpoint(),
|
||||
session=session_token,
|
||||
)
|
||||
with pytest.raises(Exception, match=SESSION_NOT_FOUND):
|
||||
delete_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
shell=self.shell,
|
||||
endpoint=non_container_node.get_rpc_endpoint(),
|
||||
session=session_token,
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,31 +21,31 @@ from python_keywords.object_access import can_put_object
|
|||
from wallet import WalletFile
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
from steps.cluster_test_base import ClusterTestBase
|
||||
from steps.session_token import ContainerVerb, get_container_signed_token
|
||||
|
||||
|
||||
class TestSessionTokenContainer:
|
||||
class TestSessionTokenContainer(ClusterTestBase):
|
||||
@pytest.fixture(scope="module")
|
||||
def static_sessions(
|
||||
self,
|
||||
owner_wallet: WalletFile,
|
||||
user_wallet: WalletFile,
|
||||
client_shell: Shell,
|
||||
prepare_tmp_dir: str,
|
||||
temp_directory: str,
|
||||
) -> dict[ContainerVerb, str]:
|
||||
"""
|
||||
Returns dict with static session token file paths for all verbs with default lifetime
|
||||
"""
|
||||
return {
|
||||
verb: get_container_signed_token(
|
||||
owner_wallet, user_wallet, verb, client_shell, prepare_tmp_dir
|
||||
owner_wallet, user_wallet, verb, client_shell, temp_directory
|
||||
)
|
||||
for verb in ContainerVerb
|
||||
}
|
||||
|
||||
def test_static_session_token_container_create(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
owner_wallet: WalletFile,
|
||||
user_wallet: WalletFile,
|
||||
static_sessions: dict[ContainerVerb, str],
|
||||
|
@ -57,21 +57,26 @@ class TestSessionTokenContainer:
|
|||
cid = create_container(
|
||||
user_wallet.path,
|
||||
session_token=static_sessions[ContainerVerb.CREATE],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wait_for_creation=False,
|
||||
)
|
||||
|
||||
container_info: dict[str, str] = get_container(owner_wallet.path, cid, shell=client_shell)
|
||||
container_info: dict[str, str] = get_container(
|
||||
owner_wallet.path, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
assert container_info["ownerID"] == owner_wallet.get_address()
|
||||
|
||||
assert cid not in list_containers(user_wallet.path, shell=client_shell)
|
||||
assert cid in list_containers(owner_wallet.path, shell=client_shell)
|
||||
assert cid not in list_containers(
|
||||
user_wallet.path, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
assert cid in list_containers(
|
||||
owner_wallet.path, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
|
||||
@pytest.mark.skip("Failed with timeout")
|
||||
def test_static_session_token_container_create_with_other_verb(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
owner_wallet: WalletFile,
|
||||
user_wallet: WalletFile,
|
||||
static_sessions: dict[ContainerVerb, str],
|
||||
):
|
||||
|
@ -84,15 +89,14 @@ class TestSessionTokenContainer:
|
|||
create_container(
|
||||
user_wallet.path,
|
||||
session_token=static_sessions[verb],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wait_for_creation=False,
|
||||
)
|
||||
|
||||
@pytest.mark.skip("Failed with timeout")
|
||||
def test_static_session_token_container_create_with_other_wallet(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
owner_wallet: WalletFile,
|
||||
stranger_wallet: WalletFile,
|
||||
static_sessions: dict[ContainerVerb, str],
|
||||
):
|
||||
|
@ -104,13 +108,13 @@ class TestSessionTokenContainer:
|
|||
create_container(
|
||||
stranger_wallet.path,
|
||||
session_token=static_sessions[ContainerVerb.CREATE],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wait_for_creation=False,
|
||||
)
|
||||
|
||||
def test_static_session_token_container_delete(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
owner_wallet: WalletFile,
|
||||
user_wallet: WalletFile,
|
||||
static_sessions: dict[ContainerVerb, str],
|
||||
|
@ -119,20 +123,27 @@ class TestSessionTokenContainer:
|
|||
Validate static session with delete operation
|
||||
"""
|
||||
with allure.step("Create container"):
|
||||
cid = create_container(owner_wallet.path, shell=client_shell, wait_for_creation=False)
|
||||
cid = create_container(
|
||||
owner_wallet.path,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
wait_for_creation=False,
|
||||
)
|
||||
with allure.step("Delete container with static session token"):
|
||||
delete_container(
|
||||
wallet=user_wallet.path,
|
||||
cid=cid,
|
||||
session_token=static_sessions[ContainerVerb.DELETE],
|
||||
shell=client_shell,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
|
||||
assert cid not in list_containers(owner_wallet.path, shell=client_shell)
|
||||
assert cid not in list_containers(
|
||||
owner_wallet.path, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
|
||||
)
|
||||
|
||||
def test_static_session_token_container_set_eacl(
|
||||
self,
|
||||
client_shell: Shell,
|
||||
owner_wallet: WalletFile,
|
||||
user_wallet: WalletFile,
|
||||
stranger_wallet: WalletFile,
|
||||
|
@ -142,9 +153,14 @@ class TestSessionTokenContainer:
|
|||
Validate static session with set eacl operation
|
||||
"""
|
||||
with allure.step("Create container"):
|
||||
cid = create_container(owner_wallet.path, basic_acl=PUBLIC_ACL, shell=client_shell)
|
||||
cid = create_container(
|
||||
owner_wallet.path,
|
||||
basic_acl=PUBLIC_ACL,
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
)
|
||||
file_path = generate_file()
|
||||
assert can_put_object(stranger_wallet.path, cid, file_path, client_shell)
|
||||
assert can_put_object(stranger_wallet.path, cid, file_path, self.shell, self.cluster)
|
||||
|
||||
with allure.step(f"Deny all operations for other via eACL"):
|
||||
eacl_deny = [
|
||||
|
@ -154,10 +170,11 @@ class TestSessionTokenContainer:
|
|||
set_eacl(
|
||||
user_wallet.path,
|
||||
cid,
|
||||
create_eacl(cid, eacl_deny, shell=client_shell),
|
||||
shell=client_shell,
|
||||
create_eacl(cid, eacl_deny, shell=self.shell),
|
||||
shell=self.shell,
|
||||
endpoint=self.cluster.default_rpc_endpoint,
|
||||
session_token=static_sessions[ContainerVerb.SETEACL],
|
||||
)
|
||||
wait_for_cache_expired()
|
||||
|
||||
assert not can_put_object(stranger_wallet.path, cid, file_path, client_shell)
|
||||
assert not can_put_object(stranger_wallet.path, cid, file_path, self.shell, self.cluster)
|
||||
|
|
|
@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional, Union
|
|||
|
||||
import allure
|
||||
import base58
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, WALLET_CONFIG
|
||||
from data_formatters import get_wallet_public_key
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.shell import Shell
|
||||
|
@ -116,10 +116,10 @@ class EACLRule:
|
|||
|
||||
|
||||
@allure.title("Get extended ACL")
|
||||
def get_eacl(wallet_path: str, cid: str, shell: Shell) -> Optional[str]:
|
||||
def get_eacl(wallet_path: str, cid: str, shell: Shell, endpoint: str) -> Optional[str]:
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
try:
|
||||
result = cli.container.get_eacl(wallet=wallet_path, rpc_endpoint=NEOFS_ENDPOINT, cid=cid)
|
||||
result = cli.container.get_eacl(wallet=wallet_path, rpc_endpoint=endpoint, cid=cid)
|
||||
except RuntimeError as exc:
|
||||
logger.info("Extended ACL table is not set for this container")
|
||||
logger.info(f"Got exception while getting eacl: {exc}")
|
||||
|
@ -135,12 +135,13 @@ def set_eacl(
|
|||
cid: str,
|
||||
eacl_table_path: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
session_token: Optional[str] = None,
|
||||
) -> None:
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
cli.container.set_eacl(
|
||||
wallet=wallet_path,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
cid=cid,
|
||||
table=eacl_table_path,
|
||||
await_mode=True,
|
||||
|
@ -166,7 +167,11 @@ def create_eacl(cid: str, rules_list: List[EACLRule], shell: Shell) -> str:
|
|||
|
||||
|
||||
def form_bearertoken_file(
|
||||
wif: str, cid: str, eacl_rule_list: List[Union[EACLRule, EACLPubKey]], shell: Shell
|
||||
wif: str,
|
||||
cid: str,
|
||||
eacl_rule_list: List[Union[EACLRule, EACLPubKey]],
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
) -> str:
|
||||
"""
|
||||
This function fetches eACL for given <cid> on behalf of <wif>,
|
||||
|
@ -176,7 +181,7 @@ def form_bearertoken_file(
|
|||
enc_cid = _encode_cid_for_eacl(cid)
|
||||
file_path = os.path.join(os.getcwd(), ASSETS_DIR, str(uuid.uuid4()))
|
||||
|
||||
eacl = get_eacl(wif, cid, shell=shell)
|
||||
eacl = get_eacl(wif, cid, shell, endpoint)
|
||||
json_eacl = dict()
|
||||
if eacl:
|
||||
eacl = eacl.replace("eACL: ", "").split("Signature")[0]
|
||||
|
|
|
@ -15,7 +15,8 @@ from typing import Optional
|
|||
|
||||
import allure
|
||||
import neofs_verbs
|
||||
from common import NEOFS_NETMAP, WALLET_CONFIG
|
||||
from cluster import StorageNode
|
||||
from common import WALLET_CONFIG
|
||||
from neofs_testlib.shell import Shell
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
@ -27,6 +28,7 @@ def get_link_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
nodes: list[StorageNode],
|
||||
bearer: str = "",
|
||||
wallet_config: str = WALLET_CONFIG,
|
||||
is_direct: bool = True,
|
||||
|
@ -38,6 +40,7 @@ def get_link_object(
|
|||
cid (str): Container ID which stores the Large Object
|
||||
oid (str): Large Object ID
|
||||
shell: executor for cli command
|
||||
nodes: list of nodes to do search on
|
||||
bearer (optional, str): path to Bearer token file
|
||||
wallet_config (optional, str): path to the neofs-cli config file
|
||||
is_direct: send request directly to the node or not; this flag
|
||||
|
@ -47,14 +50,15 @@ def get_link_object(
|
|||
When no Link Object ID is found after all Storage Nodes polling,
|
||||
the function throws an error.
|
||||
"""
|
||||
for node in NEOFS_NETMAP:
|
||||
for node in nodes:
|
||||
endpoint = node.get_rpc_endpoint()
|
||||
try:
|
||||
resp = neofs_verbs.head_object(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=shell,
|
||||
endpoint=node,
|
||||
endpoint=endpoint,
|
||||
is_raw=True,
|
||||
is_direct=is_direct,
|
||||
bearer=bearer,
|
||||
|
@ -63,13 +67,15 @@ def get_link_object(
|
|||
if resp["link"]:
|
||||
return resp["link"]
|
||||
except Exception:
|
||||
logger.info(f"No Link Object found on {node}; continue")
|
||||
logger.info(f"No Link Object found on {endpoint}; continue")
|
||||
logger.error(f"No Link Object for {cid}/{oid} found among all Storage Nodes")
|
||||
return None
|
||||
|
||||
|
||||
@allure.step("Get Last Object")
|
||||
def get_last_object(wallet: str, cid: str, oid: str, shell: Shell) -> Optional[str]:
|
||||
def get_last_object(
|
||||
wallet: str, cid: str, oid: str, shell: Shell, nodes: list[StorageNode]
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Args:
|
||||
wallet (str): path to the wallet on whose behalf the Storage Nodes
|
||||
|
@ -77,19 +83,21 @@ def get_last_object(wallet: str, cid: str, oid: str, shell: Shell) -> Optional[s
|
|||
cid (str): Container ID which stores the Large Object
|
||||
oid (str): Large Object ID
|
||||
shell: executor for cli command
|
||||
nodes: list of nodes to do search on
|
||||
Returns:
|
||||
(str): Last Object ID
|
||||
When no Last Object ID is found after all Storage Nodes polling,
|
||||
the function throws an error.
|
||||
"""
|
||||
for node in NEOFS_NETMAP:
|
||||
for node in nodes:
|
||||
endpoint = node.get_rpc_endpoint()
|
||||
try:
|
||||
resp = neofs_verbs.head_object(
|
||||
wallet, cid, oid, shell=shell, endpoint=node, is_raw=True, is_direct=True
|
||||
wallet, cid, oid, shell=shell, endpoint=endpoint, is_raw=True, is_direct=True
|
||||
)
|
||||
if resp["lastPart"]:
|
||||
return resp["lastPart"]
|
||||
except Exception:
|
||||
logger.info(f"No Last Object found on {node}; continue")
|
||||
logger.info(f"No Last Object found on {endpoint}; continue")
|
||||
logger.error(f"No Last Object for {cid}/{oid} found among all Storage Nodes")
|
||||
return None
|
||||
|
|
|
@ -11,7 +11,7 @@ from typing import Optional, Union
|
|||
|
||||
import allure
|
||||
import json_transformers
|
||||
from common import NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||
from common import NEOFS_CLI_EXEC, WALLET_CONFIG
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.shell import Shell
|
||||
|
||||
|
@ -24,6 +24,7 @@ DEFAULT_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
|
|||
def create_container(
|
||||
wallet: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
rule: str = DEFAULT_PLACEMENT_RULE,
|
||||
basic_acl: str = "",
|
||||
attributes: Optional[dict] = None,
|
||||
|
@ -49,6 +50,7 @@ def create_container(
|
|||
the session token; this parameter makes sense
|
||||
when paired with `session_token`
|
||||
shell: executor for cli command
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
options (optional, dict): any other options to pass to the call
|
||||
name (optional, str): container name attribute
|
||||
await_mode (bool): block execution until container is persisted
|
||||
|
@ -60,7 +62,7 @@ def create_container(
|
|||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
result = cli.container.create(
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=session_wallet if session_wallet else wallet,
|
||||
policy=rule,
|
||||
basic_acl=basic_acl,
|
||||
|
@ -76,16 +78,16 @@ def create_container(
|
|||
logger.info("Container created; waiting until it is persisted in the sidechain")
|
||||
|
||||
if wait_for_creation:
|
||||
wait_for_container_creation(wallet, cid, shell=shell)
|
||||
wait_for_container_creation(wallet, cid, shell, endpoint)
|
||||
|
||||
return cid
|
||||
|
||||
|
||||
def wait_for_container_creation(
|
||||
wallet: str, cid: str, shell: Shell, attempts: int = 15, sleep_interval: int = 1
|
||||
wallet: str, cid: str, shell: Shell, endpoint: str, attempts: int = 15, sleep_interval: int = 1
|
||||
):
|
||||
for _ in range(attempts):
|
||||
containers = list_containers(wallet, shell=shell)
|
||||
containers = list_containers(wallet, shell, endpoint)
|
||||
if cid in containers:
|
||||
return
|
||||
logger.info(f"There is no {cid} in {containers} yet; sleep {sleep_interval} and continue")
|
||||
|
@ -96,11 +98,11 @@ def wait_for_container_creation(
|
|||
|
||||
|
||||
def wait_for_container_deletion(
|
||||
wallet: str, cid: str, shell: Shell, attempts: int = 30, sleep_interval: int = 1
|
||||
wallet: str, cid: str, shell: Shell, endpoint: str, attempts: int = 30, sleep_interval: int = 1
|
||||
):
|
||||
for _ in range(attempts):
|
||||
try:
|
||||
get_container(wallet, cid, shell=shell)
|
||||
get_container(wallet, cid, shell=shell, endpoint=endpoint)
|
||||
sleep(sleep_interval)
|
||||
continue
|
||||
except Exception as err:
|
||||
|
@ -111,18 +113,19 @@ def wait_for_container_deletion(
|
|||
|
||||
|
||||
@allure.step("List Containers")
|
||||
def list_containers(wallet: str, shell: Shell) -> list[str]:
|
||||
def list_containers(wallet: str, shell: Shell, endpoint: str) -> list[str]:
|
||||
"""
|
||||
A wrapper for `neofs-cli container list` call. It returns all the
|
||||
available containers for the given wallet.
|
||||
Args:
|
||||
wallet (str): a wallet on whose behalf we list the containers
|
||||
shell: executor for cli command
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
Returns:
|
||||
(list): list of containers
|
||||
"""
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
result = cli.container.list(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet)
|
||||
result = cli.container.list(rpc_endpoint=endpoint, wallet=wallet)
|
||||
logger.info(f"Containers: \n{result}")
|
||||
return result.stdout.split()
|
||||
|
||||
|
@ -132,6 +135,7 @@ def get_container(
|
|||
wallet: str,
|
||||
cid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
json_mode: bool = True,
|
||||
) -> Union[dict, str]:
|
||||
"""
|
||||
|
@ -141,14 +145,14 @@ def get_container(
|
|||
wallet (str): path to a wallet on whose behalf we get the container
|
||||
cid (str): ID of the container to get
|
||||
shell: executor for cli command
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
json_mode (bool): return container in JSON format
|
||||
Returns:
|
||||
(dict, str): dict of container attributes
|
||||
"""
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
result = cli.container.get(
|
||||
rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, json_mode=json_mode
|
||||
)
|
||||
result = cli.container.get(rpc_endpoint=endpoint, wallet=wallet, cid=cid, json_mode=json_mode)
|
||||
|
||||
if not json_mode:
|
||||
return result.stdout
|
||||
|
@ -166,7 +170,12 @@ def get_container(
|
|||
# TODO: make the error message about a non-found container more user-friendly
|
||||
# https://github.com/nspcc-dev/neofs-contract/issues/121
|
||||
def delete_container(
|
||||
wallet: str, cid: str, shell: Shell, force: bool = False, session_token: Optional[str] = None
|
||||
wallet: str,
|
||||
cid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
force: bool = False,
|
||||
session_token: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
A wrapper for `neofs-cli container delete` call.
|
||||
|
@ -174,6 +183,7 @@ def delete_container(
|
|||
wallet (str): path to a wallet on whose behalf we delete the container
|
||||
cid (str): ID of the container to delete
|
||||
shell: executor for cli command
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
force (bool): do not check whether container contains locks and remove immediately
|
||||
session_token: a path to session token file
|
||||
This function doesn't return anything.
|
||||
|
@ -181,7 +191,7 @@ def delete_container(
|
|||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
cli.container.delete(
|
||||
wallet=wallet, cid=cid, rpc_endpoint=NEOFS_ENDPOINT, force=force, session=session_token
|
||||
wallet=wallet, cid=cid, rpc_endpoint=endpoint, force=force, session=session_token
|
||||
)
|
||||
|
||||
|
||||
|
@ -212,10 +222,10 @@ def _parse_cid(output: str) -> str:
|
|||
|
||||
|
||||
@allure.step("Search container by name")
|
||||
def search_container_by_name(wallet: str, name: str, shell: Shell):
|
||||
list_cids = list_containers(wallet, shell)
|
||||
def search_container_by_name(wallet: str, name: str, shell: Shell, endpoint: str):
|
||||
list_cids = list_containers(wallet, shell, endpoint)
|
||||
for cid in list_cids:
|
||||
cont_info = get_container(wallet, cid, shell, True)
|
||||
cont_info = get_container(wallet, cid, shell, endpoint, True)
|
||||
if cont_info.get("attributes").get("Name", None) == name:
|
||||
return cid
|
||||
return None
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from acl import EACLOperation
|
||||
from cluster import Cluster
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.object_access import (
|
||||
can_delete_object,
|
||||
|
@ -19,17 +20,21 @@ def check_full_access_to_container(
|
|||
oid: str,
|
||||
file_name: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
):
|
||||
assert can_put_object(wallet, cid, file_name, shell, bearer, wallet_config, xhdr)
|
||||
assert can_get_head_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
assert can_get_range_of_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
assert can_get_range_hash_of_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
assert can_search_object(wallet, cid, shell, oid, bearer, wallet_config, xhdr)
|
||||
assert can_get_object(wallet, cid, oid, file_name, shell, bearer, wallet_config, xhdr)
|
||||
assert can_delete_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
endpoint = cluster.default_rpc_endpoint
|
||||
assert can_put_object(wallet, cid, file_name, shell, cluster, bearer, wallet_config, xhdr)
|
||||
assert can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr)
|
||||
assert can_get_range_of_object(wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr)
|
||||
assert can_get_range_hash_of_object(
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
assert can_search_object(wallet, cid, shell, endpoint, oid, bearer, wallet_config, xhdr)
|
||||
assert can_get_object(wallet, cid, oid, file_name, shell, cluster, bearer, wallet_config, xhdr)
|
||||
assert can_delete_object(wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr)
|
||||
|
||||
|
||||
def check_no_access_to_container(
|
||||
|
@ -38,17 +43,25 @@ def check_no_access_to_container(
|
|||
oid: str,
|
||||
file_name: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
):
|
||||
assert not can_put_object(wallet, cid, file_name, shell, bearer, wallet_config, xhdr)
|
||||
assert not can_get_head_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
assert not can_get_range_of_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
assert not can_get_range_hash_of_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
assert not can_search_object(wallet, cid, shell, oid, bearer, wallet_config, xhdr)
|
||||
assert not can_get_object(wallet, cid, oid, file_name, shell, bearer, wallet_config, xhdr)
|
||||
assert not can_delete_object(wallet, cid, oid, shell, bearer, wallet_config, xhdr)
|
||||
endpoint = cluster.default_rpc_endpoint
|
||||
assert not can_put_object(wallet, cid, file_name, shell, cluster, bearer, wallet_config, xhdr)
|
||||
assert not can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr)
|
||||
assert not can_get_range_of_object(
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
assert not can_get_range_hash_of_object(
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
assert not can_search_object(wallet, cid, shell, endpoint, oid, bearer, wallet_config, xhdr)
|
||||
assert not can_get_object(
|
||||
wallet, cid, oid, file_name, shell, cluster, bearer, wallet_config, xhdr
|
||||
)
|
||||
assert not can_delete_object(wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr)
|
||||
|
||||
|
||||
def check_custom_access_to_container(
|
||||
|
@ -57,42 +70,44 @@ def check_custom_access_to_container(
|
|||
oid: str,
|
||||
file_name: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
deny_operations: Optional[List[EACLOperation]] = None,
|
||||
ignore_operations: Optional[List[EACLOperation]] = None,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
):
|
||||
endpoint = cluster.default_rpc_endpoint
|
||||
deny_operations = [op.value for op in deny_operations or []]
|
||||
ignore_operations = [op.value for op in ignore_operations or []]
|
||||
checks: dict = {}
|
||||
if EACLOperation.PUT.value not in ignore_operations:
|
||||
checks[EACLOperation.PUT.value] = can_put_object(
|
||||
wallet, cid, file_name, shell, bearer, wallet_config, xhdr
|
||||
wallet, cid, file_name, shell, cluster, bearer, wallet_config, xhdr
|
||||
)
|
||||
if EACLOperation.HEAD.value not in ignore_operations:
|
||||
checks[EACLOperation.HEAD.value] = can_get_head_object(
|
||||
wallet, cid, oid, shell, bearer, wallet_config, xhdr
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
if EACLOperation.GET_RANGE.value not in ignore_operations:
|
||||
checks[EACLOperation.GET_RANGE.value] = can_get_range_of_object(
|
||||
wallet, cid, oid, shell, bearer, wallet_config, xhdr
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
if EACLOperation.GET_RANGE_HASH.value not in ignore_operations:
|
||||
checks[EACLOperation.GET_RANGE_HASH.value] = can_get_range_hash_of_object(
|
||||
wallet, cid, oid, shell, bearer, wallet_config, xhdr
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
if EACLOperation.SEARCH.value not in ignore_operations:
|
||||
checks[EACLOperation.SEARCH.value] = can_search_object(
|
||||
wallet, cid, shell, oid, bearer, wallet_config, xhdr
|
||||
wallet, cid, shell, endpoint, oid, bearer, wallet_config, xhdr
|
||||
)
|
||||
if EACLOperation.GET.value not in ignore_operations:
|
||||
checks[EACLOperation.GET.value] = can_get_object(
|
||||
wallet, cid, oid, file_name, shell, bearer, wallet_config, xhdr
|
||||
wallet, cid, oid, file_name, shell, cluster, bearer, wallet_config, xhdr
|
||||
)
|
||||
if EACLOperation.DELETE.value not in ignore_operations:
|
||||
checks[EACLOperation.DELETE.value] = can_delete_object(
|
||||
wallet, cid, oid, shell, bearer, wallet_config, xhdr
|
||||
wallet, cid, oid, shell, endpoint, bearer, wallet_config, xhdr
|
||||
)
|
||||
|
||||
failed_checks = [
|
||||
|
@ -114,6 +129,7 @@ def check_read_only_container(
|
|||
oid: str,
|
||||
file_name: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -128,4 +144,5 @@ def check_read_only_container(
|
|||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
cluster=cluster,
|
||||
)
|
||||
|
|
|
@ -3,15 +3,8 @@ import logging
|
|||
from time import sleep
|
||||
|
||||
import allure
|
||||
from common import (
|
||||
IR_WALLET_PASS,
|
||||
IR_WALLET_PATH,
|
||||
MAINNET_BLOCK_TIME,
|
||||
MORPH_ENDPOINT,
|
||||
NEOFS_ADM_CONFIG_PATH,
|
||||
NEOFS_ADM_EXEC,
|
||||
NEOGO_EXECUTABLE,
|
||||
)
|
||||
from cluster import Cluster
|
||||
from common import MAINNET_BLOCK_TIME, NEOFS_ADM_CONFIG_PATH, NEOFS_ADM_EXEC, NEOGO_EXECUTABLE
|
||||
from neofs_testlib.cli import NeofsAdm, NeoGo
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet
|
||||
|
@ -22,28 +15,32 @@ logger = logging.getLogger("NeoLogger")
|
|||
|
||||
|
||||
@allure.step("Ensure fresh epoch")
|
||||
def ensure_fresh_epoch(shell: Shell) -> int:
|
||||
def ensure_fresh_epoch(shell: Shell, cluster: Cluster) -> int:
|
||||
# ensure new fresh epoch to avoid epoch switch during test session
|
||||
current_epoch = get_epoch(shell)
|
||||
tick_epoch(shell)
|
||||
epoch = get_epoch(shell)
|
||||
current_epoch = get_epoch(shell, cluster)
|
||||
tick_epoch(shell, cluster)
|
||||
epoch = get_epoch(shell, cluster)
|
||||
assert epoch > current_epoch, "Epoch wasn't ticked"
|
||||
return epoch
|
||||
|
||||
|
||||
@allure.step("Get Epoch")
|
||||
def get_epoch(shell: Shell):
|
||||
def get_epoch(shell: Shell, cluster: Cluster):
|
||||
morph_chain = cluster.morph_chain_nodes[0]
|
||||
morph_endpoint = morph_chain.get_endpoint()
|
||||
|
||||
neogo = NeoGo(shell=shell, neo_go_exec_path=NEOGO_EXECUTABLE)
|
||||
out = neogo.contract.testinvokefunction(
|
||||
scripthash=get_contract_hash("netmap.neofs", shell=shell),
|
||||
scripthash=get_contract_hash(morph_chain, "netmap.neofs", shell=shell),
|
||||
method="epoch",
|
||||
rpc_endpoint=MORPH_ENDPOINT,
|
||||
rpc_endpoint=morph_endpoint,
|
||||
)
|
||||
return int(json.loads(out.stdout.replace("\n", ""))["stack"][0]["value"])
|
||||
|
||||
|
||||
@allure.step("Tick Epoch")
|
||||
def tick_epoch(shell: Shell):
|
||||
def tick_epoch(shell: Shell, cluster: Cluster):
|
||||
|
||||
if NEOFS_ADM_EXEC and NEOFS_ADM_CONFIG_PATH:
|
||||
# If neofs-adm is available, then we tick epoch with it (to be consistent with UAT tests)
|
||||
neofsadm = NeofsAdm(
|
||||
|
@ -52,21 +49,30 @@ def tick_epoch(shell: Shell):
|
|||
neofsadm.morph.force_new_epoch()
|
||||
return
|
||||
|
||||
# Otherwise we tick epoch using transaction
|
||||
cur_epoch = get_epoch(shell)
|
||||
# Use first node by default
|
||||
|
||||
ir_address = get_last_address_from_wallet(IR_WALLET_PATH, IR_WALLET_PASS)
|
||||
# Otherwise we tick epoch using transaction
|
||||
cur_epoch = get_epoch(shell, cluster)
|
||||
|
||||
ir_node = cluster.ir_nodes[0]
|
||||
# In case if no local_wallet_path is provided, we use wallet_path
|
||||
ir_wallet_path = ir_node.get_wallet_path()
|
||||
ir_wallet_pass = ir_node.get_wallet_password()
|
||||
ir_address = get_last_address_from_wallet(ir_wallet_path, ir_wallet_pass)
|
||||
|
||||
morph_chain = cluster.morph_chain_nodes[0]
|
||||
morph_endpoint = morph_chain.get_endpoint()
|
||||
|
||||
neogo = NeoGo(shell, neo_go_exec_path=NEOGO_EXECUTABLE)
|
||||
neogo.contract.invokefunction(
|
||||
wallet=IR_WALLET_PATH,
|
||||
wallet_password=IR_WALLET_PASS,
|
||||
scripthash=get_contract_hash("netmap.neofs", shell=shell),
|
||||
wallet=ir_wallet_path,
|
||||
wallet_password=ir_wallet_pass,
|
||||
scripthash=get_contract_hash(morph_chain, "netmap.neofs", shell=shell),
|
||||
method="newEpoch",
|
||||
arguments=f"int:{cur_epoch + 1}",
|
||||
multisig_hash=f"{ir_address}:Global",
|
||||
address=ir_address,
|
||||
rpc_endpoint=MORPH_ENDPOINT,
|
||||
rpc_endpoint=morph_endpoint,
|
||||
force=True,
|
||||
gas=1,
|
||||
)
|
||||
|
|
|
@ -1,55 +1,51 @@
|
|||
import logging
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from common import NEOFS_NETMAP_DICT
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from cluster import Cluster, StorageNode
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.node_management import node_healthcheck
|
||||
from python_keywords.node_management import storage_node_healthcheck
|
||||
from storage_policy import get_nodes_with_object
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
||||
@allure.step("Wait for object replication")
|
||||
def wait_object_replication_on_nodes(
|
||||
wallet: str,
|
||||
def wait_object_replication(
|
||||
cid: str,
|
||||
oid: str,
|
||||
expected_copies: int,
|
||||
shell: Shell,
|
||||
excluded_nodes: Optional[list[str]] = None,
|
||||
) -> list[str]:
|
||||
excluded_nodes = excluded_nodes or []
|
||||
nodes: list[StorageNode],
|
||||
) -> list[StorageNode]:
|
||||
sleep_interval, attempts = 15, 20
|
||||
nodes = []
|
||||
for __attempt in range(attempts):
|
||||
nodes = get_nodes_with_object(wallet, cid, oid, shell=shell, skip_nodes=excluded_nodes)
|
||||
if len(nodes) >= expected_copies:
|
||||
return nodes
|
||||
nodes_with_object = []
|
||||
for _ in range(attempts):
|
||||
nodes_with_object = get_nodes_with_object(cid, oid, shell=shell, nodes=nodes)
|
||||
if len(nodes_with_object) >= expected_copies:
|
||||
return nodes_with_object
|
||||
sleep(sleep_interval)
|
||||
raise AssertionError(
|
||||
f"Expected {expected_copies} copies of object, but found {len(nodes)}. "
|
||||
f"Expected {expected_copies} copies of object, but found {len(nodes_with_object)}. "
|
||||
f"Waiting time {sleep_interval * attempts}"
|
||||
)
|
||||
|
||||
|
||||
@allure.step("Wait for storage node returned to cluster")
|
||||
def wait_all_storage_node_returned(hosting: Hosting) -> None:
|
||||
@allure.step("Wait for storage nodes returned to cluster")
|
||||
def wait_all_storage_nodes_returned(cluster: Cluster) -> None:
|
||||
sleep_interval, attempts = 15, 20
|
||||
for __attempt in range(attempts):
|
||||
if is_all_storage_node_returned(hosting):
|
||||
if is_all_storage_nodes_returned(cluster):
|
||||
return
|
||||
sleep(sleep_interval)
|
||||
raise AssertionError("Storage node(s) is broken")
|
||||
|
||||
|
||||
def is_all_storage_node_returned(hosting: Hosting) -> bool:
|
||||
def is_all_storage_nodes_returned(cluster: Cluster) -> bool:
|
||||
with allure.step("Run health check for all storage nodes"):
|
||||
for node_name in NEOFS_NETMAP_DICT.keys():
|
||||
for node in cluster.storage_nodes:
|
||||
try:
|
||||
health_check = node_healthcheck(hosting, node_name)
|
||||
health_check = storage_node_healthcheck(node)
|
||||
except Exception as err:
|
||||
logger.warning(f"Node healthcheck fails with error {err}")
|
||||
return False
|
||||
|
|
|
@ -9,7 +9,6 @@ from urllib.parse import quote_plus
|
|||
import allure
|
||||
import requests
|
||||
from cli_helpers import _cmd_run
|
||||
from common import HTTP_GATE
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
@ -17,13 +16,14 @@ ASSETS_DIR = os.getenv("ASSETS_DIR", "TemporaryDir/")
|
|||
|
||||
|
||||
@allure.step("Get via HTTP Gate")
|
||||
def get_via_http_gate(cid: str, oid: str):
|
||||
def get_via_http_gate(cid: str, oid: str, endpoint: str):
|
||||
"""
|
||||
This function gets given object from HTTP gate
|
||||
:param cid: CID to get object from
|
||||
:param oid: object OID
|
||||
cid: container id to get object from
|
||||
oid: object ID
|
||||
endpoint: http gate endpoint
|
||||
"""
|
||||
request = f"{HTTP_GATE}/get/{cid}/{oid}"
|
||||
request = f"{endpoint}/get/{cid}/{oid}"
|
||||
resp = requests.get(request, stream=True)
|
||||
|
||||
if not resp.ok:
|
||||
|
@ -44,13 +44,14 @@ def get_via_http_gate(cid: str, oid: str):
|
|||
|
||||
|
||||
@allure.step("Get via Zip HTTP Gate")
|
||||
def get_via_zip_http_gate(cid: str, prefix: str):
|
||||
def get_via_zip_http_gate(cid: str, prefix: str, endpoint: str):
|
||||
"""
|
||||
This function gets given object from HTTP gate
|
||||
:param cid: CID to get object from
|
||||
:param prefix: common prefix
|
||||
cid: container id to get object from
|
||||
prefix: common prefix
|
||||
endpoint: http gate endpoint
|
||||
"""
|
||||
request = f"{HTTP_GATE}/zip/{cid}/{prefix}"
|
||||
request = f"{endpoint}/zip/{cid}/{prefix}"
|
||||
resp = requests.get(request, stream=True)
|
||||
|
||||
if not resp.ok:
|
||||
|
@ -75,15 +76,16 @@ def get_via_zip_http_gate(cid: str, prefix: str):
|
|||
|
||||
|
||||
@allure.step("Get via HTTP Gate by attribute")
|
||||
def get_via_http_gate_by_attribute(cid: str, attribute: dict):
|
||||
def get_via_http_gate_by_attribute(cid: str, attribute: dict, endpoint: str):
|
||||
"""
|
||||
This function gets given object from HTTP gate
|
||||
:param cid: CID to get object from
|
||||
:param attribute: attribute name: attribute value pair
|
||||
cid: CID to get object from
|
||||
attribute: attribute {name: attribute} value pair
|
||||
endpoint: http gate endpoint
|
||||
"""
|
||||
attr_name = list(attribute.keys())[0]
|
||||
attr_value = quote_plus(str(attribute.get(attr_name)))
|
||||
request = f"{HTTP_GATE}/get_by_attribute/{cid}/{quote_plus(str(attr_name))}/{attr_value}"
|
||||
request = f"{endpoint}/get_by_attribute/{cid}/{quote_plus(str(attr_name))}/{attr_value}"
|
||||
resp = requests.get(request, stream=True)
|
||||
|
||||
if not resp.ok:
|
||||
|
@ -104,14 +106,15 @@ def get_via_http_gate_by_attribute(cid: str, attribute: dict):
|
|||
|
||||
|
||||
@allure.step("Upload via HTTP Gate")
|
||||
def upload_via_http_gate(cid: str, path: str, headers: dict = None) -> str:
|
||||
def upload_via_http_gate(cid: str, path: str, endpoint: str, headers: dict = None) -> str:
|
||||
"""
|
||||
This function upload given object through HTTP gate
|
||||
:param cid: CID to get object from
|
||||
:param path: File path to upload
|
||||
:param headers: Object header
|
||||
cid: CID to get object from
|
||||
path: File path to upload
|
||||
endpoint: http gate endpoint
|
||||
headers: Object header
|
||||
"""
|
||||
request = f"{HTTP_GATE}/upload/{cid}"
|
||||
request = f"{endpoint}/upload/{cid}"
|
||||
files = {"upload_file": open(path, "rb")}
|
||||
body = {"filename": path}
|
||||
resp = requests.post(request, files=files, data=body, headers=headers)
|
||||
|
@ -134,15 +137,16 @@ def upload_via_http_gate(cid: str, path: str, headers: dict = None) -> str:
|
|||
|
||||
@allure.step("Upload via HTTP Gate using Curl")
|
||||
def upload_via_http_gate_curl(
|
||||
cid: str, filepath: str, large_object=False, headers: dict = None
|
||||
cid: str, filepath: str, endpoint: str, large_object=False, headers: dict = None
|
||||
) -> str:
|
||||
"""
|
||||
This function upload given object through HTTP gate using curl utility.
|
||||
:param cid: CID to get object from
|
||||
:param filepath: File path to upload
|
||||
:param headers: Object header
|
||||
cid: CID to get object from
|
||||
filepath: File path to upload
|
||||
headers: Object header
|
||||
endpoint: http gate endpoint
|
||||
"""
|
||||
request = f"{HTTP_GATE}/upload/{cid}"
|
||||
request = f"{endpoint}/upload/{cid}"
|
||||
files = f"file=@{filepath};filename={os.path.basename(filepath)}"
|
||||
cmd = f"curl -F '{files}' {request}"
|
||||
if large_object:
|
||||
|
@ -156,13 +160,14 @@ def upload_via_http_gate_curl(
|
|||
|
||||
|
||||
@allure.step("Get via HTTP Gate using Curl")
|
||||
def get_via_http_curl(cid: str, oid: str) -> str:
|
||||
def get_via_http_curl(cid: str, oid: str, endpoint: str) -> str:
|
||||
"""
|
||||
This function gets given object from HTTP gate using curl utility.
|
||||
:param cid: CID to get object from
|
||||
:param oid: object OID
|
||||
cid: CID to get object from
|
||||
oid: object OID
|
||||
endpoint: http gate endpoint
|
||||
"""
|
||||
request = f"{HTTP_GATE}/get/{cid}/{oid}"
|
||||
request = f"{endpoint}/get/{cid}/{oid}"
|
||||
file_path = os.path.join(os.getcwd(), ASSETS_DIR, f"{cid}_{oid}_{str(uuid.uuid4())}")
|
||||
|
||||
cmd = f"curl {request} > {file_path}"
|
||||
|
|
|
@ -1,29 +1,77 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import uuid
|
||||
from typing import Any, Optional
|
||||
|
||||
import allure
|
||||
import json_transformers
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, NEOFS_NETMAP, WALLET_CONFIG
|
||||
from cluster import Cluster
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, WALLET_CONFIG
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.shell import Shell
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
||||
@allure.step("Get object")
|
||||
@allure.step("Get object from random node")
|
||||
def get_object_from_random_node(
|
||||
wallet: str,
|
||||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
write_object: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
no_progress: bool = True,
|
||||
session: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
GET from NeoFS random storage node
|
||||
|
||||
Args:
|
||||
wallet: wallet on whose behalf GET is done
|
||||
cid: ID of Container where we get the Object from
|
||||
oid: Object ID
|
||||
shell: executor for cli command
|
||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||
write_object (optional, str): path to downloaded file, appends to `--file` key
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
wallet_config(optional, str): path to the wallet config
|
||||
no_progress(optional, bool): do not show progress bar
|
||||
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||
session (optional, dict): path to a JSON-encoded container session token
|
||||
Returns:
|
||||
(str): path to downloaded file
|
||||
"""
|
||||
endpoint = cluster.get_random_storage_rpc_endpoint()
|
||||
return get_object(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell,
|
||||
endpoint,
|
||||
bearer,
|
||||
write_object,
|
||||
xhdr,
|
||||
wallet_config,
|
||||
no_progress,
|
||||
session,
|
||||
)
|
||||
|
||||
|
||||
@allure.step("Get object from {endpoint}")
|
||||
def get_object(
|
||||
wallet: str,
|
||||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str = None,
|
||||
bearer: Optional[str] = None,
|
||||
write_object: str = "",
|
||||
endpoint: str = "",
|
||||
write_object: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
no_progress: bool = True,
|
||||
|
@ -37,9 +85,9 @@ def get_object(
|
|||
cid (str): ID of Container where we get the Object from
|
||||
oid (str): Object ID
|
||||
shell: executor for cli command
|
||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||
write_object (optional, str): path to downloaded file, appends to `--file` key
|
||||
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
bearer: path to Bearer Token file, appends to `--bearer` key
|
||||
write_object: path to downloaded file, appends to `--file` key
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
wallet_config(optional, str): path to the wallet config
|
||||
no_progress(optional, bool): do not show progress bar
|
||||
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||
|
@ -52,12 +100,9 @@ def get_object(
|
|||
write_object = str(uuid.uuid4())
|
||||
file_path = os.path.join(ASSETS_DIR, write_object)
|
||||
|
||||
if not endpoint:
|
||||
endpoint = random.sample(NEOFS_NETMAP, 1)[0]
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
cli.object.get(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
|
@ -71,15 +116,15 @@ def get_object(
|
|||
return file_path
|
||||
|
||||
|
||||
@allure.step("Get Range Hash")
|
||||
@allure.step("Get Range Hash from {endpoint}")
|
||||
def get_range_hash(
|
||||
wallet: str,
|
||||
cid: str,
|
||||
oid: str,
|
||||
range_cut: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: Optional[str] = None,
|
||||
endpoint: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
session: Optional[str] = None,
|
||||
|
@ -102,10 +147,9 @@ def get_range_hash(
|
|||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
result = cli.object.hash(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
|
@ -119,16 +163,69 @@ def get_range_hash(
|
|||
return result.stdout.split(":")[1].strip()
|
||||
|
||||
|
||||
@allure.step("Put object")
|
||||
@allure.step("Put object to random node")
|
||||
def put_object_to_random_node(
|
||||
wallet: str,
|
||||
path: str,
|
||||
cid: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
attributes: Optional[dict] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
expire_at: Optional[int] = None,
|
||||
no_progress: bool = True,
|
||||
session: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
PUT of given file to a random storage node.
|
||||
|
||||
Args:
|
||||
wallet: wallet on whose behalf PUT is done
|
||||
path: path to file to be PUT
|
||||
cid: ID of Container where we get the Object from
|
||||
shell: executor for cli command
|
||||
cluster: cluster under test
|
||||
bearer: path to Bearer Token file, appends to `--bearer` key
|
||||
attributes: User attributes in form of Key1=Value1,Key2=Value2
|
||||
cluster: cluster under test
|
||||
wallet_config: path to the wallet config
|
||||
no_progress: do not show progress bar
|
||||
expire_at: Last epoch in the life of the object
|
||||
xhdr: Request X-Headers in form of Key=Value
|
||||
session: path to a JSON-encoded container session token
|
||||
Returns:
|
||||
ID of uploaded Object
|
||||
"""
|
||||
|
||||
endpoint = cluster.get_random_storage_rpc_endpoint()
|
||||
return put_object(
|
||||
wallet,
|
||||
path,
|
||||
cid,
|
||||
shell,
|
||||
endpoint,
|
||||
bearer,
|
||||
attributes,
|
||||
xhdr,
|
||||
wallet_config,
|
||||
expire_at,
|
||||
no_progress,
|
||||
session,
|
||||
)
|
||||
|
||||
|
||||
@allure.step("Put object at {endpoint} in container {cid}")
|
||||
def put_object(
|
||||
wallet: str,
|
||||
path: str,
|
||||
cid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: Optional[str] = None,
|
||||
attributes: Optional[dict] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
endpoint: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
expire_at: Optional[int] = None,
|
||||
no_progress: bool = True,
|
||||
|
@ -138,25 +235,21 @@ def put_object(
|
|||
PUT of given file.
|
||||
|
||||
Args:
|
||||
wallet (str): wallet on whose behalf PUT is done
|
||||
path (str): path to file to be PUT
|
||||
cid (str): ID of Container where we get the Object from
|
||||
wallet: wallet on whose behalf PUT is done
|
||||
path: path to file to be PUT
|
||||
cid: ID of Container where we get the Object from
|
||||
shell: executor for cli command
|
||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||
attributes (optional, str): User attributes in form of Key1=Value1,Key2=Value2
|
||||
endpoint(optional, str): NeoFS endpoint to send request to
|
||||
wallet_config(optional, str): path to the wallet config
|
||||
no_progress(optional, bool): do not show progress bar
|
||||
expire_at (optional, int): Last epoch in the life of the object
|
||||
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||
session (optional, dict): path to a JSON-encoded container session token
|
||||
bearer: path to Bearer Token file, appends to `--bearer` key
|
||||
attributes: User attributes in form of Key1=Value1,Key2=Value2
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
wallet_config: path to the wallet config
|
||||
no_progress: do not show progress bar
|
||||
expire_at: Last epoch in the life of the object
|
||||
xhdr: Request X-Headers in form of Key=Value
|
||||
session: path to a JSON-encoded container session token
|
||||
Returns:
|
||||
(str): ID of uploaded Object
|
||||
"""
|
||||
if not endpoint:
|
||||
endpoint = random.sample(NEOFS_NETMAP, 1)[0]
|
||||
if not endpoint:
|
||||
logger.info(f"---DEB:\n{NEOFS_NETMAP}")
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
result = cli.object.put(
|
||||
|
@ -178,13 +271,13 @@ def put_object(
|
|||
return oid.strip()
|
||||
|
||||
|
||||
@allure.step("Delete object")
|
||||
@allure.step("Delete object {cid}/{oid} from {endpoint}")
|
||||
def delete_object(
|
||||
wallet: str,
|
||||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: Optional[str] = None,
|
||||
endpoint: str = None,
|
||||
bearer: str = "",
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -194,21 +287,22 @@ def delete_object(
|
|||
DELETE an Object.
|
||||
|
||||
Args:
|
||||
wallet (str): wallet on whose behalf DELETE is done
|
||||
cid (str): ID of Container where we get the Object from
|
||||
oid (str): ID of Object we are going to delete
|
||||
wallet: wallet on whose behalf DELETE is done
|
||||
cid: ID of Container where we get the Object from
|
||||
oid: ID of Object we are going to delete
|
||||
shell: executor for cli command
|
||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
wallet_config(optional, str): path to the wallet config
|
||||
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||
session (optional, dict): path to a JSON-encoded container session token
|
||||
bearer: path to Bearer Token file, appends to `--bearer` key
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
wallet_config: path to the wallet config
|
||||
xhdr: Request X-Headers in form of Key=Value
|
||||
session: path to a JSON-encoded container session token
|
||||
Returns:
|
||||
(str): Tombstone ID
|
||||
"""
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
result = cli.object.delete(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
|
@ -229,7 +323,7 @@ def get_range(
|
|||
oid: str,
|
||||
range_cut: str,
|
||||
shell: Shell,
|
||||
endpoint: Optional[str] = None,
|
||||
endpoint: str = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
bearer: str = "",
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -239,16 +333,16 @@ def get_range(
|
|||
GETRANGE an Object.
|
||||
|
||||
Args:
|
||||
wallet (str): wallet on whose behalf GETRANGE is done
|
||||
cid (str): ID of Container where we get the Object from
|
||||
oid (str): ID of Object we are going to request
|
||||
range_cut (str): range to take data from in the form offset:length
|
||||
wallet: wallet on whose behalf GETRANGE is done
|
||||
cid: ID of Container where we get the Object from
|
||||
oid: ID of Object we are going to request
|
||||
range_cut: range to take data from in the form offset:length
|
||||
shell: executor for cli command
|
||||
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||
wallet_config(optional, str): path to the wallet config
|
||||
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||
session (optional, dict): path to a JSON-encoded container session token
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
bearer: path to Bearer Token file, appends to `--bearer` key
|
||||
wallet_config: path to the wallet config
|
||||
xhdr: Request X-Headers in form of Key=Value
|
||||
session: path to a JSON-encoded container session token
|
||||
Returns:
|
||||
(str, bytes) - path to the file with range content and content of this file as bytes
|
||||
"""
|
||||
|
@ -256,7 +350,7 @@ def get_range(
|
|||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
cli.object.range(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
|
@ -278,9 +372,9 @@ def lock_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
lifetime: Optional[int] = None,
|
||||
expire_at: Optional[int] = None,
|
||||
endpoint: Optional[str] = None,
|
||||
address: Optional[str] = None,
|
||||
bearer: Optional[str] = None,
|
||||
session: Optional[str] = None,
|
||||
|
@ -298,7 +392,8 @@ def lock_object(
|
|||
oid: Object ID.
|
||||
lifetime: Lock lifetime.
|
||||
expire_at: Lock expiration epoch.
|
||||
endpoint: Remote node address.
|
||||
shell: executor for cli command
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
session: Path to a JSON-encoded container session token.
|
||||
ttl: TTL value in request meta header (default 2).
|
||||
wallet: WIF (NEP-2) string or path to the wallet or binary key.
|
||||
|
@ -310,7 +405,7 @@ def lock_object(
|
|||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
result = cli.object.lock(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
lifetime=lifetime,
|
||||
expire_at=expire_at,
|
||||
address=address,
|
||||
|
@ -334,8 +429,8 @@ def search_object(
|
|||
wallet: str,
|
||||
cid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: str = "",
|
||||
endpoint: Optional[str] = None,
|
||||
filters: Optional[dict] = None,
|
||||
expected_objects_list: Optional[list] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
|
@ -348,26 +443,26 @@ def search_object(
|
|||
SEARCH an Object.
|
||||
|
||||
Args:
|
||||
wallet (str): wallet on whose behalf SEARCH is done
|
||||
cid (str): ID of Container where we get the Object from
|
||||
wallet: wallet on whose behalf SEARCH is done
|
||||
cid: ID of Container where we get the Object from
|
||||
shell: executor for cli command
|
||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
filters (optional, dict): key=value pairs to filter Objects
|
||||
expected_objects_list (optional, list): a list of ObjectIDs to compare found Objects with
|
||||
wallet_config(optional, str): path to the wallet config
|
||||
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||
session (optional, dict): path to a JSON-encoded container session token
|
||||
bearer: path to Bearer Token file, appends to `--bearer` key
|
||||
endpoint: NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
filters: key=value pairs to filter Objects
|
||||
expected_objects_list: a list of ObjectIDs to compare found Objects with
|
||||
wallet_config: path to the wallet config
|
||||
xhdr: Request X-Headers in form of Key=Value
|
||||
session: path to a JSON-encoded container session token
|
||||
phy: Search physically stored objects.
|
||||
root: Search for user objects.
|
||||
|
||||
Returns:
|
||||
(list): list of found ObjectIDs
|
||||
list of found ObjectIDs
|
||||
"""
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
result = cli.object.search(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
bearer=bearer,
|
||||
|
@ -401,8 +496,8 @@ def search_object(
|
|||
def get_netmap_netinfo(
|
||||
wallet: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
wallet_config: Optional[str] = None,
|
||||
endpoint: Optional[str] = None,
|
||||
address: Optional[str] = None,
|
||||
ttl: Optional[int] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -411,7 +506,7 @@ def get_netmap_netinfo(
|
|||
Get netmap netinfo output from node
|
||||
|
||||
Args:
|
||||
wallet (str): wallet on whose behalf SEARCH is done
|
||||
wallet (str): wallet on whose behalf request is done
|
||||
shell: executor for cli command
|
||||
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||
address: Address of wallet account
|
||||
|
@ -426,7 +521,7 @@ def get_netmap_netinfo(
|
|||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
output = cli.netmap.netinfo(
|
||||
wallet=wallet,
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
address=address,
|
||||
ttl=ttl,
|
||||
xhdr=xhdr,
|
||||
|
@ -452,9 +547,9 @@ def head_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: str = "",
|
||||
xhdr: Optional[dict] = None,
|
||||
endpoint: Optional[str] = None,
|
||||
json_output: bool = True,
|
||||
is_raw: bool = False,
|
||||
is_direct: bool = False,
|
||||
|
@ -489,7 +584,7 @@ def head_object(
|
|||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, wallet_config or WALLET_CONFIG)
|
||||
result = cli.object.head(
|
||||
rpc_endpoint=endpoint or NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=oid,
|
||||
|
|
|
@ -6,17 +6,10 @@ from dataclasses import dataclass
|
|||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from common import (
|
||||
MORPH_BLOCK_TIME,
|
||||
NEOFS_CLI_EXEC,
|
||||
NEOFS_NETMAP_DICT,
|
||||
STORAGE_WALLET_CONFIG,
|
||||
STORAGE_WALLET_PASS,
|
||||
)
|
||||
from data_formatters import get_wallet_public_key
|
||||
from cluster import Cluster, StorageNode
|
||||
from common import MORPH_BLOCK_TIME, NEOFS_CLI_EXEC
|
||||
from epoch import tick_epoch
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from utility import parse_time
|
||||
|
||||
|
@ -39,183 +32,189 @@ class HealthStatus:
|
|||
return HealthStatus(network, health)
|
||||
|
||||
|
||||
@allure.step("Stop storage nodes")
|
||||
def stop_nodes(hosting: Hosting, number: int, nodes: list[str]) -> list[str]:
|
||||
@allure.step("Stop random storage nodes")
|
||||
def stop_random_storage_nodes(number: int, nodes: list[StorageNode]) -> list[StorageNode]:
|
||||
"""
|
||||
Shuts down the given number of randomly selected storage nodes.
|
||||
Args:
|
||||
number (int): the number of nodes to shut down
|
||||
nodes (list): the list of nodes for possible shut down
|
||||
number: the number of storage nodes to stop
|
||||
nodes: the list of storage nodes to stop
|
||||
Returns:
|
||||
(list): the list of nodes that were shut down
|
||||
the list of nodes that were stopped
|
||||
"""
|
||||
nodes_to_stop = random.sample(nodes, number)
|
||||
for node in nodes_to_stop:
|
||||
host = hosting.get_host_by_service(node)
|
||||
host.stop_service(node)
|
||||
node.stop_service()
|
||||
return nodes_to_stop
|
||||
|
||||
|
||||
@allure.step("Start storage nodes")
|
||||
def start_nodes(hosting: Hosting, nodes: list[str]) -> None:
|
||||
@allure.step("Start storage node")
|
||||
def start_storage_nodes(nodes: list[StorageNode]) -> None:
|
||||
"""
|
||||
The function starts specified storage nodes.
|
||||
Args:
|
||||
nodes (list): the list of nodes to start
|
||||
nodes: the list of nodes to start
|
||||
"""
|
||||
for node in nodes:
|
||||
host = hosting.get_host_by_service(node)
|
||||
host.start_service(node)
|
||||
node.start_service()
|
||||
|
||||
|
||||
@allure.step("Get Locode")
|
||||
def get_locode() -> str:
|
||||
endpoint_values = random.choice(list(NEOFS_NETMAP_DICT.values()))
|
||||
locode = endpoint_values["UN-LOCODE"]
|
||||
logger.info(f"Random locode chosen: {locode}")
|
||||
@allure.step("Get Locode from random storage node")
|
||||
def get_locode_from_random_node(cluster: Cluster) -> str:
|
||||
node = random.choice(cluster.storage_nodes)
|
||||
locode = node.get_un_locode()
|
||||
logger.info(f"Chosen '{locode}' locode from node {node}")
|
||||
return locode
|
||||
|
||||
|
||||
@allure.step("Healthcheck for node {node_name}")
|
||||
def node_healthcheck(hosting: Hosting, node_name: str) -> HealthStatus:
|
||||
@allure.step("Healthcheck for storage node {node}")
|
||||
def storage_node_healthcheck(node: StorageNode) -> HealthStatus:
|
||||
"""
|
||||
The function returns node's health status.
|
||||
The function returns storage node's health status.
|
||||
Args:
|
||||
node_name str: node name for which health status should be retrieved.
|
||||
node: storage node for which health status should be retrieved.
|
||||
Returns:
|
||||
health status as HealthStatus object.
|
||||
"""
|
||||
command = "control healthcheck"
|
||||
output = _run_control_command_with_retries(hosting, node_name, command)
|
||||
output = _run_control_command_with_retries(node, command)
|
||||
return HealthStatus.from_stdout(output)
|
||||
|
||||
|
||||
@allure.step("Set status for node {node_name}")
|
||||
def node_set_status(hosting: Hosting, node_name: str, status: str, retries: int = 0) -> None:
|
||||
@allure.step("Set status for {node}")
|
||||
def storage_node_set_status(node: StorageNode, status: str, retries: int = 0) -> None:
|
||||
"""
|
||||
The function sets particular status for given node.
|
||||
Args:
|
||||
node_name: node name for which status should be set.
|
||||
node: node for which status should be set.
|
||||
status: online or offline.
|
||||
retries (optional, int): number of retry attempts if it didn't work from the first time
|
||||
"""
|
||||
command = f"control set-status --status {status}"
|
||||
_run_control_command_with_retries(hosting, node_name, command, retries)
|
||||
_run_control_command_with_retries(node, command, retries)
|
||||
|
||||
|
||||
@allure.step("Get netmap snapshot")
|
||||
def get_netmap_snapshot(node_name: str, shell: Shell) -> str:
|
||||
def get_netmap_snapshot(node: StorageNode, shell: Shell) -> str:
|
||||
"""
|
||||
The function returns string representation of netmap snapshot.
|
||||
Args:
|
||||
node_name str: node name from which netmap snapshot should be requested.
|
||||
node: node from which netmap snapshot should be requested.
|
||||
Returns:
|
||||
string representation of netmap
|
||||
"""
|
||||
node_info = NEOFS_NETMAP_DICT[node_name]
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, config_file=STORAGE_WALLET_CONFIG)
|
||||
|
||||
storage_wallet_config = node.get_wallet_config_path()
|
||||
storage_wallet_path = node.get_wallet_path()
|
||||
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, config_file=storage_wallet_config)
|
||||
return cli.netmap.snapshot(
|
||||
rpc_endpoint=node_info["rpc"],
|
||||
wallet=node_info["wallet_path"],
|
||||
rpc_endpoint=node.get_rpc_endpoint(),
|
||||
wallet=storage_wallet_path,
|
||||
).stdout
|
||||
|
||||
|
||||
@allure.step("Get shard list for node {node_name}")
|
||||
def node_shard_list(hosting: Hosting, node_name: str) -> list[str]:
|
||||
@allure.step("Get shard list for {node}")
|
||||
def node_shard_list(node: StorageNode) -> list[str]:
|
||||
"""
|
||||
The function returns list of shards for specified node.
|
||||
The function returns list of shards for specified storage node.
|
||||
Args:
|
||||
node_name str: node name for which shards should be returned.
|
||||
node: node for which shards should be returned.
|
||||
Returns:
|
||||
list of shards.
|
||||
"""
|
||||
command = "control shards list"
|
||||
output = _run_control_command_with_retries(hosting, node_name, command)
|
||||
output = _run_control_command_with_retries(node, command)
|
||||
return re.findall(r"Shard (.*):", output)
|
||||
|
||||
|
||||
@allure.step("Shard set for node {node_name}")
|
||||
def node_shard_set_mode(hosting: Hosting, node_name: str, shard: str, mode: str) -> str:
|
||||
@allure.step("Shard set for {node}")
|
||||
def node_shard_set_mode(node: StorageNode, shard: str, mode: str) -> str:
|
||||
"""
|
||||
The function sets mode for specified shard.
|
||||
Args:
|
||||
node_name str: node name on which shard mode should be set.
|
||||
node: node on which shard mode should be set.
|
||||
"""
|
||||
command = f"control shards set-mode --id {shard} --mode {mode}"
|
||||
return _run_control_command_with_retries(hosting, node_name, command)
|
||||
return _run_control_command_with_retries(node, command)
|
||||
|
||||
|
||||
@allure.step("Drop object from node {node_name}")
|
||||
def drop_object(hosting: Hosting, node_name: str, cid: str, oid: str) -> str:
|
||||
@allure.step("Drop object from {node}")
|
||||
def drop_object(node: StorageNode, cid: str, oid: str) -> str:
|
||||
"""
|
||||
The function drops object from specified node.
|
||||
Args:
|
||||
node_name str: node name from which object should be dropped.
|
||||
node_id str: node from which object should be dropped.
|
||||
"""
|
||||
command = f"control drop-objects -o {cid}/{oid}"
|
||||
return _run_control_command_with_retries(hosting, node_name, command)
|
||||
return _run_control_command_with_retries(node, command)
|
||||
|
||||
|
||||
@allure.step("Delete data of node {node_name}")
|
||||
def delete_node_data(hosting: Hosting, node_name: str) -> None:
|
||||
host = hosting.get_host_by_service(node_name)
|
||||
host.stop_service(node_name)
|
||||
host.delete_storage_node_data(node_name)
|
||||
@allure.step("Delete data from host for node {node}")
|
||||
def delete_node_data(node: StorageNode) -> None:
|
||||
node.stop_service()
|
||||
node.host.delete_storage_node_data(node.name)
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
|
||||
|
||||
@allure.step("Exclude node {node_to_exclude} from network map")
|
||||
def exclude_node_from_network_map(
|
||||
hosting: Hosting, node_to_exclude: str, alive_node: str, shell: Shell
|
||||
node_to_exclude: StorageNode,
|
||||
alive_node: StorageNode,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
) -> None:
|
||||
node_wallet_path = NEOFS_NETMAP_DICT[node_to_exclude]["wallet_path"]
|
||||
node_netmap_key = get_wallet_public_key(node_wallet_path, STORAGE_WALLET_PASS)
|
||||
node_netmap_key = node_to_exclude.get_wallet_public_key()
|
||||
|
||||
node_set_status(hosting, node_to_exclude, status="offline")
|
||||
storage_node_set_status(node_to_exclude, status="offline")
|
||||
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=shell)
|
||||
tick_epoch(shell, cluster)
|
||||
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=shell)
|
||||
snapshot = get_netmap_snapshot(node=alive_node, shell=shell)
|
||||
assert (
|
||||
node_netmap_key not in snapshot
|
||||
), f"Expected node with key {node_netmap_key} not in network map"
|
||||
), f"Expected node with key {node_netmap_key} to be absent in network map"
|
||||
|
||||
|
||||
@allure.step("Include node {node_to_include} into network map")
|
||||
def include_node_to_network_map(
|
||||
hosting: Hosting, node_to_include: str, alive_node: str, shell: Shell
|
||||
node_to_include: StorageNode,
|
||||
alive_node: StorageNode,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
) -> None:
|
||||
node_set_status(hosting, node_to_include, status="online")
|
||||
storage_node_set_status(node_to_include, status="online")
|
||||
|
||||
# Per suggestion of @fyrchik we need to wait for 2 blocks after we set status and after tick epoch.
|
||||
# First sleep can be ommited afer https://github.com/nspcc-dev/neofs-node/issues/1790 complete.
|
||||
# First sleep can be omitted after https://github.com/nspcc-dev/neofs-node/issues/1790 complete.
|
||||
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME) * 2)
|
||||
tick_epoch(shell=shell)
|
||||
tick_epoch(shell, cluster)
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME) * 2)
|
||||
|
||||
check_node_in_map(node_to_include, shell, alive_node)
|
||||
|
||||
|
||||
@allure.step("Check node {node_name} in network map")
|
||||
def check_node_in_map(node_name: str, shell: Shell, alive_node: Optional[str] = None) -> None:
|
||||
alive_node = alive_node or node_name
|
||||
node_wallet_path = NEOFS_NETMAP_DICT[node_name]["wallet_path"]
|
||||
node_netmap_key = get_wallet_public_key(node_wallet_path, STORAGE_WALLET_PASS)
|
||||
@allure.step("Check node {node} in network map")
|
||||
def check_node_in_map(
|
||||
node: StorageNode, shell: Shell, alive_node: Optional[StorageNode] = None
|
||||
) -> None:
|
||||
alive_node = alive_node or node
|
||||
|
||||
logger.info(f"Node {node_name} netmap key: {node_netmap_key}")
|
||||
node_netmap_key = node.get_wallet_public_key()
|
||||
logger.info(f"Node ({node.label}) netmap key: {node_netmap_key}")
|
||||
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=shell)
|
||||
assert node_netmap_key in snapshot, f"Expected node with key {node_netmap_key} in network map"
|
||||
snapshot = get_netmap_snapshot(alive_node, shell)
|
||||
assert (
|
||||
node_netmap_key in snapshot
|
||||
), f"Expected node with key {node_netmap_key} to be in network map"
|
||||
|
||||
|
||||
def _run_control_command_with_retries(
|
||||
hosting: Hosting, node_name: str, command: str, retries: int = 0
|
||||
) -> str:
|
||||
def _run_control_command_with_retries(node: StorageNode, command: str, retries: int = 0) -> str:
|
||||
for attempt in range(1 + retries): # original attempt + specified retries
|
||||
try:
|
||||
return _run_control_command(hosting, node_name, command)
|
||||
return _run_control_command(node, command)
|
||||
except AssertionError as err:
|
||||
if attempt < retries:
|
||||
logger.warning(f"Command {command} failed with error {err} and will be retried")
|
||||
|
@ -223,16 +222,16 @@ def _run_control_command_with_retries(
|
|||
raise AssertionError(f"Command {command} failed with error {err}") from err
|
||||
|
||||
|
||||
def _run_control_command(hosting: Hosting, service_name: str, command: str) -> None:
|
||||
host = hosting.get_host_by_service(service_name)
|
||||
def _run_control_command(node: StorageNode, command: str) -> None:
|
||||
host = node.host
|
||||
|
||||
service_config = host.get_service_config(service_name)
|
||||
service_config = host.get_service_config(node.name)
|
||||
wallet_path = service_config.attributes["wallet_path"]
|
||||
wallet_password = service_config.attributes["wallet_password"]
|
||||
control_endpoint = service_config.attributes["control_endpoint"]
|
||||
|
||||
shell = host.get_shell()
|
||||
wallet_config_path = f"/tmp/{service_name}-config.yaml"
|
||||
wallet_config_path = f"/tmp/{node.name}-config.yaml"
|
||||
wallet_config = f'password: "{wallet_password}"'
|
||||
shell.exec(f"echo '{wallet_config}' > {wallet_config_path}")
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from cluster import Cluster
|
||||
from file_helper import get_file_hash
|
||||
from grpc_responses import OBJECT_ACCESS_DENIED, error_matches_status
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.neofs_verbs import (
|
||||
delete_object,
|
||||
get_object,
|
||||
get_object_from_random_node,
|
||||
get_range,
|
||||
get_range_hash,
|
||||
head_object,
|
||||
put_object,
|
||||
put_object_to_random_node,
|
||||
search_object,
|
||||
)
|
||||
|
||||
|
@ -23,13 +24,14 @@ def can_get_object(
|
|||
oid: str,
|
||||
file_name: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
) -> bool:
|
||||
with allure.step("Try get object from container"):
|
||||
try:
|
||||
got_file_path = get_object(
|
||||
got_file_path = get_object_from_random_node(
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
|
@ -37,6 +39,7 @@ def can_get_object(
|
|||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
cluster=cluster,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
@ -52,6 +55,7 @@ def can_put_object(
|
|||
cid: str,
|
||||
file_name: str,
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -59,7 +63,7 @@ def can_put_object(
|
|||
) -> bool:
|
||||
with allure.step("Try put object to container"):
|
||||
try:
|
||||
put_object(
|
||||
put_object_to_random_node(
|
||||
wallet,
|
||||
file_name,
|
||||
cid,
|
||||
|
@ -68,6 +72,7 @@ def can_put_object(
|
|||
xhdr=xhdr,
|
||||
attributes=attributes,
|
||||
shell=shell,
|
||||
cluster=cluster,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
@ -82,6 +87,7 @@ def can_delete_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -89,7 +95,14 @@ def can_delete_object(
|
|||
with allure.step("Try delete object from container"):
|
||||
try:
|
||||
delete_object(
|
||||
wallet, cid, oid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr, shell=shell
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
bearer=bearer,
|
||||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
@ -104,6 +117,7 @@ def can_get_head_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -118,6 +132,7 @@ def can_get_head_object(
|
|||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
@ -132,6 +147,7 @@ def can_get_range_of_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -147,6 +163,7 @@ def can_get_range_of_object(
|
|||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
@ -161,6 +178,7 @@ def can_get_range_hash_of_object(
|
|||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
xhdr: Optional[dict] = None,
|
||||
|
@ -176,6 +194,7 @@ def can_get_range_hash_of_object(
|
|||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
@ -189,6 +208,7 @@ def can_search_object(
|
|||
wallet: str,
|
||||
cid: str,
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
oid: Optional[str] = None,
|
||||
bearer: Optional[str] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
|
@ -197,7 +217,13 @@ def can_search_object(
|
|||
with allure.step("Try search object in container"):
|
||||
try:
|
||||
oids = search_object(
|
||||
wallet, cid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr, shell=shell
|
||||
wallet,
|
||||
cid,
|
||||
bearer=bearer,
|
||||
wallet_config=wallet_config,
|
||||
xhdr=xhdr,
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
)
|
||||
except OPERATION_ERROR_TYPE as err:
|
||||
assert error_matches_status(
|
||||
|
|
|
@ -6,19 +6,9 @@ import time
|
|||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from common import (
|
||||
GAS_HASH,
|
||||
MAINNET_BLOCK_TIME,
|
||||
MAINNET_SINGLE_ADDR,
|
||||
MAINNET_WALLET_PASS,
|
||||
MAINNET_WALLET_PATH,
|
||||
MORPH_ENDPOINT,
|
||||
NEO_MAINNET_ENDPOINT,
|
||||
NEOFS_CONTRACT,
|
||||
NEOGO_EXECUTABLE,
|
||||
)
|
||||
from cluster import MainChain, MorphChain
|
||||
from common import GAS_HASH, MAINNET_BLOCK_TIME, NEOFS_CONTRACT, NEOGO_EXECUTABLE
|
||||
from neo3 import wallet as neo3_wallet
|
||||
from neofs_testlib.blockchain import RPCClient
|
||||
from neofs_testlib.cli import NeoGo
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.converters import contract_hash_to_address
|
||||
|
@ -32,30 +22,26 @@ TX_PERSIST_TIMEOUT = 15 # seconds
|
|||
ASSET_POWER_MAINCHAIN = 10**8
|
||||
ASSET_POWER_SIDECHAIN = 10**12
|
||||
|
||||
morph_rpc_client = RPCClient(MORPH_ENDPOINT)
|
||||
mainnet_rpc_client = RPCClient(NEO_MAINNET_ENDPOINT)
|
||||
|
||||
def get_nns_contract_hash(morph_chain: MorphChain) -> str:
|
||||
return morph_chain.rpc_client.get_contract_state(1)["hash"]
|
||||
|
||||
|
||||
def get_nns_contract_hash() -> str:
|
||||
rpc_client = RPCClient(MORPH_ENDPOINT)
|
||||
return rpc_client.get_contract_state(1)["hash"]
|
||||
|
||||
|
||||
def get_contract_hash(resolve_name: str, shell: Shell) -> str:
|
||||
nns_contract_hash = get_nns_contract_hash()
|
||||
def get_contract_hash(morph_chain: MorphChain, resolve_name: str, shell: Shell) -> str:
|
||||
nns_contract_hash = get_nns_contract_hash(morph_chain)
|
||||
neogo = NeoGo(shell=shell, neo_go_exec_path=NEOGO_EXECUTABLE)
|
||||
out = neogo.contract.testinvokefunction(
|
||||
scripthash=nns_contract_hash,
|
||||
method="resolve",
|
||||
arguments=f"string:{resolve_name} int:16",
|
||||
rpc_endpoint=MORPH_ENDPOINT,
|
||||
rpc_endpoint=morph_chain.get_endpoint(),
|
||||
)
|
||||
stack_data = json.loads(out.stdout.replace("\n", ""))["stack"][0]["value"]
|
||||
return bytes.decode(base64.b64decode(stack_data[0]["value"]))
|
||||
|
||||
|
||||
@allure.step("Withdraw Mainnet Gas")
|
||||
def withdraw_mainnet_gas(shell: Shell, wlt: str, amount: int):
|
||||
def withdraw_mainnet_gas(shell: Shell, main_chain: MainChain, wlt: str, amount: int):
|
||||
address = get_last_address_from_wallet(wlt, EMPTY_PASSWORD)
|
||||
scripthash = neo3_wallet.Account.address_to_script_hash(address)
|
||||
|
||||
|
@ -63,7 +49,7 @@ def withdraw_mainnet_gas(shell: Shell, wlt: str, amount: int):
|
|||
out = neogo.contract.invokefunction(
|
||||
wallet=wlt,
|
||||
address=address,
|
||||
rpc_endpoint=NEO_MAINNET_ENDPOINT,
|
||||
rpc_endpoint=main_chain.get_endpoint(),
|
||||
scripthash=NEOFS_CONTRACT,
|
||||
method="withdraw",
|
||||
arguments=f"{scripthash} int:{amount}",
|
||||
|
@ -79,7 +65,7 @@ def withdraw_mainnet_gas(shell: Shell, wlt: str, amount: int):
|
|||
raise AssertionError(f"TX {tx} hasn't been processed")
|
||||
|
||||
|
||||
def transaction_accepted(tx_id: str):
|
||||
def transaction_accepted(main_chain: MainChain, tx_id: str):
|
||||
"""
|
||||
This function returns True in case of accepted TX.
|
||||
Args:
|
||||
|
@ -91,7 +77,7 @@ def transaction_accepted(tx_id: str):
|
|||
try:
|
||||
for _ in range(0, TX_PERSIST_TIMEOUT):
|
||||
time.sleep(1)
|
||||
resp = mainnet_rpc_client.get_transaction_height(tx_id)
|
||||
resp = main_chain.rpc_client.get_transaction_height(tx_id)
|
||||
if resp is not None:
|
||||
logger.info(f"TX is accepted in block: {resp}")
|
||||
return True
|
||||
|
@ -102,7 +88,7 @@ def transaction_accepted(tx_id: str):
|
|||
|
||||
|
||||
@allure.step("Get NeoFS Balance")
|
||||
def get_balance(shell: Shell, wallet_path: str, wallet_password: str = ""):
|
||||
def get_balance(shell: Shell, morph_chain: MorphChain, wallet_path: str, wallet_password: str = ""):
|
||||
"""
|
||||
This function returns NeoFS balance for given wallet.
|
||||
"""
|
||||
|
@ -111,8 +97,8 @@ def get_balance(shell: Shell, wallet_path: str, wallet_password: str = ""):
|
|||
acc = wallet.accounts[-1]
|
||||
payload = [{"type": "Hash160", "value": str(acc.script_hash)}]
|
||||
try:
|
||||
resp = morph_rpc_client.invoke_function(
|
||||
get_contract_hash("balance.neofs", shell=shell), "balanceOf", payload
|
||||
resp = morph_chain.rpc_client.invoke_function(
|
||||
get_contract_hash(morph_chain, "balance.neofs", shell=shell), "balanceOf", payload
|
||||
)
|
||||
logger.info(f"Got response \n{resp}")
|
||||
value = int(resp["stack"][0]["value"])
|
||||
|
@ -126,9 +112,10 @@ def get_balance(shell: Shell, wallet_path: str, wallet_password: str = ""):
|
|||
def transfer_gas(
|
||||
shell: Shell,
|
||||
amount: int,
|
||||
wallet_from_path: str = MAINNET_WALLET_PATH,
|
||||
wallet_from_password: str = MAINNET_WALLET_PASS,
|
||||
address_from: str = MAINNET_SINGLE_ADDR,
|
||||
main_chain: MainChain,
|
||||
wallet_from_path: Optional[str] = None,
|
||||
wallet_from_password: Optional[str] = None,
|
||||
address_from: Optional[str] = None,
|
||||
address_to: Optional[str] = None,
|
||||
wallet_to_path: Optional[str] = None,
|
||||
wallet_to_password: Optional[str] = None,
|
||||
|
@ -148,11 +135,20 @@ def transfer_gas(
|
|||
address_to: The address of the wallet to transfer assets to.
|
||||
amount: Amount of gas to transfer.
|
||||
"""
|
||||
wallet_from_path = wallet_from_path or main_chain.get_wallet_path()
|
||||
wallet_from_password = (
|
||||
wallet_from_password
|
||||
if wallet_from_password is not None
|
||||
else main_chain.get_wallet_password()
|
||||
)
|
||||
address_from = address_from or get_last_address_from_wallet(
|
||||
wallet_from_path, wallet_from_password
|
||||
)
|
||||
address_to = address_to or get_last_address_from_wallet(wallet_to_path, wallet_to_password)
|
||||
|
||||
neogo = NeoGo(shell, neo_go_exec_path=NEOGO_EXECUTABLE)
|
||||
out = neogo.nep17.transfer(
|
||||
rpc_endpoint=NEO_MAINNET_ENDPOINT,
|
||||
rpc_endpoint=main_chain.get_endpoint(),
|
||||
wallet=wallet_from_path,
|
||||
wallet_password=wallet_from_password,
|
||||
amount=amount,
|
||||
|
@ -164,13 +160,19 @@ def transfer_gas(
|
|||
txid = out.stdout.strip().split("\n")[-1]
|
||||
if len(txid) != 64:
|
||||
raise Exception("Got no TXID after run the command")
|
||||
if not transaction_accepted(txid):
|
||||
if not transaction_accepted(main_chain, txid):
|
||||
raise AssertionError(f"TX {txid} hasn't been processed")
|
||||
time.sleep(parse_time(MAINNET_BLOCK_TIME))
|
||||
|
||||
|
||||
@allure.step("NeoFS Deposit")
|
||||
def deposit_gas(shell: Shell, amount: int, wallet_from_path: str, wallet_from_password: str):
|
||||
def deposit_gas(
|
||||
shell: Shell,
|
||||
main_chain: MainChain,
|
||||
amount: int,
|
||||
wallet_from_path: str,
|
||||
wallet_from_password: str,
|
||||
):
|
||||
"""
|
||||
Transferring GAS from given wallet to NeoFS contract address.
|
||||
"""
|
||||
|
@ -182,6 +184,7 @@ def deposit_gas(shell: Shell, amount: int, wallet_from_path: str, wallet_from_pa
|
|||
)
|
||||
transfer_gas(
|
||||
shell=shell,
|
||||
main_chain=main_chain,
|
||||
amount=amount,
|
||||
wallet_from_path=wallet_from_path,
|
||||
wallet_from_password=wallet_from_password,
|
||||
|
@ -191,8 +194,8 @@ def deposit_gas(shell: Shell, amount: int, wallet_from_path: str, wallet_from_pa
|
|||
|
||||
|
||||
@allure.step("Get Mainnet Balance")
|
||||
def get_mainnet_balance(address: str):
|
||||
resp = mainnet_rpc_client.get_nep17_balances(address=address)
|
||||
def get_mainnet_balance(main_chain: MainChain, address: str):
|
||||
resp = main_chain.rpc_client.get_nep17_balances(address=address)
|
||||
logger.info(f"Got getnep17balances response: {resp}")
|
||||
for balance in resp["balance"]:
|
||||
if balance["assethash"] == GAS_HASH:
|
||||
|
@ -201,8 +204,8 @@ def get_mainnet_balance(address: str):
|
|||
|
||||
|
||||
@allure.step("Get Sidechain Balance")
|
||||
def get_sidechain_balance(address: str):
|
||||
resp = morph_rpc_client.get_nep17_balances(address=address)
|
||||
def get_sidechain_balance(morph_chain: MorphChain, address: str):
|
||||
resp = morph_chain.rpc_client.get_nep17_balances(address=address)
|
||||
logger.info(f"Got getnep17balances response: {resp}")
|
||||
for balance in resp["balance"]:
|
||||
if balance["assethash"] == GAS_HASH:
|
||||
|
|
|
@ -6,7 +6,8 @@ import logging
|
|||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from common import COMPLEX_OBJ_SIZE, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, SIMPLE_OBJ_SIZE, WALLET_CONFIG
|
||||
from cluster import Cluster
|
||||
from common import COMPLEX_OBJ_SIZE, NEOFS_CLI_EXEC, SIMPLE_OBJ_SIZE, WALLET_CONFIG
|
||||
from complex_object_actions import get_link_object
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.shell import Shell
|
||||
|
@ -18,6 +19,7 @@ logger = logging.getLogger("NeoLogger")
|
|||
@allure.step("Put Storagegroup")
|
||||
def put_storagegroup(
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
objects: list,
|
||||
|
@ -47,7 +49,7 @@ def put_storagegroup(
|
|||
lifetime=lifetime,
|
||||
members=objects,
|
||||
bearer=bearer,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
)
|
||||
gid = result.stdout.split("\n")[1].split(": ")[1]
|
||||
return gid
|
||||
|
@ -56,6 +58,7 @@ def put_storagegroup(
|
|||
@allure.step("List Storagegroup")
|
||||
def list_storagegroup(
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
bearer: Optional[str] = None,
|
||||
|
@ -78,7 +81,7 @@ def list_storagegroup(
|
|||
wallet=wallet,
|
||||
cid=cid,
|
||||
bearer=bearer,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
)
|
||||
# throwing off the first string of output
|
||||
found_objects = result.stdout.split("\n")[1:]
|
||||
|
@ -88,6 +91,7 @@ def list_storagegroup(
|
|||
@allure.step("Get Storagegroup")
|
||||
def get_storagegroup(
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
gid: str,
|
||||
|
@ -112,7 +116,7 @@ def get_storagegroup(
|
|||
cid=cid,
|
||||
bearer=bearer,
|
||||
id=gid,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
)
|
||||
|
||||
# TODO: temporary solution for parsing output. Needs to be replaced with
|
||||
|
@ -136,6 +140,7 @@ def get_storagegroup(
|
|||
@allure.step("Delete Storagegroup")
|
||||
def delete_storagegroup(
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
gid: str,
|
||||
|
@ -160,7 +165,7 @@ def delete_storagegroup(
|
|||
cid=cid,
|
||||
bearer=bearer,
|
||||
id=gid,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
rpc_endpoint=endpoint,
|
||||
)
|
||||
tombstone_id = result.stdout.strip().split("\n")[1].split(": ")[1]
|
||||
return tombstone_id
|
||||
|
@ -169,6 +174,7 @@ def delete_storagegroup(
|
|||
@allure.step("Verify list operation over Storagegroup")
|
||||
def verify_list_storage_group(
|
||||
shell: Shell,
|
||||
endpoint: str,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
gid: str,
|
||||
|
@ -176,7 +182,12 @@ def verify_list_storage_group(
|
|||
wallet_config: str = WALLET_CONFIG,
|
||||
):
|
||||
storage_groups = list_storagegroup(
|
||||
shell=shell, wallet=wallet, cid=cid, bearer=bearer, wallet_config=wallet_config
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
bearer=bearer,
|
||||
wallet_config=wallet_config,
|
||||
)
|
||||
assert gid in storage_groups
|
||||
|
||||
|
@ -184,6 +195,7 @@ def verify_list_storage_group(
|
|||
@allure.step("Verify get operation over Storagegroup")
|
||||
def verify_get_storage_group(
|
||||
shell: Shell,
|
||||
cluster: Cluster,
|
||||
wallet: str,
|
||||
cid: str,
|
||||
gid: str,
|
||||
|
@ -193,16 +205,24 @@ def verify_get_storage_group(
|
|||
wallet_config: str = WALLET_CONFIG,
|
||||
):
|
||||
obj_parts = []
|
||||
endpoint = cluster.default_rpc_endpoint
|
||||
if object_size == COMPLEX_OBJ_SIZE:
|
||||
for obj in obj_list:
|
||||
link_oid = get_link_object(
|
||||
wallet, cid, obj, shell=shell, bearer=bearer, wallet_config=wallet_config
|
||||
wallet,
|
||||
cid,
|
||||
obj,
|
||||
shell=shell,
|
||||
nodes=cluster.storage_nodes,
|
||||
bearer=bearer,
|
||||
wallet_config=wallet_config,
|
||||
)
|
||||
obj_head = head_object(
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
oid=link_oid,
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
is_raw=True,
|
||||
bearer=bearer,
|
||||
wallet_config=wallet_config,
|
||||
|
@ -212,6 +232,7 @@ def verify_get_storage_group(
|
|||
obj_num = len(obj_list)
|
||||
storagegroup_data = get_storagegroup(
|
||||
shell=shell,
|
||||
endpoint=endpoint,
|
||||
wallet=wallet,
|
||||
cid=cid,
|
||||
gid=gid,
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
import allure
|
||||
import complex_object_actions
|
||||
import neofs_verbs
|
||||
from common import NEOFS_NETMAP
|
||||
from cluster import StorageNode
|
||||
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
|
||||
from neofs_testlib.shell import Shell
|
||||
|
||||
|
@ -19,7 +19,9 @@ logger = logging.getLogger("NeoLogger")
|
|||
|
||||
|
||||
@allure.step("Get Object Copies")
|
||||
def get_object_copies(complexity: str, wallet: str, cid: str, oid: str, shell: Shell) -> int:
|
||||
def get_object_copies(
|
||||
complexity: str, wallet: str, cid: str, oid: str, shell: Shell, nodes: list[StorageNode]
|
||||
) -> int:
|
||||
"""
|
||||
The function performs requests to all nodes of the container and
|
||||
finds out if they store a copy of the object. The procedure is
|
||||
|
@ -37,14 +39,16 @@ def get_object_copies(complexity: str, wallet: str, cid: str, oid: str, shell: S
|
|||
(int): the number of object copies in the container
|
||||
"""
|
||||
return (
|
||||
get_simple_object_copies(wallet, cid, oid, shell)
|
||||
get_simple_object_copies(wallet, cid, oid, shell, nodes)
|
||||
if complexity == "Simple"
|
||||
else get_complex_object_copies(wallet, cid, oid, shell)
|
||||
else get_complex_object_copies(wallet, cid, oid, shell, nodes)
|
||||
)
|
||||
|
||||
|
||||
@allure.step("Get Simple Object Copies")
|
||||
def get_simple_object_copies(wallet: str, cid: str, oid: str, shell: Shell) -> int:
|
||||
def get_simple_object_copies(
|
||||
wallet: str, cid: str, oid: str, shell: Shell, nodes: list[StorageNode]
|
||||
) -> int:
|
||||
"""
|
||||
To figure out the number of a simple object copies, only direct
|
||||
HEAD requests should be made to the every node of the container.
|
||||
|
@ -55,14 +59,15 @@ def get_simple_object_copies(wallet: str, cid: str, oid: str, shell: Shell) -> i
|
|||
cid (str): ID of the container
|
||||
oid (str): ID of the Object
|
||||
shell: executor for cli command
|
||||
nodes: nodes to search on
|
||||
Returns:
|
||||
(int): the number of object copies in the container
|
||||
"""
|
||||
copies = 0
|
||||
for node in NEOFS_NETMAP:
|
||||
for node in nodes:
|
||||
try:
|
||||
response = neofs_verbs.head_object(
|
||||
wallet, cid, oid, shell=shell, endpoint=node, is_direct=True
|
||||
wallet, cid, oid, shell=shell, endpoint=node.get_rpc_endpoint(), is_direct=True
|
||||
)
|
||||
if response:
|
||||
logger.info(f"Found object {oid} on node {node}")
|
||||
|
@ -74,7 +79,9 @@ def get_simple_object_copies(wallet: str, cid: str, oid: str, shell: Shell) -> i
|
|||
|
||||
|
||||
@allure.step("Get Complex Object Copies")
|
||||
def get_complex_object_copies(wallet: str, cid: str, oid: str, shell: Shell) -> int:
|
||||
def get_complex_object_copies(
|
||||
wallet: str, cid: str, oid: str, shell: Shell, nodes: list[StorageNode]
|
||||
) -> int:
|
||||
"""
|
||||
To figure out the number of a complex object copies, we firstly
|
||||
need to retrieve its Last object. We consider that the number of
|
||||
|
@ -90,37 +97,40 @@ def get_complex_object_copies(wallet: str, cid: str, oid: str, shell: Shell) ->
|
|||
Returns:
|
||||
(int): the number of object copies in the container
|
||||
"""
|
||||
last_oid = complex_object_actions.get_last_object(wallet, cid, oid, shell)
|
||||
last_oid = complex_object_actions.get_last_object(wallet, cid, oid, shell, nodes)
|
||||
assert last_oid, f"No Last Object for {cid}/{oid} found among all Storage Nodes"
|
||||
return get_simple_object_copies(wallet, cid, last_oid, shell)
|
||||
return get_simple_object_copies(wallet, cid, last_oid, shell, nodes)
|
||||
|
||||
|
||||
@allure.step("Get Nodes With Object")
|
||||
def get_nodes_with_object(
|
||||
wallet: str, cid: str, oid: str, shell: Shell, skip_nodes: Optional[list[str]] = None
|
||||
) -> list[str]:
|
||||
cid: str, oid: str, shell: Shell, nodes: list[StorageNode]
|
||||
) -> list[StorageNode]:
|
||||
"""
|
||||
The function returns list of nodes which store
|
||||
the given object.
|
||||
Args:
|
||||
wallet (str): the path to the wallet on whose behalf
|
||||
we request the nodes
|
||||
cid (str): ID of the container which store the object
|
||||
oid (str): object ID
|
||||
shell: executor for cli command
|
||||
skip_nodes (list): list of nodes that should be excluded from check
|
||||
nodes: nodes to find on
|
||||
Returns:
|
||||
(list): nodes which store the object
|
||||
"""
|
||||
nodes_to_search = NEOFS_NETMAP
|
||||
if skip_nodes:
|
||||
nodes_to_search = [node for node in NEOFS_NETMAP if node not in skip_nodes]
|
||||
|
||||
nodes_list = []
|
||||
for node in nodes_to_search:
|
||||
for node in nodes:
|
||||
wallet = node.get_wallet_path()
|
||||
wallet_config = node.get_wallet_config_path()
|
||||
try:
|
||||
res = neofs_verbs.head_object(
|
||||
wallet, cid, oid, shell=shell, endpoint=node, is_direct=True
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=shell,
|
||||
endpoint=node.get_rpc_endpoint(),
|
||||
is_direct=True,
|
||||
wallet_config=wallet_config,
|
||||
)
|
||||
if res is not None:
|
||||
logger.info(f"Found object {oid} on node {node}")
|
||||
|
@ -132,7 +142,9 @@ def get_nodes_with_object(
|
|||
|
||||
|
||||
@allure.step("Get Nodes Without Object")
|
||||
def get_nodes_without_object(wallet: str, cid: str, oid: str, shell: Shell) -> List[str]:
|
||||
def get_nodes_without_object(
|
||||
wallet: str, cid: str, oid: str, shell: Shell, nodes: list[StorageNode]
|
||||
) -> list[StorageNode]:
|
||||
"""
|
||||
The function returns list of nodes which do not store
|
||||
the given object.
|
||||
|
@ -146,10 +158,10 @@ def get_nodes_without_object(wallet: str, cid: str, oid: str, shell: Shell) -> L
|
|||
(list): nodes which do not store the object
|
||||
"""
|
||||
nodes_list = []
|
||||
for node in NEOFS_NETMAP:
|
||||
for node in nodes:
|
||||
try:
|
||||
res = neofs_verbs.head_object(
|
||||
wallet, cid, oid, shell=shell, endpoint=node, is_direct=True
|
||||
wallet, cid, oid, shell=shell, endpoint=node.get_rpc_endpoint(), is_direct=True
|
||||
)
|
||||
if res is None:
|
||||
nodes_list.append(node)
|
||||
|
|
|
@ -10,8 +10,10 @@ logger = logging.getLogger("NeoLogger")
|
|||
|
||||
|
||||
@allure.step("Verify Head Tombstone")
|
||||
def verify_head_tombstone(wallet_path: str, cid: str, oid_ts: str, oid: str, shell: Shell):
|
||||
header = head_object(wallet_path, cid, oid_ts, shell=shell)["header"]
|
||||
def verify_head_tombstone(
|
||||
wallet_path: str, cid: str, oid_ts: str, oid: str, shell: Shell, endpoint: str
|
||||
):
|
||||
header = head_object(wallet_path, cid, oid_ts, shell=shell, endpoint=endpoint)["header"]
|
||||
|
||||
s_oid = header["sessionToken"]["body"]["object"]["target"]["objects"]
|
||||
logger.info(f"Header Session OIDs is {s_oid}")
|
||||
|
|
|
@ -17,13 +17,6 @@ NEOFS_CONTRACT_CACHE_TIMEOUT = os.getenv("NEOFS_CONTRACT_CACHE_TIMEOUT", "30s")
|
|||
# of 1min plus 15 seconds for GC pass itself)
|
||||
STORAGE_GC_TIME = os.getenv("STORAGE_GC_TIME", "75s")
|
||||
|
||||
# TODO: we should use hosting instead of these endpoints
|
||||
NEOFS_ENDPOINT = os.getenv("NEOFS_ENDPOINT", "s01.neofs.devenv:8080")
|
||||
NEO_MAINNET_ENDPOINT = os.getenv("NEO_MAINNET_ENDPOINT", "http://main-chain.neofs.devenv:30333")
|
||||
MORPH_ENDPOINT = os.getenv("MORPH_ENDPOINT", "http://morph-chain.neofs.devenv:30333")
|
||||
HTTP_GATE = os.getenv("HTTP_GATE", "http://http.neofs.devenv")
|
||||
S3_GATE = os.getenv("S3_GATE", "https://s3.neofs.devenv:8080")
|
||||
|
||||
GAS_HASH = os.getenv("GAS_HASH", "0xd2a4cff31913016155e38e474a2c06d08be276cf")
|
||||
|
||||
NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
|
||||
|
@ -43,81 +36,12 @@ BACKGROUND_READERS_COUNT = os.getenv("BACKGROUND_READERS_COUNT", 10)
|
|||
BACKGROUND_OBJ_SIZE = os.getenv("BACKGROUND_OBJ_SIZE", 1024)
|
||||
BACKGROUND_LOAD_MAX_TIME = os.getenv("BACKGROUND_LOAD_MAX_TIME", 600)
|
||||
|
||||
# Configuration of storage nodes
|
||||
# TODO: we should use hosting instead of all these variables
|
||||
STORAGE_RPC_ENDPOINT_1 = os.getenv("STORAGE_RPC_ENDPOINT_1", "s01.neofs.devenv:8080")
|
||||
STORAGE_RPC_ENDPOINT_2 = os.getenv("STORAGE_RPC_ENDPOINT_2", "s02.neofs.devenv:8080")
|
||||
STORAGE_RPC_ENDPOINT_3 = os.getenv("STORAGE_RPC_ENDPOINT_3", "s03.neofs.devenv:8080")
|
||||
STORAGE_RPC_ENDPOINT_4 = os.getenv("STORAGE_RPC_ENDPOINT_4", "s04.neofs.devenv:8080")
|
||||
|
||||
STORAGE_CONTROL_ENDPOINT_1 = os.getenv("STORAGE_CONTROL_ENDPOINT_1", "s01.neofs.devenv:8081")
|
||||
STORAGE_CONTROL_ENDPOINT_2 = os.getenv("STORAGE_CONTROL_ENDPOINT_2", "s02.neofs.devenv:8081")
|
||||
STORAGE_CONTROL_ENDPOINT_3 = os.getenv("STORAGE_CONTROL_ENDPOINT_3", "s03.neofs.devenv:8081")
|
||||
STORAGE_CONTROL_ENDPOINT_4 = os.getenv("STORAGE_CONTROL_ENDPOINT_4", "s04.neofs.devenv:8081")
|
||||
|
||||
STORAGE_WALLET_PATH_1 = os.getenv(
|
||||
"STORAGE_WALLET_PATH_1", os.path.join(DEVENV_PATH, "services", "storage", "wallet01.json")
|
||||
)
|
||||
STORAGE_WALLET_PATH_2 = os.getenv(
|
||||
"STORAGE_WALLET_PATH_2", os.path.join(DEVENV_PATH, "services", "storage", "wallet02.json")
|
||||
)
|
||||
STORAGE_WALLET_PATH_3 = os.getenv(
|
||||
"STORAGE_WALLET_PATH_3", os.path.join(DEVENV_PATH, "services", "storage", "wallet03.json")
|
||||
)
|
||||
STORAGE_WALLET_PATH_4 = os.getenv(
|
||||
"STORAGE_WALLET_PATH_4", os.path.join(DEVENV_PATH, "services", "storage", "wallet04.json")
|
||||
)
|
||||
STORAGE_WALLET_PATH = STORAGE_WALLET_PATH_1
|
||||
STORAGE_WALLET_PASS = os.getenv("STORAGE_WALLET_PASS", "")
|
||||
|
||||
NEOFS_NETMAP_DICT = {
|
||||
"s01": {
|
||||
"rpc": STORAGE_RPC_ENDPOINT_1,
|
||||
"control": STORAGE_CONTROL_ENDPOINT_1,
|
||||
"wallet_path": STORAGE_WALLET_PATH_1,
|
||||
"UN-LOCODE": "RU MOW",
|
||||
},
|
||||
"s02": {
|
||||
"rpc": STORAGE_RPC_ENDPOINT_2,
|
||||
"control": STORAGE_CONTROL_ENDPOINT_2,
|
||||
"wallet_path": STORAGE_WALLET_PATH_2,
|
||||
"UN-LOCODE": "RU LED",
|
||||
},
|
||||
"s03": {
|
||||
"rpc": STORAGE_RPC_ENDPOINT_3,
|
||||
"control": STORAGE_CONTROL_ENDPOINT_3,
|
||||
"wallet_path": STORAGE_WALLET_PATH_3,
|
||||
"UN-LOCODE": "SE STO",
|
||||
},
|
||||
"s04": {
|
||||
"rpc": STORAGE_RPC_ENDPOINT_4,
|
||||
"control": STORAGE_CONTROL_ENDPOINT_4,
|
||||
"wallet_path": STORAGE_WALLET_PATH_4,
|
||||
"UN-LOCODE": "FI HEL",
|
||||
},
|
||||
}
|
||||
NEOFS_NETMAP = [node["rpc"] for node in NEOFS_NETMAP_DICT.values()]
|
||||
|
||||
# Paths to CLI executables on machine that runs tests
|
||||
NEOGO_EXECUTABLE = os.getenv("NEOGO_EXECUTABLE", "neo-go")
|
||||
NEOFS_CLI_EXEC = os.getenv("NEOFS_CLI_EXEC", "neofs-cli")
|
||||
NEOFS_AUTHMATE_EXEC = os.getenv("NEOFS_AUTHMATE_EXEC", "neofs-authmate")
|
||||
NEOFS_ADM_EXEC = os.getenv("NEOFS_ADM_EXEC", "neofs-adm")
|
||||
|
||||
MAINNET_WALLET_PATH = os.getenv(
|
||||
"MAINNET_WALLET_PATH", os.path.join(DEVENV_PATH, "services", "chain", "node-wallet.json")
|
||||
)
|
||||
MAINNET_SINGLE_ADDR = os.getenv("MAINNET_SINGLE_ADDR", "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP")
|
||||
MAINNET_WALLET_PASS = os.getenv("MAINNET_WALLET_PASS", "one")
|
||||
|
||||
IR_WALLET_PATH = os.getenv("IR_WALLET_PATH", os.path.join(DEVENV_PATH, "services", "ir", "az.json"))
|
||||
IR_WALLET_PASS = os.getenv("IR_WALLET_PASS", "one")
|
||||
|
||||
S3_GATE_WALLET_PATH = os.getenv(
|
||||
"S3_GATE_WALLET_PATH", os.path.join(DEVENV_PATH, "services", "s3_gate", "wallet.json")
|
||||
)
|
||||
S3_GATE_WALLET_PASS = os.getenv("S3_GATE_WALLET_PASS", "s3")
|
||||
|
||||
# Config for neofs-adm utility. Optional if tests are running against devenv
|
||||
NEOFS_ADM_CONFIG_PATH = os.getenv("NEOFS_ADM_CONFIG_PATH")
|
||||
|
||||
|
@ -134,15 +58,3 @@ S3_GATE_SERVICE_NAME_REGEX = r"s3-gate\d\d"
|
|||
WALLET_CONFIG = os.path.join(os.getcwd(), "wallet_config.yml")
|
||||
with open(WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": WALLET_PASS}, file)
|
||||
|
||||
STORAGE_WALLET_CONFIG = os.path.join(os.getcwd(), "storage_wallet_config.yml")
|
||||
with open(STORAGE_WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": STORAGE_WALLET_PASS}, file)
|
||||
|
||||
MAINNET_WALLET_CONFIG = os.path.join(os.getcwd(), "mainnet_wallet_config.yml")
|
||||
with open(MAINNET_WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": MAINNET_WALLET_PASS}, file)
|
||||
|
||||
IR_WALLET_CONFIG = os.path.join(os.getcwd(), "ir_wallet_config.yml")
|
||||
with open(IR_WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": IR_WALLET_PASS}, file)
|
||||
|
|
Loading…
Reference in a new issue