NeoFS Yeouido testcases (#16)
* Update Header Verification with new CLI full headers output #14 * Add verification for the Link headers for complex objects #13 * Add Tombstone verification #6 * Support NeoFS Yeouido #7
This commit is contained in:
parent
bb3c2bd208
commit
708bf2a012
8 changed files with 342 additions and 183 deletions
|
@ -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,130 +613,204 @@ 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)
|
||||
|
||||
if user_headers:
|
||||
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:
|
||||
|
@ -744,19 +819,46 @@ def head_object(private_key: str, cid: str, oid: str, bearer: str, user_headers:
|
|||
return complProc.stdout
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
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()
|
||||
|
||||
#SystemHeader
|
||||
logger.info("Input: %s" % header)
|
||||
# Header - Constant attributes
|
||||
|
||||
# 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,19 +877,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
|
||||
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)
|
||||
|
@ -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:
|
||||
|
|
|
@ -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']
|
||||
|
||||
GAS_HASH = '0xb5df804bbadefea726afb5d3f4e8a6f6d32d2a20'
|
|
@ -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):
|
||||
|
|
|
@ -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']
|
||||
|
||||
GAS_HASH = '668e0c1f9d7b70a99dd9e06eadd4c784d641afbc'
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
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}
|
||||
|
@ -87,10 +81,14 @@ 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}
|
||||
|
||||
#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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in a new issue