Compare commits
15 commits
master
...
support/v0
Author | SHA1 | Date | |
---|---|---|---|
56fcbe8b77 | |||
bef01eec48 | |||
14316c1fd7 | |||
a1ab09a074 | |||
4a9e3facff | |||
2140ce7f89 | |||
1ec6540063 | |||
72d30c7e06 | |||
431902c118 | |||
b66d04faf8 | |||
e3b27a7643 | |||
26962617c9 | |||
24fdcd88df | |||
4eb4963f17 | |||
af9c726624 |
28 changed files with 403 additions and 255 deletions
|
@ -164,3 +164,15 @@ def get_file_content(
|
|||
content = file.read()
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def sanitize_for_file_name(string: str) -> str:
|
||||
"""
|
||||
Returns string with only alpha num string with all other characters replaced by '_'
|
||||
which is valid string to use as file name
|
||||
|
||||
Args:
|
||||
string: string to sanitize
|
||||
"""
|
||||
|
||||
return "".join(character if character.isalnum() else "_" for character in string).strip("_")
|
||||
|
|
|
@ -5,7 +5,6 @@ from time import sleep
|
|||
|
||||
import allure
|
||||
from neofs_testlib.shell import Shell
|
||||
|
||||
from remote_process import RemoteProcess
|
||||
|
||||
EXIT_RESULT_CODE = 0
|
||||
|
|
|
@ -41,9 +41,7 @@ class RemoteProcess:
|
|||
Returns:
|
||||
RemoteProcess instance for further examination
|
||||
"""
|
||||
remote_process = cls(
|
||||
cmd=command, process_dir=f"/tmp/proc_{uuid.uuid4()}", shell=shell
|
||||
)
|
||||
remote_process = cls(cmd=command, process_dir=f"/tmp/proc_{uuid.uuid4()}", shell=shell)
|
||||
remote_process._create_process_dir()
|
||||
remote_process._generate_command_script(command)
|
||||
remote_process._start_process()
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import os
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
import allure
|
||||
import yaml
|
||||
from common import STORAGE_GC_TIME
|
||||
from neofs_testlib.hosting import Hosting
|
||||
|
||||
|
||||
def parse_time(value: str) -> int:
|
||||
|
@ -60,3 +64,16 @@ def wait_for_gc_pass_on_storage_nodes() -> None:
|
|||
wait_time = parse_time(STORAGE_GC_TIME)
|
||||
with allure.step(f"Wait {wait_time}s until GC completes on storage nodes"):
|
||||
time.sleep(wait_time)
|
||||
|
||||
|
||||
def get_wallet_password(hosting: Hosting, service_name: str) -> Optional[str]:
|
||||
service_config = hosting.get_service_config(service_name)
|
||||
return service_config.attributes.get("wallet_password")
|
||||
|
||||
|
||||
def create_wallet_config(hosting: Hosting, service_name: str) -> Optional[str]:
|
||||
password = get_wallet_password(hosting=hosting, service_name=service_name)
|
||||
wallet_config_path = os.path.join(os.getcwd(), f"{service_name}_wallet_config.yml")
|
||||
with open(wallet_config_path, "w") as file:
|
||||
yaml.dump({"password": password}, file)
|
||||
return wallet_config_path
|
||||
|
|
|
@ -6,6 +6,8 @@ log_format = %(asctime)s [%(levelname)4s] %(message)s
|
|||
log_cli_date_format = %Y-%m-%d %H:%M:%S
|
||||
log_date_format = %H:%M:%S
|
||||
markers =
|
||||
# controller markers
|
||||
no_log_analyze: skip critical errors analyzer at the end of test
|
||||
# special markers
|
||||
sanity: small tests subset
|
||||
staging: test to be excluded from run in verifier/pr-validation/sanity jobs and run test in staging job
|
||||
|
|
|
@ -1,68 +1 @@
|
|||
aiodns==3.0.0
|
||||
aiohttp==3.7.4.post0
|
||||
aioresponses==0.7.2
|
||||
allure-pytest==2.9.45
|
||||
allure-python-commons==2.9.45
|
||||
async-timeout==3.0.1
|
||||
asynctest==0.13.0
|
||||
attrs==21.4.0
|
||||
base58==2.1.0
|
||||
bitarray==2.3.4
|
||||
black==22.8.0
|
||||
boto3==1.16.33
|
||||
botocore==1.19.33
|
||||
certifi==2022.5.18
|
||||
cffi==1.15.0
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.0.12
|
||||
coverage==6.3.3
|
||||
docker==4.4.0
|
||||
docutils==0.17.1
|
||||
Events==0.4
|
||||
flake8==4.0.1
|
||||
idna==3.3
|
||||
iniconfig==1.1.1
|
||||
isort==5.10.1
|
||||
jmespath==0.10.0
|
||||
jsonschema==4.5.1
|
||||
lz4==3.1.3
|
||||
mccabe==0.6.1
|
||||
mmh3==3.0.0
|
||||
multidict==6.0.2
|
||||
mypy==0.950
|
||||
mypy-extensions==0.4.3
|
||||
neo-mamba==0.10.0
|
||||
neo3crypto==0.2.1
|
||||
neo3vm==0.9.0
|
||||
neo3vm-stubs==0.9.0
|
||||
neofs-testlib==0.3.0
|
||||
netaddr==0.8.0
|
||||
orjson==3.6.8
|
||||
packaging==21.3
|
||||
paramiko==2.10.3
|
||||
pexpect==4.8.0
|
||||
pluggy==1.0.0
|
||||
pre-commit==2.20.0
|
||||
ptyprocess==0.7.0
|
||||
py==1.11.0
|
||||
pybiginteger==1.2.6
|
||||
pybiginteger-stubs==1.2.6
|
||||
pycares==4.1.2
|
||||
pycodestyle==2.8.0
|
||||
pycparser==2.21
|
||||
pycryptodome==3.11.0
|
||||
pyflakes==2.4.0
|
||||
pyparsing==3.0.9
|
||||
pyrsistent==0.18.1
|
||||
pytest==7.1.2
|
||||
python-dateutil==2.8.2
|
||||
requests==2.28.0
|
||||
robotframework==4.1.2
|
||||
s3transfer==0.3.7
|
||||
six==1.16.0
|
||||
tenacity==8.0.1
|
||||
tomli==2.0.1
|
||||
typing-extensions==4.2.0
|
||||
urllib3==1.26.9
|
||||
websocket-client==1.3.2
|
||||
yarl==1.7.2
|
||||
-r ../requirements.txt
|
||||
|
|
|
@ -3,11 +3,10 @@ from dataclasses import asdict
|
|||
|
||||
import allure
|
||||
from common import STORAGE_NODE_SERVICE_NAME_REGEX
|
||||
from k6 import K6, LoadParams, LoadResults
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import SSHShell
|
||||
|
||||
from k6 import K6, LoadParams, LoadResults
|
||||
|
||||
|
||||
@allure.title("Get services endpoints")
|
||||
def get_services_endpoints(
|
||||
|
|
|
@ -12,14 +12,9 @@ import urllib3
|
|||
from botocore.config import Config
|
||||
from botocore.exceptions import ClientError
|
||||
from cli_helpers import _cmd_run, _configure_aws_cli, _run_with_passwd
|
||||
from common import (
|
||||
NEOFS_AUTHMATE_EXEC,
|
||||
NEOFS_ENDPOINT,
|
||||
S3_GATE,
|
||||
S3_GATE_WALLET_PASS,
|
||||
S3_GATE_WALLET_PATH,
|
||||
)
|
||||
from common import NEOFS_AUTHMATE_EXEC, NEOFS_ENDPOINT, S3_GATE, S3_GATE_WALLET_PATH
|
||||
from data_formatters import get_wallet_public_key
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.container import list_containers
|
||||
|
||||
|
@ -43,7 +38,7 @@ class TestS3GateBase:
|
|||
|
||||
@pytest.fixture(scope="class", autouse=True)
|
||||
@allure.title("[Class/Autouse]: Create S3 client")
|
||||
def s3_client(self, prepare_wallet_and_deposit, client_shell: Shell, request):
|
||||
def s3_client(self, prepare_wallet_and_deposit, client_shell: Shell, request, hosting: Hosting):
|
||||
wallet = prepare_wallet_and_deposit
|
||||
s3_bearer_rules_file = f"{os.getcwd()}/robot/resources/files/s3_bearer_rules.json"
|
||||
|
||||
|
@ -53,7 +48,7 @@ class TestS3GateBase:
|
|||
access_key_id,
|
||||
secret_access_key,
|
||||
owner_private_key,
|
||||
) = init_s3_credentials(wallet, s3_bearer_rules_file=s3_bearer_rules_file)
|
||||
) = init_s3_credentials(wallet, hosting, s3_bearer_rules_file=s3_bearer_rules_file)
|
||||
containers_list = list_containers(wallet, shell=client_shell)
|
||||
assert cid in containers_list, f"Expected cid {cid} in {containers_list}"
|
||||
|
||||
|
@ -65,11 +60,22 @@ class TestS3GateBase:
|
|||
TestS3GateBase.wallet = wallet
|
||||
|
||||
|
||||
def get_wallet_password(hosting: Hosting, s3_service_name: str) -> str:
|
||||
service_config = hosting.get_service_config(s3_service_name)
|
||||
return service_config.attributes.get("wallet_password")
|
||||
|
||||
|
||||
@allure.step("Init S3 Credentials")
|
||||
def init_s3_credentials(wallet_path: str, s3_bearer_rules_file: Optional[str] = None):
|
||||
def init_s3_credentials(
|
||||
wallet_path: str,
|
||||
hosting: Hosting,
|
||||
s3_bearer_rules_file: Optional[str] = None,
|
||||
s3_service_name: str = "s3-gate01",
|
||||
):
|
||||
bucket = str(uuid.uuid4())
|
||||
s3_bearer_rules = s3_bearer_rules_file or "robot/resources/files/s3_bearer_rules.json"
|
||||
gate_public_key = get_wallet_public_key(S3_GATE_WALLET_PATH, S3_GATE_WALLET_PASS)
|
||||
s3_password = get_wallet_password(hosting, s3_service_name)
|
||||
gate_public_key = get_wallet_public_key(S3_GATE_WALLET_PATH, s3_password)
|
||||
cmd = (
|
||||
f"{NEOFS_AUTHMATE_EXEC} --debug --with-log --timeout {CREDENTIALS_CREATE_TIMEOUT} "
|
||||
f"issue-secret --wallet {wallet_path} --gate-public-key={gate_public_key} "
|
||||
|
|
|
@ -5,20 +5,14 @@ from typing import Dict, List, Optional
|
|||
|
||||
import allure
|
||||
import pytest
|
||||
from common import (
|
||||
ASSETS_DIR,
|
||||
IR_WALLET_CONFIG,
|
||||
IR_WALLET_PATH,
|
||||
STORAGE_WALLET_CONFIG,
|
||||
STORAGE_WALLET_PATH,
|
||||
WALLET_CONFIG,
|
||||
WALLET_PASS,
|
||||
)
|
||||
import yaml
|
||||
from common import ASSETS_DIR, IR_WALLET_PATH, STORAGE_WALLET_PATH, WALLET_CONFIG, WALLET_PASS
|
||||
from file_helper import generate_file
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from python_keywords.acl import EACLRole
|
||||
from python_keywords.container import create_container
|
||||
from python_keywords.neofs_verbs import put_object
|
||||
from utility import create_wallet_config
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
OBJECT_COUNT = 5
|
||||
|
@ -42,13 +36,16 @@ class Wallets:
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wallets(prepare_wallet_and_deposit):
|
||||
def wallets(prepare_wallet_and_deposit, hosting):
|
||||
other_wallets_paths = [
|
||||
os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json") for _ in range(2)
|
||||
]
|
||||
for other_wallet_path in other_wallets_paths:
|
||||
init_wallet(other_wallet_path, WALLET_PASS)
|
||||
|
||||
ir_wallet_config = create_wallet_config(hosting, "ir01")
|
||||
storage_wallet_config = create_wallet_config(hosting, "s01")
|
||||
|
||||
yield Wallets(
|
||||
wallets={
|
||||
EACLRole.USER: [
|
||||
|
@ -59,8 +56,8 @@ def wallets(prepare_wallet_and_deposit):
|
|||
for other_wallet_path in other_wallets_paths
|
||||
],
|
||||
EACLRole.SYSTEM: [
|
||||
Wallet(wallet_path=IR_WALLET_PATH, config_path=IR_WALLET_CONFIG),
|
||||
Wallet(wallet_path=STORAGE_WALLET_PATH, config_path=STORAGE_WALLET_CONFIG),
|
||||
Wallet(wallet_path=IR_WALLET_PATH, config_path=ir_wallet_config),
|
||||
Wallet(wallet_path=STORAGE_WALLET_PATH, config_path=storage_wallet_config),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
import yaml
|
||||
from common import (
|
||||
ASSETS_DIR,
|
||||
COMPLEX_OBJ_SIZE,
|
||||
FREE_STORAGE,
|
||||
IR_WALLET_CONFIG,
|
||||
IR_WALLET_PASS,
|
||||
IR_WALLET_PATH,
|
||||
SIMPLE_OBJ_SIZE,
|
||||
WALLET_PASS,
|
||||
|
@ -18,6 +17,7 @@ from common import (
|
|||
from epoch import tick_epoch
|
||||
from file_helper import generate_file
|
||||
from grpc_responses import OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from python_keywords.acl import (
|
||||
|
@ -40,6 +40,7 @@ from python_keywords.storage_group import (
|
|||
verify_get_storage_group,
|
||||
verify_list_storage_group,
|
||||
)
|
||||
from utility import get_wallet_password
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
deposit = 30
|
||||
|
@ -73,16 +74,15 @@ class TestStorageGroup:
|
|||
)
|
||||
|
||||
@allure.title("Test Storage Group in Private Container")
|
||||
def test_storagegroup_basic_private_container(self, client_shell, object_size):
|
||||
def test_storagegroup_basic_private_container(self, client_shell, object_size, hosting):
|
||||
cid = create_container(self.main_wallet, shell=client_shell)
|
||||
file_path = generate_file(object_size)
|
||||
password = get_wallet_password(hosting=hosting, service_name="ir01")
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
objects = [oid]
|
||||
storage_group = put_storagegroup(
|
||||
shell=client_shell,
|
||||
wallet=self.main_wallet,
|
||||
cid=cid,
|
||||
objects=objects)
|
||||
shell=client_shell, wallet=self.main_wallet, cid=cid, objects=objects
|
||||
)
|
||||
|
||||
self.expect_success_for_storagegroup_operations(
|
||||
shell=client_shell,
|
||||
|
@ -104,10 +104,11 @@ class TestStorageGroup:
|
|||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
hosting=hosting,
|
||||
)
|
||||
|
||||
@allure.title("Test Storage Group in Public Container")
|
||||
def test_storagegroup_basic_public_container(self, client_shell, object_size):
|
||||
def test_storagegroup_basic_public_container(self, client_shell, object_size, hosting):
|
||||
cid = create_container(self.main_wallet, basic_acl="public-read-write", shell=client_shell)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
|
@ -132,10 +133,11 @@ class TestStorageGroup:
|
|||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
hosting=hosting,
|
||||
)
|
||||
|
||||
@allure.title("Test Storage Group in Read-Only Container")
|
||||
def test_storagegroup_basic_ro_container(self, client_shell, object_size):
|
||||
def test_storagegroup_basic_ro_container(self, client_shell, object_size, hosting):
|
||||
cid = create_container(self.main_wallet, basic_acl="public-read", shell=client_shell)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
|
@ -161,6 +163,7 @@ class TestStorageGroup:
|
|||
cid=cid,
|
||||
obj_list=objects,
|
||||
object_size=object_size,
|
||||
hosting=hosting,
|
||||
)
|
||||
|
||||
@allure.title("Test Storage Group with Bearer Allow")
|
||||
|
@ -211,8 +214,9 @@ class TestStorageGroup:
|
|||
bearer=bearer_file,
|
||||
)
|
||||
|
||||
@pytest.mark.skip
|
||||
@allure.title("Test to check Storage Group lifetime")
|
||||
def test_storagegroup_lifetime(self, client_shell, object_size):
|
||||
def test_storagegroup_lifetime(self, client_shell, object_size, hosting):
|
||||
cid = create_container(self.main_wallet, shell=client_shell)
|
||||
file_path = generate_file(object_size)
|
||||
oid = put_object(self.main_wallet, file_path, cid, shell=client_shell)
|
||||
|
@ -220,7 +224,7 @@ class TestStorageGroup:
|
|||
storage_group = put_storagegroup(client_shell, self.main_wallet, cid, objects, lifetime=1)
|
||||
with allure.step("Tick two epochs"):
|
||||
for _ in range(2):
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
|
||||
get_storagegroup(
|
||||
shell=client_shell, wallet=self.main_wallet, cid=cid, gid=storage_group
|
||||
|
@ -303,33 +307,48 @@ class TestStorageGroup:
|
|||
@staticmethod
|
||||
@allure.step("Run Storage Group Operations On Systems's Behalf In RO Container")
|
||||
def storagegroup_operations_by_system_ro_container(
|
||||
shell: Shell, wallet: str, cid: str, obj_list: list, object_size: int
|
||||
shell: Shell, wallet: str, cid: str, obj_list: list, object_size: int, hosting: Hosting
|
||||
):
|
||||
"""
|
||||
In this func we create a Storage Group on Inner Ring's key behalf
|
||||
and include an Object created on behalf of some user. We expect
|
||||
that System key is granted to make all operations except PUT and DELETE.
|
||||
"""
|
||||
password = get_wallet_password(hosting=hosting, service_name="ir01")
|
||||
ir_wallet_config = os.path.join(os.getcwd(), "ir_wallet_config.yml")
|
||||
with open(ir_wallet_config, "w") as file:
|
||||
yaml.dump({"password": password}, file)
|
||||
if not FREE_STORAGE:
|
||||
deposit = 30
|
||||
transfer_gas(
|
||||
shell=shell,
|
||||
amount=deposit + 1,
|
||||
wallet_to_path=IR_WALLET_PATH,
|
||||
wallet_to_password=IR_WALLET_PASS,
|
||||
wallet_to_password=password,
|
||||
)
|
||||
deposit_gas(
|
||||
shell=shell,
|
||||
amount=deposit,
|
||||
wallet_from_path=IR_WALLET_PATH,
|
||||
wallet_from_password=IR_WALLET_PASS,
|
||||
wallet_from_password=password,
|
||||
)
|
||||
storage_group = put_storagegroup(shell, wallet, cid, obj_list)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
put_storagegroup(shell, IR_WALLET_PATH, cid, obj_list, wallet_config=IR_WALLET_CONFIG)
|
||||
put_storagegroup(
|
||||
shell=shell,
|
||||
wallet=IR_WALLET_PATH,
|
||||
cid=cid,
|
||||
objects=obj_list,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
verify_list_storage_group(
|
||||
IR_WALLET_PATH, cid, storage_group, wallet_config=IR_WALLET_CONFIG
|
||||
shell=shell,
|
||||
wallet=IR_WALLET_PATH,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
|
||||
verify_get_storage_group(
|
||||
shell=shell,
|
||||
wallet=IR_WALLET_PATH,
|
||||
|
@ -337,7 +356,7 @@ class TestStorageGroup:
|
|||
gid=storage_group,
|
||||
obj_list=obj_list,
|
||||
object_size=object_size,
|
||||
wallet_config=IR_WALLET_CONFIG,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
||||
delete_storagegroup(
|
||||
|
@ -345,5 +364,5 @@ class TestStorageGroup:
|
|||
wallet=IR_WALLET_PATH,
|
||||
cid=cid,
|
||||
gid=storage_group,
|
||||
wallet_config=IR_WALLET_CONFIG,
|
||||
wallet_config=ir_wallet_config,
|
||||
)
|
||||
|
|
|
@ -29,6 +29,7 @@ from python_keywords.object_access import (
|
|||
can_put_object,
|
||||
can_search_object,
|
||||
)
|
||||
from utility import create_wallet_config
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
|
||||
|
@ -230,13 +231,21 @@ class TestEACLContainer:
|
|||
drop_object(hosting, node_name=[*NEOFS_NETMAP_DICT][0], cid=cid, oid=oid)
|
||||
|
||||
storage_wallet_path = NEOFS_NETMAP_DICT[[*NEOFS_NETMAP_DICT][0]]["wallet_path"]
|
||||
storage_wallet_conf = create_wallet_config(hosting, [*NEOFS_NETMAP_DICT][0])
|
||||
with allure.step("Wait for dropped object replicated"):
|
||||
wait_object_replication_on_nodes(
|
||||
storage_wallet_path, cid, oid, self.NODE_COUNT, shell=client_shell
|
||||
storage_wallet_path,
|
||||
cid,
|
||||
oid,
|
||||
self.NODE_COUNT,
|
||||
shell=client_shell,
|
||||
wallet_config=storage_wallet_conf,
|
||||
)
|
||||
|
||||
@allure.title("Testcase to validate NeoFS system operations with extended ACL")
|
||||
def test_extended_actions_system(self, wallets, client_shell, eacl_container_with_objects):
|
||||
def test_extended_actions_system(
|
||||
self, wallets, client_shell, eacl_container_with_objects, hosting
|
||||
):
|
||||
user_wallet = wallets.get_wallet()
|
||||
ir_wallet, storage_wallet = wallets.get_wallets_list(role=EACLRole.SYSTEM)[:2]
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
@ -10,16 +11,27 @@ import yaml
|
|||
from binary_version_helper import get_local_binaries_versions, get_remote_binaries_versions
|
||||
from common import ASSETS_DIR, FREE_STORAGE, HOSTING_CONFIG_FILE, NEOFS_NETMAP_DICT, WALLET_PASS
|
||||
from env_properties import save_env_properties
|
||||
from file_helper import sanitize_for_file_name
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.reporter import AllureHandler, get_reporter
|
||||
from neofs_testlib.shell import LocalShell, Shell
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from payment_neogo import deposit_gas, transfer_gas
|
||||
from pytest import FixtureRequest
|
||||
from python_keywords.node_management import node_healthcheck
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items):
|
||||
# Make network tests last based on @pytest.mark.node_mgmt
|
||||
def priority(item: pytest.Item) -> int:
|
||||
is_node_mgmt_test = item.get_closest_marker("node_mgmt")
|
||||
return 0 if not is_node_mgmt_test else 1
|
||||
|
||||
items.sort(key=lambda item: priority(item))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def configure_testlib():
|
||||
get_reporter().register_handler(AllureHandler())
|
||||
|
@ -72,6 +84,24 @@ def prepare_tmp_dir():
|
|||
shutil.rmtree(full_path)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
@allure.title("Analyze logs")
|
||||
def analyze_logs(prepare_tmp_dir: str, hosting: Hosting, request: FixtureRequest):
|
||||
start_time = datetime.utcnow()
|
||||
yield
|
||||
end_time = datetime.utcnow()
|
||||
|
||||
# Skip tests where we expect failures in logs
|
||||
if request.node.get_closest_marker("no_log_analyze"):
|
||||
with allure.step("Skip analyze logs due to no_log_analyze mark"):
|
||||
return
|
||||
|
||||
# Test name may exceed os NAME_MAX (255 bytes), so we use timestamp instead
|
||||
logs_dir = os.path.join(prepare_tmp_dir, f"logs_{sanitize_for_file_name(str(start_time))}")
|
||||
dump_logs(hosting, logs_dir, start_time, end_time)
|
||||
check_logs(logs_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
@allure.title("Collect logs")
|
||||
def collect_logs(prepare_tmp_dir, hosting: Hosting):
|
||||
|
@ -81,15 +111,8 @@ def collect_logs(prepare_tmp_dir, hosting: Hosting):
|
|||
|
||||
# Dump logs to temp directory (because they might be too large to keep in RAM)
|
||||
logs_dir = os.path.join(prepare_tmp_dir, "logs")
|
||||
os.makedirs(logs_dir)
|
||||
|
||||
for host in hosting.hosts:
|
||||
host.dump_logs(logs_dir, since=start_time, until=end_time)
|
||||
|
||||
# Zip all files and attach to Allure because it is more convenient to download a single
|
||||
# zip with all logs rather than mess with individual logs files per service or node
|
||||
logs_zip_file_path = shutil.make_archive(logs_dir, "zip", logs_dir)
|
||||
allure.attach.file(logs_zip_file_path, name="logs.zip", extension="zip")
|
||||
dump_logs(hosting, logs_dir, start_time, end_time)
|
||||
attach_logs(logs_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
|
@ -108,6 +131,10 @@ def run_health_check(collect_logs, hosting: Hosting):
|
|||
@pytest.fixture(scope="session")
|
||||
@allure.title("Prepare wallet and deposit")
|
||||
def prepare_wallet_and_deposit(client_shell, prepare_tmp_dir):
|
||||
return create_wallet_with_gas(client_shell, prepare_tmp_dir)
|
||||
|
||||
|
||||
def create_wallet_with_gas(client_shell, prepare_tmp_dir):
|
||||
wallet_path = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
|
||||
init_wallet(wallet_path, WALLET_PASS)
|
||||
allure.attach.file(wallet_path, os.path.basename(wallet_path), allure.attachment_type.JSON)
|
||||
|
@ -128,3 +155,39 @@ def prepare_wallet_and_deposit(client_shell, prepare_tmp_dir):
|
|||
)
|
||||
|
||||
return wallet_path
|
||||
|
||||
|
||||
def check_logs(logs_dir: str):
|
||||
problem_pattern = r"\Wpanic\W|\Woom\W"
|
||||
|
||||
log_file_paths = []
|
||||
for directory_path, _, file_names in os.walk(logs_dir):
|
||||
log_file_paths += [
|
||||
os.path.join(directory_path, file_name)
|
||||
for file_name in file_names
|
||||
if re.match(r"\.(txt|log)", os.path.splitext(file_name)[-1], flags=re.IGNORECASE)
|
||||
]
|
||||
|
||||
logs_with_problem = []
|
||||
for file_path in log_file_paths:
|
||||
with open(file_path, "r") as log_file:
|
||||
if re.search(problem_pattern, log_file.read(), flags=re.IGNORECASE):
|
||||
attach_logs(logs_dir)
|
||||
logs_with_problem.append(file_path)
|
||||
if logs_with_problem:
|
||||
raise pytest.fail(f"System logs {', '.join(logs_with_problem)} contain critical errors")
|
||||
|
||||
|
||||
def dump_logs(hosting: Hosting, logs_dir: str, since: datetime, until: datetime) -> None:
|
||||
# Dump logs to temp directory (because they might be too large to keep in RAM)
|
||||
os.makedirs(logs_dir)
|
||||
|
||||
for host in hosting.hosts:
|
||||
host.dump_logs(logs_dir, since=since, until=until)
|
||||
|
||||
|
||||
def attach_logs(logs_dir: str) -> None:
|
||||
# Zip all files and attach to Allure because it is more convenient to download a single
|
||||
# zip with all logs rather than mess with individual logs files per service or node
|
||||
logs_zip_file_path = shutil.make_archive(logs_dir, "zip", logs_dir)
|
||||
allure.attach.file(logs_zip_file_path, name="logs.zip", extension="zip")
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import allure
|
||||
import pytest
|
||||
from epoch import tick_epoch
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from python_keywords.container import (
|
||||
create_container,
|
||||
delete_container,
|
||||
|
@ -18,7 +19,7 @@ from wellknown_acl import PRIVATE_ACL_F
|
|||
@pytest.mark.parametrize("name", ["", "test-container"], ids=["No name", "Set particular name"])
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.container
|
||||
def test_container_creation(client_shell, prepare_wallet_and_deposit, name):
|
||||
def test_container_creation(client_shell, prepare_wallet_and_deposit, name, hosting):
|
||||
scenario_title = f"with name {name}" if name else "without name"
|
||||
allure.dynamic.title(f"User can create container {scenario_title}")
|
||||
|
||||
|
@ -58,14 +59,14 @@ def test_container_creation(client_shell, prepare_wallet_and_deposit, name):
|
|||
|
||||
with allure.step("Delete container and check it was deleted"):
|
||||
delete_container(wallet, cid, shell=client_shell)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
wait_for_container_deletion(wallet, cid, shell=client_shell)
|
||||
|
||||
|
||||
@allure.title("Parallel container creation and deletion")
|
||||
@pytest.mark.sanity
|
||||
@pytest.mark.container
|
||||
def test_container_creation_deletion_parallel(client_shell, prepare_wallet_and_deposit):
|
||||
def test_container_creation_deletion_parallel(client_shell, prepare_wallet_and_deposit, hosting):
|
||||
containers_count = 3
|
||||
wallet = prepare_wallet_and_deposit
|
||||
placement_rule = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
|
||||
|
@ -92,5 +93,5 @@ def test_container_creation_deletion_parallel(client_shell, prepare_wallet_and_d
|
|||
with allure.step("Delete containers and check they were deleted"):
|
||||
for cid in cids:
|
||||
delete_container(wallet, cid, shell=client_shell)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
wait_for_container_deletion(wallet, cid, shell=client_shell)
|
||||
|
|
|
@ -3,14 +3,12 @@ from enum import Enum
|
|||
import allure
|
||||
import pytest
|
||||
from common import (
|
||||
HTTP_GATE_SERVICE_NAME_REGEX,
|
||||
LOAD_NODE_SSH_PRIVATE_KEY_PATH,
|
||||
LOAD_NODE_SSH_USER,
|
||||
LOAD_NODES,
|
||||
STORAGE_NODE_SERVICE_NAME_REGEX,
|
||||
HTTP_GATE_SERVICE_NAME_REGEX,
|
||||
)
|
||||
from neofs_testlib.hosting import Hosting
|
||||
|
||||
from k6 import LoadParams
|
||||
from load import (
|
||||
clear_cache_and_data,
|
||||
|
@ -18,6 +16,7 @@ from load import (
|
|||
multi_node_k6_run,
|
||||
prepare_k6_instances,
|
||||
)
|
||||
from neofs_testlib.hosting import Hosting
|
||||
|
||||
|
||||
class LoadTime(Enum):
|
||||
|
|
|
@ -11,7 +11,6 @@ from common import (
|
|||
NEOFS_CONTRACT_CACHE_TIMEOUT,
|
||||
NEOFS_NETMAP_DICT,
|
||||
STORAGE_RPC_ENDPOINT_1,
|
||||
STORAGE_WALLET_PASS,
|
||||
)
|
||||
from data_formatters import get_wallet_public_key
|
||||
from epoch import tick_epoch
|
||||
|
@ -38,7 +37,12 @@ from python_keywords.node_management import (
|
|||
stop_nodes,
|
||||
)
|
||||
from storage_policy import get_nodes_with_object, get_simple_object_copies
|
||||
from utility import parse_time, placement_policy_from_container, wait_for_gc_pass_on_storage_nodes
|
||||
from utility import (
|
||||
get_wallet_password,
|
||||
parse_time,
|
||||
placement_policy_from_container,
|
||||
wait_for_gc_pass_on_storage_nodes,
|
||||
)
|
||||
from wellknown_acl import PUBLIC_ACL
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
@ -109,12 +113,12 @@ def return_nodes(shell: Shell, hosting: Hosting, alive_node: Optional[str] = Non
|
|||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
for __attempt in range(3):
|
||||
try:
|
||||
tick_epoch(shell=shell)
|
||||
tick_epoch(shell=shell, hosting=hosting)
|
||||
break
|
||||
except RuntimeError:
|
||||
sleep(3)
|
||||
|
||||
check_node_in_map(node, shell=shell, alive_node=alive_node)
|
||||
check_node_in_map(node, shell=shell, hosting=hosting, alive_node=alive_node)
|
||||
|
||||
|
||||
@allure.title("Add one node to cluster")
|
||||
|
@ -141,7 +145,7 @@ def test_add_nodes(
|
|||
)
|
||||
alive_node = choice([node for node in NEOFS_NETMAP_DICT if node != additional_node])
|
||||
|
||||
check_node_in_map(additional_node, shell=client_shell, alive_node=alive_node)
|
||||
check_node_in_map(additional_node, shell=client_shell, hosting=hosting, alive_node=alive_node)
|
||||
|
||||
# Add node to recovery list before messing with it
|
||||
check_nodes.append(additional_node)
|
||||
|
@ -197,10 +201,11 @@ def test_nodes_management(prepare_tmp_dir, client_shell, hosting: Hosting):
|
|||
|
||||
# Calculate public key that identifies node in netmap
|
||||
random_node_wallet_path = NEOFS_NETMAP_DICT[random_node]["wallet_path"]
|
||||
random_node_netmap_key = get_wallet_public_key(random_node_wallet_path, STORAGE_WALLET_PASS)
|
||||
node_wallet_password = get_wallet_password(hosting=hosting, service_name=random_node)
|
||||
random_node_netmap_key = get_wallet_public_key(random_node_wallet_path, node_wallet_password)
|
||||
|
||||
with allure.step("Check node {random_node} is in netmap"):
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell, hosting=hosting)
|
||||
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
||||
|
||||
with allure.step("Run health check for all storage nodes"):
|
||||
|
@ -212,24 +217,24 @@ def test_nodes_management(prepare_tmp_dir, client_shell, hosting: Hosting):
|
|||
node_set_status(hosting, random_node, status="offline")
|
||||
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
with allure.step(f"Check node {random_node} went to offline"):
|
||||
health_check = node_healthcheck(hosting, random_node)
|
||||
assert health_check.health_status == "READY" and health_check.network_status == "OFFLINE"
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell, hosting=hosting)
|
||||
assert random_node_netmap_key not in snapshot, f"Expected node {random_node} not in netmap"
|
||||
|
||||
with allure.step(f"Check node {random_node} went to online"):
|
||||
node_set_status(hosting, random_node, status="online")
|
||||
|
||||
sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
with allure.step(f"Check node {random_node} went to online"):
|
||||
health_check = node_healthcheck(hosting, random_node)
|
||||
assert health_check.health_status == "READY" and health_check.network_status == "ONLINE"
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell)
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=client_shell, hosting=hosting)
|
||||
assert random_node_netmap_key in snapshot, f"Expected node {random_node} in netmap"
|
||||
|
||||
|
||||
|
@ -349,7 +354,7 @@ def test_placement_policy_negative(
|
|||
@pytest.mark.node_mgmt
|
||||
@allure.title("NeoFS object replication on node failover")
|
||||
def test_replication(
|
||||
prepare_wallet_and_deposit, client_shell: Shell, after_run_start_all_nodes, hosting: Hosting
|
||||
prepare_wallet_and_deposit, client_shell: Shell, after_run_start_all_nodes, hosting: Hosting
|
||||
):
|
||||
"""
|
||||
Test checks object replication on storage not failover and come back.
|
||||
|
@ -372,7 +377,7 @@ def test_replication(
|
|||
wait_for_expected_object_copies(client_shell, wallet, cid, oid)
|
||||
|
||||
start_nodes(hosting, stopped_nodes)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
for node_name in node_names:
|
||||
wait_for_node_go_online(hosting, node_name)
|
||||
|
@ -508,13 +513,13 @@ def wait_for_node_to_be_ready(hosting: Hosting, node_name: str) -> None:
|
|||
|
||||
@allure.step("Wait for {expected_copies} object copies in the wallet")
|
||||
def wait_for_expected_object_copies(
|
||||
shell: Shell, wallet: str, cid: str, oid: str, expected_copies: int = 2
|
||||
shell: Shell, wallet: str, cid: str, oid: str, hosting: Hosting, expected_copies: int = 2
|
||||
) -> None:
|
||||
for i in range(2):
|
||||
copies = get_simple_object_copies(wallet, cid, oid)
|
||||
if copies == expected_copies:
|
||||
break
|
||||
tick_epoch(shell=shell)
|
||||
tick_epoch(shell=shell, hosting=hosting)
|
||||
sleep(parse_time(NEOFS_CONTRACT_CACHE_TIMEOUT))
|
||||
else:
|
||||
raise AssertionError(f"There are no {expected_copies} copies during time")
|
||||
|
|
|
@ -11,6 +11,7 @@ from container import create_container
|
|||
from epoch import tick_epoch
|
||||
from file_helper import generate_file, get_file_content, get_file_hash
|
||||
from grpc_responses import OBJECT_ALREADY_REMOVED, OUT_OF_RANGE, error_matches_status
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from pytest import FixtureRequest
|
||||
from python_keywords.neofs_verbs import (
|
||||
|
@ -94,7 +95,7 @@ def generate_ranges(file_size: int, max_object_size: int) -> list[(int, int)]:
|
|||
return file_ranges_to_test
|
||||
|
||||
|
||||
def delete_objects(storage_objects: list, client_shell: Shell) -> None:
|
||||
def delete_objects(storage_objects: list, client_shell: Shell, hosting: Hosting) -> None:
|
||||
with allure.step("Delete objects"):
|
||||
for storage_object in storage_objects:
|
||||
storage_object.tombstone = delete_object(
|
||||
|
@ -108,7 +109,7 @@ def delete_objects(storage_objects: list, client_shell: Shell) -> None:
|
|||
shell=client_shell,
|
||||
)
|
||||
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
sleep(CLEANUP_TIMEOUT)
|
||||
|
||||
with allure.step("Get objects and check errors"):
|
||||
|
@ -129,7 +130,7 @@ def delete_objects(storage_objects: list, client_shell: Shell) -> None:
|
|||
scope="session",
|
||||
)
|
||||
def storage_objects(
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest, hosting: Hosting
|
||||
) -> list[StorageObjectInfo]:
|
||||
wallet = prepare_wallet_and_deposit
|
||||
# Separate containers for complex/simple objects to avoid side-effects
|
||||
|
@ -163,7 +164,7 @@ def storage_objects(
|
|||
yield storage_objects
|
||||
|
||||
# Teardown after all tests done with current param
|
||||
delete_objects(storage_objects, client_shell)
|
||||
delete_objects(storage_objects, client_shell, hosting)
|
||||
|
||||
|
||||
@allure.title("Validate object storage policy by native API")
|
||||
|
@ -290,7 +291,11 @@ def test_search_object_api(
|
|||
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
|
||||
)
|
||||
def test_object_search_should_return_tombstone_items(
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest, object_size: int
|
||||
prepare_wallet_and_deposit: str,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
object_size: int,
|
||||
hosting: Hosting,
|
||||
):
|
||||
"""
|
||||
Validate object search with removed items
|
||||
|
@ -321,7 +326,7 @@ def test_object_search_should_return_tombstone_items(
|
|||
assert result == [storage_object.oid]
|
||||
|
||||
with allure.step("Delete file"):
|
||||
delete_objects([storage_object], client_shell)
|
||||
delete_objects([storage_object], client_shell, hosting)
|
||||
|
||||
with allure.step("Search deleted object with --root"):
|
||||
# Root Search object should return nothing
|
||||
|
|
|
@ -7,6 +7,7 @@ from container import create_container
|
|||
from epoch import get_epoch, tick_epoch
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from grpc_responses import OBJECT_NOT_FOUND
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from pytest import FixtureRequest
|
||||
from python_keywords.neofs_verbs import get_object, put_object
|
||||
|
@ -22,7 +23,11 @@ logger = logging.getLogger("NeoLogger")
|
|||
"object_size", [SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], ids=["simple object", "complex object"]
|
||||
)
|
||||
def test_object_api_lifetime(
|
||||
prepare_wallet_and_deposit: str, client_shell: Shell, request: FixtureRequest, object_size: int
|
||||
prepare_wallet_and_deposit: str,
|
||||
client_shell: Shell,
|
||||
request: FixtureRequest,
|
||||
object_size: int,
|
||||
hosting: Hosting,
|
||||
):
|
||||
"""
|
||||
Test object deleted after expiration epoch.
|
||||
|
@ -42,7 +47,7 @@ def test_object_api_lifetime(
|
|||
|
||||
with allure.step("Tick two epochs"):
|
||||
for _ in range(2):
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
# Wait for GC, because object with expiration is counted as alive until GC removes it
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import logging
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
import yaml
|
||||
from common import ASSETS_DIR, FREE_STORAGE, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG, WALLET_PASS
|
||||
from common import FREE_STORAGE, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG, WALLET_PASS
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet, init_wallet
|
||||
from neofs_testlib.shell import CommandResult, Shell
|
||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet
|
||||
|
||||
from testsuites.conftest import create_wallet_with_gas
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
DEPOSIT_AMOUNT = 30
|
||||
|
@ -16,51 +18,30 @@ DEPOSIT_AMOUNT = 30
|
|||
@pytest.mark.payments
|
||||
@pytest.mark.skipif(FREE_STORAGE, reason="Test only works on public network with paid storage")
|
||||
class TestBalanceAccounting:
|
||||
@pytest.fixture(autouse=True)
|
||||
def prepare_two_wallets(self, prepare_wallet_and_deposit):
|
||||
self.user_wallet = prepare_wallet_and_deposit
|
||||
self.address = get_last_address_from_wallet(self.user_wallet, WALLET_PASS)
|
||||
another_wallet = os.path.join(os.getcwd(), ASSETS_DIR, f"{str(uuid.uuid4())}.json")
|
||||
init_wallet(another_wallet, WALLET_PASS)
|
||||
self.another_address = get_last_address_from_wallet(another_wallet, WALLET_PASS)
|
||||
@pytest.fixture(scope="class")
|
||||
def main_wallet(self, client_shell, prepare_tmp_dir) -> str:
|
||||
return create_wallet_with_gas(client_shell=client_shell, prepare_tmp_dir=prepare_tmp_dir)
|
||||
|
||||
@allure.title("Test balance request with wallet and address")
|
||||
def test_balance_wallet_address(self, client_shell):
|
||||
cli = NeofsCli(client_shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
result = cli.accounting.balance(
|
||||
wallet=self.user_wallet,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
address=self.address,
|
||||
)
|
||||
assert int(result.stdout.rstrip()) == DEPOSIT_AMOUNT
|
||||
@pytest.fixture(scope="class")
|
||||
def other_wallet(self, client_shell, prepare_tmp_dir) -> str:
|
||||
return create_wallet_with_gas(client_shell=client_shell, prepare_tmp_dir=prepare_tmp_dir)
|
||||
|
||||
@allure.title("Test balance request with wallet only")
|
||||
def test_balance_wallet(self, client_shell):
|
||||
cli = NeofsCli(client_shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
result = cli.accounting.balance(wallet=self.user_wallet, rpc_endpoint=NEOFS_ENDPOINT)
|
||||
assert int(result.stdout.rstrip()) == DEPOSIT_AMOUNT
|
||||
@pytest.fixture(scope="class")
|
||||
def cli(self, client_shell: Shell) -> NeofsCli:
|
||||
return NeofsCli(client_shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
|
||||
@allure.title("Test balance request with wallet and wrong address")
|
||||
def test_balance_wrong_address(self, client_shell):
|
||||
with pytest.raises(Exception, match="address option must be specified and valid"):
|
||||
cli = NeofsCli(client_shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
|
||||
cli.accounting.balance(
|
||||
wallet=self.user_wallet,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
address=self.another_address,
|
||||
@allure.step("Check deposit amount")
|
||||
def check_amount(self, result: CommandResult) -> None:
|
||||
amount_str = result.stdout.rstrip()
|
||||
|
||||
try:
|
||||
amount = int(amount_str)
|
||||
except Exception as ex:
|
||||
pytest.fail(
|
||||
f"Amount parse error, should be parsable as int({DEPOSIT_AMOUNT}), but given {amount_str}: {ex}"
|
||||
)
|
||||
|
||||
@allure.title("Test balance request with config file")
|
||||
def test_balance_api(self, prepare_tmp_dir, client_shell):
|
||||
config_file = self.write_api_config(
|
||||
config_dir=prepare_tmp_dir, endpoint=NEOFS_ENDPOINT, wallet=self.user_wallet
|
||||
)
|
||||
logger.info(f"Config with API endpoint: {config_file}")
|
||||
|
||||
cli = NeofsCli(client_shell, NEOFS_CLI_EXEC, config_file=config_file)
|
||||
result = cli.accounting.balance()
|
||||
|
||||
assert int(result.stdout.rstrip()) == DEPOSIT_AMOUNT
|
||||
assert amount == DEPOSIT_AMOUNT
|
||||
|
||||
@staticmethod
|
||||
@allure.step("Write config with API endpoint")
|
||||
|
@ -76,3 +57,39 @@ class TestBalanceAccounting:
|
|||
with open(api_config_file, "w") as file:
|
||||
yaml.dump(api_config, file)
|
||||
return api_config_file
|
||||
|
||||
@allure.title("Test balance request with wallet and address")
|
||||
def test_balance_wallet_address(self, main_wallet: str, cli: NeofsCli):
|
||||
result = cli.accounting.balance(
|
||||
wallet=main_wallet,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
address=get_last_address_from_wallet(main_wallet, WALLET_PASS),
|
||||
)
|
||||
|
||||
self.check_amount(result)
|
||||
|
||||
@allure.title("Test balance request with wallet only")
|
||||
def test_balance_wallet(self, main_wallet: str, cli: NeofsCli):
|
||||
result = cli.accounting.balance(wallet=main_wallet, rpc_endpoint=NEOFS_ENDPOINT)
|
||||
self.check_amount(result)
|
||||
|
||||
@allure.title("Test balance request with wallet and wrong address")
|
||||
def test_balance_wrong_address(self, main_wallet: str, other_wallet: str, cli: NeofsCli):
|
||||
with pytest.raises(Exception, match="address option must be specified and valid"):
|
||||
cli.accounting.balance(
|
||||
wallet=main_wallet,
|
||||
rpc_endpoint=NEOFS_ENDPOINT,
|
||||
address=get_last_address_from_wallet(other_wallet, WALLET_PASS),
|
||||
)
|
||||
|
||||
@allure.title("Test balance request with config file")
|
||||
def test_balance_api(self, prepare_tmp_dir: str, main_wallet: str, client_shell: Shell):
|
||||
config_file = self.write_api_config(
|
||||
config_dir=prepare_tmp_dir, endpoint=NEOFS_ENDPOINT, wallet=main_wallet
|
||||
)
|
||||
logger.info(f"Config with API endpoint: {config_file}")
|
||||
|
||||
cli = NeofsCli(client_shell, NEOFS_CLI_EXEC, config_file=config_file)
|
||||
result = cli.accounting.balance()
|
||||
|
||||
self.check_amount(result)
|
||||
|
|
|
@ -66,7 +66,7 @@ class TestS3Gate(TestS3GateBase):
|
|||
s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket)
|
||||
|
||||
@allure.title("Test S3 Bucket API")
|
||||
def test_s3_buckets(self, client_shell):
|
||||
def test_s3_buckets(self, client_shell, hosting):
|
||||
"""
|
||||
Test base S3 Bucket API (Create/List/Head/Delete).
|
||||
"""
|
||||
|
@ -110,7 +110,7 @@ class TestS3Gate(TestS3GateBase):
|
|||
|
||||
with allure.step(f"Delete empty bucket {bucket_2}"):
|
||||
s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket_2)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
with allure.step(f"Check bucket {bucket_2} deleted"):
|
||||
with pytest.raises(Exception, match=r".*Not Found.*"):
|
||||
|
@ -126,7 +126,7 @@ class TestS3Gate(TestS3GateBase):
|
|||
|
||||
with allure.step(f"Delete bucket {bucket_1}"):
|
||||
s3_gate_bucket.delete_bucket_s3(self.s3_client, bucket_1)
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
with allure.step(f"Check bucket {bucket_1} deleted"):
|
||||
with pytest.raises(Exception, match=r".*Not Found.*"):
|
||||
|
|
|
@ -8,9 +8,9 @@ import pytest
|
|||
from common import ASSETS_DIR, COMPLEX_OBJ_SIZE, FREE_STORAGE, SIMPLE_OBJ_SIZE, WALLET_PASS
|
||||
from data_formatters import get_wallet_public_key
|
||||
from file_helper import concat_files, generate_file, generate_file_with_content, get_file_hash
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
from python_keywords.payment_neogo import deposit_gas, transfer_gas
|
||||
from s3_helper import assert_object_lock_mode, check_objects_in_bucket, set_bucket_versioning
|
||||
from neofs_testlib.utils.wallet import init_wallet
|
||||
|
||||
from steps import s3_gate_bucket, s3_gate_object
|
||||
from steps.aws_cli_client import AwsCliClient
|
||||
|
|
|
@ -9,6 +9,7 @@ from common import COMPLEX_OBJ_SIZE
|
|||
from container import create_container
|
||||
from epoch import get_epoch, tick_epoch
|
||||
from file_helper import generate_file, get_file_hash
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from python_keywords.http_gate import (
|
||||
get_via_http_curl,
|
||||
|
@ -147,7 +148,7 @@ class TestHttpGate:
|
|||
self.get_object_by_attr_and_verify_hashes(oid, file_path, cid, attributes)
|
||||
|
||||
@allure.title("Test Expiration-Epoch in HTTP header")
|
||||
def test_expiration_epoch_in_http(self, client_shell):
|
||||
def test_expiration_epoch_in_http(self, client_shell, hosting):
|
||||
cid = create_container(
|
||||
self.wallet, shell=client_shell, rule=self.PLACEMENT_RULE, basic_acl=PUBLIC_ACL
|
||||
)
|
||||
|
@ -170,7 +171,7 @@ class TestHttpGate:
|
|||
get_via_http_gate(cid=cid, oid=oid)
|
||||
|
||||
for expired_objects, not_expired_objects in [(oids[:1], oids[1:]), (oids[:2], oids[2:])]:
|
||||
tick_epoch(shell=client_shell)
|
||||
tick_epoch(shell=client_shell, hosting=hosting)
|
||||
|
||||
# Wait for GC, because object with expiration is counted as alive until GC removes it
|
||||
wait_for_gc_pass_on_storage_nodes()
|
||||
|
|
|
@ -1,8 +1,68 @@
|
|||
robotframework==4.1.2
|
||||
requests==2.25.1
|
||||
pexpect==4.8.0
|
||||
aiodns==3.0.0
|
||||
aiohttp==3.7.4.post0
|
||||
aioresponses==0.7.2
|
||||
allure-pytest==2.9.45
|
||||
allure-python-commons==2.9.45
|
||||
async-timeout==3.0.1
|
||||
asynctest==0.13.0
|
||||
attrs==21.4.0
|
||||
base58==2.1.0
|
||||
bitarray==2.3.4
|
||||
black==22.8.0
|
||||
boto3==1.16.33
|
||||
docker==4.4.0
|
||||
botocore==1.19.33
|
||||
urllib3==1.26.3
|
||||
base58==1.0.3
|
||||
certifi==2022.5.18
|
||||
cffi==1.15.0
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.0.12
|
||||
coverage==6.3.3
|
||||
docker==4.4.0
|
||||
docutils==0.17.1
|
||||
Events==0.4
|
||||
flake8==4.0.1
|
||||
idna==3.3
|
||||
iniconfig==1.1.1
|
||||
isort==5.10.1
|
||||
jmespath==0.10.0
|
||||
jsonschema==4.5.1
|
||||
lz4==3.1.3
|
||||
mccabe==0.6.1
|
||||
mmh3==3.0.0
|
||||
multidict==6.0.2
|
||||
mypy==0.950
|
||||
mypy-extensions==0.4.3
|
||||
neo-mamba==0.10.0
|
||||
neo3crypto==0.2.1
|
||||
neo3vm==0.9.0
|
||||
neo3vm-stubs==0.9.0
|
||||
neofs-testlib==0.4.0
|
||||
netaddr==0.8.0
|
||||
orjson==3.6.8
|
||||
packaging==21.3
|
||||
paramiko==2.10.3
|
||||
pexpect==4.8.0
|
||||
pluggy==1.0.0
|
||||
pre-commit==2.20.0
|
||||
ptyprocess==0.7.0
|
||||
py==1.11.0
|
||||
pybiginteger==1.2.6
|
||||
pybiginteger-stubs==1.2.6
|
||||
pycares==4.1.2
|
||||
pycodestyle==2.8.0
|
||||
pycparser==2.21
|
||||
pycryptodome==3.11.0
|
||||
pyflakes==2.4.0
|
||||
pyparsing==3.0.9
|
||||
pyrsistent==0.18.1
|
||||
pytest==7.1.2
|
||||
python-dateutil==2.8.2
|
||||
requests==2.28.0
|
||||
robotframework==4.1.2
|
||||
s3transfer==0.3.7
|
||||
six==1.16.0
|
||||
tenacity==8.0.1
|
||||
tomli==2.0.1
|
||||
typing-extensions==4.2.0
|
||||
urllib3==1.26.9
|
||||
websocket-client==1.3.2
|
||||
yarl==1.7.2
|
||||
|
|
|
@ -4,7 +4,6 @@ from time import sleep
|
|||
|
||||
import allure
|
||||
from common import (
|
||||
IR_WALLET_PASS,
|
||||
IR_WALLET_PATH,
|
||||
MAINNET_BLOCK_TIME,
|
||||
MORPH_ENDPOINT,
|
||||
|
@ -13,10 +12,11 @@ from common import (
|
|||
NEOGO_EXECUTABLE,
|
||||
)
|
||||
from neofs_testlib.cli import NeofsAdm, NeoGo
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet
|
||||
from payment_neogo import get_contract_hash
|
||||
from utility import parse_time
|
||||
from utility import get_wallet_password, parse_time
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
@ -33,7 +33,7 @@ def get_epoch(shell: Shell):
|
|||
|
||||
|
||||
@allure.step("Tick Epoch")
|
||||
def tick_epoch(shell: Shell):
|
||||
def tick_epoch(shell: Shell, hosting: Hosting):
|
||||
if NEOFS_ADM_EXEC and NEOFS_ADM_CONFIG_PATH:
|
||||
# If neofs-adm is available, then we tick epoch with it (to be consistent with UAT tests)
|
||||
neofsadm = NeofsAdm(
|
||||
|
@ -44,13 +44,13 @@ def tick_epoch(shell: Shell):
|
|||
|
||||
# Otherwise we tick epoch using transaction
|
||||
cur_epoch = get_epoch(shell)
|
||||
|
||||
ir_address = get_last_address_from_wallet(IR_WALLET_PATH, IR_WALLET_PASS)
|
||||
ir_wallet_password = get_wallet_password(hosting, "ir01")
|
||||
ir_address = get_last_address_from_wallet(IR_WALLET_PATH, ir_wallet_password)
|
||||
|
||||
neogo = NeoGo(shell, neo_go_exec_path=NEOGO_EXECUTABLE)
|
||||
neogo.contract.invokefunction(
|
||||
wallet=IR_WALLET_PATH,
|
||||
wallet_password=IR_WALLET_PASS,
|
||||
wallet_password=ir_wallet_password,
|
||||
scripthash=get_contract_hash("netmap.neofs", shell=shell),
|
||||
method="newEpoch",
|
||||
arguments=f"int:{cur_epoch + 1}",
|
||||
|
|
|
@ -20,12 +20,15 @@ def wait_object_replication_on_nodes(
|
|||
expected_copies: int,
|
||||
shell: Shell,
|
||||
excluded_nodes: Optional[list[str]] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
) -> list[str]:
|
||||
excluded_nodes = excluded_nodes or []
|
||||
sleep_interval, attempts = 15, 20
|
||||
nodes = []
|
||||
for __attempt in range(attempts):
|
||||
nodes = get_nodes_with_object(wallet, cid, oid, shell=shell, skip_nodes=excluded_nodes)
|
||||
nodes = get_nodes_with_object(
|
||||
wallet, cid, oid, shell=shell, skip_nodes=excluded_nodes, wallet_config=wallet_config
|
||||
)
|
||||
if len(nodes) >= expected_copies:
|
||||
return nodes
|
||||
sleep(sleep_interval)
|
||||
|
|
|
@ -6,19 +6,13 @@ from dataclasses import dataclass
|
|||
from typing import Optional
|
||||
|
||||
import allure
|
||||
from common import (
|
||||
MORPH_BLOCK_TIME,
|
||||
NEOFS_CLI_EXEC,
|
||||
NEOFS_NETMAP_DICT,
|
||||
STORAGE_WALLET_CONFIG,
|
||||
STORAGE_WALLET_PASS,
|
||||
)
|
||||
from common import MORPH_BLOCK_TIME, NEOFS_CLI_EXEC, NEOFS_NETMAP_DICT
|
||||
from data_formatters import get_wallet_public_key
|
||||
from epoch import tick_epoch
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.hosting import Hosting
|
||||
from neofs_testlib.shell import Shell
|
||||
from utility import parse_time
|
||||
from utility import create_wallet_config, get_wallet_password, parse_time
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
@ -104,7 +98,7 @@ def node_set_status(hosting: Hosting, node_name: str, status: str, retries: int
|
|||
|
||||
|
||||
@allure.step("Get netmap snapshot")
|
||||
def get_netmap_snapshot(node_name: str, shell: Shell) -> str:
|
||||
def get_netmap_snapshot(node_name: str, shell: Shell, hosting: Hosting) -> str:
|
||||
"""
|
||||
The function returns string representation of netmap snapshot.
|
||||
Args:
|
||||
|
@ -113,7 +107,8 @@ def get_netmap_snapshot(node_name: str, shell: Shell) -> str:
|
|||
string representation of netmap
|
||||
"""
|
||||
node_info = NEOFS_NETMAP_DICT[node_name]
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, config_file=STORAGE_WALLET_CONFIG)
|
||||
storage_wallet_config = create_wallet_config(hosting, node_name)
|
||||
cli = NeofsCli(shell, NEOFS_CLI_EXEC, config_file=storage_wallet_config)
|
||||
return cli.netmap.snapshot(
|
||||
rpc_endpoint=node_info["rpc"],
|
||||
wallet=node_info["wallet_path"],
|
||||
|
@ -169,14 +164,15 @@ def exclude_node_from_network_map(
|
|||
hosting: Hosting, node_to_exclude: str, alive_node: str, shell: Shell
|
||||
) -> None:
|
||||
node_wallet_path = NEOFS_NETMAP_DICT[node_to_exclude]["wallet_path"]
|
||||
node_netmap_key = get_wallet_public_key(node_wallet_path, STORAGE_WALLET_PASS)
|
||||
node_wallet_password = get_wallet_password(hosting=hosting, service_name=node_to_exclude)
|
||||
node_netmap_key = get_wallet_public_key(node_wallet_path, node_wallet_password)
|
||||
|
||||
node_set_status(hosting, node_to_exclude, status="offline")
|
||||
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=shell)
|
||||
tick_epoch(shell=shell, hosting=hosting)
|
||||
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=shell)
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=shell, hosting=hosting)
|
||||
assert (
|
||||
node_netmap_key not in snapshot
|
||||
), f"Expected node with key {node_netmap_key} not in network map"
|
||||
|
@ -188,21 +184,25 @@ def include_node_to_network_map(
|
|||
) -> None:
|
||||
node_set_status(hosting, node_to_include, status="online")
|
||||
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME))
|
||||
tick_epoch(shell=shell)
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME) * 2)
|
||||
tick_epoch(shell=shell, hosting=hosting)
|
||||
time.sleep(parse_time(MORPH_BLOCK_TIME) * 2)
|
||||
|
||||
check_node_in_map(node_to_include, shell, alive_node)
|
||||
check_node_in_map(node_to_include, shell, hosting, alive_node)
|
||||
|
||||
|
||||
@allure.step("Check node {node_name} in network map")
|
||||
def check_node_in_map(node_name: str, shell: Shell, alive_node: Optional[str] = None) -> None:
|
||||
def check_node_in_map(
|
||||
node_name: str, shell: Shell, hosting: Hosting, alive_node: Optional[str] = None
|
||||
) -> None:
|
||||
alive_node = alive_node or node_name
|
||||
node_wallet_path = NEOFS_NETMAP_DICT[node_name]["wallet_path"]
|
||||
node_netmap_key = get_wallet_public_key(node_wallet_path, STORAGE_WALLET_PASS)
|
||||
node_wallet_password = get_wallet_password(hosting=hosting, service_name=node_name)
|
||||
node_netmap_key = get_wallet_public_key(node_wallet_path, node_wallet_password)
|
||||
|
||||
logger.info(f"Node {node_name} netmap key: {node_netmap_key}")
|
||||
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=shell)
|
||||
snapshot = get_netmap_snapshot(node_name=alive_node, shell=shell, hosting=hosting)
|
||||
assert node_netmap_key in snapshot, f"Expected node with key {node_netmap_key} in network map"
|
||||
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ import uuid
|
|||
import allure
|
||||
import json_transformers
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||
from neo3 import wallet
|
||||
from neofs_testlib.cli import NeofsCli
|
||||
from neofs_testlib.shell import Shell
|
||||
from neo3 import wallet
|
||||
|
||||
logger = logging.getLogger("NeoLogger")
|
||||
|
||||
|
|
|
@ -97,7 +97,12 @@ def get_complex_object_copies(wallet: str, cid: str, oid: str, shell: Shell) ->
|
|||
|
||||
@allure.step("Get Nodes With Object")
|
||||
def get_nodes_with_object(
|
||||
wallet: str, cid: str, oid: str, shell: Shell, skip_nodes: Optional[list[str]] = None
|
||||
wallet: str,
|
||||
cid: str,
|
||||
oid: str,
|
||||
shell: Shell,
|
||||
skip_nodes: Optional[list[str]] = None,
|
||||
wallet_config: Optional[str] = None,
|
||||
) -> list[str]:
|
||||
"""
|
||||
The function returns list of nodes which store
|
||||
|
@ -120,7 +125,13 @@ def get_nodes_with_object(
|
|||
for node in nodes_to_search:
|
||||
try:
|
||||
res = neofs_verbs.head_object(
|
||||
wallet, cid, oid, shell=shell, endpoint=node, is_direct=True
|
||||
wallet,
|
||||
cid,
|
||||
oid,
|
||||
shell=shell,
|
||||
endpoint=node,
|
||||
is_direct=True,
|
||||
wallet_config=wallet_config,
|
||||
)
|
||||
if res is not None:
|
||||
logger.info(f"Found object {oid} on node {node}")
|
||||
|
|
|
@ -64,7 +64,7 @@ STORAGE_WALLET_PATH_4 = os.getenv(
|
|||
"STORAGE_WALLET_PATH_4", os.path.join(DEVENV_PATH, "services", "storage", "wallet04.json")
|
||||
)
|
||||
STORAGE_WALLET_PATH = STORAGE_WALLET_PATH_1
|
||||
STORAGE_WALLET_PASS = os.getenv("STORAGE_WALLET_PASS", "")
|
||||
|
||||
|
||||
NEOFS_NETMAP_DICT = {
|
||||
"s01": {
|
||||
|
@ -107,7 +107,6 @@ MAINNET_SINGLE_ADDR = os.getenv("MAINNET_SINGLE_ADDR", "NfgHwwTi3wHAS8aFAN243C5v
|
|||
MAINNET_WALLET_PASS = os.getenv("MAINNET_WALLET_PASS", "one")
|
||||
|
||||
IR_WALLET_PATH = os.getenv("IR_WALLET_PATH", os.path.join(DEVENV_PATH, "services", "ir", "az.json"))
|
||||
IR_WALLET_PASS = os.getenv("IR_WALLET_PASS", "one")
|
||||
|
||||
S3_GATE_WALLET_PATH = os.getenv(
|
||||
"S3_GATE_WALLET_PATH", os.path.join(DEVENV_PATH, "services", "s3_gate", "wallet.json")
|
||||
|
@ -130,15 +129,3 @@ S3_GATE_SERVICE_NAME_REGEX = r"s3-gate\d\d"
|
|||
WALLET_CONFIG = os.path.join(os.getcwd(), "wallet_config.yml")
|
||||
with open(WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": WALLET_PASS}, file)
|
||||
|
||||
STORAGE_WALLET_CONFIG = os.path.join(os.getcwd(), "storage_wallet_config.yml")
|
||||
with open(STORAGE_WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": STORAGE_WALLET_PASS}, file)
|
||||
|
||||
MAINNET_WALLET_CONFIG = os.path.join(os.getcwd(), "mainnet_wallet_config.yml")
|
||||
with open(MAINNET_WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": MAINNET_WALLET_PASS}, file)
|
||||
|
||||
IR_WALLET_CONFIG = os.path.join(os.getcwd(), "ir_wallet_config.yml")
|
||||
with open(IR_WALLET_CONFIG, "w") as file:
|
||||
yaml.dump({"password": IR_WALLET_PASS}, file)
|
||||
|
|
Loading…
Reference in a new issue