diff --git a/robot/resources/lib/neofs.py b/robot/resources/lib/neofs.py index 28a2454d..be3be6e9 100644 --- a/robot/resources/lib/neofs.py +++ b/robot/resources/lib/neofs.py @@ -12,6 +12,7 @@ import random import base64 import base58 import docker +import json if os.getenv('ROBOT_PROFILE') == 'selectel_smoke': from selectelcdn_smoke_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, @@ -612,151 +613,252 @@ def search_object(private_key: str, cid: str, keys: str, bearer: str, filters: s raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) +@keyword('Verify Split Chain') +def verify_split_chain(private_key: str, cid: str, oid: str): + + header_virtual_parsed = dict() + header_last_parsed = dict() + + marker_last_obj = 0 + marker_link_obj = 0 + + final_verif_data = dict() + + # Get Latest object + logger.info("Collect Split objects information and verify chain of the objects.") + nodes = _get_storage_nodes(private_key) + for node in nodes: + header_virtual = head_object(private_key, cid, oid, '', '', '--raw --ttl 1', node, True) + parsed_header_virtual = parse_object_virtual_raw_header(header_virtual) + + if 'Last object' in parsed_header_virtual.keys(): + header_last = head_object(private_key, cid, parsed_header_virtual['Last object'], '', '', '--raw') + header_last_parsed = parse_object_system_header(header_last) + marker_last_obj = 1 + + # Recursive chain validation up to the first object + final_verif_data = _verify_child_link(private_key, cid, oid, header_last_parsed, final_verif_data) + break + + if marker_last_obj == 0: + raise Exception("Latest object has not been found.") + + # Get Linking object + logger.info("Compare Split objects result information with Linking object.") + for node in nodes: + + header_virtual = head_object(private_key, cid, oid, '', '', '--raw --ttl 1', node, True) + parsed_header_virtual = parse_object_virtual_raw_header(header_virtual) + if 'Linking object' in parsed_header_virtual.keys(): + + header_link = head_object(private_key, cid, parsed_header_virtual['Linking object'], '', '', '--raw') + header_link_parsed = parse_object_system_header(header_link) + marker_link_obj = 1 + + reversed_list = final_verif_data['ID List'][::-1] + + if header_link_parsed['Split ChildID'] == reversed_list: + logger.info("Split objects list from Linked Object is equal to expected %s" % ', '.join(header_link_parsed['Split ChildID'])) + else: + raise Exception("Split objects list from Linking Object (%s) is not equal to expected (%s)" % ', '.join(header_link_parsed['Split ChildID']), ', '.join(reversed_list) ) + + if int(header_link_parsed['PayloadLength']) == 0: + logger.info("Linking object Payload is equal to expected - zero size.") + else: + raise Exception("Linking object Payload is not equal to expected. Should be zero.") + + if header_link_parsed['Type'] == 'regular': + logger.info("Linking Object Type is 'regular' as expected.") + else: + raise Exception("Object Type is not 'regular'.") + + if header_link_parsed['Split ID'] == final_verif_data['Split ID']: + logger.info("Linking Object Split ID is equal to expected %s." % final_verif_data['Split ID'] ) + else: + raise Exception("Split ID from Linking Object (%s) is not equal to expected (%s)" % header_link_parsed['Split ID'], ffinal_verif_data['Split ID'] ) + + break + + if marker_link_obj == 0: + raise Exception("Linked object has not been found.") + + + logger.info("Compare Split objects result information with Virtual object.") + + header_virtual = head_object(private_key, cid, oid, '', '', '') + header_virtual_parsed = parse_object_system_header(header_virtual) + + if int(header_virtual_parsed['PayloadLength']) == int(final_verif_data['PayloadLength']): + logger.info("Split objects PayloadLength are equal to Virtual Object Payload %s" % header_virtual_parsed['PayloadLength']) + else: + raise Exception("Split objects PayloadLength from Virtual Object (%s) is not equal to expected (%s)" % header_virtual_parsed['PayloadLength'], final_verif_data['PayloadLength'] ) + + if header_link_parsed['Type'] == 'regular': + logger.info("Virtual Object Type is 'regular' as expected.") + else: + raise Exception("Object Type is not 'regular'.") + + return 1 + + +def _verify_child_link(private_key: str, cid: str, oid: str, header_last_parsed: dict, final_verif_data: dict): + + if 'PayloadLength' in final_verif_data.keys(): + final_verif_data['PayloadLength'] = int(final_verif_data['PayloadLength']) + int(header_last_parsed['PayloadLength']) + else: + final_verif_data['PayloadLength'] = int(header_last_parsed['PayloadLength']) + + if header_last_parsed['Type'] != 'regular': + raise Exception("Object Type is not 'regular'.") + + if 'Split ID' in final_verif_data.keys(): + if final_verif_data['Split ID'] != header_last_parsed['Split ID']: + raise Exception("Object Split ID (%s) is not expected (%s)." % header_last_parsed['Split ID'], final_verif_data['Split ID']) + else: + final_verif_data['Split ID'] = header_last_parsed['Split ID'] + + if 'ID List' in final_verif_data.keys(): + final_verif_data['ID List'].append(header_last_parsed['ID']) + else: + final_verif_data['ID List'] = [] + final_verif_data['ID List'].append(header_last_parsed['ID']) + + if 'Split PreviousID' in header_last_parsed.keys(): + header_virtual = head_object(private_key, cid, header_last_parsed['Split PreviousID'], '', '', '--raw') + parsed_header_virtual = parse_object_system_header(header_virtual) + + final_verif_data = _verify_child_link(private_key, cid, oid, parsed_header_virtual, final_verif_data) + else: + logger.info("Chain of the objects has been parsed from the last object ot the first.") + + return final_verif_data + -''' @keyword('Verify Head Tombstone') -def verify_head_tombstone(private_key: str, cid: str, oid: str): +def verify_head_tombstone(private_key: str, cid: str, oid_ts: str, oid: str, addr: str): - ObjectCmd = f'neofs-cli --rpc-endpoint {NEOFS_ENDPOINT} --key {private_key} object head --cid {cid} --oid {oid} --full-headers' + ObjectCmd = f'neofs-cli --rpc-endpoint {NEOFS_ENDPOINT} --key {private_key} object head --cid {cid} --oid {oid_ts} --json' 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) + - if re.search(r'Type=Tombstone\s+Value=MARKED', complProc.stdout): - logger.info("Tombstone header 'Type=Tombstone Value=MARKED' was parsed from command output") + full_headers = json.loads(complProc.stdout) + logger.info("Output: %s" % full_headers) + + # Header verification + header_cid = full_headers["header"]["containerID"]["value"] + if (_json_cli_decode(header_cid) == cid): + logger.info("Header CID is expected: %s (%s in the output)" % (cid, header_cid)) else: - raise Exception("Tombstone header 'Type=Tombstone Value=MARKED' was not found in the command output: \t%s" % (complProc.stdout)) + raise Exception("Header CID is not expected.") + + header_owner = full_headers["header"]["ownerID"]["value"] + if (_json_cli_decode(header_owner) == addr): + logger.info("Header ownerID is expected: %s (%s in the output)" % (addr, header_owner)) + else: + raise Exception("Header ownerID is not expected.") + + header_type = full_headers["header"]["objectType"] + if (header_type == "TOMBSTONE"): + logger.info("Header Type is expected: %s" % header_type) + else: + raise Exception("Header Type is not expected.") + + header_session_type = full_headers["header"]["sessionToken"]["body"]["object"]["verb"] + if (header_session_type == "DELETE"): + logger.info("Header Session Type is expected: %s" % header_session_type) + else: + raise Exception("Header Session Type is not expected.") + + header_session_cid = full_headers["header"]["sessionToken"]["body"]["object"]["address"]["containerID"]["value"] + if (_json_cli_decode(header_session_cid) == cid): + logger.info("Header ownerID is expected: %s (%s in the output)" % (addr, header_session_cid)) + else: + raise Exception("Header Session CID is not expected.") + + header_session_oid = full_headers["header"]["sessionToken"]["body"]["object"]["address"]["objectID"]["value"] + if (_json_cli_decode(header_session_oid) == oid): + logger.info("Header Session OID (deleted object) is expected: %s (%s in the output)" % (oid, header_session_oid)) + else: + raise Exception("Header Session OID (deleted object) is not expected.") 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)) - - if not re.search(r'PayloadLength=0', output): - raise Exception("Payload is not equal to zero in the parent object %s." % obj) - - if not child_obj_list: - 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" - first_obj = None - child_obj_list_headers = {} - - for obj in child_obj_list: - postfix = f'object head --cid {cid} --oid {obj} --full-headers' - output = _exec_cli_cmd(private_key, postfix) - child_obj_list_headers[obj] = output - if re.search(r'Type=Previous ID=00000000-0000-0000-0000-000000000000', output): - first_obj = obj - logger.info("First child object %s has been found" % first_obj) - - 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() - - -def _check_linked_object(obj:str, child_obj_list_headers:dict, payload_size:int, payload:int, parent_id:str): - - output = child_obj_list_headers[obj] - logger.info("Verify headers of the child object %s" % obj) - - if not re.search(r'Type=Previous ID=%s' % parent_id, output): - raise Exception("Incorrect previos ID %s in the child object %s." % parent_id, obj) - else: - logger.info("Previous ID is equal for expected: %s" % parent_id) - - m = re.search(r'PayloadLength=(\d+)', output) - 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) - - elif payload == payload_size: - if not re.search(r'Type=Next ID=00000000-0000-0000-0000-000000000000', output): - 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: - # next object should be in the expected list - logger.info(m.group(1)) - if m.group(1) not in child_obj_list_headers.keys(): - raise Exception(f'Next object {m.group(1)} is not in the expected list: {child_obj_list_headers.keys()}.') - else: - logger.info(f'Next object {m.group(1)} is in the expected list: {child_obj_list_headers.keys()}.') - - _check_linked_object(m.group(1), child_obj_list_headers, payload_size, payload, obj) - - else: - raise Exception("Can not get Next object ID for the object %s." % obj) - -''' +def _json_cli_decode(data: str): + return base58.b58encode(base64.b64decode(data)).decode("utf-8") @keyword('Head object') -def head_object(private_key: str, cid: str, oid: str, bearer: str, user_headers:str=""): +def head_object(private_key: str, cid: str, oid: str, bearer_token: str="", user_headers:str="", keys:str="", endpoint: str="", ignore_failure: bool = False): options = "" - bearer_token = "" - if bearer: - bearer_token = f"--bearer {bearer}" + if bearer_token: + bearer_token = f"--bearer {bearer_token}" - ObjectCmd = f'neofs-cli --rpc-endpoint {NEOFS_ENDPOINT} --key {private_key} object head --cid {cid} --oid {oid} {bearer_token} {options}' + if endpoint == "": + endpoint = NEOFS_ENDPOINT + + ObjectCmd = f'neofs-cli --rpc-endpoint {endpoint} --key {private_key} object head --cid {cid} --oid {oid} {bearer_token} {keys}' 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) - for key in user_headers.split(","): - # user_header = f'Key={key} Val={user_headers_dict[key]}' - if re.search(r'(%s)' % key, complProc.stdout): - 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)) + if user_headers: + for key in user_headers.split(","): + if re.search(r'(%s)' % key, complProc.stdout): + 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)) + if ignore_failure: + logger.info("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) + return e.output + else: + raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) +@keyword('Parse Object Virtual Raw Header') +def parse_object_virtual_raw_header(header: str): + # Header - Optional attributes + + result_header = dict() + + m = re.search(r'Split ID:\s+([\w-]+)', header) + if m != None: + if m.start() != m.end(): # e.g., if match found something + result_header['Split ID'] = m.group(1) + + m = re.search(r'Linking object:\s+(\w+)', header) + if m != None: + if m.start() != m.end(): # e.g., if match found something + result_header['Linking object'] = m.group(1) + + m = re.search(r'Last object:\s+(\w+)', header) + if m != None: + if m.start() != m.end(): # e.g., if match found something + result_header['Last object'] = m.group(1) + + logger.info("Result: %s" % result_header) + return result_header @keyword('Parse Object System Header') def parse_object_system_header(header: str): result_header = dict() + + # Header - Constant attributes - #SystemHeader - logger.info("Input: %s" % header) # ID - m = re.search(r'ID: (\w+)', header) + m = re.search(r'^ID: (\w+)', header) if m.start() != m.end(): # e.g., if match found something result_header['ID'] = m.group(1) else: @@ -775,20 +877,7 @@ 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 - result_header['PayloadLength'] = m.group(1) - else: - raise Exception("no PayloadLength was parsed from object header: \t%s" % output) - - # 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) - else: - raise Exception("no CreatedAtUnixTime was parsed from object header: \t%s" % output) - + # CreatedAtEpoch m = re.search(r'CreatedAt: (\d+)', header) if m.start() != m.end(): # e.g., if match found something @@ -796,9 +885,61 @@ def parse_object_system_header(header: str): else: raise Exception("no CreatedAtEpoch 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 + result_header['PayloadLength'] = m.group(1) + else: + raise Exception("no PayloadLength was parsed from object header: \t%s" % output) + + # HomoHash + m = re.search(r'HomoHash:\s+(\w+)', header) + if m.start() != m.end(): # e.g., if match found something + result_header['HomoHash'] = m.group(1) + else: + raise Exception("no HomoHash was parsed from object header: \t%s" % output) + + # Checksum + m = re.search(r'Checksum:\s+(\w+)', header) + if m.start() != m.end(): # e.g., if match found something + result_header['Checksum'] = m.group(1) + else: + raise Exception("no Checksum was parsed from object header: \t%s" % output) + + # Type + m = re.search(r'Type:\s+(\w+)', header) + if m.start() != m.end(): # e.g., if match found something + result_header['Type'] = m.group(1) + else: + raise Exception("no Type was parsed from object header: \t%s" % output) + + + # Header - Optional attributes + m = re.search(r'Split ID:\s+([\w-]+)', header) + if m != None: + if m.start() != m.end(): # e.g., if match found something + result_header['Split ID'] = m.group(1) + + m = re.search(r'Split PreviousID:\s+(\w+)', header) + if m != None: + if m.start() != m.end(): # e.g., if match found something + result_header['Split PreviousID'] = m.group(1) + + m = re.search(r'Split ParentID:\s+(\w+)', header) + if m != None: + if m.start() != m.end(): # e.g., if match found something + result_header['Split ParentID'] = m.group(1) + + # Split ChildID list + found_objects = re.findall(r'Split ChildID:\s+(\w+)', header) + if found_objects: + result_header['Split ChildID'] = found_objects + + logger.info("Result: %s" % result_header) return result_header + @keyword('Delete object') def delete_object(private_key: str, cid: str, oid: str, bearer: str): @@ -812,6 +953,10 @@ def delete_object(private_key: str, cid: str, oid: str, bearer: str): complProc = subprocess.run(ObjectCmd, check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, shell=True) logger.info("Output: %s" % complProc.stdout) + + tombstone = _parse_oid(complProc.stdout) + return tombstone + except subprocess.CalledProcessError as e: raise Exception("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output)) @@ -1029,7 +1174,7 @@ def _search_object(node:str, private_key: str, cid:str, oid: str): if re.search(r'local node is outside of object placement', e.output): logger.info("Server is not presented in container.") - elif ( re.search(r'timed out after 30 seconds', e.output) or re.search(r'no route to host', e.output) ): + elif ( re.search(r'timed out after 30 seconds', e.output) or re.search(r'no route to host', e.output) or re.search(r'i/o timeout', e.output)): logger.warn("Node is unavailable") else: diff --git a/robot/resources/lib/neofs_int_vars.py b/robot/resources/lib/neofs_int_vars.py index a97619d9..f003a8d8 100644 --- a/robot/resources/lib/neofs_int_vars.py +++ b/robot/resources/lib/neofs_int_vars.py @@ -3,10 +3,11 @@ 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" +NEO_MAINNET_ENDPOINT = "http://main_chain.neofs.devenv:30333" -NEOFS_NEO_API_ENDPOINT = 'http://main_chain.neofs.devenv:30333' +NEOFS_NEO_API_ENDPOINT = 'http://morph_chain.neofs.devenv:30333' HTTP_GATE = 'http://http.neofs.devenv' S3_GATE = 'https://s3.neofs.devenv:8080' -NEOFS_CONTRACT = "5f490fbd8010fd716754073ee960067d28549b7d" -NEOFS_NETMAP = ['s01.neofs.devenv:8080', 's02.neofs.devenv:8080','s03.neofs.devenv:8080','s04.neofs.devenv:8080'] \ No newline at end of file +NEOFS_NETMAP = ['s01.neofs.devenv:8080', 's02.neofs.devenv:8080','s03.neofs.devenv:8080','s04.neofs.devenv:8080'] + +GAS_HASH = '0xb5df804bbadefea726afb5d3f4e8a6f6d32d2a20' \ No newline at end of file diff --git a/robot/resources/lib/payment_neogo.py b/robot/resources/lib/payment_neogo.py index 2de451ea..5be6d42a 100644 --- a/robot/resources/lib/payment_neogo.py +++ b/robot/resources/lib/payment_neogo.py @@ -15,14 +15,14 @@ import robot.errors from robot.libraries.BuiltIn import BuiltIn ROBOT_AUTO_KEYWORDS = False -NEOFS_CONTRACT = "5f490fbd8010fd716754073ee960067d28549b7d" +NEOFS_CONTRACT = "ce96811ca25577c058484dab10dd8db2defc5eed" if os.getenv('ROBOT_PROFILE') == 'selectel_smoke': from selectelcdn_smoke_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, - NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT) + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT, GAS_HASH) else: from neofs_int_vars import (NEOGO_CLI_PREFIX, NEO_MAINNET_ENDPOINT, - NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT) + NEOFS_NEO_API_ENDPOINT, NEOFS_ENDPOINT, GAS_HASH) @keyword('Init wallet') @@ -98,8 +98,8 @@ def dump_privkey(wallet: str, address: str): @keyword('Transfer Mainnet Gas') def transfer_mainnet_gas(wallet: str, address: str, address_to: str, amount: int, wallet_pass:str=''): - cmd = ( f"{NEOGO_CLI_PREFIX} wallet nep5 transfer -w {wallet} -r {NEOFS_NEO_API_ENDPOINT} --from {address} " - f"--to {address_to} --token gas --amount {amount}" ) + cmd = ( f"{NEOGO_CLI_PREFIX} wallet nep17 transfer -w {wallet} -r {NEO_MAINNET_ENDPOINT} --from {address} " + f"--to {address_to} --token GAS --amount {amount}" ) logger.info(f"Executing command: {cmd}") out = _run_sh_with_passwd(wallet_pass, cmd) @@ -112,7 +112,7 @@ def transfer_mainnet_gas(wallet: str, address: str, address_to: str, amount: int @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 {NEOFS_NEO_API_ENDPOINT} " + cmd = ( f"{NEOGO_CLI_PREFIX} contract invokefunction -w {wallet} -a {address} -r {NEO_MAINNET_ENDPOINT} " f"{NEOFS_CONTRACT} withdraw {scripthash} int:{amount} -- {scripthash}" ) logger.info(f"Executing command: {cmd}") @@ -129,16 +129,18 @@ def withdraw_mainnet_gas(wallet: str, address: str, scripthash: str, amount: int @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) + headers = {'Content-type': 'application/json'} + data = { "jsonrpc": "2.0", "id": 5, "method": "getnep17balances", "params": [ address ] } + response = requests.post(NEO_MAINNET_ENDPOINT, json=data, headers=headers, verify=False) - out = complProc.stdout - logger.info(out) + if not response.ok: + raise Exception(f"""Failed: + request: {data}, + response: {response.text}, + status code: {response.status_code} {response.reason}""") - m = re.search(r'"668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","amount":"([\d\.]+)"', out) + m = re.search(rf'"{GAS_HASH}","amount":"([\d\.]+)"', response.text) if not m.start() != m.end(): raise Exception("Can not get mainnet gas balance.") @@ -150,16 +152,16 @@ def mainnet_balance(address: str): @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}.") + gas_expected = int(expected * 10**8) + if int(amount) != int(gas_expected): + raise Exception(f"Expected amount ({gas_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"-r {NEO_MAINNET_ENDPOINT} {NEOFS_CONTRACT} " f"deposit {scripthash} int:{amount} bytes: -- {scripthash}") logger.info(f"Executing command: {cmd}") @@ -185,20 +187,21 @@ 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 +'\"] }\'' + headers = {'Content-type': 'application/json'} + data = { "jsonrpc": "2.0", "id": 5, "method": "gettransactionheight", "params": [ tx_id ] } + response = requests.post(NEO_MAINNET_ENDPOINT, json=data, headers=headers, verify=False) - logger.info(f"Executing command: {TX_request}") + if not response.ok: + raise Exception(f"""Failed: + request: {data}, + response: {response.text}, + status code: {response.status_code} {response.reason}""") - complProc = subprocess.run(TX_request, check=True, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) - logger.info(complProc.stdout) - response = json.loads(complProc.stdout) - - if (response['result'] == 0): + if (response.text == 0): raise Exception( "Transaction is not found in the blocks." ) - logger.info("Transaction has been found in the block %s." % response['result'] ) - return response['result'] + logger.info("Transaction has been found in the block %s." % response.text ) + return response.text @keyword('Get Transaction') def get_transaction(tx_id: str): @@ -208,10 +211,18 @@ def get_transaction(tx_id: str): :param tx_id: transaction 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": "getapplicationlog", "params": [\"'+tx_id+'\"] }\'' - complProc = subprocess.run(TX_request, check=True, universal_newlines=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15, shell=True) - logger.info(complProc.stdout) + headers = {'Content-type': 'application/json'} + data = { "jsonrpc": "2.0", "id": 5, "method": "getapplicationlog", "params": [ tx_id ] } + response = requests.post(NEO_MAINNET_ENDPOINT, json=data, headers=headers, verify=False) + + if not response.ok: + raise Exception(f"""Failed: + request: {data}, + response: {response.text}, + status code: {response.status_code} {response.reason}""") + else: + logger.info(response.text) + @keyword('Get Balance') def get_balance(privkey: str): diff --git a/robot/resources/lib/selectelcdn_smoke_vars.py b/robot/resources/lib/selectelcdn_smoke_vars.py index eb2e6998..64aa7387 100644 --- a/robot/resources/lib/selectelcdn_smoke_vars.py +++ b/robot/resources/lib/selectelcdn_smoke_vars.py @@ -8,5 +8,6 @@ NEO_MAINNET_ENDPOINT = "http://92.53.71.51:20332" NEOFS_NEO_API_ENDPOINT = "http://92.53.71.51:20332" HTTP_GATE = 'http://92.53.71.51:38080' S3_GATE = 'https://92.53.71.51:28080' -NEOFS_CONTRACT = "5f490fbd8010fd716754073ee960067d28549b7d" -NEOFS_NETMAP = ['92.53.71.51:18080', '92.53.71.52:18080','92.53.71.53:18080','92.53.71.54:18080', '92.53.71.55:18080'] \ No newline at end of file +NEOFS_NETMAP = ['92.53.71.51:18080', '92.53.71.52:18080','92.53.71.53:18080','92.53.71.54:18080', '92.53.71.55:18080'] + +GAS_HASH = '668e0c1f9d7b70a99dd9e06eadd4c784d641afbc' \ No newline at end of file diff --git a/robot/testsuites/integration/acl_bearer.robot b/robot/testsuites/integration/acl_bearer.robot index 5cdf34b6..1a23e702 100644 --- a/robot/testsuites/integration/acl_bearer.robot +++ b/robot/testsuites/integration/acl_bearer.robot @@ -344,7 +344,9 @@ Check eACL Deny and Allow All Bearer Filter UserHeader Equal Run Keyword And Expect Error * ... Head object ${USER_KEY} ${CID} ${S_OID_USER_2} bearer_allow_all_user - Delete object ${USER_KEY} ${CID} ${S_OID_USER} bearer_allow_all_user + # Delete can not be filtered by UserHeader. + Run Keyword And Expect Error * + ... Delete object ${USER_KEY} ${CID} ${S_OID_USER} bearer_allow_all_user Run Keyword And Expect Error * ... Delete object ${USER_KEY} ${CID} ${S_OID_USER_2} bearer_allow_all_user diff --git a/robot/testsuites/integration/object_complex.robot b/robot/testsuites/integration/object_complex.robot index e290b64e..4a9739be 100644 --- a/robot/testsuites/integration/object_complex.robot +++ b/robot/testsuites/integration/object_complex.robot @@ -39,28 +39,22 @@ NeoFS Complex Object Operations Container Existing ${PRIV_KEY} ${CID} Wait Until Keyword Succeeds 2 min 30 sec - ... Expected Balance ${PRIV_KEY} 50 -0.0007 + ... Expected Balance ${PRIV_KEY} 50 -7e-08 ${SIZE} = Set Variable 20e+6 ${FILE} = Generate file of bytes ${SIZE} ${FILE_HASH} = Get file hash ${FILE} - ${S_OID} = Put object to NeoFS ${PRIV_KEY} ${FILE} ${CID} ${EMPTY} ${EMPTY} ${H_OID} = Put object to NeoFS ${PRIV_KEY} ${FILE} ${CID} ${EMPTY} ${FILE_USR_HEADER} - ${H_OID_OTH} = Put object to NeoFS ${PRIV_KEY} ${FILE} ${CID} ${EMPTY} ${FILE_USR_HEADER_OTH} + ${H_OID_OTH} = Put object to NeoFS ${PRIV_KEY} ${FILE} ${CID} ${EMPTY} ${FILE_USR_HEADER_OTH} + + Should Be True '${S_OID}'!='${H_OID}' and '${H_OID}'!='${H_OID_OTH}' Validate storage policy for object ${PRIV_KEY} 2 ${CID} ${S_OID} Validate storage policy for object ${PRIV_KEY} 2 ${CID} ${H_OID} Validate storage policy for object ${PRIV_KEY} 2 ${CID} ${H_OID_OTH} - -# @{Link_obj_S} = Verify linked objects ${PRIV_KEY} ${CID} ${S_OID} ${SIZE} -# @{Link_obj_H} = Verify linked objects ${PRIV_KEY} ${CID} ${H_OID} ${SIZE} -# @{Full_obj_list} = Create List @{Link_obj_S} @{Link_obj_H} ${S_OID} ${H_OID} -# Search object ${PRIV_KEY} ${CID} ${EMPTY} ${EMPTY} @{Full_obj_list} - - @{S_OBJ_ALL} = Create List ${S_OID} ${H_OID} ${H_OID_OTH} @{S_OBJ_H} = Create List ${H_OID} @{S_OBJ_H_OTH} = Create List ${H_OID_OTH} @@ -86,12 +80,16 @@ NeoFS Complex Object Operations Head object ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} Head object ${PRIV_KEY} ${CID} ${H_OID} ${EMPTY} ${FILE_USR_HEADER} - - Delete object ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} - Delete object ${PRIV_KEY} ${CID} ${H_OID} ${EMPTY} + + Verify Split Chain ${PRIV_KEY} ${CID} ${S_OID} + Verify Split Chain ${PRIV_KEY} ${CID} ${H_OID} + + ${TOMBSTONE_S} = Delete object ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} + ${TOMBSTONE_H} = Delete object ${PRIV_KEY} ${CID} ${H_OID} ${EMPTY} + + Verify Head tombstone ${PRIV_KEY} ${CID} ${TOMBSTONE_S} ${S_OID} ${ADDR} + Verify Head tombstone ${PRIV_KEY} ${CID} ${TOMBSTONE_H} ${H_OID} ${ADDR} - #Verify Head tombstone ${PRIV_KEY} ${CID} ${S_OID} - Sleep 2min Run Keyword And Expect Error * diff --git a/robot/testsuites/integration/object_simple.robot b/robot/testsuites/integration/object_simple.robot index fba23fcb..6dd19700 100644 --- a/robot/testsuites/integration/object_simple.robot +++ b/robot/testsuites/integration/object_simple.robot @@ -38,7 +38,7 @@ NeoFS Simple Object Operations Container Existing ${PRIV_KEY} ${CID} Wait Until Keyword Succeeds 2 min 30 sec - ... Expected Balance ${PRIV_KEY} 50 -0.0007 + ... Expected Balance ${PRIV_KEY} 50 -7e-08 ${FILE} = Generate file of bytes 1024 ${FILE_HASH} = Get file hash ${FILE} @@ -75,9 +75,11 @@ NeoFS Simple Object Operations Head object ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} Head object ${PRIV_KEY} ${CID} ${H_OID} ${EMPTY} ${FILE_USR_HEADER} - Delete object ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} - Delete object ${PRIV_KEY} ${CID} ${H_OID} ${EMPTY} - #Verify Head tombstone ${PRIV_KEY} ${CID} ${S_OID} + ${TOMBSTONE_S} = Delete object ${PRIV_KEY} ${CID} ${S_OID} ${EMPTY} + ${TOMBSTONE_H} = Delete object ${PRIV_KEY} ${CID} ${H_OID} ${EMPTY} + + Verify Head tombstone ${PRIV_KEY} ${CID} ${TOMBSTONE_S} ${S_OID} ${ADDR} + Verify Head tombstone ${PRIV_KEY} ${CID} ${TOMBSTONE_H} ${H_OID} ${ADDR} Sleep 2min diff --git a/robot/testsuites/integration/withdraw.robot b/robot/testsuites/integration/withdraw.robot index ce610ae8..a96062b5 100644 --- a/robot/testsuites/integration/withdraw.robot +++ b/robot/testsuites/integration/withdraw.robot @@ -30,7 +30,7 @@ NeoFS Deposit and Withdraw Sleep 1 min - Expexted Mainnet Balance ${ADDR} 4.86192020 + Expexted Mainnet Balance ${ADDR} 4.84454920 ${NEOFS_BALANCE} = Get Balance ${PRIV_KEY} ${TX} = Withdraw Mainnet Gas ${WALLET} ${ADDR} ${SCRIPT_HASH} 50 @@ -40,6 +40,5 @@ NeoFS Deposit and Withdraw Sleep 1 min Get Balance ${PRIV_KEY} Expected Balance ${PRIV_KEY} ${NEOFS_BALANCE} -50 - Expexted Mainnet Balance ${ADDR} 54.82554860 - - + Expexted Mainnet Balance ${ADDR} 54.80800160 + \ No newline at end of file