diff --git a/pytest_tests/helpers/grpc_responses.py b/pytest_tests/helpers/grpc_responses.py index e7bc572..9979b82 100644 --- a/pytest_tests/helpers/grpc_responses.py +++ b/pytest_tests/helpers/grpc_responses.py @@ -9,6 +9,7 @@ CONTAINER_NOT_FOUND = "code = 3072.*message = container not found" OBJECT_ACCESS_DENIED = "code = 2048.*message = access to object operation denied" OBJECT_NOT_FOUND = "code = 2049.*message = object not found" OBJECT_ALREADY_REMOVED = "code = 2052.*message = object already removed" +SESSION_NOT_FOUND = "code = 4096.*message = session token not found" def error_matches_status(error: Exception, status_pattern: str) -> bool: diff --git a/pytest_tests/pytest.ini b/pytest_tests/pytest.ini index aab93e7..18efbe5 100644 --- a/pytest_tests/pytest.ini +++ b/pytest_tests/pytest.ini @@ -17,6 +17,7 @@ markers = curl: tests for HTTP gate with curl utility long: long tests (with long execution time) node_mgmt: neofs control commands + session_token: tests for operations with session token acl: tests for basic and extended ACL storage_group: tests for storage groups failover: tests for system recovery after a failure diff --git a/pytest_tests/testsuites/conftest.py b/pytest_tests/testsuites/conftest.py index 9ff18dc..2df6ce6 100644 --- a/pytest_tests/testsuites/conftest.py +++ b/pytest_tests/testsuites/conftest.py @@ -4,18 +4,25 @@ import re import shutil from datetime import datetime -import allure import pytest import wallet from cli_helpers import _cmd_run from cli_utils import NeofsAdm, NeofsCli -from common import (ASSETS_DIR, FREE_STORAGE, INFRASTRUCTURE_TYPE, MAINNET_WALLET_PATH, - NEOFS_NETMAP_DICT) +from common import ( + ASSETS_DIR, + FREE_STORAGE, + INFRASTRUCTURE_TYPE, + MAINNET_WALLET_PATH, + NEOFS_NETMAP_DICT +) from env_properties import save_env_properties from payment_neogo import neofs_deposit, transfer_mainnet_gas from python_keywords.node_management import node_healthcheck from robot.api import deco from service_helper import get_storage_service_helper +from wallet import init_wallet + +import allure def robot_keyword_adapter(name=None, tags=(), types=()): @@ -24,28 +31,28 @@ def robot_keyword_adapter(name=None, tags=(), types=()): deco.keyword = robot_keyword_adapter -logger = logging.getLogger('NeoLogger') +logger = logging.getLogger("NeoLogger") -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def cloud_infrastructure_check(): if INFRASTRUCTURE_TYPE != "CLOUD_VM": - pytest.skip('Test only works on SberCloud infrastructure') + pytest.skip("Test only works on SberCloud infrastructure") yield -@pytest.fixture(scope='session', autouse=True) -@allure.title('Check binary versions') +@pytest.fixture(scope="session", autouse=True) +@allure.title("Check binary versions") def check_binary_versions(request): # Collect versions of local binaries - binaries = ['neo-go', 'neofs-authmate'] + binaries = ["neo-go", "neofs-authmate"] local_binaries = _get_binaries_version_local(binaries) try: - local_binaries['neofs-adm'] = NeofsAdm().version.get() + local_binaries["neofs-adm"] = NeofsAdm().version.get() except RuntimeError: - logger.info(f'neofs-adm not installed') - local_binaries['neofs-cli'] = NeofsCli().version.get() + logger.info(f"neofs-adm not installed") + local_binaries["neofs-cli"] = NeofsCli().version.get() # Collect versions of remote binaries helper = get_storage_service_helper() @@ -53,9 +60,9 @@ def check_binary_versions(request): all_binaries = {**local_binaries, **remote_binaries} # Get version of aws binary - out = _cmd_run('aws --version') + out = _cmd_run("aws --version") out_lines = out.split("\n") - all_binaries["AWS"] = out_lines[0] if out_lines else 'Unknown' + all_binaries["AWS"] = out_lines[0] if out_lines else "Unknown" save_env_properties(request.config, all_binaries) @@ -63,16 +70,16 @@ def check_binary_versions(request): def _get_binaries_version_local(binaries: list) -> dict: env_out = {} for binary in binaries: - out = _cmd_run(f'{binary} --version') - version = re.search(r'version[:\s]*(.+)', out, re.IGNORECASE) - env_out[binary] = version.group(1).strip() if version else 'Unknown' + out = _cmd_run(f"{binary} --version") + version = re.search(r"version[:\s]*(.+)", out, re.IGNORECASE) + env_out[binary] = version.group(1).strip() if version else "Unknown" return env_out -@pytest.fixture(scope='session') -@allure.title('Prepare tmp directory') +@pytest.fixture(scope="session") +@allure.title("Prepare tmp directory") def prepare_tmp_dir(): - full_path = f'{os.getcwd()}/{ASSETS_DIR}' + full_path = f"{os.getcwd()}/{ASSETS_DIR}" shutil.rmtree(full_path, ignore_errors=True) os.mkdir(full_path) yield full_path @@ -99,25 +106,30 @@ def collect_logs(prepare_tmp_dir): allure.attach.file(logs_zip_file_path, name="logs.zip", extension="zip") -@pytest.fixture(scope='session', autouse=True) -@allure.title('Run health check for all storage nodes') +@pytest.fixture(scope="session", autouse=True) +@allure.title("Run health check for all storage nodes") def run_health_check(collect_logs): failed_nodes = [] for node_name in NEOFS_NETMAP_DICT.keys(): health_check = node_healthcheck(node_name) - if health_check.health_status != 'READY' or health_check.network_status != 'ONLINE': + if ( + health_check.health_status != "READY" + or health_check.network_status != "ONLINE" + ): failed_nodes.append(node_name) if failed_nodes: - raise AssertionError(f'Nodes {failed_nodes} are not healthy') + raise AssertionError(f"Nodes {failed_nodes} are not healthy") -@pytest.fixture(scope='session') -@allure.title('Prepare wallet and deposit') +@pytest.fixture(scope="session") +@allure.title("Prepare wallet and deposit") def prepare_wallet_and_deposit(prepare_tmp_dir): wallet_path, addr, _ = wallet.init_wallet(ASSETS_DIR) - logger.info(f'Init wallet: {wallet_path},\naddr: {addr}') - allure.attach.file(wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON) + logger.info(f"Init wallet: {wallet_path},\naddr: {addr}") + allure.attach.file( + wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON + ) if not FREE_STORAGE: deposit = 30 diff --git a/pytest_tests/testsuites/session_token/test_object_session_token.py b/pytest_tests/testsuites/session_token/test_object_session_token.py new file mode 100644 index 0000000..6fabb4c --- /dev/null +++ b/pytest_tests/testsuites/session_token/test_object_session_token.py @@ -0,0 +1,209 @@ +import random + +import pytest +from common import COMPLEX_OBJ_SIZE, NEOFS_NETMAP_DICT, SIMPLE_OBJ_SIZE +from grpc_responses import SESSION_NOT_FOUND +from payment_neogo import _address_from_wallet +from python_keywords.container import create_container +from python_keywords.neofs_verbs import ( + delete_object, + get_object, + get_range, + head_object, + put_object, + search_object, +) +from python_keywords.session_token import create_session_token +from python_keywords.utility_keywords import generate_file + +import allure + + +@allure.title("Test Object Operations with Session Token") +@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, 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 + """ + + with allure.step("Init wallet"): + wallet = prepare_wallet_and_deposit + address = _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("Create Session Token"): + session_token = create_session_token(address, wallet, rpc=session_token_node) + + 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, rule=placement_policy) + + with allure.step("Put Objects"): + file_path = generate_file(object_size) + oid = put_object(wallet=wallet, path=file_path, cid=cid) + oid_delete = put_object(wallet=wallet, path=file_path, cid=cid) + + with allure.step("Node not in container but granted a session token"): + put_object( + wallet=wallet, + path=file_path, + cid=cid, + endpoint=session_token_node, + session=session_token, + ) + head_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=session_token_node, + session=session_token, + ) + search_object( + wallet=wallet, + cid=cid, + endpoint=session_token_node, + expected_objects_list=[oid], + session=session_token, + ) + get_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=session_token_node, + session=session_token, + ) + get_range( + wallet=wallet, + cid=cid, + oid=oid, + range_cut="0:256", + endpoint=session_token_node, + session=session_token, + ) + delete_object( + wallet=wallet, + cid=cid, + oid=oid_delete, + 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): + put_object( + wallet=wallet, + path=file_path, + cid=cid, + endpoint=container_node, + session=session_token, + ) + head_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=container_node, + session=session_token, + ) + search_object( + wallet=wallet, + cid=cid, + endpoint=container_node, + expected_objects_list=[oid], + session=session_token, + ) + get_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=container_node, + session=session_token, + ) + get_range( + wallet=wallet, + cid=cid, + oid=oid, + range_cut="0:256", + endpoint=container_node, + session=session_token, + ) + with pytest.raises(Exception, match=SESSION_NOT_FOUND): + delete_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=container_node, + 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, + endpoint=noncontainer_node, + session=session_token, + ) + head_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=noncontainer_node, + session=session_token, + ) + search_object( + wallet=wallet, + cid=cid, + endpoint=noncontainer_node, + expected_objects_list=[oid], + session=session_token, + ) + get_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=noncontainer_node, + session=session_token, + ) + get_range( + wallet=wallet, + cid=cid, + oid=oid, + range_cut="0:256", + endpoint=noncontainer_node, + session=session_token, + ) + with pytest.raises(Exception, match=SESSION_NOT_FOUND): + delete_object( + wallet=wallet, + cid=cid, + oid=oid, + endpoint=noncontainer_node, + session=session_token, + ) diff --git a/robot/resources/lib/python_keywords/neofs_verbs.py b/robot/resources/lib/python_keywords/neofs_verbs.py index d81f75e..83869f3 100644 --- a/robot/resources/lib/python_keywords/neofs_verbs.py +++ b/robot/resources/lib/python_keywords/neofs_verbs.py @@ -19,10 +19,19 @@ from robot.api.deco import keyword ROBOT_AUTO_KEYWORDS = False -@keyword('Get object') -def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = None, write_object: str = "", - endpoint: str = "", xhdr: Optional[dict] = None, wallet_config: Optional[str] = None, - no_progress: bool = True) -> str: +@keyword("Get object") +def get_object( + wallet: str, + cid: str, + oid: str, + bearer_token: Optional[str] = None, + write_object: str = "", + endpoint: str = "", + xhdr: Optional[dict] = None, + wallet_config: Optional[str] = None, + no_progress: bool = True, + session: Optional[str] = None, +) -> str: """ GET from NeoFS. @@ -36,6 +45,7 @@ def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = No 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 """ @@ -49,16 +59,33 @@ def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = No endpoint = random.sample(NEOFS_NETMAP, 1)[0] cli = NeofsCli(config=wallet_config) - cli.object.get(rpc_endpoint=endpoint, wallet=wallet, cid=cid, oid=oid, file=file_path, - bearer=bearer_token, no_progress=no_progress, xhdr=xhdr) + cli.object.get( + rpc_endpoint=endpoint or NEOFS_ENDPOINT, + wallet=wallet, + cid=cid, + oid=oid, + file=file_path, + bearer=bearer_token, + no_progress=no_progress, + xhdr=xhdr, + session=session, + ) return file_path # TODO: make `bearer_token` optional -@keyword('Get Range Hash') -def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut: str, - wallet_config: Optional[str] = None, xhdr: Optional[dict] = None): +@keyword("Get Range Hash") +def get_range_hash( + wallet: str, + cid: str, + oid: str, + bearer_token: str, + range_cut: str, + endpoint: Optional[str] = None, + wallet_config: Optional[str] = None, + xhdr: Optional[dict] = None, +): """ GETRANGEHASH of given Object. @@ -66,9 +93,10 @@ def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut wallet (str): wallet on whose behalf GETRANGEHASH is done cid (str): ID of Container where we get the Object from oid (str): Object ID + bearer_token (optional, str): path to Bearer Token file, appends to `--bearer` key range_cut (str): Range to take hash from in the form offset1:length1,..., value to pass to the `--range` parameter - bearer_token (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 Returns: @@ -77,17 +105,34 @@ def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut wallet_config = wallet_config or WALLET_CONFIG cli = NeofsCli(config=wallet_config) - output = cli.object.hash(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut, - bearer=bearer_token, xhdr=xhdr) + output = cli.object.hash( + rpc_endpoint=endpoint or NEOFS_ENDPOINT, + wallet=wallet, + cid=cid, + oid=oid, + range=range_cut, + bearer=bearer_token, + xhdr=xhdr, + ) # cutting off output about range offset and length - return output.split(':')[1].strip() + return output.split(":")[1].strip() -@keyword('Put object') -def put_object(wallet: str, path: str, cid: str, bearer: str = "", attributes: Optional[dict] = None, - xhdr: Optional[dict] = None, endpoint: str = "", wallet_config: Optional[str] = None, - expire_at: Optional[int] = None, no_progress: bool = True): +@keyword("Put object") +def put_object( + wallet: str, + path: str, + cid: str, + bearer: str = "", + 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, + session: Optional[str] = None, +): """ PUT of given file. @@ -102,6 +147,7 @@ def put_object(wallet: str, path: str, cid: str, bearer: str = "", attributes: O 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 Returns: (str): ID of uploaded Object """ @@ -109,21 +155,39 @@ def put_object(wallet: str, path: str, cid: str, bearer: str = "", attributes: O if not endpoint: endpoint = random.sample(NEOFS_NETMAP, 1)[0] if not endpoint: - logger.info(f'---DEB:\n{NEOFS_NETMAP}') + logger.info(f"---DEB:\n{NEOFS_NETMAP}") cli = NeofsCli(config=wallet_config) - output = cli.object.put(rpc_endpoint=endpoint, wallet=wallet, file=path, cid=cid, attributes=attributes, - bearer=bearer, expire_at=expire_at, no_progress=no_progress, xhdr=xhdr) + output = cli.object.put( + rpc_endpoint=endpoint, + wallet=wallet, + file=path, + cid=cid, + attributes=attributes, + bearer=bearer, + expire_at=expire_at, + no_progress=no_progress, + xhdr=xhdr, + session=session, + ) # splitting CLI output to lines and taking the penultimate line - id_str = output.strip().split('\n')[-2] - oid = id_str.split(':')[1] + id_str = output.strip().split("\n")[-2] + oid = id_str.split(":")[1] return oid.strip() -@keyword('Delete object') -def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_config: Optional[str] = None, - xhdr: Optional[dict] = None): +@keyword("Delete object") +def delete_object( + wallet: str, + cid: str, + oid: str, + endpoint: Optional[str] = None, + bearer: str = "", + wallet_config: Optional[str] = None, + xhdr: Optional[dict] = None, + session: Optional[str] = None, +): """ DELETE an Object. @@ -132,25 +196,42 @@ def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_conf cid (str): ID of Container where we get the Object from oid (str): ID of Object we are going to delete 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 Returns: (str): Tombstone ID """ - wallet_config = wallet_config or WALLET_CONFIG cli = NeofsCli(config=wallet_config) - output = cli.object.delete(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, bearer=bearer, - xhdr=xhdr) + output = cli.object.delete( + rpc_endpoint=endpoint or NEOFS_ENDPOINT, + wallet=wallet, + cid=cid, + oid=oid, + bearer=bearer, + xhdr=xhdr, + session=session, + ) - id_str = output.split('\n')[1] - tombstone = id_str.split(':')[1] + id_str = output.split("\n")[1] + tombstone = id_str.split(":")[1] return tombstone.strip() -@keyword('Get Range') -def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: Optional[str] = None, - bearer: str = "", xhdr: Optional[dict] = None): +@keyword("Get Range") +def get_range( + wallet: str, + cid: str, + oid: str, + range_cut: str, + endpoint: Optional[str] = None, + wallet_config: Optional[str] = None, + bearer: str = "", + xhdr: Optional[dict] = None, + session: Optional[str] = None, +): """ GETRANGE an Object. @@ -159,9 +240,11 @@ def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: Op 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 + 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 Returns: (str, bytes) - path to the file with range content and content of this file as bytes """ @@ -169,18 +252,35 @@ def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: Op range_file = f"{ASSETS_DIR}/{uuid.uuid4()}" cli = NeofsCli(config=wallet_config) - cli.object.range(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut, file=range_file, - bearer=bearer, xhdr=xhdr) + cli.object.range( + rpc_endpoint=endpoint or NEOFS_ENDPOINT, + wallet=wallet, + cid=cid, + oid=oid, + range=range_cut, + file=range_file, + bearer=bearer, + xhdr=xhdr, + session=session, + ) - with open(range_file, 'rb') as fout: + with open(range_file, "rb") as fout: content = fout.read() return range_file, content -@keyword('Search object') -def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dict] = None, - expected_objects_list: Optional[list] = None, wallet_config: Optional[str] = None, - xhdr: Optional[dict] = None) -> list: +@keyword("Search object") +def search_object( + wallet: str, + cid: str, + bearer: str = "", + endpoint: Optional[str] = None, + filters: Optional[dict] = None, + expected_objects_list: Optional[list] = None, + wallet_config: Optional[str] = None, + xhdr: Optional[dict] = None, + session: Optional[str] = None, +) -> list: """ SEARCH an Object. @@ -188,10 +288,12 @@ def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dic wallet (str): wallet on whose behalf SEARCH is done cid (str): ID of Container where we get the Object from 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 Returns: (list): list of found ObjectIDs """ @@ -199,26 +301,51 @@ def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dic wallet_config = wallet_config or WALLET_CONFIG cli = NeofsCli(config=wallet_config) output = cli.object.search( - rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, bearer=bearer, xhdr=xhdr, - filters=[f'{filter_key} EQ {filter_val}' for filter_key, filter_val in filters.items()] if filters else None) + rpc_endpoint=endpoint or NEOFS_ENDPOINT, + wallet=wallet, + cid=cid, + bearer=bearer, + xhdr=xhdr, + filters=[ + f"{filter_key} EQ {filter_val}" + for filter_key, filter_val in filters.items() + ] + if filters + else None, + session=session, + ) - found_objects = re.findall(r'(\w{43,44})', output) + found_objects = re.findall(r"(\w{43,44})", output) if expected_objects_list: if sorted(found_objects) == sorted(expected_objects_list): - logger.info(f"Found objects list '{found_objects}' ", - f"is equal for expected list '{expected_objects_list}'") + logger.info( + f"Found objects list '{found_objects}' ", + f"is equal for expected list '{expected_objects_list}'", + ) else: - logger.warn(f"Found object list {found_objects} ", - f"is not equal to expected list '{expected_objects_list}'") + logger.warn( + f"Found object list {found_objects} ", + f"is not equal to expected list '{expected_objects_list}'", + ) return found_objects -@keyword('Head object') -def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "", - xhdr: Optional[dict] = None, endpoint: str = None, json_output: bool = True, - is_raw: bool = False, is_direct: bool = False, wallet_config: Optional[str] = None): +@keyword("Head object") +def head_object( + wallet: str, + cid: str, + oid: str, + bearer_token: str = "", + xhdr: Optional[dict] = None, + endpoint: Optional[str] = None, + json_output: bool = True, + is_raw: bool = False, + is_direct: bool = False, + wallet_config: Optional[str] = None, + session: Optional[str] = None, +): """ HEAD an Object. @@ -236,6 +363,7 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "", turns into `--ttl 1` 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 Returns: depending on the `json_output` parameter value, the function returns (dict): HEAD response in JSON format @@ -245,9 +373,18 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "", wallet_config = wallet_config or WALLET_CONFIG cli = NeofsCli(config=wallet_config) - output = cli.object.head(rpc_endpoint=endpoint or NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, - bearer=bearer_token, json_mode=json_output, raw=is_raw, - ttl=1 if is_direct else None, xhdr=xhdr) + output = cli.object.head( + rpc_endpoint=endpoint or NEOFS_ENDPOINT, + wallet=wallet, + cid=cid, + oid=oid, + bearer=bearer_token, + json_mode=json_output, + raw=is_raw, + ttl=1 if is_direct else None, + xhdr=xhdr, + session=session, + ) if not json_output: return output @@ -260,26 +397,26 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "", # Here we cut off first string and try to parse again. logger.info(f"failed to parse output: {exc}") logger.info("parsing output in another way") - fst_line_idx = output.find('\n') + fst_line_idx = output.find("\n") decoded = json.loads(output[fst_line_idx:]) # If response is Complex Object header, it has `splitId` key - if 'splitId' in decoded.keys(): + if "splitId" in decoded.keys(): logger.info("decoding split header") return json_transformers.decode_split_header(decoded) # If response is Last or Linking Object header, # it has `header` dictionary and non-null `split` dictionary - if 'split' in decoded['header'].keys(): - if decoded['header']['split']: + if "split" in decoded["header"].keys(): + if decoded["header"]["split"]: logger.info("decoding linking object") return json_transformers.decode_linking_object(decoded) - if decoded['header']['objectType'] == 'STORAGE_GROUP': + if decoded["header"]["objectType"] == "STORAGE_GROUP": logger.info("decoding storage group") return json_transformers.decode_storage_group(decoded) - if decoded['header']['objectType'] == 'TOMBSTONE': + if decoded["header"]["objectType"] == "TOMBSTONE": logger.info("decoding tombstone") return json_transformers.decode_tombstone(decoded) diff --git a/robot/resources/lib/python_keywords/session_token.py b/robot/resources/lib/python_keywords/session_token.py index e2e50a7..619386e 100644 --- a/robot/resources/lib/python_keywords/session_token.py +++ b/robot/resources/lib/python_keywords/session_token.py @@ -9,94 +9,109 @@ import json import os import uuid -from neo3 import wallet -from common import WALLET_CONFIG, ASSETS_DIR -from cli_helpers import _cmd_run import json_transformers - -from robot.api.deco import keyword +from cli_helpers import _cmd_run, _run_with_passwd +from common import ASSETS_DIR, NEOFS_ENDPOINT, WALLET_CONFIG +from neo3 import wallet from robot.api import logger from robot.api.deco import keyword ROBOT_AUTO_KEYWORDS = False # path to neofs-cli executable -NEOFS_CLI_EXEC = os.getenv('NEOFS_CLI_EXEC', 'neofs-cli') +NEOFS_CLI_EXEC = os.getenv("NEOFS_CLI_EXEC", "neofs-cli") -@keyword('Generate Session Token') -def generate_session_token(owner: str, session_wallet: str, cid: str = '') -> str: +@keyword("Generate Session Token") +def generate_session_token(owner: str, session_wallet: str, cid: str = "") -> str: """ - This function generates session token for ContainerSessionContext - and writes it to the file. It is able to prepare session token file - for a specific container () or for every container (adds - "wildcard" field). - Args: - owner(str): wallet address of container owner - session_wallet(str): the path to wallet to which we grant the - access via session token - cid(optional, str): container ID of the container; if absent, - we assume the session token is generated for any - container - Returns: - (str): the path to the generated session token file + This function generates session token for ContainerSessionContext + and writes it to the file. It is able to prepare session token file + for a specific container () or for every container (adds + "wildcard" field). + Args: + owner(str): wallet address of container owner + session_wallet(str): the path to wallet to which we grant the + access via session token + cid(optional, str): container ID of the container; if absent, + we assume the session token is generated for any + container + Returns: + (str): the path to the generated session token file """ file_path = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}" - session_wlt_content = '' + session_wlt_content = "" with open(session_wallet) as fout: session_wlt_content = json.load(fout) session_wlt = wallet.Wallet.from_json(session_wlt_content, password="") pub_key_64 = base64.b64encode( - bytes.fromhex( - str(session_wlt.accounts[0].public_key) - ) - ).decode('utf-8') + bytes.fromhex(str(session_wlt.accounts[0].public_key)) + ).decode("utf-8") session_token = { "body": { "id": f"{base64.b64encode(uuid.uuid4().bytes).decode('utf-8')}", - "ownerID": { - "value": f"{json_transformers.encode_for_json(owner)}" - }, - "lifetime": { - "exp": "100000000", - "nbf": "0", - "iat": "0" - }, + "ownerID": {"value": f"{json_transformers.encode_for_json(owner)}"}, + "lifetime": {"exp": "100000000", "nbf": "0", "iat": "0"}, "sessionKey": f"{pub_key_64}", "container": { "verb": "PUT", - "wildcard": cid != '', - **({"containerID": - {"value": f"{base64.b64encode(cid.encode('utf-8')).decode('utf-8')}"} - } if cid != '' else {} - ) - } + "wildcard": cid != "", + **( + { + "containerID": { + "value": f"{base64.b64encode(cid.encode('utf-8')).decode('utf-8')}" + } + } + if cid != "" + else {} + ), + }, } } logger.info(f"Got this Session Token: {session_token}") - with open(file_path, 'w', encoding='utf-8') as session_token_file: + with open(file_path, "w", encoding="utf-8") as session_token_file: json.dump(session_token, session_token_file, ensure_ascii=False, indent=4) return file_path -@keyword('Sign Session Token') +@keyword("Create Session Token") +def create_session_token(owner: str, wallet_path: str, rpc: str = NEOFS_ENDPOINT): + """ + Create session token for an object. + Args: + owner(str): user that writes the token + session_wallet(str): the path to wallet to which we grant the + access via session token + Returns: + (str): the path to the generated session token file + """ + session_token = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}" + cmd = ( + f"{NEOFS_CLI_EXEC} session create --address {owner} -w {wallet_path} " + f"--out {session_token} --rpc-endpoint {rpc}" + ) + _run_with_passwd(cmd) + return session_token + + +@keyword("Sign Session Token") def sign_session_token(session_token: str, wlt: str): """ - This function signs the session token by the given wallet. - Args: - session_token(str): the path to the session token file - wlt(str): the path to the signing wallet - Returns: - (str): the path to the signed token + This function signs the session token by the given wallet. + Args: + session_token(str): the path to the session token file + wlt(str): the path to the signing wallet + Returns: + (str): the path to the signed token """ signed_token = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}" cmd = ( - f'{NEOFS_CLI_EXEC} util sign session-token --from {session_token} ' - f'-w {wlt} --to {signed_token} --config {WALLET_CONFIG}' + f"{NEOFS_CLI_EXEC} util sign session-token --from {session_token} " + f"-w {wlt} --to {signed_token} --config {WALLET_CONFIG}" ) _cmd_run(cmd) return signed_token