From 8f4dc841df2d74316393f67e603e320e15dec977 Mon Sep 17 00:00:00 2001 From: Evgeniy Zayats Date: Wed, 20 Dec 2023 20:35:06 -0500 Subject: [PATCH 1/4] Bump neofs-testlib 1.1.11 -> 1.1.14 Tenacity dependency is also updated due to new version used by new neofs-testlib version. Signed-off-by: Evgeniy Zayats --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f2ee25a8b..e40ae19c3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,7 +29,7 @@ mmh3==3.0.0 multidict==6.0.2 mypy==0.950 mypy-extensions==0.4.3 -neofs-testlib==1.1.11 +neofs-testlib==1.1.14 netaddr==0.8.0 packaging==21.3 paramiko==2.10.3 @@ -52,7 +52,7 @@ requests==2.31.0 robotframework==4.1.2 s3transfer==0.3.7 six==1.16.0 -tenacity==8.0.1 +tenacity>=8.0.1 tomli==2.0.1 typing-extensions==4.2.0 urllib3==1.26.18 From d401fa096bee008aa49a5e580f816818b18d2ce6 Mon Sep 17 00:00:00 2001 From: Evgeniy Zayats Date: Wed, 20 Dec 2023 20:37:02 -0500 Subject: [PATCH 2/4] Add new dynamic env library for custom dev env deployments Signed-off-by: Evgeniy Zayats --- .../lib/neofs_env/neofs_env_test_base.py | 32 +++ .../lib/neofs_env/neofs_epoch.py | 70 ++++++ .../lib/s3/s3_gate_base.py | 227 ++++++++++++++++++ dynamic_env_pytest_tests/pytest.ini | 45 ++++ dynamic_env_pytest_tests/requirements.txt | 1 + dynamic_env_pytest_tests/tests/conftest.py | 137 +++++++++++ pyproject.toml | 2 +- robot/resources/files/container_policy.json | 3 + venv/no-dev-env-pytest/environment.sh | 5 + 9 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 dynamic_env_pytest_tests/lib/neofs_env/neofs_env_test_base.py create mode 100644 dynamic_env_pytest_tests/lib/neofs_env/neofs_epoch.py create mode 100644 dynamic_env_pytest_tests/lib/s3/s3_gate_base.py create mode 100644 dynamic_env_pytest_tests/pytest.ini create mode 100644 dynamic_env_pytest_tests/requirements.txt create mode 100644 dynamic_env_pytest_tests/tests/conftest.py create mode 100644 robot/resources/files/container_policy.json create mode 100644 venv/no-dev-env-pytest/environment.sh diff --git a/dynamic_env_pytest_tests/lib/neofs_env/neofs_env_test_base.py b/dynamic_env_pytest_tests/lib/neofs_env/neofs_env_test_base.py new file mode 100644 index 000000000..558975bce --- /dev/null +++ b/dynamic_env_pytest_tests/lib/neofs_env/neofs_env_test_base.py @@ -0,0 +1,32 @@ +import allure +import neofs_env.neofs_epoch as neofs_epoch +import pytest +from neofs_testlib.env.env import NeoFSEnv +from neofs_testlib.shell import Shell + + +class NeofsEnvTestBase: + shell: Shell + neofs_env: NeoFSEnv + + @pytest.fixture(scope="session", autouse=True) + def fill_mandatory_dependencies(self, neofs_env: NeoFSEnv): + NeofsEnvTestBase.shell = neofs_env.shell + NeofsEnvTestBase.neofs_env = neofs_env + yield + + def tick_epoch(self): + neofs_epoch.tick_epoch(self.neofs_env) + + def get_epoch(self): + return neofs_epoch.get_epoch(self.neofs_env) + + def ensure_fresh_epoch(self): + return neofs_epoch.ensure_fresh_epoch(self.neofs_env) + + @allure.step("Tick epochs and wait for epoch alignment") + def tick_epochs_and_wait(self, epochs_to_tick: int): + current_epoch = self.get_epoch() + for _ in range(epochs_to_tick): + self.tick_epoch() + neofs_epoch.wait_for_epochs_align(self.neofs_env, current_epoch) diff --git a/dynamic_env_pytest_tests/lib/neofs_env/neofs_epoch.py b/dynamic_env_pytest_tests/lib/neofs_env/neofs_epoch.py new file mode 100644 index 000000000..cdb86a190 --- /dev/null +++ b/dynamic_env_pytest_tests/lib/neofs_env/neofs_epoch.py @@ -0,0 +1,70 @@ +import logging +from typing import Optional + +import allure +from neofs_testlib.env.env import NeoFSEnv, StorageNode +from test_control import wait_for_success + +logger = logging.getLogger("NeoLogger") + + +@allure.step("Ensure fresh epoch") +def ensure_fresh_epoch(neofs_env: NeoFSEnv, alive_node: Optional[StorageNode] = None) -> int: + # ensure new fresh epoch to avoid epoch switch during test session + alive_node = alive_node if alive_node else neofs_env.storage_nodes[0] + current_epoch = get_epoch(neofs_env, alive_node) + tick_epoch_and_wait(neofs_env, current_epoch, alive_node) + epoch = get_epoch(neofs_env, alive_node) + assert epoch > current_epoch, "Epoch wasn't ticked" + return epoch + + +@allure.step("Wait for epochs align in whole cluster") +@wait_for_success(60, 5) +def wait_for_epochs_align(neofs_env: NeoFSEnv, epoch_number: Optional[int] = None) -> bool: + epochs = [] + for node in neofs_env.storage_nodes: + current_epoch = get_epoch(neofs_env, node) + assert ( + epoch_number is None or current_epoch > epoch_number + ), f"Epoch {current_epoch} wasn't ticked yet. Expected epoch > {epoch_number}" + epochs.append(current_epoch) + unique_epochs = list(set(epochs)) + assert ( + len(unique_epochs) == 1 + ), f"unaligned epochs found, {epochs}, count of unique epochs {len(unique_epochs)}" + + +@allure.step("Get Epoch") +def get_epoch(neofs_env: NeoFSEnv, alive_node: Optional[StorageNode] = None): + alive_node = alive_node if alive_node else neofs_env.storage_nodes[0] + cli = neofs_env.neofs_cli(alive_node.cli_config) + epoch = cli.netmap.epoch(alive_node.endpoint, alive_node.wallet.path) + return int(epoch.stdout) + + +@allure.step("Tick Epoch") +def tick_epoch(neofs_env: NeoFSEnv, alive_node: Optional[StorageNode] = None): + """ + Tick epoch using neofs-adm or NeoGo if neofs-adm is not available (DevEnv) + Args: + neofs_env: neofs env instance under test + alive_node: node to send requests to (first node in cluster by default) + """ + + alive_node = alive_node if alive_node else neofs_env.storage_nodes[0] + neofs_env.neofs_adm().morph.force_new_epoch( + rpc_endpoint=f"http://{neofs_env.morph_rpc}", + alphabet_wallets=neofs_env.alphabet_wallets_dir, + ) + + +@allure.step("Tick Epoch and wait for epochs align") +def tick_epoch_and_wait( + neofs_env: NeoFSEnv, + current_epoch: Optional[int] = None, + node: Optional[StorageNode] = None, +): + current_epoch = current_epoch if current_epoch else get_epoch(neofs_env, node) + tick_epoch(neofs_env, node) + wait_for_epochs_align(neofs_env, current_epoch) diff --git a/dynamic_env_pytest_tests/lib/s3/s3_gate_base.py b/dynamic_env_pytest_tests/lib/s3/s3_gate_base.py new file mode 100644 index 000000000..896c7aacb --- /dev/null +++ b/dynamic_env_pytest_tests/lib/s3/s3_gate_base.py @@ -0,0 +1,227 @@ +import json +import logging +import os +import re +import uuid +from typing import Any, Optional + +import allure +import boto3 +import pexpect +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 +from neofs_env.neofs_env_test_base import NeofsEnvTestBase +from neofs_testlib.env.env import NeoFSEnv, NodeWallet +from neofs_testlib.shell import Shell +from neofs_testlib.utils.wallet import get_last_public_key_from_wallet +from pytest import FixtureRequest + +# Disable warnings on self-signed certificate which the +# boto library produces on requests to S3-gate in dev-env +urllib3.disable_warnings() + +logger = logging.getLogger("NeoLogger") +CREDENTIALS_CREATE_TIMEOUT = "1m" + +# Number of attempts that S3 clients will attempt per each request (1 means single attempt +# without any retries) +MAX_REQUEST_ATTEMPTS = 1 +RETRY_MODE = "standard" + + +def _run_with_passwd(cmd: str, password: str) -> str: + child = pexpect.spawn(cmd) + child.delaybeforesend = 1 + child.expect(".*") + child.sendline(f"{password}\r") + child.wait() + cmd = child.read() + return cmd.decode() + + +class TestNeofsS3GateBase(NeofsEnvTestBase): + s3_client: Any = None + + @pytest.fixture(scope="class", autouse=True) + @allure.title("[Class/Autouse]: Create S3 client") + def s3_client( + self, + default_wallet: NodeWallet, + client_shell: Shell, + request: FixtureRequest, + neofs_env: NeoFSEnv, + ) -> 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] + ( + cid, + bucket, + access_key_id, + secret_access_key, + owner_private_key, + ) = init_s3_credentials( + wallet, neofs_env, s3_bearer_rules_file=s3_bearer_rules_file, policy=policy + ) + + cli = neofs_env.neofs_cli(neofs_env.generate_cli_config(wallet)) + result = cli.container.list(rpc_endpoint=neofs_env.sn_rpc, wallet=wallet.path) + containers_list = result.stdout.split() + 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, f"https://{neofs_env.s3_gw.address}" + ) + else: + client = configure_boto3_client( + access_key_id, secret_access_key, f"https://{neofs_env.s3_gw.address}" + ) + TestNeofsS3GateBase.s3_client = client + TestNeofsS3GateBase.wallet = wallet + + @pytest.fixture + @allure.title("Create/delete bucket") + def bucket(self): + bucket = s3_gate_bucket.create_bucket_s3(self.s3_client, bucket_configuration="rep-1") + yield bucket + self.delete_all_object_in_bucket(bucket) + + @pytest.fixture + @allure.title("Create two buckets") + def two_buckets(self): + bucket_1 = s3_gate_bucket.create_bucket_s3(self.s3_client, bucket_configuration="rep-1") + bucket_2 = s3_gate_bucket.create_bucket_s3(self.s3_client, bucket_configuration="rep-1") + yield bucket_1, bucket_2 + for bucket in [bucket_1, bucket_2]: + self.delete_all_object_in_bucket(bucket) + + def delete_all_object_in_bucket(self, bucket): + versioning_status = s3_gate_bucket.get_bucket_versioning_status(self.s3_client, bucket) + if versioning_status == s3_gate_bucket.VersioningStatus.ENABLED.value: + # From versioned bucket we should delete all versions and delete markers of all objects + objects_versions = s3_gate_object.list_objects_versions_s3(self.s3_client, bucket) + if objects_versions: + s3_gate_object.delete_object_versions_s3_without_dm( + self.s3_client, bucket, objects_versions + ) + objects_delete_markers = s3_gate_object.list_objects_delete_markers_s3( + self.s3_client, bucket + ) + if objects_delete_markers: + s3_gate_object.delete_object_versions_s3_without_dm( + self.s3_client, bucket, objects_delete_markers + ) + + else: + # From non-versioned bucket it's sufficient to delete objects by key + objects = s3_gate_object.list_objects_s3(self.s3_client, bucket) + if objects: + s3_gate_object.delete_objects_s3(self.s3_client, bucket, objects) + objects_delete_markers = s3_gate_object.list_objects_delete_markers_s3( + self.s3_client, bucket + ) + if objects_delete_markers: + s3_gate_object.delete_object_versions_s3_without_dm( + self.s3_client, bucket, objects_delete_markers + ) + + # Delete the bucket itself + s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket) + + +@allure.step("Init S3 Credentials") +def init_s3_credentials( + wallet: NodeWallet, + neofs_env: NeoFSEnv, + s3_bearer_rules_file: Optional[str] = None, + policy: Optional[dict] = None, +) -> tuple: + bucket = str(uuid.uuid4()) + s3_bearer_rules = s3_bearer_rules_file or "robot/resources/files/s3_bearer_rules.json" + policy = policy or "robot/resources/files/container_policy.json" + + gate_public_key = get_last_public_key_from_wallet( + neofs_env.s3_gw.wallet.path, neofs_env.s3_gw.wallet.password + ) + cmd = ( + f"{neofs_env.neofs_s3_authmate_path} --debug --with-log --timeout 1m " + f"issue-secret --wallet {wallet.path} --gate-public-key={gate_public_key} " + f"--peer {neofs_env.storage_nodes[0].endpoint} --container-friendly-name {bucket} " + f"--bearer-rules {s3_bearer_rules} --container-placement-policy 'REP 1' " + f"--container-policy {policy}" + ) + + logger.info(f"Executing command: {cmd}") + + try: + output = _run_with_passwd(cmd, wallet.password) + + # output contains some debug info and then several JSON structures, so we find each + # JSON structure by curly brackets (naive approach, but works while JSON is not nested) + # and then we take JSON containing secret_access_key + json_blocks = re.findall(r"\{.*?\}", output, re.DOTALL) + for json_block in json_blocks: + try: + parsed_json_block = json.loads(json_block) + if "secret_access_key" in parsed_json_block: + return ( + parsed_json_block["container_id"], + bucket, + parsed_json_block["access_key_id"], + parsed_json_block["secret_access_key"], + parsed_json_block["owner_private_key"], + ) + except json.JSONDecodeError: + raise AssertionError(f"Could not parse info from output\n{output}") + raise AssertionError(f"Could not find AWS credentials in output:\n{output}") + except Exception as exc: + raise RuntimeError(f"Failed to init s3 credentials because of error\n{exc}") from exc + + +@allure.step("Configure S3 client (boto3)") +def configure_boto3_client(access_key_id: str, secret_access_key: str, s3gate_endpoint: str): + try: + session = boto3.Session() + config = Config( + retries={ + "max_attempts": MAX_REQUEST_ATTEMPTS, + "mode": RETRY_MODE, + } + ) + + s3_client = session.client( + service_name="s3", + aws_access_key_id=access_key_id, + aws_secret_access_key=secret_access_key, + config=config, + endpoint_url=s3gate_endpoint, + verify=False, + ) + return s3_client + except ClientError as err: + raise Exception( + f'Error Message: {err.response["Error"]["Message"]}\n' + f'Http status code: {err.response["ResponseMetadata"]["HTTPStatusCode"]}' + ) from err + + +@allure.step("Configure S3 client (aws cli)") +def configure_cli_client(access_key_id: str, secret_access_key: str, s3gate_endpoint: str): + try: + 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}") + return client + except Exception as err: + if "command was not found or was not executable" in str(err): + pytest.skip("AWS CLI was not found") + else: + raise RuntimeError("Error while configuring AwsCliClient") from err diff --git a/dynamic_env_pytest_tests/pytest.ini b/dynamic_env_pytest_tests/pytest.ini new file mode 100644 index 000000000..b6276362a --- /dev/null +++ b/dynamic_env_pytest_tests/pytest.ini @@ -0,0 +1,45 @@ +[pytest] +log_cli = 1 +log_cli_level = debug +log_cli_format = %(asctime)s [%(levelname)4s] %(message)s +log_format = %(asctime)s [%(levelname)4s] %(message)s +log_cli_date_format = %Y-%m-%d %H:%M:%S +log_date_format = %H:%M:%S +markers = +# special markers + staging: test to be excluded from run in verifier/pr-validation/sanity jobs and run test in staging job + sanity: test runs in sanity testrun + smoke: test runs in smoke testrun +# functional markers + container: tests for container creation + grpc_api: standard gRPC API tests + grpc_control: tests related to using neofs-cli control commands + grpc_object_lock: gRPC lock tests + http_gate: HTTP gate contract + s3_gate: All S3 gate tests + s3_gate_base: Base S3 gate tests + s3_gate_bucket: Bucket S3 gate tests + s3_gate_locking: Locking S3 gate tests + s3_gate_multipart: S3 gate tests with multipart object + s3_gate_object: Object S3 gate tests + s3_gate_tagging: Tagging S3 gate tests + s3_gate_versioning: Versioning S3 gate tests + long: long tests (with long execution time) + node_mgmt: neofs control commands + session_token: tests for operations with session token + static_session: tests for operations with static session token + bearer: tests for bearer tokens + acl: All tests for ACL + acl_basic: tests for basic ACL + acl_bearer: tests for ACL with bearer + acl_extended: tests for extended ACL + acl_filters: tests for extended ACL with filters and headers + storage_group: tests for storage groups + failover: tests for system recovery after a failure + failover_panic: tests for system recovery after panic reboot of a node + failover_network: tests for network failure + failover_reboot: tests for system recovery after reboot of a node + add_nodes: add nodes to cluster + check_binaries: check neofs installed binaries versions + payments: tests for payment associated operations + load: performance tests diff --git a/dynamic_env_pytest_tests/requirements.txt b/dynamic_env_pytest_tests/requirements.txt new file mode 100644 index 000000000..3c8d7e782 --- /dev/null +++ b/dynamic_env_pytest_tests/requirements.txt @@ -0,0 +1 @@ +-r ../requirements.txt diff --git a/dynamic_env_pytest_tests/tests/conftest.py b/dynamic_env_pytest_tests/tests/conftest.py new file mode 100644 index 000000000..0c47e94f7 --- /dev/null +++ b/dynamic_env_pytest_tests/tests/conftest.py @@ -0,0 +1,137 @@ +import os +import shutil +import uuid +from typing import Optional + +import allure +import pytest +from common import ( + ASSETS_DIR, + COMPLEX_OBJECT_CHUNKS_COUNT, + COMPLEX_OBJECT_TAIL_SIZE, + SIMPLE_OBJECT_SIZE, + TEST_FILES_DIR, + TEST_OBJECTS_DIR, +) +from neofs_testlib.env.env import NeoFSEnv, NodeWallet +from neofs_testlib.shell import Shell +from neofs_testlib.utils.wallet import init_wallet +from python_keywords.neofs_verbs import get_netmap_netinfo + + +def pytest_addoption(parser): + parser.addoption( + "--persist-env", action="store_true", default=False, help="persist deployed env" + ) + parser.addoption("--load-env", action="store", help="load persisted env from file") + + +@pytest.fixture(scope="session") +def neofs_env(request): + if request.config.getoption("--load-env"): + neofs_env = NeoFSEnv.load(request.config.getoption("--load-env")) + else: + neofs_env = NeoFSEnv.simple() + + neofs_env.neofs_adm().morph.set_config( + rpc_endpoint=f"http://{neofs_env.morph_rpc}", + alphabet_wallets=neofs_env.alphabet_wallets_dir, + post_data=f"ContainerFee=0 ContainerAliasFee=0", + ) + + yield neofs_env + + if request.config.getoption("--persist-env"): + neofs_env.persist() + else: + if not request.config.getoption("--load-env"): + neofs_env.kill() + + +@pytest.fixture(scope="session") +@allure.title("Prepare default wallet and deposit") +def default_wallet(temp_directory): + return create_wallet() + + +@pytest.fixture(scope="session") +def client_shell(neofs_env: NeoFSEnv) -> Shell: + yield neofs_env.shell + + +@allure.title("Prepare wallet and deposit") +def create_wallet(name: Optional[str] = None) -> NodeWallet: + if name is None: + wallet_name = f"{str(uuid.uuid4())}.json" + else: + wallet_name = f"{name}.json" + + wallet_path = os.path.join(os.getcwd(), ASSETS_DIR, wallet_name) + wallet_password = "password" + wallet_address = init_wallet(wallet_path, wallet_password) + + allure.attach.file(wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON) + + return NodeWallet(path=wallet_path, address=wallet_address, password=wallet_password) + + +@pytest.fixture(scope="session") +def max_object_size(neofs_env: NeoFSEnv, client_shell: Shell) -> int: + storage_node = neofs_env.storage_nodes[0] + net_info = get_netmap_netinfo( + wallet=storage_node.wallet.path, + wallet_config=storage_node.cli_config, + endpoint=storage_node.endpoint, + shell=client_shell, + ) + yield net_info["maximum_object_size"] + + +@pytest.fixture(scope="session") +def simple_object_size(max_object_size: int) -> int: + yield int(SIMPLE_OBJECT_SIZE) if int(SIMPLE_OBJECT_SIZE) < max_object_size else max_object_size + + +@pytest.fixture(scope="session") +def complex_object_size(max_object_size: int) -> int: + return max_object_size * int(COMPLEX_OBJECT_CHUNKS_COUNT) + int(COMPLEX_OBJECT_TAIL_SIZE) + + +@pytest.fixture(scope="session") +@allure.title("Prepare tmp directory") +def temp_directory() -> str: + with allure.step("Prepare tmp directory"): + full_path = os.path.join(os.getcwd(), ASSETS_DIR) + create_dir(full_path) + + yield full_path + + with allure.step("Remove tmp directory"): + remove_dir(full_path) + + +@pytest.fixture(scope="module", autouse=True) +@allure.title(f"Prepare test files directories") +def artifacts_directory(temp_directory: str) -> None: + dirs = [TEST_FILES_DIR, TEST_OBJECTS_DIR] + for dir_name in dirs: + with allure.step(f"Prepare {dir_name} directory"): + full_path = os.path.join(temp_directory, dir_name) + create_dir(full_path) + + yield + + for dir_name in dirs: + with allure.step(f"Remove {dir_name} directory"): + remove_dir(full_path) + + +def create_dir(dir_path: str) -> None: + with allure.step("Create directory"): + remove_dir(dir_path) + os.mkdir(dir_path) + + +def remove_dir(dir_path: str) -> None: + with allure.step("Remove directory"): + shutil.rmtree(dir_path, ignore_errors=True) diff --git a/pyproject.toml b/pyproject.toml index 2af61c8b0..3d5128bf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.isort] profile = "black" -src_paths = ["pytest_tests", "robot"] +src_paths = ["dynamic_env_pytest_tests", "pytest_tests", "robot"] line_length = 100 [tool.black] diff --git a/robot/resources/files/container_policy.json b/robot/resources/files/container_policy.json new file mode 100644 index 000000000..eafdfe66f --- /dev/null +++ b/robot/resources/files/container_policy.json @@ -0,0 +1,3 @@ +{ + "rep-1": "REP 1" +} diff --git a/venv/no-dev-env-pytest/environment.sh b/venv/no-dev-env-pytest/environment.sh new file mode 100644 index 000000000..e71795f61 --- /dev/null +++ b/venv/no-dev-env-pytest/environment.sh @@ -0,0 +1,5 @@ +# DevEnv variables +export NEOFS_MORPH_DISABLE_CACHE=true +popd > /dev/null + +export PYTHONPATH=${PYTHONPATH}:${VIRTUAL_ENV}/../robot/resources/lib/:${VIRTUAL_ENV}/../robot/resources/lib/python_keywords:${VIRTUAL_ENV}/../robot/resources/lib/robot:${VIRTUAL_ENV}/../robot/variables:${VIRTUAL_ENV}/../pytest_tests/helpers:${VIRTUAL_ENV}/../pytest_tests/steps:${VIRTUAL_ENV}/../pytest_tests/resources:${VIRTUAL_ENV}/../dynamic_env_pytest_tests/lib From 665249e409d052af767d5be0c1a160768966488b Mon Sep 17 00:00:00 2001 From: Evgeniy Zayats Date: Wed, 20 Dec 2023 20:37:51 -0500 Subject: [PATCH 3/4] Migrate s3 acl tests to a new dynamic env Signed-off-by: Evgeniy Zayats --- .../tests/services/s3_gate/test_s3_ACL.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 dynamic_env_pytest_tests/tests/services/s3_gate/test_s3_ACL.py diff --git a/dynamic_env_pytest_tests/tests/services/s3_gate/test_s3_ACL.py b/dynamic_env_pytest_tests/tests/services/s3_gate/test_s3_ACL.py new file mode 100644 index 000000000..d768944ce --- /dev/null +++ b/dynamic_env_pytest_tests/tests/services/s3_gate/test_s3_ACL.py @@ -0,0 +1,80 @@ +import allure +import pytest +from file_helper import generate_file +from s3.s3_gate_base import TestNeofsS3GateBase +from s3_helper import assert_bucket_s3_acl, assert_object_s3_acl, object_key_from_file_path + +from pytest_tests.steps import s3_gate_bucket, s3_gate_object + + +def pytest_generate_tests(metafunc): + if "s3_client" in metafunc.fixturenames: + metafunc.parametrize("s3_client", ["aws cli", "boto3"], indirect=True) + + +@pytest.mark.acl +@pytest.mark.s3_gate +class TestS3GateACL(TestNeofsS3GateBase): + @pytest.mark.sanity + @allure.title("Test S3: Object ACL") + def test_s3_object_ACL(self, bucket, simple_object_size): + file_path = generate_file(simple_object_size) + file_name = object_key_from_file_path(file_path) + + with allure.step("Put object into bucket, Check ACL is empty"): + s3_gate_object.put_object_s3(self.s3_client, bucket, file_path) + obj_acl = s3_gate_object.get_object_acl_s3(self.s3_client, bucket, file_name) + assert obj_acl == [], f"Expected ACL is empty, got {obj_acl}" + + with allure.step("Put object ACL = public-read"): + acl = "public-read" + s3_gate_object.put_object_acl_s3(self.s3_client, bucket, file_name, acl) + obj_acl = s3_gate_object.get_object_acl_s3(self.s3_client, bucket, file_name) + assert_object_s3_acl(acl_grants=obj_acl, permitted_users="AllUsers", acl=acl) + + with allure.step("Put object ACL = private"): + acl = "private" + s3_gate_object.put_object_acl_s3(self.s3_client, bucket, file_name, acl) + obj_acl = s3_gate_object.get_object_acl_s3(self.s3_client, bucket, file_name) + assert_object_s3_acl(acl_grants=obj_acl, permitted_users="CanonicalUser", acl=acl) + + with allure.step( + "Put object with grant-read uri=http://acs.amazonaws.com/groups/global/AllUsers" + ): + s3_gate_object.put_object_acl_s3( + self.s3_client, + bucket, + file_name, + grant_read="uri=http://acs.amazonaws.com/groups/global/AllUsers", + ) + obj_acl = s3_gate_object.get_object_acl_s3(self.s3_client, bucket, file_name) + assert_object_s3_acl(acl_grants=obj_acl, permitted_users="AllUsers", acl="grant-read") + + @allure.title("Test S3: Bucket ACL") + def test_s3_bucket_ACL(self): + with allure.step("Create bucket with ACL = public-read-write"): + acl = "public-read-write" + bucket = s3_gate_bucket.create_bucket_s3( + self.s3_client, True, acl=acl, bucket_configuration="rep-1" + ) + bucket_acl = s3_gate_bucket.get_bucket_acl(self.s3_client, bucket) + assert_bucket_s3_acl(acl_grants=bucket_acl, permitted_users="AllUsers", acl=acl) + + with allure.step("Change bucket ACL to private"): + acl = "private" + s3_gate_bucket.put_bucket_acl_s3(self.s3_client, bucket, acl=acl) + bucket_acl = s3_gate_bucket.get_bucket_acl(self.s3_client, bucket) + assert_bucket_s3_acl(acl_grants=bucket_acl, permitted_users="CanonicalUser", acl=acl) + + with allure.step( + "Change bucket acl to --grant-write uri=http://acs.amazonaws.com/groups/global/AllUsers" + ): + s3_gate_bucket.put_bucket_acl_s3( + self.s3_client, + bucket, + grant_write="uri=http://acs.amazonaws.com/groups/global/AllUsers", + ) + bucket_acl = s3_gate_bucket.get_bucket_acl(self.s3_client, bucket) + assert_bucket_s3_acl( + acl_grants=bucket_acl, permitted_users="AllUsers", acl="grant-write" + ) From 909c945e5f4cb58f17fa1dc0d40dc5ec6e4468bf Mon Sep 17 00:00:00 2001 From: Evgeniy Zayats Date: Wed, 20 Dec 2023 20:38:19 -0500 Subject: [PATCH 4/4] Add github workflow to validate tests from new dynamic env Signed-off-by: Evgeniy Zayats --- .github/workflows/run-tests.yml | 250 ++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 000000000..5f336ef0e --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,250 @@ +name: Run automated system tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + - support/** + types: [opened, synchronize] + paths-ignore: + - '**/*.md' + release: + types: + - published + workflow_dispatch: + inputs: + neofs_testcases_ref: + description: 'neofs-testcases ref. Default ref - latest master. Examples: v0.36.0, 8fdcc6d7e798e6511be8806b81894622e72d7fdc, branch_name' + required: false + default: '' + +permissions: write-all + +jobs: + run_system_tests: + runs-on: ubuntu-latest + timeout-minutes: 500 + steps: + - name: Get the current date + id: date + run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT + + - name: Set RUN_ID + env: + TIMESTAMP: ${{ steps.date.outputs.timestamp }} + run: echo "RUN_ID=${{ github.run_number }}-$TIMESTAMP" >> $GITHUB_ENV + + - name: Checkout neofs-testcases repository + uses: actions/checkout@v4 + with: + path: neofs-testcases + + - name: Download latest stable neofs-cli + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-node' + version: 'tags/v0.39.2' + file: 'neofs-cli-amd64' + target: 'neofs-testcases/neofs-cli' + + - name: Download latest stable neofs-adm + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-node' + version: 'tags/v0.39.2' + file: 'neofs-adm-amd64' + target: 'neofs-testcases/neofs-adm' + + - name: Download latest stable neofs-ir + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-node' + version: 'tags/v0.39.2' + file: 'neofs-ir-amd64' + target: 'neofs-testcases/neofs-ir' + + - name: Download latest stable neofs-lens + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-node' + version: 'tags/v0.39.2' + file: 'neofs-lens-amd64' + target: 'neofs-testcases/neofs-lens' + + - name: Download latest stable neofs-node + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-node' + version: 'tags/v0.39.2' + file: 'neofs-node-amd64' + target: 'neofs-testcases/neofs-node' + + - name: Checkout neofs-s3-gw repository + uses: actions/checkout@v4 + with: + repository: nspcc-dev/neofs-s3-gw + ref: '014a5bf493d00b8cce478e2c96c84ad9de8fe617' + path: neofs-s3-gw + + - name: Download latest stable neofs-rest-gw + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-rest-gw' + version: 'tags/v0.6.0' + file: 'neofs-rest-gw-linux-amd64' + target: 'neofs-testcases/neofs-rest-gw' + + - name: Download latest stable neofs-http-gw + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neofs-http-gw' + version: 'tags/v0.28.0' + file: 'neofs-http-gw-linux-amd64' + target: 'neofs-testcases/neofs-http-gw' + + - name: Download latest stable neo-go + uses: dsaltares/fetch-gh-release-asset@1.1.1 + with: + repo: 'nspcc-dev/neo-go' + version: 'tags/v0.104.0' + file: 'neo-go-linux-amd64' + target: 'neofs-testcases/neo-go' + + - name: Chmod latest stable binaries + run: | + sudo chmod a+x neofs-cli + sudo chmod a+x neofs-adm + sudo chmod a+x neofs-ir + sudo chmod a+x neofs-lens + sudo chmod a+x neofs-node + sudo chmod a+x neofs-rest-gw + sudo chmod a+x neofs-http-gw + sudo chmod a+x neo-go + working-directory: neofs-testcases + + - name: Set up Go + uses: actions/setup-go@v4 + with: + cache: true + go-version: '1.20' + - run: go version + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10.11' + - run: python --version + +# Hashlib uses OpenSSL for ripemd160 and apparently OpenSSL disabled some older crypto algos around version 3.0 +# in November 2021. All the functions are still there but require manual enabling. +# See https://github.com/openssl/openssl/issues/16994 +# But we use ripemd160 for tests. +# For ripemd160 to be supported, we need the openssl configuration file to contain the following lines: +# openssl_conf = openssl_init +# +# [openssl_init] +# providers = provider_sect +# +# [provider_sect] +# default = default_sect +# legacy = legacy_sect +# +# [default_sect] +# activate = 1 +# +# [legacy_sect] +# activate = 1 + - name: Fix OpenSSL ripemd160 + run: | + sudo python ./tools/src/openssl_config_fix.py + working-directory: neofs-testcases + + - name: Build neofs-s3-gw + timeout-minutes: 5 + run: | + make all + echo "$(pwd)/bin" >> $GITHUB_PATH + working-directory: neofs-s3-gw + + - name: Copy binaries to testcases directory + timeout-minutes: 30 + run: | + cp ${GITHUB_WORKSPACE}/neofs-s3-gw/bin/* . + echo "$(pwd)" >> $GITHUB_PATH + working-directory: neofs-testcases + + - name: Prepare venv + timeout-minutes: 30 + run: | + make venv.no-dev-env-pytest + working-directory: neofs-testcases + + - name: Log environment + run: | + echo "Check free space" + df -h + echo "==========================================" + + echo "Check neo-go version" + neo-go --version + echo "==========================================" + + echo "Check neofs-s3-authmate version" + neofs-s3-authmate --version + echo "==========================================" + + echo "Check neofs-s3-gw version" + echo "==========================================" + neofs-s3-gw --version + echo "==========================================" + + echo "Check neofs-adm version" + neofs-adm --version + echo "==========================================" + + echo "Check neofs-ir version" + neofs-ir --version + echo "==========================================" + + echo "Check neofs-lens version" + neofs-lens --version + echo "==========================================" + + echo "Check neofs-cli version" + neofs-cli --version + echo "==========================================" + + echo "Check current dir" + ls -lah + echo "==========================================" + working-directory: neofs-testcases + +################################################################ + - name: Run Sanity tests for pull requests + timeout-minutes: 120 + if: github.event_name == 'pull_request' + run: | + source venv.no-dev-env-pytest/bin/activate && pytest -s --alluredir=${GITHUB_WORKSPACE}/allure-results dynamic_env_pytest_tests/tests + working-directory: neofs-testcases + +################################################################ + - name: Generate Allure report + timeout-minutes: 60 + uses: simple-elf/allure-report-action@v1.6 + if: always() + id: allure-report + with: + keep_reports: 100000 + allure_results: allure-results + allure_report: allure-report + allure_history: allure-history + + - name: Archive allure report raw results + uses: actions/upload-artifact@v3 + if: always() + with: + name: allure-report-raw-results + path: allure-results