Merged in feature/INFRA-236 (pull request #8)
selectel cdn smoke tests Approved-by: Anatoly Bogatyrev <anatoly@nspcc.ru>
This commit is contained in:
commit
84310cbd8e
10 changed files with 309 additions and 163 deletions
19
README.md
19
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
|
||||
http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html
|
||||
|
|
41
robot/resources/lib/gates.py
Normal file
41
robot/resources/lib/gates.py
Normal file
|
@ -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
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
9
robot/resources/lib/neofs_int_vars.py
Normal file
9
robot/resources/lib/neofs_int_vars.py
Normal file
|
@ -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 = ''
|
|
@ -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(".*")
|
||||
|
|
9
robot/resources/lib/selectelcdn_smoke_vars.py
Normal file
9
robot/resources/lib/selectelcdn_smoke_vars.py
Normal file
|
@ -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'
|
33
robot/testsuites/smoke/selectelcdn_smoke.robot
Normal file
33
robot/testsuites/smoke/selectelcdn_smoke.robot
Normal file
|
@ -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}
|
|
@ -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"
|
||||
NEOFS_EPOCH_TIMEOUT = "30sec"
|
||||
|
|
8
robot/variables/selectelcdn_smoke.py
Normal file
8
robot/variables/selectelcdn_smoke.py
Normal file
|
@ -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'
|
72
wallets/selectel_mainnet_wallet.json
Normal file
72
wallets/selectel_mainnet_wallet.json
Normal file
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue