frostfs-testcases/pytest_tests/testsuites/network/test_node_management.py
Vladimir Domnich d9d74baa72 Add test suites for acl, container and node management
Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>

commit f7c68cfb423e3213179521954dccb6053fc6382d
Merge: e234b61 99bfe6b
    Merge branch 'avolkov/add_ssh' into internal_tmp_b

commit 99bfe6b56cd75590f868313910068cf1a80bd43f
    Tick one more epoch.

commit bd70bc49391d578cdda727edb4dcd181b832bf1e
    Start nodes in case of test fail.

commit b3888ec62cfc3c18b1dff58962a94a3094342186
    Catch json decode error.

commit c18e415b783ec3e4ce804f43c19246240c186a97
    Add ssh-key access.

commit 7dbdeb653b7d5b7ab3874b546e05a48b502c2460
    Add some tests.

commit 844367c68638c7f97ba4860dd0069c07f499d66d
    Add some tests for nodes management.

commit 1b84b37048dcd3cc0888aa54639975fc11fb2d75
    Add some tests for nodes management.

commit b30c1336a6919e0c8e500bdf2a9be3d5a14470ea
    Add ssh execution option.

commit 2df40eca74ee20bd668778715185ffddda63cb05
    Change AWS cli v1 to cli v2.

commit 7403da3d7c2a5963cfbb12b7c0f3d1d641f52a7e
    Change AWS cli v1 to cli v2.

commit b110dcdb655a585e6c53e6ebc3eae7bf1f1e792f
    Change AWS cli v1 to cli v2.

commit 6183756a4c064c932ee193c2e08a79343017fa49
    Change AWS cli v1 to cli v2.

commit 398006544d60896faa3fc6e6a9dbb51ada06759c
    Fix container run.

commit e7202136dabbe7e2d3da508e0a2ec55a0d5cb67a
    Added tests with AWS CLI.

commit 042e1478ee1fd700c8572cbc6d0d9e6b312b8e8d
    Fix PR comments.

commit e234b61dbb9b8b10812e069322ab03615af0d44e
    Add debug for env.

commit 14febd06713dc03a8207bb80384acb4a7d32df0e
    Move env variables for pytest docker into env file.

commit bafdc6131b5ac855a43b672be194cde2ccf6f75b
    Move env variables for pytest docker into env file.

commit 27c2c6b11f51d2e3c085d44b814cb4c00f81b376
    Move env variables for pytest docker into env file.

commit e4db4948978e092adb83aeacdf06619f5ca2f242
    Merge branch 'master' into avolkov/try_pytest

commit c83a7e625e8daba3a40b65a1d69b2b1323e9ae28
    WIP.

commit 42489bbf8058acd2926cdb04074dc9a8ff86a0a0
    Merge branch 'avolkov/try_pytest' into internal_tmp_b

commit 62526d94dc2bf72372125bea119fa66f670cf7e1
    Improve allure attachments.

commit 4564dae697cb069ac45bc4ba7eb0b5bbdcf1d153
    Merge branch 'avolkov/try_pytest' into internal_tmp_b

commit ab65810b23410ca7382ed4bdd257addfa6619659
    Added tests for S3 API.

commit 846c495a846c977f3e5f0bada01e5a9691a81e3d
    Let's get NEOFS_IR_CONTRACTS_NEOFS from env.

commit c39bd88568b70ffcb76b76d68531b17d3747829d
    Added S3 test for versioning.

commit d7c9f351abc7e02d4ebf162475604a2d6b46e712
    Merge remote-tracking branch 'origin/avolkov/try_pytest' into internal_tmp_b

commit bfbed22a50ce4cb6a49de383cfef66452ba9f4c1
    Added some tests for S3 API and curl tests for HTTP.

commit 1c49def3ddd0b3f7cf97f131e269ad465c70a680
    Add yadro submodule

commit 2a91685f9108101ab523e05cc9287d0f5a20196b
    Fix.

commit 33fc2813e205766e69ef74a42a10850db6c63ce6
    Add debug.

commit aaaceca59e4c67253ecd4a741667b7327d1fb679
    Add env variables for data nodes.

commit 001cb26bcc22c8543fb2672564e898928d20622b
Merge: b48a87d c70da26
    Merge branch 'avolkov/try_pytest' into tmp_b

commit b48a87d9a09309fea671573ba6cf303c31b11b6a
    Added submodule

commit c70da265d319950977774e34740276f324eb57a7
    Added tests for S3 bucket API.

commit 3d335abe6de45d1859454f1ddf85a97514667b8f
    Added tests for S3 object API.

commit 2ac829c700f5bc20c28953f1d40cd953fed8b390
    flake8 changes for python_keywords module.

commit 2de5963e96b13a5e944906b695e5d9c0829de9ad
    Add pytest tests.

commit 4472c079b9dfd979b7c101bea32893c80cb1fe57
    Add pytest tests.

Signed-off-by: a.y.volkov <a.y.volkov@yadro.com>
2022-08-05 13:29:30 +03:00

322 lines
14 KiB
Python

import logging
from random import choice
from time import sleep
import allure
import pytest
from common import (COMPLEX_OBJ_SIZE, MAINNET_BLOCK_TIME, NEOFS_CONTRACT_CACHE_TIMEOUT,
NEOFS_NETMAP_DICT, SHARD_0_GC_SLEEP)
from contract_keywords import tick_epoch
from utility_keywords import generate_file
from python_keywords.container import create_container, get_container
from python_keywords.neofs_verbs import (delete_object, get_object,
head_object, put_object)
from python_keywords.node_management import (drop_object, get_netmap_snapshot,
get_locode,
node_healthcheck,
node_set_status, node_shard_list,
node_shard_set_mode,
start_nodes_remote,
stop_nodes_remote)
from storage_policy import get_nodes_with_object, get_simple_object_copies
from utility import robot_time_to_int
from wellknown_acl import PUBLIC_ACL
from utility import placement_policy_from_container
logger = logging.getLogger('NeoLogger')
@pytest.fixture
@allure.title('Create container and pick the node with data')
def crate_container_and_pick_node(create_remote_connection, prepare_wallet_and_deposit):
wallet = prepare_wallet_and_deposit
file_path = generate_file()
placement_rule = 'REP 1 IN X CBF 1 SELECT 1 FROM * AS X'
cid = create_container(wallet, rule=placement_rule, basic_acl=PUBLIC_ACL)
oid = put_object(wallet, file_path, cid)
nodes = get_nodes_with_object(wallet, cid, oid)
assert len(nodes) == 1
node = nodes[0]
node_name = choice([node_name for node_name, params in NEOFS_NETMAP_DICT.items() if params.get('rpc') == node])
yield cid, node_name
shards = node_shard_list(create_remote_connection, node_name)
assert shards
for shard in shards:
node_shard_set_mode(create_remote_connection, node_name, shard, 'read-write')
node_shard_list(create_remote_connection, node_name)
@pytest.fixture
def start_node_if_needed(create_remote_connection):
yield
try:
start_nodes_remote(create_remote_connection, list(NEOFS_NETMAP_DICT.keys()))
except Exception as err:
logger.error(f'Node start fails with error:\n{err}')
@allure.title('Control Operations with storage nodes')
@pytest.mark.node_mgmt
def test_nodes_management(prepare_tmp_dir, create_remote_connection):
"""
This test checks base control operations with storage nodes (healthcheck, netmap-snapshot, set-status).
"""
random_node = choice(list(NEOFS_NETMAP_DICT))
alive_node = choice([node for node in NEOFS_NETMAP_DICT if node != random_node])
snapshot = get_netmap_snapshot(create_remote_connection, node_name=alive_node)
assert random_node in snapshot, f'Expected node {random_node} in netmap'
with allure.step('Run health check for all storage nodes'):
for node_name in NEOFS_NETMAP_DICT.keys():
health_check = node_healthcheck(create_remote_connection, node_name)
assert health_check.health_status == 'READY' and health_check.network_status == 'ONLINE'
with allure.step(f'Move node {random_node} to offline state'):
node_set_status(create_remote_connection, random_node, status='offline')
sleep(robot_time_to_int(MAINNET_BLOCK_TIME))
tick_epoch()
with allure.step(f'Check node {random_node} went to offline'):
health_check = node_healthcheck(create_remote_connection, random_node)
assert health_check.health_status == 'READY' and health_check.network_status == 'STATUS_UNDEFINED'
snapshot = get_netmap_snapshot(create_remote_connection, node_name=alive_node)
assert random_node 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(create_remote_connection, random_node, status='online')
sleep(robot_time_to_int(MAINNET_BLOCK_TIME))
tick_epoch()
with allure.step(f'Check node {random_node} went to online'):
health_check = node_healthcheck(create_remote_connection, random_node)
assert health_check.health_status == 'READY' and health_check.network_status == 'ONLINE'
snapshot = get_netmap_snapshot(create_remote_connection, node_name=alive_node)
assert random_node in snapshot, f'Expected node {random_node} in netmap'
@pytest.mark.parametrize('placement_rule,expected_copies', [
('REP 2 IN X CBF 2 SELECT 2 FROM * AS X', 2),
('REP 2 IN X CBF 1 SELECT 2 FROM * AS X', 2),
('REP 3 IN X CBF 1 SELECT 3 FROM * AS X', 3),
('REP 1 IN X CBF 1 SELECT 1 FROM * AS X', 1),
('REP 1 IN X CBF 2 SELECT 1 FROM * AS X', 1),
('REP 4 IN X CBF 1 SELECT 4 FROM * AS X', 4),
('REP 2 IN X CBF 1 SELECT 4 FROM * AS X', 2),
])
@pytest.mark.node_mgmt
@allure.title('Test object copies based on placement policy')
def test_placement_policy(prepare_wallet_and_deposit, placement_rule, expected_copies):
"""
This test checks object's copies based on container's placement policy.
"""
wallet = prepare_wallet_and_deposit
file_path = generate_file()
validate_object_copies(wallet, placement_rule, file_path, expected_copies)
@pytest.mark.parametrize('placement_rule,expected_copies,nodes', [
('REP 4 IN X CBF 1 SELECT 4 FROM * AS X', 4, ['s01', 's02', 's03', 's04']),
('REP 1 IN LOC_PLACE CBF 1 SELECT 1 FROM LOC_SW AS LOC_PLACE FILTER Country EQ Sweden AS LOC_SW', 1, ['s03']),
("REP 1 CBF 1 SELECT 1 FROM LOC_SPB FILTER 'UN-LOCODE' EQ 'RU LED' AS LOC_SPB", 1, ['s02']),
("REP 1 IN LOC_SPB_PLACE REP 1 IN LOC_MSK_PLACE CBF 1 SELECT 1 FROM LOC_SPB AS LOC_SPB_PLACE "
"SELECT 1 FROM LOC_MSK AS LOC_MSK_PLACE "
"FILTER 'UN-LOCODE' EQ 'RU LED' AS LOC_SPB FILTER 'UN-LOCODE' EQ 'RU MOW' AS LOC_MSK", 2, ['s01', 's02']),
('REP 4 CBF 1 SELECT 4 FROM LOC_EU FILTER Continent EQ Europe AS LOC_EU', 4, ['s01', 's02', 's03', 's04']),
("REP 1 CBF 1 SELECT 1 FROM LOC_SPB "
"FILTER 'UN-LOCODE' NE 'RU MOW' AND 'UN-LOCODE' NE 'SE STO' AND 'UN-LOCODE' NE 'FI HEL' AS LOC_SPB", 1, ['s02']),
("REP 2 CBF 1 SELECT 2 FROM LOC_RU FILTER SubDivCode NE 'AB' AND SubDivCode NE '18' AS LOC_RU", 2, ['s01', 's02']),
("REP 2 CBF 1 SELECT 2 FROM LOC_RU FILTER Country EQ 'Russia' AS LOC_RU", 2, ['s01', 's02']),
("REP 2 CBF 1 SELECT 2 FROM LOC_EU FILTER Country NE 'Russia' AS LOC_EU", 2, ['s03', 's04']),
])
@pytest.mark.node_mgmt
@allure.title('Test object copies and storage nodes based on placement policy')
def test_placement_policy_with_nodes(prepare_wallet_and_deposit, placement_rule, expected_copies, nodes):
"""
Based on container's placement policy check that storage nodes are piked correctly and object has
correct copies amount.
"""
wallet = prepare_wallet_and_deposit
file_path = generate_file()
cid, oid, found_nodes = validate_object_copies(wallet, placement_rule, file_path, expected_copies)
expected_nodes = [NEOFS_NETMAP_DICT[node_name].get('rpc') for node_name in nodes]
assert set(found_nodes) == set(expected_nodes), f'Expected nodes {expected_nodes}, got {found_nodes}'
@pytest.mark.parametrize('placement_rule,expected_copies', [
('REP 2 IN X CBF 2 SELECT 6 FROM * AS X', 2),
])
@pytest.mark.node_mgmt
@allure.title('Negative cases for placement policy')
def test_placement_policy_negative(prepare_wallet_and_deposit, placement_rule, expected_copies):
"""
Negative test for placement policy.
"""
wallet = prepare_wallet_and_deposit
file_path = generate_file()
with pytest.raises(RuntimeError, match='.*not enough nodes to SELECT from.*'):
validate_object_copies(wallet, placement_rule, file_path, expected_copies)
@pytest.mark.node_mgmt
@allure.title('NeoFS object replication on node failover')
def test_replication(prepare_wallet_and_deposit, create_remote_connection, start_node_if_needed):
"""
Test checks object replication on storage not failover and come back.
"""
wallet = prepare_wallet_and_deposit
file_path = generate_file()
expected_nodes_count = 2
cid = create_container(wallet, basic_acl=PUBLIC_ACL)
oid = put_object(wallet, file_path, cid)
nodes = get_nodes_with_object(wallet, cid, oid)
assert len(nodes) == expected_nodes_count, f'Expected {expected_nodes_count} copies, got {len(nodes)}'
node_names = [name for name, config in NEOFS_NETMAP_DICT.items() if config.get('rpc') in nodes]
stopped_nodes = stop_nodes_remote(create_remote_connection, 1, node_names)
wait_for_expected_object_copies(wallet, cid, oid)
start_nodes_remote(create_remote_connection, stopped_nodes)
tick_epoch()
for node_name in node_names:
wait_for_node_go_online(create_remote_connection, node_name)
wait_for_expected_object_copies(wallet, cid, oid)
@pytest.mark.node_mgmt
@allure.title('NeoFS object could be dropped using control command')
def test_drop_object(prepare_wallet_and_deposit, create_remote_connection):
"""
Test checks object could be dropped using `neofs-cli control drop-objects` command.
"""
wallet = prepare_wallet_and_deposit
file_path_simple, file_path_complex = generate_file(), generate_file(COMPLEX_OBJ_SIZE)
locode = get_locode()
rule = f"REP 1 CBF 1 SELECT 1 FROM * FILTER 'UN-LOCODE' EQ '{locode}' AS LOC"
cid = create_container(wallet, rule=rule)
oid_simple = put_object(wallet, file_path_simple, cid)
oid_complex = put_object(wallet, file_path_complex, cid)
for oid in (oid_simple, oid_complex):
get_object(wallet, cid, oid)
head_object(wallet, cid, oid)
nodes = get_nodes_with_object(wallet, cid, oid_simple)
node_name = choice([name for name, config in NEOFS_NETMAP_DICT.items() if config.get('rpc') in nodes])
for oid in (oid_simple, oid_complex):
with allure.step(f'Drop object {oid}'):
get_object(wallet, cid, oid)
head_object(wallet, cid, oid)
drop_object(create_remote_connection, node_name, cid, oid)
wait_for_obj_dropped(wallet, cid, oid, get_object)
wait_for_obj_dropped(wallet, cid, oid, head_object)
@pytest.mark.node_mgmt
@pytest.mark.skip(reason='Need to clarify scenario')
@allure.title('Control Operations with storage nodes')
def test_shards(prepare_wallet_and_deposit, create_remote_connection, crate_container_and_pick_node):
"""
This test checks base control operations with storage nodes (healthcheck, netmap-snapshot, set-status).
"""
wallet = prepare_wallet_and_deposit
file_path = generate_file()
cid, node_name = crate_container_and_pick_node
original_oid = put_object(wallet, file_path, cid)
# for mode in ('read-only', 'degraded'):
for mode in ('degraded',):
shards = node_shard_list(create_remote_connection, node_name)
assert shards
for shard in shards:
node_shard_set_mode(create_remote_connection, node_name, shard, mode)
shards = node_shard_list(create_remote_connection, node_name)
assert shards
with pytest.raises(RuntimeError):
put_object(wallet, file_path, cid)
with pytest.raises(RuntimeError):
delete_object(wallet, cid, original_oid)
# head_object(wallet, cid, original_oid)
get_object(wallet, cid, original_oid)
for shard in shards:
node_shard_set_mode(create_remote_connection, node_name, shard, 'read-write')
shards = node_shard_list(create_remote_connection, node_name)
assert shards
oid = put_object(wallet, file_path, cid)
delete_object(wallet, cid, oid)
@allure.step('Validate object has {expected_copies} copies')
def validate_object_copies(wallet: str, placement_rule: str, file_path: str, expected_copies: int):
cid = create_container(wallet, rule=placement_rule, basic_acl=PUBLIC_ACL)
got_policy = placement_policy_from_container(get_container(wallet, cid, flag=''))
assert got_policy == placement_rule.replace('\'', ''), \
f'Expected \n{placement_rule} and got policy \n{got_policy} are the same'
oid = put_object(wallet, file_path, cid)
nodes = get_nodes_with_object(wallet, cid, oid)
assert len(nodes) == expected_copies, f'Expected {expected_copies} copies, got {len(nodes)}'
return cid, oid, nodes
@allure.step('Wait for node {node_name} goes online')
def wait_for_node_go_online(create_remote_connection, node_name: str):
timeout, attempts = 5, 20
for _ in range(attempts):
try:
health_check = node_healthcheck(create_remote_connection, node_name)
assert health_check.health_status == 'READY' and health_check.network_status == 'ONLINE'
return
except Exception as err:
logger.warning(f'Node {node_name} is not online:\n{err}')
sleep(timeout)
continue
raise AssertionError(f'Node {node_name} does not go online during timeout {timeout * attempts}')
@allure.step('Wait for {expected_copies} object copies in the wallet')
def wait_for_expected_object_copies(wallet: str, cid: str, oid: str, expected_copies: int = 2):
for i in range(2):
copies = get_simple_object_copies(wallet, cid, oid)
if copies == expected_copies:
break
tick_epoch()
sleep(robot_time_to_int(NEOFS_CONTRACT_CACHE_TIMEOUT))
else:
raise AssertionError(f'There are no {expected_copies} copies during time')
@allure.step('Wait for object to be dropped')
def wait_for_obj_dropped(wallet: str, cid: str, oid: str, checker):
for _ in range(3):
try:
checker(wallet, cid, oid)
sleep(robot_time_to_int(SHARD_0_GC_SLEEP))
except Exception as err:
if 'object not found' in str(err):
break
else:
raise AssertionError(f'Object {oid} is not dropped from node')