diff --git a/README.md b/README.md index ea5566a6..8e599550 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ * object_complex.robot - операции над простым объектом * object_simple.robot - операции над большим объектом * withdraw.robot - оперция Deposit и Withdraw с счета NeoFS - * netmap_simple.robot - проверка Placement policy + * netmap_simple.robot - проверка Placement policy * replication.robot - базовый тесткейс проверки репликации объектов ### Запуск тесткейсов в докере @@ -54,6 +54,21 @@ export BUILD_NEOFS_NODE=<commit or branch> ``` +### Запуск smoke-тестов + +Есть сьют со smoke-тестами для CDN-гейтов `robot/testsuites/smoke/selectelcdn_smoke.robot`. +Ему требуются отдельные переменные, в отличие от сьютов NeoFS, которые запускаются на +девэнве. Чтобы библиотеки кейвордов их использовали, нужно установить переменную +окружения +``` +export ROBOT_PROFILE=selectel_smoke +``` +По умолчанию кейворды используют переменные из файла `robot/resources/lib/neofs_int_vars.py`. +``` +robot --outputdir artifacts/ robot/testsuites/smoke/selectelcdn_smoke.robot +``` + + ### Генерация документации Для генерации документации по шагам: @@ -110,4 +125,4 @@ On keywords definition, one should specify variable type, e.g. path: str ### Robot-framework User Guide -http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html \ No newline at end of file +http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html diff --git a/robot/resources/lib/gates.py b/robot/resources/lib/gates.py new file mode 100644 index 00000000..d33b9ddf --- /dev/null +++ b/robot/resources/lib/gates.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 + +import logging +import os +import requests + +from robot.api.deco import keyword +from robot.api import logger +import robot.errors +from robot.libraries.BuiltIn import BuiltIn + + +ROBOT_AUTO_KEYWORDS = False + +if os.getenv('ROBOT_PROFILE') == 'selectel_smoke': + from selectelcdn_smoke_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT, HTTP_GATE) +else: + from neofs_int_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT, HTTP_GATE) + + +@keyword('Get via HTTP Gate') +def get_via_http_gate(cid: str, oid: str): + """ + This function gets given object from HTTP gate + :param cid: CID to get object from + :param oid: object OID + """ + resp = requests.get(f'{HTTP_GATE}/get/{cid}/{oid}') + if not resp.ok: + logger.info(f"""Failed to get object via HTTP gate: + request: {resp.request.path_url}, + response: {resp.text}, + status code: {resp.status_code} {resp.reason}""") + return + + filename = os.path.curdir + f"/{cid}_{oid}" + with open(filename, "w+") as f: + f.write(resp.text) + return filename diff --git a/robot/resources/lib/neofs.py b/robot/resources/lib/neofs.py index 775a22b8..4ca9f094 100644 --- a/robot/resources/lib/neofs.py +++ b/robot/resources/lib/neofs.py @@ -8,12 +8,17 @@ import uuid import hashlib from robot.api.deco import keyword from robot.api import logger -import random +import random +if os.getenv('ROBOT_PROFILE') == 'selectel_smoke': + from selectelcdn_smoke_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT) +else: + from neofs_int_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT) ROBOT_AUTO_KEYWORDS = False -NEOFS_ENDPOINT = "s01.neofs.devenv:8080" CLI_PREFIX = "" @keyword('Form WIF from String') @@ -27,7 +32,7 @@ def form_wif_from_string(private_key: str): logger.info("Output: %s" % output) m = re.search(r'WIF\s+(\w+)', output) - if m.start() != m.end(): + if m.start() != m.end(): wif = m.group(1) else: raise Exception("Can not get WIF.") @@ -46,7 +51,7 @@ def get_scripthash(privkey: str): logger.info("Output: %s" % output) m = re.search(r'ScriptHash3.0 (\w+)', output) - if m.start() != m.end(): + if m.start() != m.end(): scripthash = m.group(1) else: raise Exception("Can not get ScriptHash.") @@ -175,7 +180,7 @@ def get_eacl(private_key: bytes, cid: str): stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=150, shell=True) output = complProc.stdout logger.info("Output: %s" % output) - + return output except subprocess.CalledProcessError as e: @@ -184,7 +189,7 @@ def get_eacl(private_key: bytes, cid: str): else: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) - + @keyword('Set eACL') def set_eacl(private_key: str, cid: str, eacl: str, add_keys: str = ""): @@ -199,7 +204,7 @@ def set_eacl(private_key: str, cid: str, eacl: str, add_keys: str = ""): @keyword('Form BearerToken file for all ops') -def form_bearertoken_file_for_all_ops(file_name: str, private_key: str, cid: str, action: str, target_role: str, lifetime_exp: str ): +def form_bearertoken_file_for_all_ops(file_name: str, private_key: str, cid: str, action: str, target_role: str, lifetime_exp: str ): eacl = get_eacl(private_key, cid) input_records = "" @@ -307,10 +312,10 @@ def form_bearertoken_file_for_all_ops(file_name: str, private_key: str, cid: str return file_name - + @keyword('Form BearerToken file filter for all ops') -def form_bearertoken_file_filter_for_all_ops(file_name: str, private_key: str, cid: str, action: str, target_role: str, lifetime_exp: str, matchType: str, key: str, value: str): +def form_bearertoken_file_filter_for_all_ops(file_name: str, private_key: str, cid: str, action: str, target_role: str, lifetime_exp: str, matchType: str, key: str, value: str): # SEARCH should be allowed without filters to use GET, HEAD, DELETE, and SEARCH? Need to clarify. @@ -471,8 +476,8 @@ def form_bearertoken_file_filter_for_all_ops(file_name: str, private_key: str, c @keyword('Form eACL json file') -def form_eacl_json_file(file_name: str, operation: str, action: str, matchType: str, key: str, value: str, target_role: str): - +def form_eacl_json_file(file_name: str, operation: str, action: str, matchType: str, key: str, value: str, target_role: str): + myjson = """ { "records": [ @@ -509,9 +514,9 @@ def form_eacl_json_file(file_name: str, operation: str, action: str, matchType: def get_range(private_key: str, cid: str, oid: str, range_file: str, bearer: str, range_cut: str): bearer_token = "" - if bearer: + if bearer: bearer_token = f"--bearer {bearer}" - + Cmd = f'neofs-cli --rpc-endpoint {NEOFS_ENDPOINT} --key {private_key} object range --cid {cid} --oid {oid} {bearer_token} --range {range_cut} --file {range_file} ' logger.info("Cmd: %s" % Cmd) @@ -526,10 +531,10 @@ def get_range(private_key: str, cid: str, oid: str, range_file: str, bearer: str @keyword('Create container') def create_container(private_key: str, basic_acl:str="", rule:str="REP 2 IN X CBF 1 SELECT 2 FROM * AS X"): - + if basic_acl != "": basic_acl = "--basic-acl " + basic_acl - + createContainerCmd = f'neofs-cli --rpc-endpoint {NEOFS_ENDPOINT} --key {private_key} container create --policy "{rule}" {basic_acl} --await' logger.info("Cmd: %s" % createContainerCmd) complProc = subprocess.run(createContainerCmd, check=True, universal_newlines=True, @@ -549,7 +554,7 @@ def container_existing(private_key: str, cid: str): complProc = subprocess.run(Cmd, check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) logger.info("Output: %s" % complProc.stdout) - + _find_cid(complProc.stdout, cid) return @@ -569,14 +574,14 @@ def generate_file_of_bytes(size): fout.write(os.urandom(size)) logger.info("Random binary file with size %s bytes has been generated." % str(size)) - return filename + return os.path.abspath(os.getcwd()) + '/' + filename @keyword('Search object') def search_object(private_key: str, cid: str, keys: str, bearer: str, filters: str, *expected_objects_list ): bearer_token = "" - if bearer: + if bearer: bearer_token = f"--bearer {bearer}" @@ -594,7 +599,7 @@ def search_object(private_key: str, cid: str, keys: str, bearer: str, filters: s if expected_objects_list: found_objects = re.findall(r'(\w{43,44})', complProc.stdout) - + if sorted(found_objects) == sorted(expected_objects_list): logger.info("Found objects list '{}' is equal for expected list '{}'".format(found_objects, expected_objects_list)) else: @@ -603,7 +608,7 @@ def search_object(private_key: str, cid: str, keys: str, bearer: str, filters: s except subprocess.CalledProcessError as e: - raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) + raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) ''' @keyword('Verify Head Tombstone') @@ -620,23 +625,20 @@ def verify_head_tombstone(private_key: str, cid: str, oid: str): logger.info("Tombstone header 'Type=Tombstone Value=MARKED' was parsed from command output") else: raise Exception("Tombstone header 'Type=Tombstone Value=MARKED' was not found in the command output: \t%s" % (complProc.stdout)) - + except subprocess.CalledProcessError as e: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) -''' - -''' @keyword('Verify linked objects') def verify_linked_objects(private_key: bytes, cid: str, oid: str, payload_size: float): - + payload_size = int(float(payload_size)) # Get linked objects from first postfix = f'object head --cid {cid} --oid {oid} --full-headers' output = _exec_cli_cmd(private_key, postfix) child_obj_list = [] - + for m in re.finditer(r'Type=Child ID=([\w-]+)', output): child_obj_list.append(m.group(1)) @@ -647,7 +649,7 @@ def verify_linked_objects(private_key: bytes, cid: str, oid: str, payload_size: raise Exception("Child objects was not found.") else: logger.info("Child objects: %s" % child_obj_list) - + # HEAD and validate each child object: payload = 0 parent_id = "00000000-0000-0000-0000-000000000000" @@ -665,7 +667,7 @@ def verify_linked_objects(private_key: bytes, cid: str, oid: str, payload_size: if not first_obj: raise Exception("Can not find first object with zero Parent ID.") else: - + _check_linked_object(first_obj, child_obj_list_headers, payload_size, payload, parent_id) return child_obj_list_headers.keys() @@ -682,11 +684,11 @@ def _check_linked_object(obj:str, child_obj_list_headers:dict, payload_size:int, logger.info("Previous ID is equal for expected: %s" % parent_id) m = re.search(r'PayloadLength=(\d+)', output) - if m.start() != m.end(): + if m.start() != m.end(): payload += int(m.group(1)) else: raise Exception("Can not get payload for the object %s." % obj) - + if payload > payload_size: raise Exception("Payload exceeds expected total payload %s." % payload_size) @@ -695,10 +697,10 @@ def _check_linked_object(obj:str, child_obj_list_headers:dict, payload_size:int, raise Exception("Incorrect previos ID in the last child object %s." % obj) else: logger.info("Next ID is correct for the final child object: %s" % obj) - + else: m = re.search(r'Type=Next ID=([\w-]+)', output) - if m: + if m: # next object should be in the expected list logger.info(m.group(1)) if m.group(1) not in child_obj_list_headers.keys(): @@ -735,13 +737,13 @@ def head_object(private_key: str, cid: str, oid: str, bearer: str, user_headers: logger.info("User header %s was parsed from command output" % key) else: raise Exception("User header %s was not found in the command output: \t%s" % (key, complProc.stdout)) - + return complProc.stdout except subprocess.CalledProcessError as e: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) - + @keyword('Parse Object System Header') @@ -770,7 +772,6 @@ def parse_object_system_header(header: str): result_header['OwnerID'] = m.group(1) else: raise Exception("no OwnerID was parsed from object header: \t%s" % output) - # PayloadLength m = re.search(r'Size: (\d+)', header) if m.start() != m.end(): # e.g., if match found something @@ -778,7 +779,7 @@ def parse_object_system_header(header: str): else: raise Exception("no PayloadLength was parsed from object header: \t%s" % output) - # CreatedAtUnixTime + # CreatedAtUnixTime m = re.search(r'Timestamp=(\d+)', header) if m.start() != m.end(): # e.g., if match found something result_header['CreatedAtUnixTime'] = m.group(1) @@ -795,7 +796,6 @@ def parse_object_system_header(header: str): logger.info("Result: %s" % result_header) return result_header - @keyword('Delete object') def delete_object(private_key: str, cid: str, oid: str, bearer: str): @@ -809,7 +809,7 @@ def delete_object(private_key: str, cid: str, oid: str, bearer: str): stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) logger.info("Output: %s" % complProc.stdout) except subprocess.CalledProcessError as e: - raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) + raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) @keyword('Get file hash') @@ -826,46 +826,17 @@ def verify_file_hash(filename, expected_hash): else: raise Exception("File hash '{}' is not equal to {}".format(file_hash, expected_hash)) -''' -@keyword('Create storage group') -def create_storage_group(private_key: bytes, cid: str, *objects_list): - objects = "" - - for oid in objects_list: - objects = f'{objects} --oid {oid}' - - ObjectCmd = f'{CLI_PREFIX}neofs-cli --host {NEOFS_ENDPOINT} --key {binascii.hexlify(private_key).decode()} sg put --cid {cid} {objects}' - complProc = subprocess.run(ObjectCmd, check=True, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) - logger.info("Output: %s" % complProc.stdout) - sgid = _parse_oid(complProc.stdout) - return sgid - - -@keyword('Get storage group') -def get_storage_group(private_key: bytes, cid: str, sgid: str): - ObjectCmd = f'{CLI_PREFIX}neofs-cli --host {NEOFS_ENDPOINT} --key {binascii.hexlify(private_key).decode()} sg get --cid {cid} --sgid {sgid}' - logger.info("Cmd: %s" % ObjectCmd) - try: - complProc = subprocess.run(ObjectCmd, check=True, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) - logger.info("Output: %s" % complProc.stdout) - except subprocess.CalledProcessError as e: - raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) -''' - - @keyword('Cleanup File') # remove temp files def cleanup_file(filename: str): if os.path.isfile(filename): try: os.remove(filename) - except OSError as e: + except OSError as e: raise Exception("Error: '%s' - %s." % (e.filename, e.strerror)) - else: + else: raise Exception("Error: '%s' file not found" % filename) - + logger.info("File '%s' has been deleted." % filename) @@ -895,10 +866,10 @@ def put_object(private_key: str, path: str, cid: str, bearer: str, user_headers: @keyword('Get Range Hash') def get_range_hash(private_key: str, cid: str, oid: str, bearer_token: str, range_cut: str): - - if bearer_token: + + if bearer_token: bearer_token = f"--bearer {bearer}" - + ObjectCmd = f'neofs-cli --rpc-endpoint {NEOFS_ENDPOINT} --key {private_key} object hash --cid {cid} --oid {oid} --range {range_cut} {bearer_token}' logger.info("Cmd: %s" % ObjectCmd) @@ -909,7 +880,6 @@ def get_range_hash(private_key: str, cid: str, oid: str, bearer_token: str, rang except subprocess.CalledProcessError as e: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) - @keyword('Get object from NeoFS') def get_object(private_key: str, cid: str, oid: str, bearer_token: str, read_object: str): @@ -940,7 +910,7 @@ def _exec_cli_cmd(private_key: bytes, postfix: str): except subprocess.CalledProcessError as e: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) - + return complProc.stdout @@ -978,7 +948,7 @@ def _parse_oid(output: str): oid = m.group(1) else: raise Exception("no OID was parsed from command output: \t%s" % output) - + return oid def _parse_cid(output: str): @@ -991,7 +961,7 @@ def _parse_cid(output: str): if not m.start() != m.end(): # e.g., if match found something raise Exception("no CID was parsed from command output: \t%s" % (output)) cid = m.group(1) - + return cid def _get_storage_nodes(private_key: bytes): @@ -1003,7 +973,7 @@ def _get_storage_nodes(private_key: bytes): #logger.info("Netmap: %s" % output) #for m in re.finditer(r'"address":"/ip4/(\d+\.\d+\.\d+\.\d+)/tcp/(\d+)"', output): # storage_nodes.append(m.group(1)+":"+m.group(2)) - + #if not storage_nodes: # raise Exception("Storage nodes was not found.") @@ -1039,9 +1009,9 @@ def _search_object(node:str, private_key: str, cid:str, oid: str): elif ( re.search(r'timed out after 30 seconds', e.output) or re.search(r'no route to host', e.output) ): logger.warn("Node is unavailable") - + else: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) - \ No newline at end of file + diff --git a/robot/resources/lib/neofs_int_vars.py b/robot/resources/lib/neofs_int_vars.py new file mode 100644 index 00000000..7e2ea29d --- /dev/null +++ b/robot/resources/lib/neofs_int_vars.py @@ -0,0 +1,9 @@ +#!/usr/bin/python3 +import os + +NEOFS_ENDPOINT = "s01.neofs.devenv:8080" +NEOGO_CLI_PREFIX = "docker exec -it main_chain neo-go" +NEO_MAINNET_ENDPOINT = "main_chain.neofs.devenv:30333" + +NEOFS_NEO_API_ENDPOINT = 'http://main_chain.neofs.devenv:30333' +HTTP_GATE = '' diff --git a/robot/resources/lib/payment_neogo.py b/robot/resources/lib/payment_neogo.py index 9c2ae859..8bad043f 100644 --- a/robot/resources/lib/payment_neogo.py +++ b/robot/resources/lib/payment_neogo.py @@ -4,24 +4,27 @@ import subprocess import pexpect import re import uuid +import logging +import requests +import json +import os from robot.api.deco import keyword from robot.api import logger - -import logging import robot.errors -import requests -import json - from robot.libraries.BuiltIn import BuiltIn ROBOT_AUTO_KEYWORDS = False +if os.getenv('ROBOT_PROFILE') == 'selectel_smoke': + from selectelcdn_smoke_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT) +else: + from neofs_int_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT) NEOFS_CONTRACT = "5f490fbd8010fd716754073ee960067d28549b7d" -NEOGO_CLI_PREFIX = "docker exec -it main_chain neo-go" -NEO_MAINNET_ENDPOINT = "main_chain.neofs.devenv:30333" @keyword('Init wallet') def init_wallet(): @@ -30,7 +33,7 @@ def init_wallet(): cmd = ( f"{NEOGO_CLI_PREFIX} wallet init -w {filename}" ) logger.info(f"Executing shell command: {cmd}") - out = _run_sh(cmd) + out = _run_sh(cmd) logger.info(f"Command completed with output: {out}") return filename @@ -55,11 +58,11 @@ def dump_address(wallet: str): cmd = ( f"{NEOGO_CLI_PREFIX} wallet dump -w {wallet}" ) logger.info(f"Executing command: {cmd}") - out = _run_sh(cmd) + out = _run_sh(cmd) logger.info(f"Command completed with output: {out}") m = re.search(r'"address": "(\w+)"', out) - if m.start() != m.end(): + if m.start() != m.end(): address = m.group(1) else: raise Exception("Can not get address.") @@ -76,8 +79,7 @@ def dump_privkey(wallet: str, address: str): return out - -@keyword('Transfer Mainnet Gas') +@keyword('Transfer Mainnet Gas') def transfer_mainnet_gas(wallet: str, address: str, address_to: str, amount: int): cmd = ( f"{NEOGO_CLI_PREFIX} wallet nep5 transfer -w {wallet} -r http://main_chain.neofs.devenv:30333 --from {address} " f"--to {address_to} --token gas --amount {amount}" ) @@ -91,7 +93,7 @@ def transfer_mainnet_gas(wallet: str, address: str, address_to: str, amount: int return out -@keyword('Withdraw Mainnet Gas') +@keyword('Withdraw Mainnet Gas') def withdraw_mainnet_gas(wallet: str, address: str, scripthash: str, amount: int): cmd = ( f"{NEOGO_CLI_PREFIX} contract invokefunction -w {wallet} -a {address} -r http://main_chain.neofs.devenv:30333 " f"{NEOFS_CONTRACT} withdraw {scripthash} int:{amount} -- {scripthash}" ) @@ -100,56 +102,6 @@ def withdraw_mainnet_gas(wallet: str, address: str, scripthash: str, amount: int out = _run_sh_with_passwd('', cmd) logger.info(f"Command completed with output: {out}") - m = re.match(r'^Sent invocation transaction (\w{64})$', out) - if m is None: - raise Exception("Can not get Tx.") - - tx = m.group(1) - - return tx - - -@keyword('Mainnet Balance') -def mainnet_balance(address: str): - request = 'curl -X POST '+NEO_MAINNET_ENDPOINT+' --cacert ca/nspcc-ca.pem -H \'Content-Type: application/json\' -d \'{ "jsonrpc": "2.0", "id": 5, "method": "getnep5balances", "params": [\"'+address+'\"] }\'' - logger.info(f"Executing request: {request}") - - complProc = subprocess.run(request, check=True, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) - - out = complProc.stdout - logger.info(out) - - m = re.search(r'"668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","amount":"([\d\.]+)"', out) - if not m.start() != m.end(): - raise Exception("Can not get mainnet gas balance.") - - amount = m.group(1) - - return amount - - -@keyword('Expexted Mainnet Balance') -def expected_mainnet_balance(address: str, expected: float): - - amount = mainnet_balance(address) - - if float(amount) != float(expected): - raise Exception(f"Expected amount ({expected}) of GAS has not been found. Found {amount}.") - - return True - - -@keyword('NeoFS Deposit') -def neofs_deposit(wallet: str, address: str, scripthash: str, amount: int): - cmd = ( f"{NEOGO_CLI_PREFIX} contract invokefunction -w {wallet} -a {address} " - f"-r http://main_chain.neofs.devenv:30333 {NEOFS_CONTRACT} " - f"deposit {scripthash} int:{amount} bytes: -- {scripthash}") - - logger.info(f"Executing command: {cmd}") - out = _run_sh_with_passwd('', cmd) - logger.info(f"Command completed with output: {out}") - m = re.match(r'^Sent invocation transaction (\w{64})$', out) if m is None: raise Exception("Can not get Tx.") @@ -158,6 +110,52 @@ def neofs_deposit(wallet: str, address: str, scripthash: str, amount: int): return tx +@keyword('Mainnet Balance') +def mainnet_balance(address: str): + request = 'curl -X POST '+NEO_MAINNET_ENDPOINT+' --cacert ca/nspcc-ca.pem -H \'Content-Type: application/json\' -d \'{ "jsonrpc": "2.0", "id": 5, "method": "getnep5balances", "params": [\"'+address+'\"] }\'' + logger.info(f"Executing request: {request}") + + complProc = subprocess.run(request, check=True, universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) + + out = complProc.stdout + logger.info(out) + + m = re.search(r'"668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","amount":"([\d\.]+)"', out) + if not m.start() != m.end(): + raise Exception("Can not get mainnet gas balance.") + + amount = m.group(1) + + return amount + + +@keyword('Expexted Mainnet Balance') +def expected_mainnet_balance(address: str, expected: float): + amount = mainnet_balance(address) + + if float(amount) != float(expected): + raise Exception(f"Expected amount ({expected}) of GAS has not been found. Found {amount}.") + + return True + +@keyword('NeoFS Deposit') +def neofs_deposit(wallet: str, address: str, scripthash: str, amount: int, wallet_pass:str=''): + cmd = ( f"{NEOGO_CLI_PREFIX} contract invokefunction -w {wallet} -a {address} " + f"-r {NEOFS_NEO_API_ENDPOINT} {NEOFS_CONTRACT} " + f"deposit {scripthash} int:{amount} bytes: -- {scripthash}") + + logger.info(f"Executing command: {cmd}") + out = _run_sh_with_passwd(wallet_pass, cmd) + logger.info(f"Command completed with output: {out}") + + m = re.match(r'^Sent invocation transaction (\w{64})$', out) + if m is None: + raise Exception("Can not get Tx.") + + tx = m.group(1) + + return tx @keyword('Transaction accepted in block') def transaction_accepted_in_block(tx_id): @@ -169,11 +167,10 @@ def transaction_accepted_in_block(tx_id): """ logger.info("Transaction id: %s" % tx_id) - + TX_request = 'curl -X POST '+NEO_MAINNET_ENDPOINT+' --cacert ca/nspcc-ca.pem -H \'Content-Type: application/json\' -d \'{ "jsonrpc": "2.0", "id": 5, "method": "gettransactionheight", "params": [\"'+ tx_id +'\"] }\'' - + logger.info(f"Executing command: {TX_request}") - complProc = subprocess.run(TX_request, check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) @@ -185,7 +182,6 @@ def transaction_accepted_in_block(tx_id): logger.info("Transaction has been found in the block %s." % response['result'] ) return response['result'] - @keyword('Get Transaction') def get_transaction(tx_id: str): @@ -199,8 +195,6 @@ def get_transaction(tx_id: str): complProc = subprocess.run(TX_request, check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) logger.info(complProc.stdout) - - @keyword('Get Balance') def get_balance(privkey: str): @@ -232,19 +226,18 @@ def expected_balance(privkey: str, init_amount: float, deposit_size: float): return deposit_change - def _get_balance_request(privkey: str): ''' Internal method. ''' - Cmd = f'neofs-cli --key {privkey} --rpc-endpoint s01.neofs.devenv:8080 accounting balance' + Cmd = f'neofs-cli --key {privkey} --rpc-endpoint {NEOFS_ENDPOINT} accounting balance' logger.info("Cmd: %s" % Cmd) complProc = subprocess.run(Cmd, check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=150, shell=True) output = complProc.stdout logger.info("Output: %s" % output) - - + + m = re.match(r'(-?[\d.\.?\d*]+)', output ) if m is None: BuiltIn().fatal_error('Can not parse balance: "%s"' % output) @@ -254,7 +247,6 @@ def _get_balance_request(privkey: str): return balance - def _run_sh(args): complProc = subprocess.run(args, check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -264,7 +256,6 @@ def _run_sh(args): return errors return output - def _run_sh_with_passwd(passwd, cmd): p = pexpect.spawn(cmd) p.expect(".*") diff --git a/robot/resources/lib/selectelcdn_smoke_vars.py b/robot/resources/lib/selectelcdn_smoke_vars.py new file mode 100644 index 00000000..d9ad407b --- /dev/null +++ b/robot/resources/lib/selectelcdn_smoke_vars.py @@ -0,0 +1,9 @@ +#!/usr/bin/python3 + +NEOFS_ENDPOINT = "92.53.71.51:18080" +NEOGO_CLI_PREFIX = "neo-go" +NEO_MAINNET_ENDPOINT = "http://92.53.71.51:20332" + +# selectel main chain on lobachevsky-1 +NEOFS_NEO_API_ENDPOINT = "http://92.53.71.51:20332" +HTTP_GATE = 'http://92.53.71.51:38080' diff --git a/robot/testsuites/smoke/selectelcdn_smoke.robot b/robot/testsuites/smoke/selectelcdn_smoke.robot new file mode 100644 index 00000000..56424862 --- /dev/null +++ b/robot/testsuites/smoke/selectelcdn_smoke.robot @@ -0,0 +1,33 @@ +# -*- coding: robot -*- + +*** Settings *** +Variables ../../variables/common.py +Variables ../../variables/selectelcdn_smoke.py + + +Library ${RESOURCES}/neofs.py +Library ${RESOURCES}/payment_neogo.py +Library ${RESOURCES}/gates.py + + +*** Test cases *** + +NeoFS Storage Smoke + [Documentation] Creates container and does PUT, GET and LIST on it via CLI and via HTTP Gate + [Timeout] 5 min + + + ${TX_DEPOSIT} = NeoFS Deposit ${WALLET} ${ADDR} ${SCRIPT_HASH} 50 one + Wait Until Keyword Succeeds 1 min 15 sec + ... Transaction accepted in block ${TX_DEPOSIT} + Get Transaction ${TX_DEPOSIT} + + ${CID} = Create container ${PRIV_KEY} public + Wait Until Keyword Succeeds 2 min 30 sec + ... Container Existing ${PRIV_KEY} ${CID} + + ${FILE} = Generate file of bytes 1024 + ${S_OID} = Put object to NeoFS ${PRIV_KEY} ${FILE} ${CID} ${EMPTY} ${EMPTY} + Get object from NeoFS ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} s_file_read + + ${FILEPATH} = Get via HTTP Gate ${CID} ${S_OID} diff --git a/robot/variables/common.py b/robot/variables/common.py index 0baa11f7..ee30c5d1 100644 --- a/robot/variables/common.py +++ b/robot/variables/common.py @@ -9,11 +9,9 @@ CERT="%s/../../ca" % ROOT # in case when test is run from root in docker ABSOLUTE_FILE_PATH="/robot/testsuites/integration" -JF_TOKEN = os.getenv('JF_TOKEN') -REG_USR = os.getenv('REG_USR') -REG_PWD = os.getenv('REG_PWD') -NEOFS_ENDPOINT = "s01.fs.localtest.nspcc.ru:8080" -NEOFS_NEO_API_ENDPOINT = "https://fs.localtest.nspcc.ru/neo_rpc/" +JF_TOKEN = os.getenv('JF_TOKEN') +REG_USR = os.getenv('REG_USR') +REG_PWD = os.getenv('REG_PWD') MORPH_BLOCK_TIMEOUT = "10sec" -NEOFS_EPOCH_TIMEOUT = "30sec" \ No newline at end of file +NEOFS_EPOCH_TIMEOUT = "30sec" diff --git a/robot/variables/selectelcdn_smoke.py b/robot/variables/selectelcdn_smoke.py new file mode 100644 index 00000000..279142c6 --- /dev/null +++ b/robot/variables/selectelcdn_smoke.py @@ -0,0 +1,8 @@ +#!/usr/bin/python3 + +# wallet that has assets in selectel mainnet +WALLET = 'wallets/selectel_mainnet_wallet.json' +# address from this wallet anf its representations +ADDR = 'NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc' +SCRIPT_HASH = 'eb88a496178256213f674eb302e44f9d85cf8aaa' +PRIV_KEY = 'KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY' diff --git a/wallets/selectel_mainnet_wallet.json b/wallets/selectel_mainnet_wallet.json new file mode 100644 index 00000000..1612925d --- /dev/null +++ b/wallets/selectel_mainnet_wallet.json @@ -0,0 +1,72 @@ +{ + "version": "3.0", + "accounts": [ + { + "address": "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", + "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "label": "", + "contract": { + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQZVEDXg=", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isdefault": false + }, + { + "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isdefault": false + }, + { + "address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK", + "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "label": "", + "contract": { + "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isdefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +}