[226] Tests: test for session token for object

A test for session token for object rewritten in pytest.

Signed-off-by: Elizaveta Chichindaeva <elizaveta@nspcc.ru>
This commit is contained in:
Elizaveta Chichindaeva 2022-09-05 12:35:46 +03:00
parent 3f6ba19a8b
commit 92cbc2e11b
6 changed files with 513 additions and 138 deletions

View file

@ -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_ACCESS_DENIED = "code = 2048.*message = access to object operation denied"
OBJECT_NOT_FOUND = "code = 2049.*message = object not found" OBJECT_NOT_FOUND = "code = 2049.*message = object not found"
OBJECT_ALREADY_REMOVED = "code = 2052.*message = object already removed" 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: def error_matches_status(error: Exception, status_pattern: str) -> bool:

View file

@ -17,6 +17,7 @@ markers =
curl: tests for HTTP gate with curl utility curl: tests for HTTP gate with curl utility
long: long tests (with long execution time) long: long tests (with long execution time)
node_mgmt: neofs control commands node_mgmt: neofs control commands
session_token: tests for operations with session token
acl: tests for basic and extended ACL acl: tests for basic and extended ACL
storage_group: tests for storage groups storage_group: tests for storage groups
failover: tests for system recovery after a failure failover: tests for system recovery after a failure

View file

@ -4,18 +4,25 @@ import re
import shutil import shutil
from datetime import datetime from datetime import datetime
import allure
import pytest import pytest
import wallet import wallet
from cli_helpers import _cmd_run from cli_helpers import _cmd_run
from cli_utils import NeofsAdm, NeofsCli from cli_utils import NeofsAdm, NeofsCli
from common import (ASSETS_DIR, FREE_STORAGE, INFRASTRUCTURE_TYPE, MAINNET_WALLET_PATH, from common import (
NEOFS_NETMAP_DICT) ASSETS_DIR,
FREE_STORAGE,
INFRASTRUCTURE_TYPE,
MAINNET_WALLET_PATH,
NEOFS_NETMAP_DICT
)
from env_properties import save_env_properties from env_properties import save_env_properties
from payment_neogo import neofs_deposit, transfer_mainnet_gas from payment_neogo import neofs_deposit, transfer_mainnet_gas
from python_keywords.node_management import node_healthcheck from python_keywords.node_management import node_healthcheck
from robot.api import deco from robot.api import deco
from service_helper import get_storage_service_helper from service_helper import get_storage_service_helper
from wallet import init_wallet
import allure
def robot_keyword_adapter(name=None, tags=(), types=()): 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 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(): def cloud_infrastructure_check():
if INFRASTRUCTURE_TYPE != "CLOUD_VM": if INFRASTRUCTURE_TYPE != "CLOUD_VM":
pytest.skip('Test only works on SberCloud infrastructure') pytest.skip("Test only works on SberCloud infrastructure")
yield yield
@pytest.fixture(scope='session', autouse=True) @pytest.fixture(scope="session", autouse=True)
@allure.title('Check binary versions') @allure.title("Check binary versions")
def check_binary_versions(request): def check_binary_versions(request):
# Collect versions of local binaries # Collect versions of local binaries
binaries = ['neo-go', 'neofs-authmate'] binaries = ["neo-go", "neofs-authmate"]
local_binaries = _get_binaries_version_local(binaries) local_binaries = _get_binaries_version_local(binaries)
try: try:
local_binaries['neofs-adm'] = NeofsAdm().version.get() local_binaries["neofs-adm"] = NeofsAdm().version.get()
except RuntimeError: except RuntimeError:
logger.info(f'neofs-adm not installed') logger.info(f"neofs-adm not installed")
local_binaries['neofs-cli'] = NeofsCli().version.get() local_binaries["neofs-cli"] = NeofsCli().version.get()
# Collect versions of remote binaries # Collect versions of remote binaries
helper = get_storage_service_helper() helper = get_storage_service_helper()
@ -53,9 +60,9 @@ def check_binary_versions(request):
all_binaries = {**local_binaries, **remote_binaries} all_binaries = {**local_binaries, **remote_binaries}
# Get version of aws binary # Get version of aws binary
out = _cmd_run('aws --version') out = _cmd_run("aws --version")
out_lines = out.split("\n") 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) save_env_properties(request.config, all_binaries)
@ -63,16 +70,16 @@ def check_binary_versions(request):
def _get_binaries_version_local(binaries: list) -> dict: def _get_binaries_version_local(binaries: list) -> dict:
env_out = {} env_out = {}
for binary in binaries: for binary in binaries:
out = _cmd_run(f'{binary} --version') out = _cmd_run(f"{binary} --version")
version = re.search(r'version[:\s]*(.+)', out, re.IGNORECASE) version = re.search(r"version[:\s]*(.+)", out, re.IGNORECASE)
env_out[binary] = version.group(1).strip() if version else 'Unknown' env_out[binary] = version.group(1).strip() if version else "Unknown"
return env_out return env_out
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
@allure.title('Prepare tmp directory') @allure.title("Prepare tmp directory")
def prepare_tmp_dir(): 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) shutil.rmtree(full_path, ignore_errors=True)
os.mkdir(full_path) os.mkdir(full_path)
yield 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") allure.attach.file(logs_zip_file_path, name="logs.zip", extension="zip")
@pytest.fixture(scope='session', autouse=True) @pytest.fixture(scope="session", autouse=True)
@allure.title('Run health check for all storage nodes') @allure.title("Run health check for all storage nodes")
def run_health_check(collect_logs): def run_health_check(collect_logs):
failed_nodes = [] failed_nodes = []
for node_name in NEOFS_NETMAP_DICT.keys(): for node_name in NEOFS_NETMAP_DICT.keys():
health_check = node_healthcheck(node_name) 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) failed_nodes.append(node_name)
if failed_nodes: 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') @pytest.fixture(scope="session")
@allure.title('Prepare wallet and deposit') @allure.title("Prepare wallet and deposit")
def prepare_wallet_and_deposit(prepare_tmp_dir): def prepare_wallet_and_deposit(prepare_tmp_dir):
wallet_path, addr, _ = wallet.init_wallet(ASSETS_DIR) wallet_path, addr, _ = wallet.init_wallet(ASSETS_DIR)
logger.info(f'Init wallet: {wallet_path},\naddr: {addr}') logger.info(f"Init wallet: {wallet_path},\naddr: {addr}")
allure.attach.file(wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON) allure.attach.file(
wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON
)
if not FREE_STORAGE: if not FREE_STORAGE:
deposit = 30 deposit = 30

View file

@ -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,
)

View file

@ -19,10 +19,19 @@ from robot.api.deco import keyword
ROBOT_AUTO_KEYWORDS = False ROBOT_AUTO_KEYWORDS = False
@keyword('Get object') @keyword("Get object")
def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = None, write_object: str = "", def get_object(
endpoint: str = "", xhdr: Optional[dict] = None, wallet_config: Optional[str] = None, wallet: str,
no_progress: bool = True) -> 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. 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 wallet_config(optional, str): path to the wallet config
no_progress(optional, bool): do not show progress bar no_progress(optional, bool): do not show progress bar
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
session (optional, dict): path to a JSON-encoded container session token
Returns: Returns:
(str): path to downloaded file (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] endpoint = random.sample(NEOFS_NETMAP, 1)[0]
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
cli.object.get(rpc_endpoint=endpoint, wallet=wallet, cid=cid, oid=oid, file=file_path, cli.object.get(
bearer=bearer_token, no_progress=no_progress, xhdr=xhdr) 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 return file_path
# TODO: make `bearer_token` optional # TODO: make `bearer_token` optional
@keyword('Get Range Hash') @keyword("Get Range Hash")
def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut: str, def get_range_hash(
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None): 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. 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 wallet (str): wallet on whose behalf GETRANGEHASH is done
cid (str): ID of Container where we get the Object from cid (str): ID of Container where we get the Object from
oid (str): Object ID 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,..., range_cut (str): Range to take hash from in the form offset1:length1,...,
value to pass to the `--range` parameter 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 wallet_config(optional, str): path to the wallet config
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
Returns: 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 wallet_config = wallet_config or WALLET_CONFIG
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
output = cli.object.hash(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut, output = cli.object.hash(
bearer=bearer_token, xhdr=xhdr) 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 # cutting off output about range offset and length
return output.split(':')[1].strip() return output.split(":")[1].strip()
@keyword('Put object') @keyword("Put object")
def put_object(wallet: str, path: str, cid: str, bearer: str = "", attributes: Optional[dict] = None, def put_object(
xhdr: Optional[dict] = None, endpoint: str = "", wallet_config: Optional[str] = None, wallet: str,
expire_at: Optional[int] = None, no_progress: bool = True): 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. 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 no_progress(optional, bool): do not show progress bar
expire_at (optional, int): Last epoch in the life of the object expire_at (optional, int): Last epoch in the life of the object
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
session (optional, dict): path to a JSON-encoded container session token
Returns: Returns:
(str): ID of uploaded Object (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: if not endpoint:
endpoint = random.sample(NEOFS_NETMAP, 1)[0] endpoint = random.sample(NEOFS_NETMAP, 1)[0]
if not endpoint: if not endpoint:
logger.info(f'---DEB:\n{NEOFS_NETMAP}') logger.info(f"---DEB:\n{NEOFS_NETMAP}")
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
output = cli.object.put(rpc_endpoint=endpoint, wallet=wallet, file=path, cid=cid, attributes=attributes, output = cli.object.put(
bearer=bearer, expire_at=expire_at, no_progress=no_progress, xhdr=xhdr) 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 # splitting CLI output to lines and taking the penultimate line
id_str = output.strip().split('\n')[-2] id_str = output.strip().split("\n")[-2]
oid = id_str.split(':')[1] oid = id_str.split(":")[1]
return oid.strip() return oid.strip()
@keyword('Delete object') @keyword("Delete object")
def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_config: Optional[str] = None, def delete_object(
xhdr: Optional[dict] = None): 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. 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 cid (str): ID of Container where we get the Object from
oid (str): ID of Object we are going to delete oid (str): ID of Object we are going to delete
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key 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 wallet_config(optional, str): path to the wallet config
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
session (optional, dict): path to a JSON-encoded container session token
Returns: Returns:
(str): Tombstone ID (str): Tombstone ID
""" """
wallet_config = wallet_config or WALLET_CONFIG wallet_config = wallet_config or WALLET_CONFIG
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
output = cli.object.delete(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, bearer=bearer, output = cli.object.delete(
xhdr=xhdr) rpc_endpoint=endpoint or NEOFS_ENDPOINT,
wallet=wallet,
cid=cid,
oid=oid,
bearer=bearer,
xhdr=xhdr,
session=session,
)
id_str = output.split('\n')[1] id_str = output.split("\n")[1]
tombstone = id_str.split(':')[1] tombstone = id_str.split(":")[1]
return tombstone.strip() return tombstone.strip()
@keyword('Get Range') @keyword("Get Range")
def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: Optional[str] = None, def get_range(
bearer: str = "", xhdr: Optional[dict] = None): 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. 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 cid (str): ID of Container where we get the Object from
oid (str): ID of Object we are going to request oid (str): ID of Object we are going to request
range_cut (str): range to take data from in the form offset:length 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 bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
wallet_config(optional, str): path to the wallet config wallet_config(optional, str): path to the wallet config
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
session (optional, dict): path to a JSON-encoded container session token
Returns: Returns:
(str, bytes) - path to the file with range content and content of this file as bytes (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()}" range_file = f"{ASSETS_DIR}/{uuid.uuid4()}"
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
cli.object.range(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut, file=range_file, cli.object.range(
bearer=bearer, xhdr=xhdr) 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() content = fout.read()
return range_file, content return range_file, content
@keyword('Search object') @keyword("Search object")
def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dict] = None, def search_object(
expected_objects_list: Optional[list] = None, wallet_config: Optional[str] = None, wallet: str,
xhdr: Optional[dict] = None) -> list: 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. 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 wallet (str): wallet on whose behalf SEARCH is done
cid (str): ID of Container where we get the Object from cid (str): ID of Container where we get the Object from
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key 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 filters (optional, dict): key=value pairs to filter Objects
expected_objects_list (optional, list): a list of ObjectIDs to compare found Objects with expected_objects_list (optional, list): a list of ObjectIDs to compare found Objects with
wallet_config(optional, str): path to the wallet config wallet_config(optional, str): path to the wallet config
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
session (optional, dict): path to a JSON-encoded container session token
Returns: Returns:
(list): list of found ObjectIDs (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 wallet_config = wallet_config or WALLET_CONFIG
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
output = cli.object.search( output = cli.object.search(
rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, bearer=bearer, xhdr=xhdr, rpc_endpoint=endpoint or NEOFS_ENDPOINT,
filters=[f'{filter_key} EQ {filter_val}' for filter_key, filter_val in filters.items()] if filters else None) 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 expected_objects_list:
if sorted(found_objects) == sorted(expected_objects_list): if sorted(found_objects) == sorted(expected_objects_list):
logger.info(f"Found objects list '{found_objects}' ", logger.info(
f"is equal for expected list '{expected_objects_list}'") f"Found objects list '{found_objects}' ",
f"is equal for expected list '{expected_objects_list}'",
)
else: else:
logger.warn(f"Found object list {found_objects} ", logger.warn(
f"is not equal to expected list '{expected_objects_list}'") f"Found object list {found_objects} ",
f"is not equal to expected list '{expected_objects_list}'",
)
return found_objects return found_objects
@keyword('Head object') @keyword("Head object")
def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "", def head_object(
xhdr: Optional[dict] = None, endpoint: str = None, json_output: bool = True, wallet: str,
is_raw: bool = False, is_direct: bool = False, wallet_config: Optional[str] = None): 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. HEAD an Object.
@ -236,6 +363,7 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "",
turns into `--ttl 1` key turns into `--ttl 1` key
wallet_config(optional, str): path to the wallet config wallet_config(optional, str): path to the wallet config
xhdr (optional, dict): Request X-Headers in form of Key=Value xhdr (optional, dict): Request X-Headers in form of Key=Value
session (optional, dict): path to a JSON-encoded container session token
Returns: Returns:
depending on the `json_output` parameter value, the function returns depending on the `json_output` parameter value, the function returns
(dict): HEAD response in JSON format (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 wallet_config = wallet_config or WALLET_CONFIG
cli = NeofsCli(config=wallet_config) cli = NeofsCli(config=wallet_config)
output = cli.object.head(rpc_endpoint=endpoint or NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, output = cli.object.head(
bearer=bearer_token, json_mode=json_output, raw=is_raw, rpc_endpoint=endpoint or NEOFS_ENDPOINT,
ttl=1 if is_direct else None, xhdr=xhdr) 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: if not json_output:
return 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. # Here we cut off first string and try to parse again.
logger.info(f"failed to parse output: {exc}") logger.info(f"failed to parse output: {exc}")
logger.info("parsing output in another way") 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:]) decoded = json.loads(output[fst_line_idx:])
# If response is Complex Object header, it has `splitId` key # 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") logger.info("decoding split header")
return json_transformers.decode_split_header(decoded) return json_transformers.decode_split_header(decoded)
# If response is Last or Linking Object header, # If response is Last or Linking Object header,
# it has `header` dictionary and non-null `split` dictionary # it has `header` dictionary and non-null `split` dictionary
if 'split' in decoded['header'].keys(): if "split" in decoded["header"].keys():
if decoded['header']['split']: if decoded["header"]["split"]:
logger.info("decoding linking object") logger.info("decoding linking object")
return json_transformers.decode_linking_object(decoded) return json_transformers.decode_linking_object(decoded)
if decoded['header']['objectType'] == 'STORAGE_GROUP': if decoded["header"]["objectType"] == "STORAGE_GROUP":
logger.info("decoding storage group") logger.info("decoding storage group")
return json_transformers.decode_storage_group(decoded) return json_transformers.decode_storage_group(decoded)
if decoded['header']['objectType'] == 'TOMBSTONE': if decoded["header"]["objectType"] == "TOMBSTONE":
logger.info("decoding tombstone") logger.info("decoding tombstone")
return json_transformers.decode_tombstone(decoded) return json_transformers.decode_tombstone(decoded)

View file

@ -9,94 +9,109 @@ import json
import os import os
import uuid import uuid
from neo3 import wallet
from common import WALLET_CONFIG, ASSETS_DIR
from cli_helpers import _cmd_run
import json_transformers import json_transformers
from cli_helpers import _cmd_run, _run_with_passwd
from robot.api.deco import keyword from common import ASSETS_DIR, NEOFS_ENDPOINT, WALLET_CONFIG
from neo3 import wallet
from robot.api import logger from robot.api import logger
from robot.api.deco import keyword from robot.api.deco import keyword
ROBOT_AUTO_KEYWORDS = False ROBOT_AUTO_KEYWORDS = False
# path to neofs-cli executable # 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') @keyword("Generate Session Token")
def generate_session_token(owner: str, session_wallet: str, cid: str = '') -> str: def generate_session_token(owner: str, session_wallet: str, cid: str = "") -> str:
""" """
This function generates session token for ContainerSessionContext This function generates session token for ContainerSessionContext
and writes it to the file. It is able to prepare session token file and writes it to the file. It is able to prepare session token file
for a specific container (<cid>) or for every container (adds for a specific container (<cid>) or for every container (adds
"wildcard" field). "wildcard" field).
Args: Args:
owner(str): wallet address of container owner owner(str): wallet address of container owner
session_wallet(str): the path to wallet to which we grant the session_wallet(str): the path to wallet to which we grant the
access via session token access via session token
cid(optional, str): container ID of the container; if absent, cid(optional, str): container ID of the container; if absent,
we assume the session token is generated for any we assume the session token is generated for any
container container
Returns: Returns:
(str): the path to the generated session token file (str): the path to the generated session token file
""" """
file_path = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}" file_path = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}"
session_wlt_content = '' session_wlt_content = ""
with open(session_wallet) as fout: with open(session_wallet) as fout:
session_wlt_content = json.load(fout) session_wlt_content = json.load(fout)
session_wlt = wallet.Wallet.from_json(session_wlt_content, password="") session_wlt = wallet.Wallet.from_json(session_wlt_content, password="")
pub_key_64 = base64.b64encode( pub_key_64 = base64.b64encode(
bytes.fromhex( bytes.fromhex(str(session_wlt.accounts[0].public_key))
str(session_wlt.accounts[0].public_key) ).decode("utf-8")
)
).decode('utf-8')
session_token = { session_token = {
"body": { "body": {
"id": f"{base64.b64encode(uuid.uuid4().bytes).decode('utf-8')}", "id": f"{base64.b64encode(uuid.uuid4().bytes).decode('utf-8')}",
"ownerID": { "ownerID": {"value": f"{json_transformers.encode_for_json(owner)}"},
"value": f"{json_transformers.encode_for_json(owner)}" "lifetime": {"exp": "100000000", "nbf": "0", "iat": "0"},
},
"lifetime": {
"exp": "100000000",
"nbf": "0",
"iat": "0"
},
"sessionKey": f"{pub_key_64}", "sessionKey": f"{pub_key_64}",
"container": { "container": {
"verb": "PUT", "verb": "PUT",
"wildcard": cid != '', "wildcard": cid != "",
**({"containerID": **(
{"value": f"{base64.b64encode(cid.encode('utf-8')).decode('utf-8')}"} {
} if cid != '' else {} "containerID": {
) "value": f"{base64.b64encode(cid.encode('utf-8')).decode('utf-8')}"
} }
}
if cid != ""
else {}
),
},
} }
} }
logger.info(f"Got this Session Token: {session_token}") 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) json.dump(session_token, session_token_file, ensure_ascii=False, indent=4)
return file_path 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): def sign_session_token(session_token: str, wlt: str):
""" """
This function signs the session token by the given wallet. This function signs the session token by the given wallet.
Args: Args:
session_token(str): the path to the session token file session_token(str): the path to the session token file
wlt(str): the path to the signing wallet wlt(str): the path to the signing wallet
Returns: Returns:
(str): the path to the signed token (str): the path to the signed token
""" """
signed_token = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}" signed_token = f"{os.getcwd()}/{ASSETS_DIR}/{uuid.uuid4()}"
cmd = ( cmd = (
f'{NEOFS_CLI_EXEC} util sign session-token --from {session_token} ' f"{NEOFS_CLI_EXEC} util sign session-token --from {session_token} "
f'-w {wlt} --to {signed_token} --config {WALLET_CONFIG}' f"-w {wlt} --to {signed_token} --config {WALLET_CONFIG}"
) )
_cmd_run(cmd) _cmd_run(cmd)
return signed_token return signed_token