2022-09-19 14:22:10 +00:00
|
|
|
import logging
|
|
|
|
import os
|
2022-10-11 08:18:08 +00:00
|
|
|
from random import choice, choices
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
import allure
|
|
|
|
import pytest
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.resources.common import ASSETS_DIR
|
|
|
|
from frostfs_testlib.s3 import AwsCliClient, Boto3ClientWrapper, S3ClientWrapper, VersioningStatus
|
|
|
|
from frostfs_testlib.shell import Shell
|
|
|
|
from frostfs_testlib.steps.epoch import tick_epoch
|
|
|
|
from frostfs_testlib.steps.s3 import s3_helper
|
|
|
|
from frostfs_testlib.storage.cluster import Cluster
|
2023-08-02 11:54:03 +00:00
|
|
|
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.utils.file_utils import (
|
2022-10-11 15:15:50 +00:00
|
|
|
generate_file,
|
|
|
|
generate_file_with_content,
|
|
|
|
get_file_content,
|
|
|
|
get_file_hash,
|
|
|
|
split_file,
|
|
|
|
)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger("NeoLogger")
|
|
|
|
|
|
|
|
|
2023-10-31 14:51:09 +00:00
|
|
|
@allure.link("https://github.com/TrueCloudLab/frostfs-s3-gw#frostfs-s3-gw", name="frostfs-s3-gateway")
|
2023-11-01 16:20:15 +00:00
|
|
|
@pytest.mark.sanity
|
2022-09-19 14:22:10 +00:00
|
|
|
@pytest.mark.s3_gate
|
2022-11-10 05:27:52 +00:00
|
|
|
@pytest.mark.s3_gate_base
|
2023-05-15 09:59:33 +00:00
|
|
|
class TestS3Gate:
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Bucket API (s3_client={s3_client})")
|
2023-05-15 09:59:33 +00:00
|
|
|
def test_s3_buckets(
|
|
|
|
self,
|
|
|
|
s3_client: S3ClientWrapper,
|
|
|
|
client_shell: Shell,
|
|
|
|
cluster: Cluster,
|
2023-08-02 11:54:03 +00:00
|
|
|
simple_object_size: ObjectSize,
|
2023-05-15 09:59:33 +00:00
|
|
|
):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test base S3 Bucket API (Create/List/Head/Delete).
|
|
|
|
"""
|
|
|
|
|
2023-08-02 11:54:03 +00:00
|
|
|
file_path = generate_file(simple_object_size.value)
|
2023-05-15 09:59:33 +00:00
|
|
|
file_name = s3_helper.object_key_from_file_path(file_path)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Create buckets"):
|
2023-05-15 09:59:33 +00:00
|
|
|
bucket_1 = s3_client.create_bucket(object_lock_enabled_for_bucket=True)
|
|
|
|
s3_helper.set_bucket_versioning(s3_client, bucket_1, VersioningStatus.ENABLED)
|
|
|
|
bucket_2 = s3_client.create_bucket()
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check buckets are presented in the system"):
|
2023-05-15 09:59:33 +00:00
|
|
|
buckets = s3_client.list_buckets()
|
2022-09-19 14:22:10 +00:00
|
|
|
assert bucket_1 in buckets, f"Expected bucket {bucket_1} is in the list"
|
|
|
|
assert bucket_2 in buckets, f"Expected bucket {bucket_2} is in the list"
|
|
|
|
|
|
|
|
with allure.step("Bucket must be empty"):
|
|
|
|
for bucket in (bucket_1, bucket_2):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects_list = s3_client.list_objects(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not objects_list, f"Expected empty bucket, got {objects_list}"
|
|
|
|
|
|
|
|
with allure.step("Check buckets are visible with S3 head command"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.head_bucket(bucket_1)
|
|
|
|
s3_client.head_bucket(bucket_2)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check we can put/list object with S3 commands"):
|
2023-05-15 09:59:33 +00:00
|
|
|
version_id = s3_client.put_object(bucket_1, file_path)
|
|
|
|
s3_client.head_object(bucket_1, file_name)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
bucket_objects = s3_client.list_objects(bucket_1)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert file_name in bucket_objects, f"Expected file {file_name} in objects list {bucket_objects}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Try to delete not empty bucket and get error"):
|
|
|
|
with pytest.raises(Exception, match=r".*The bucket you tried to delete is not empty.*"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_bucket(bucket_1)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.head_bucket(bucket_1)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step(f"Delete empty bucket {bucket_2}"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_bucket(bucket_2)
|
|
|
|
tick_epoch(client_shell, cluster)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step(f"Check bucket {bucket_2} deleted"):
|
|
|
|
with pytest.raises(Exception, match=r".*Not Found.*"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.head_bucket(bucket_2)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
buckets = s3_client.list_buckets()
|
2022-09-19 14:22:10 +00:00
|
|
|
assert bucket_1 in buckets, f"Expected bucket {bucket_1} is in the list"
|
|
|
|
assert bucket_2 not in buckets, f"Expected bucket {bucket_2} is not in the list"
|
|
|
|
|
2022-10-06 07:31:47 +00:00
|
|
|
with allure.step(f"Delete object from {bucket_1}"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object(bucket_1, file_name, version_id)
|
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket_1, expected_objects=[])
|
2022-10-06 07:31:47 +00:00
|
|
|
|
|
|
|
with allure.step(f"Delete bucket {bucket_1}"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_bucket(bucket_1)
|
|
|
|
tick_epoch(client_shell, cluster)
|
2022-10-06 07:31:47 +00:00
|
|
|
|
|
|
|
with allure.step(f"Check bucket {bucket_1} deleted"):
|
|
|
|
with pytest.raises(Exception, match=r".*Not Found.*"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.head_bucket(bucket_1)
|
2022-10-06 07:31:47 +00:00
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Object API (obj_size={object_size}, s3_client={s3_client})")
|
2022-09-19 14:22:10 +00:00
|
|
|
@pytest.mark.parametrize(
|
2023-08-02 11:54:03 +00:00
|
|
|
"object_size",
|
2023-09-08 10:35:34 +00:00
|
|
|
["simple", "complex"],
|
|
|
|
indirect=True,
|
2022-09-19 14:22:10 +00:00
|
|
|
)
|
2023-05-15 09:59:33 +00:00
|
|
|
def test_s3_api_object(
|
|
|
|
self,
|
|
|
|
s3_client: S3ClientWrapper,
|
2023-09-08 10:35:34 +00:00
|
|
|
object_size: ObjectSize,
|
2023-05-15 09:59:33 +00:00
|
|
|
two_buckets: tuple[str, str],
|
|
|
|
):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
2023-08-02 11:54:03 +00:00
|
|
|
Test base S3 Object API (Put/Head/List) for simple and complex objects.
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
2023-09-08 10:35:34 +00:00
|
|
|
file_path = generate_file(object_size.value)
|
2023-05-15 09:59:33 +00:00
|
|
|
file_name = s3_helper.object_key_from_file_path(file_path)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2022-11-29 14:16:15 +00:00
|
|
|
bucket_1, bucket_2 = two_buckets
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
for bucket in (bucket_1, bucket_2):
|
|
|
|
with allure.step("Bucket must be empty"):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects_list = s3_client.list_objects(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not objects_list, f"Expected empty bucket, got {objects_list}"
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_object(bucket, file_path)
|
|
|
|
s3_client.head_object(bucket, file_name)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
bucket_objects = s3_client.list_objects(bucket)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert file_name in bucket_objects, f"Expected file {file_name} in objects list {bucket_objects}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check object's attributes"):
|
|
|
|
for attrs in (["ETag"], ["ObjectSize", "StorageClass"]):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.get_object_attributes(bucket, file_name, attrs)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Sync directory (s3_client={s3_client})")
|
2023-10-31 14:51:09 +00:00
|
|
|
def test_s3_sync_dir(self, s3_client: S3ClientWrapper, bucket: str, simple_object_size: ObjectSize):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test checks sync directory with AWS CLI utility.
|
|
|
|
"""
|
2022-10-18 07:11:57 +00:00
|
|
|
file_path_1 = os.path.join(os.getcwd(), ASSETS_DIR, "test_sync", "test_file_1")
|
|
|
|
file_path_2 = os.path.join(os.getcwd(), ASSETS_DIR, "test_sync", "test_file_2")
|
2022-09-19 14:22:10 +00:00
|
|
|
key_to_path = {"test_file_1": file_path_1, "test_file_2": file_path_2}
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
if not isinstance(s3_client, AwsCliClient):
|
2022-09-19 14:22:10 +00:00
|
|
|
pytest.skip("This test is not supported with boto3 client")
|
|
|
|
|
2023-08-02 11:54:03 +00:00
|
|
|
generate_file_with_content(simple_object_size.value, file_path=file_path_1)
|
|
|
|
generate_file_with_content(simple_object_size.value, file_path=file_path_2)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.sync(bucket=bucket, dir_path=os.path.dirname(file_path_1))
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check objects are synced"):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects = s3_client.list_objects(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check these are the same objects"):
|
2023-10-31 14:51:09 +00:00
|
|
|
assert set(key_to_path.keys()) == set(objects), f"Expected all objects saved. Got {objects}"
|
2022-09-19 14:22:10 +00:00
|
|
|
for obj_key in objects:
|
2023-05-15 09:59:33 +00:00
|
|
|
got_object = s3_client.get_object(bucket, obj_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert get_file_hash(got_object) == get_file_hash(
|
|
|
|
key_to_path.get(obj_key)
|
|
|
|
), "Expected hashes are the same"
|
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Object versioning (s3_client={s3_client})")
|
2023-10-31 14:51:09 +00:00
|
|
|
def test_s3_api_versioning(self, s3_client: S3ClientWrapper, bucket: str, simple_object_size: ObjectSize):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test checks basic versioning functionality for S3 bucket.
|
|
|
|
"""
|
|
|
|
version_1_content = "Version 1"
|
|
|
|
version_2_content = "Version 2"
|
2023-10-31 14:51:09 +00:00
|
|
|
file_name_simple = generate_file_with_content(simple_object_size.value, content=version_1_content)
|
2022-09-19 14:22:10 +00:00
|
|
|
obj_key = os.path.basename(file_name_simple)
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_helper.set_bucket_versioning(s3_client, bucket, VersioningStatus.ENABLED)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Put several versions of object into bucket"):
|
2023-05-15 09:59:33 +00:00
|
|
|
version_id_1 = s3_client.put_object(bucket, file_name_simple)
|
2023-10-31 14:51:09 +00:00
|
|
|
generate_file_with_content(simple_object_size.value, file_path=file_name_simple, content=version_2_content)
|
2023-05-15 09:59:33 +00:00
|
|
|
version_id_2 = s3_client.put_object(bucket, file_name_simple)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check bucket shows all versions"):
|
2023-05-15 09:59:33 +00:00
|
|
|
versions = s3_client.list_objects_versions(bucket)
|
2023-10-31 14:51:09 +00:00
|
|
|
obj_versions = {version.get("VersionId") for version in versions if version.get("Key") == obj_key}
|
2022-09-19 14:22:10 +00:00
|
|
|
assert obj_versions == {
|
|
|
|
version_id_1,
|
|
|
|
version_id_2,
|
|
|
|
}, f"Expected object has versions: {version_id_1, version_id_2}"
|
|
|
|
|
|
|
|
with allure.step("Show information about particular version"):
|
|
|
|
for version_id in (version_id_1, version_id_2):
|
2023-05-15 09:59:33 +00:00
|
|
|
response = s3_client.head_object(bucket, obj_key, version_id=version_id)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert "LastModified" in response, "Expected LastModified field"
|
|
|
|
assert "ETag" in response, "Expected ETag field"
|
2023-10-31 14:51:09 +00:00
|
|
|
assert response.get("VersionId") == version_id, f"Expected VersionId is {version_id}"
|
2022-09-19 14:22:10 +00:00
|
|
|
assert response.get("ContentLength") != 0, "Expected ContentLength is not zero"
|
|
|
|
|
|
|
|
with allure.step("Check object's attributes"):
|
|
|
|
for version_id in (version_id_1, version_id_2):
|
2023-10-31 14:51:09 +00:00
|
|
|
got_attrs = s3_client.get_object_attributes(bucket, obj_key, ["ETag"], version_id=version_id)
|
2022-09-19 14:22:10 +00:00
|
|
|
if got_attrs:
|
2023-10-31 14:51:09 +00:00
|
|
|
assert got_attrs.get("VersionId") == version_id, f"Expected VersionId is {version_id}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Delete object and check it was deleted"):
|
2023-05-15 09:59:33 +00:00
|
|
|
response = s3_client.delete_object(bucket, obj_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
version_id_delete = response.get("VersionId")
|
|
|
|
|
|
|
|
with pytest.raises(Exception, match=r".*Not Found.*"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.head_object(bucket, obj_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Get content for all versions and check it is correct"):
|
|
|
|
for version, content in (
|
|
|
|
(version_id_2, version_2_content),
|
|
|
|
(version_id_1, version_1_content),
|
|
|
|
):
|
2023-05-15 09:59:33 +00:00
|
|
|
file_name = s3_client.get_object(bucket, obj_key, version_id=version)
|
2022-09-19 14:22:10 +00:00
|
|
|
got_content = get_file_content(file_name)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert got_content == content, f"Expected object content is\n{content}\nGot\n{got_content}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Restore previous object version"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object(bucket, obj_key, version_id=version_id_delete)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
file_name = s3_client.get_object(bucket, obj_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
got_content = get_file_content(file_name)
|
|
|
|
assert (
|
|
|
|
got_content == version_2_content
|
|
|
|
), f"Expected object content is\n{version_2_content}\nGot\n{got_content}"
|
|
|
|
|
2022-10-28 14:29:14 +00:00
|
|
|
@pytest.mark.s3_gate_multipart
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Object Multipart API (s3_client={s3_client})")
|
2023-10-31 14:51:09 +00:00
|
|
|
def test_s3_api_multipart(self, s3_client: S3ClientWrapper, bucket: str, simple_object_size: ObjectSize):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test checks S3 Multipart API (Create multipart upload/Abort multipart upload/List multipart upload/
|
|
|
|
Upload part/List parts/Complete multipart upload).
|
|
|
|
"""
|
|
|
|
parts_count = 3
|
2023-10-31 14:51:09 +00:00
|
|
|
file_name_large = generate_file(simple_object_size.value * 1024 * 6 * parts_count) # 5Mb - min part
|
2023-05-15 09:59:33 +00:00
|
|
|
object_key = s3_helper.object_key_from_file_path(file_name_large)
|
2022-09-19 14:22:10 +00:00
|
|
|
part_files = split_file(file_name_large, parts_count)
|
|
|
|
parts = []
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
uploads = s3_client.list_multipart_uploads(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not uploads, f"Expected there is no uploads in bucket {bucket}"
|
|
|
|
|
|
|
|
with allure.step("Create and abort multipart upload"):
|
2023-05-15 09:59:33 +00:00
|
|
|
upload_id = s3_client.create_multipart_upload(bucket, object_key)
|
|
|
|
uploads = s3_client.list_multipart_uploads(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert uploads, f"Expected there one upload in bucket {bucket}"
|
2023-10-31 14:51:09 +00:00
|
|
|
assert uploads[0].get("Key") == object_key, f"Expected correct key {object_key} in upload {uploads}"
|
|
|
|
assert uploads[0].get("UploadId") == upload_id, f"Expected correct UploadId {upload_id} in upload {uploads}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.abort_multipart_upload(bucket, object_key, upload_id)
|
|
|
|
uploads = s3_client.list_multipart_uploads(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not uploads, f"Expected there is no uploads in bucket {bucket}"
|
|
|
|
|
|
|
|
with allure.step("Create new multipart upload and upload several parts"):
|
2023-05-15 09:59:33 +00:00
|
|
|
upload_id = s3_client.create_multipart_upload(bucket, object_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
for part_id, file_path in enumerate(part_files, start=1):
|
2023-05-15 09:59:33 +00:00
|
|
|
etag = s3_client.upload_part(bucket, object_key, upload_id, part_id, file_path)
|
2022-09-19 14:22:10 +00:00
|
|
|
parts.append((part_id, etag))
|
|
|
|
|
|
|
|
with allure.step("Check all parts are visible in bucket"):
|
2023-05-15 09:59:33 +00:00
|
|
|
got_parts = s3_client.list_parts(bucket, object_key, upload_id)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert len(got_parts) == len(part_files), f"Expected {parts_count} parts, got\n{got_parts}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.complete_multipart_upload(bucket, object_key, upload_id, parts)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
uploads = s3_client.list_multipart_uploads(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not uploads, f"Expected there is no uploads in bucket {bucket}"
|
|
|
|
|
|
|
|
with allure.step("Check we can get whole object from bucket"):
|
2023-05-15 09:59:33 +00:00
|
|
|
got_object = s3_client.get_object(bucket, object_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert get_file_hash(got_object) == get_file_hash(file_name_large)
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
self.check_object_attributes(s3_client, bucket, object_key, parts_count)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Bucket tagging API (s3_client={s3_client})")
|
2023-05-15 09:59:33 +00:00
|
|
|
def test_s3_api_bucket_tagging(self, s3_client: S3ClientWrapper, bucket: str):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test checks S3 Bucket tagging API (Put tag/Get tag).
|
|
|
|
"""
|
|
|
|
key_value_pair = [("some-key", "some-value"), ("some-key-2", "some-value-2")]
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_bucket_tagging(bucket, key_value_pair)
|
|
|
|
s3_helper.check_tags_by_bucket(s3_client, bucket, key_value_pair)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_bucket_tagging(bucket)
|
|
|
|
s3_helper.check_tags_by_bucket(s3_client, bucket, [])
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Object tagging API (s3_client={s3_client})")
|
2023-10-31 14:51:09 +00:00
|
|
|
def test_s3_api_object_tagging(self, s3_client: S3ClientWrapper, bucket: str, simple_object_size: ObjectSize):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test checks S3 Object tagging API (Put tag/Get tag/Update tag).
|
|
|
|
"""
|
|
|
|
key_value_pair_bucket = [("some-key", "some-value"), ("some-key-2", "some-value-2")]
|
|
|
|
key_value_pair_obj = [
|
|
|
|
("some-key-obj", "some-value-obj"),
|
|
|
|
("some-key--obj2", "some-value--obj2"),
|
|
|
|
]
|
|
|
|
key_value_pair_obj_new = [("some-key-obj-new", "some-value-obj-new")]
|
2023-08-02 11:54:03 +00:00
|
|
|
file_name_simple = generate_file(simple_object_size.value)
|
2023-05-15 09:59:33 +00:00
|
|
|
obj_key = s3_helper.object_key_from_file_path(file_name_simple)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_bucket_tagging(bucket, key_value_pair_bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_object(bucket, file_name_simple)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
for tags in (key_value_pair_obj, key_value_pair_obj_new):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_object_tagging(bucket, obj_key, tags)
|
|
|
|
s3_helper.check_tags_by_object(
|
|
|
|
s3_client,
|
2022-10-03 13:03:55 +00:00
|
|
|
bucket,
|
|
|
|
obj_key,
|
|
|
|
tags,
|
|
|
|
)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object_tagging(bucket, obj_key)
|
|
|
|
s3_helper.check_tags_by_object(s3_client, bucket, obj_key, [])
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Delete object & delete objects (s3_client={s3_client})")
|
2023-05-15 09:59:33 +00:00
|
|
|
def test_s3_api_delete(
|
|
|
|
self,
|
|
|
|
s3_client: S3ClientWrapper,
|
|
|
|
two_buckets: tuple[str, str],
|
2023-08-02 11:54:03 +00:00
|
|
|
simple_object_size: ObjectSize,
|
|
|
|
complex_object_size: ObjectSize,
|
2023-05-15 09:59:33 +00:00
|
|
|
):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Check delete_object and delete_objects S3 API operation. From first bucket some objects deleted one by one.
|
|
|
|
From second bucket some objects deleted all at once.
|
|
|
|
"""
|
|
|
|
max_obj_count = 20
|
|
|
|
max_delete_objects = 17
|
|
|
|
put_objects = []
|
|
|
|
file_paths = []
|
2022-12-07 12:38:56 +00:00
|
|
|
obj_sizes = [simple_object_size, complex_object_size]
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2022-11-29 14:16:15 +00:00
|
|
|
bucket_1, bucket_2 = two_buckets
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step(f"Generate {max_obj_count} files"):
|
|
|
|
for _ in range(max_obj_count):
|
2023-08-02 11:54:03 +00:00
|
|
|
file_paths.append(generate_file(choice(obj_sizes).value))
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
for bucket in (bucket_1, bucket_2):
|
|
|
|
with allure.step(f"Bucket {bucket} must be empty as it just created"):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects_list = s3_client.list_objects_v2(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not objects_list, f"Expected empty bucket, got {objects_list}"
|
|
|
|
|
|
|
|
for file_path in file_paths:
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_object(bucket, file_path)
|
|
|
|
put_objects.append(s3_helper.object_key_from_file_path(file_path))
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step(f"Check all objects put in bucket {bucket} successfully"):
|
2023-05-15 09:59:33 +00:00
|
|
|
bucket_objects = s3_client.list_objects_v2(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert set(put_objects) == set(
|
|
|
|
bucket_objects
|
|
|
|
), f"Expected all objects {put_objects} in objects list {bucket_objects}"
|
|
|
|
|
|
|
|
with allure.step("Delete some objects from bucket_1 one by one"):
|
|
|
|
objects_to_delete_b1 = choices(put_objects, k=max_delete_objects)
|
|
|
|
for obj in objects_to_delete_b1:
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object(bucket_1, obj)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check deleted objects are not visible in bucket bucket_1"):
|
2023-05-15 09:59:33 +00:00
|
|
|
bucket_objects = s3_client.list_objects_v2(bucket_1)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert set(put_objects).difference(set(objects_to_delete_b1)) == set(
|
|
|
|
bucket_objects
|
|
|
|
), f"Expected all objects {put_objects} in objects list {bucket_objects}"
|
2023-05-15 09:59:33 +00:00
|
|
|
for object_key in objects_to_delete_b1:
|
|
|
|
with pytest.raises(Exception, match="The specified key does not exist"):
|
|
|
|
s3_client.get_object(bucket_1, object_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Delete some objects from bucket_2 at once"):
|
|
|
|
objects_to_delete_b2 = choices(put_objects, k=max_delete_objects)
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_objects(bucket_2, objects_to_delete_b2)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check deleted objects are not visible in bucket bucket_2"):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects_list = s3_client.list_objects_v2(bucket_2)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert set(put_objects).difference(set(objects_to_delete_b2)) == set(
|
|
|
|
objects_list
|
|
|
|
), f"Expected all objects {put_objects} in objects list {bucket_objects}"
|
2023-05-15 09:59:33 +00:00
|
|
|
for object_key in objects_to_delete_b2:
|
|
|
|
with pytest.raises(Exception, match="The specified key does not exist"):
|
|
|
|
s3_client.get_object(bucket_2, object_key)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Copy object to the same bucket (s3_client={s3_client})")
|
2023-05-15 09:59:33 +00:00
|
|
|
def test_s3_copy_same_bucket(
|
|
|
|
self,
|
|
|
|
s3_client: S3ClientWrapper,
|
|
|
|
bucket: str,
|
2023-08-02 11:54:03 +00:00
|
|
|
complex_object_size: ObjectSize,
|
|
|
|
simple_object_size: ObjectSize,
|
2023-05-15 09:59:33 +00:00
|
|
|
):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test object can be copied to the same bucket.
|
|
|
|
#TODO: delete after test_s3_copy_object will be merge
|
|
|
|
"""
|
2023-08-02 11:54:03 +00:00
|
|
|
file_path_simple = generate_file(simple_object_size.value)
|
|
|
|
file_path_large = generate_file(complex_object_size.value)
|
2023-05-15 09:59:33 +00:00
|
|
|
file_name_simple = s3_helper.object_key_from_file_path(file_path_simple)
|
|
|
|
file_name_large = s3_helper.object_key_from_file_path(file_path_large)
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket_objects = [file_name_simple, file_name_large]
|
|
|
|
|
|
|
|
with allure.step("Bucket must be empty"):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects_list = s3_client.list_objects(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not objects_list, f"Expected empty bucket, got {objects_list}"
|
|
|
|
|
|
|
|
with allure.step("Put objects into bucket"):
|
|
|
|
for file_path in (file_path_simple, file_path_large):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_object(bucket, file_path)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Copy one object into the same bucket"):
|
2023-05-15 09:59:33 +00:00
|
|
|
copy_obj_path = s3_client.copy_object(bucket, file_name_simple)
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket_objects.append(copy_obj_path)
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket, bucket_objects)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check copied object has the same content"):
|
2023-05-15 09:59:33 +00:00
|
|
|
got_copied_file = s3_client.get_object(bucket, copy_obj_path)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert get_file_hash(file_path_simple) == get_file_hash(got_copied_file), "Hashes must be the same"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Delete one object from bucket"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object(bucket, file_name_simple)
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket_objects.remove(file_name_simple)
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_helper.check_objects_in_bucket(
|
|
|
|
s3_client,
|
2022-09-23 11:09:41 +00:00
|
|
|
bucket,
|
|
|
|
expected_objects=bucket_objects,
|
|
|
|
unexpected_objects=[file_name_simple],
|
2022-09-19 14:22:10 +00:00
|
|
|
)
|
|
|
|
|
2023-09-08 10:35:34 +00:00
|
|
|
@allure.title("Copy object to another bucket (s3_client={s3_client})")
|
2023-05-15 09:59:33 +00:00
|
|
|
def test_s3_copy_to_another_bucket(
|
|
|
|
self,
|
|
|
|
s3_client: S3ClientWrapper,
|
|
|
|
two_buckets: tuple[str, str],
|
2023-08-02 11:54:03 +00:00
|
|
|
complex_object_size: ObjectSize,
|
|
|
|
simple_object_size: ObjectSize,
|
2023-05-15 09:59:33 +00:00
|
|
|
):
|
2022-09-19 14:22:10 +00:00
|
|
|
"""
|
|
|
|
Test object can be copied to another bucket.
|
|
|
|
#TODO: delete after test_s3_copy_object will be merge
|
|
|
|
"""
|
2023-08-02 11:54:03 +00:00
|
|
|
file_path_simple = generate_file(simple_object_size.value)
|
|
|
|
file_path_large = generate_file(complex_object_size.value)
|
2023-05-15 09:59:33 +00:00
|
|
|
file_name_simple = s3_helper.object_key_from_file_path(file_path_simple)
|
|
|
|
file_name_large = s3_helper.object_key_from_file_path(file_path_large)
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket_1_objects = [file_name_simple, file_name_large]
|
|
|
|
|
2022-11-29 14:16:15 +00:00
|
|
|
bucket_1, bucket_2 = two_buckets
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Buckets must be empty"):
|
|
|
|
for bucket in (bucket_1, bucket_2):
|
2023-05-15 09:59:33 +00:00
|
|
|
objects_list = s3_client.list_objects(bucket)
|
2022-09-19 14:22:10 +00:00
|
|
|
assert not objects_list, f"Expected empty bucket, got {objects_list}"
|
|
|
|
|
|
|
|
with allure.step("Put objects into one bucket"):
|
|
|
|
for file_path in (file_path_simple, file_path_large):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.put_object(bucket_1, file_path)
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Copy object from first bucket into second"):
|
2023-05-15 09:59:33 +00:00
|
|
|
copy_obj_path_b2 = s3_client.copy_object(bucket_1, file_name_large, bucket=bucket_2)
|
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket_1, expected_objects=bucket_1_objects)
|
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket_2, expected_objects=[copy_obj_path_b2])
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check copied object has the same content"):
|
2023-05-15 09:59:33 +00:00
|
|
|
got_copied_file_b2 = s3_client.get_object(bucket_2, copy_obj_path_b2)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert get_file_hash(file_path_large) == get_file_hash(got_copied_file_b2), "Hashes must be the same"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Delete one object from first bucket"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object(bucket_1, file_name_simple)
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket_1_objects.remove(file_name_simple)
|
|
|
|
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket_1, expected_objects=bucket_1_objects)
|
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket_2, expected_objects=[copy_obj_path_b2])
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Delete one object from second bucket and check it is empty"):
|
2023-05-15 09:59:33 +00:00
|
|
|
s3_client.delete_object(bucket_2, copy_obj_path_b2)
|
|
|
|
s3_helper.check_objects_in_bucket(s3_client, bucket_2, expected_objects=[])
|
2022-09-19 14:22:10 +00:00
|
|
|
|
2023-10-31 14:51:09 +00:00
|
|
|
def check_object_attributes(self, s3_client: S3ClientWrapper, bucket: str, object_key: str, parts_count: int):
|
2023-05-15 09:59:33 +00:00
|
|
|
if not isinstance(s3_client, AwsCliClient):
|
2022-09-19 14:22:10 +00:00
|
|
|
logger.warning("Attributes check is not supported for boto3 implementation")
|
|
|
|
return
|
|
|
|
|
|
|
|
with allure.step("Check object's attributes"):
|
2023-10-31 14:51:09 +00:00
|
|
|
obj_parts = s3_client.get_object_attributes(bucket, object_key, ["ObjectParts"], full_output=False)
|
|
|
|
assert obj_parts.get("TotalPartsCount") == parts_count, f"Expected TotalPartsCount is {parts_count}"
|
|
|
|
assert len(obj_parts.get("Parts")) == parts_count, f"Expected Parts cunt is {parts_count}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check object's attribute max-parts"):
|
|
|
|
max_parts = 2
|
2023-05-15 09:59:33 +00:00
|
|
|
obj_parts = s3_client.get_object_attributes(
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket,
|
|
|
|
object_key,
|
2023-05-15 09:59:33 +00:00
|
|
|
["ObjectParts"],
|
2022-09-19 14:22:10 +00:00
|
|
|
max_parts=max_parts,
|
2023-05-15 09:59:33 +00:00
|
|
|
full_output=False,
|
2022-09-19 14:22:10 +00:00
|
|
|
)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert obj_parts.get("TotalPartsCount") == parts_count, f"Expected TotalPartsCount is {parts_count}"
|
2022-09-19 14:22:10 +00:00
|
|
|
assert obj_parts.get("MaxParts") == max_parts, f"Expected MaxParts is {parts_count}"
|
2023-10-31 14:51:09 +00:00
|
|
|
assert len(obj_parts.get("Parts")) == max_parts, f"Expected Parts count is {parts_count}"
|
2022-09-19 14:22:10 +00:00
|
|
|
|
|
|
|
with allure.step("Check object's attribute part-number-marker"):
|
|
|
|
part_number_marker = 3
|
2023-05-15 09:59:33 +00:00
|
|
|
obj_parts = s3_client.get_object_attributes(
|
2022-09-19 14:22:10 +00:00
|
|
|
bucket,
|
|
|
|
object_key,
|
2023-05-15 09:59:33 +00:00
|
|
|
["ObjectParts"],
|
2022-09-19 14:22:10 +00:00
|
|
|
part_number=part_number_marker,
|
2023-05-15 09:59:33 +00:00
|
|
|
full_output=False,
|
2022-09-19 14:22:10 +00:00
|
|
|
)
|
2023-10-31 14:51:09 +00:00
|
|
|
assert obj_parts.get("TotalPartsCount") == parts_count, f"Expected TotalPartsCount is {parts_count}"
|
2022-09-19 14:22:10 +00:00
|
|
|
assert (
|
|
|
|
obj_parts.get("PartNumberMarker") == part_number_marker
|
|
|
|
), f"Expected PartNumberMarker is {part_number_marker}"
|
|
|
|
assert len(obj_parts.get("Parts")) == 1, f"Expected Parts count is {parts_count}"
|