2023-05-03 08:45:44 +00:00
|
|
|
import logging
|
|
|
|
import os.path
|
|
|
|
import random
|
|
|
|
|
|
|
|
import allure
|
|
|
|
import pytest
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
|
2023-10-12 07:30:13 +00:00
|
|
|
from frostfs_testlib.steps.cli.container import StorageContainer, StorageContainerInfo, create_container
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.steps.cli.object import get_object
|
2023-06-09 06:21:56 +00:00
|
|
|
from frostfs_testlib.steps.node_management import check_node_in_map, check_node_not_in_map
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.storage.cluster import ClusterNode, StorageNode
|
2023-06-09 06:21:56 +00:00
|
|
|
from frostfs_testlib.storage.controllers import ClusterStateController
|
2023-08-02 11:54:03 +00:00
|
|
|
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.storage.dataclasses.storage_object_info import StorageObjectInfo
|
|
|
|
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
|
|
|
|
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
|
|
|
|
from frostfs_testlib.testing.test_control import wait_for_success
|
2023-11-22 14:26:59 +00:00
|
|
|
from frostfs_testlib.utils.failover_utils import wait_object_replication
|
2023-05-15 09:59:33 +00:00
|
|
|
from frostfs_testlib.utils.file_utils import get_file_hash
|
|
|
|
from pytest import FixtureRequest
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger("NeoLogger")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.failover
|
|
|
|
@pytest.mark.failover_server
|
|
|
|
class TestFailoverServer(ClusterTestBase):
|
|
|
|
@wait_for_success(max_wait_time=120, interval=1)
|
|
|
|
def wait_node_not_in_map(self, *args, **kwargs):
|
|
|
|
check_node_not_in_map(*args, **kwargs)
|
|
|
|
|
|
|
|
@wait_for_success(max_wait_time=120, interval=1)
|
|
|
|
def wait_node_in_map(self, *args, **kwargs):
|
|
|
|
check_node_in_map(*args, **kwargs)
|
|
|
|
|
|
|
|
@allure.step("Create {count_containers} containers and {count_files} objects")
|
|
|
|
@pytest.fixture
|
|
|
|
def containers(
|
|
|
|
self,
|
|
|
|
request: FixtureRequest,
|
|
|
|
default_wallet: str,
|
|
|
|
) -> list[StorageContainer]:
|
|
|
|
|
2023-09-07 10:40:42 +00:00
|
|
|
placement_rule = "REP 2 CBF 2 SELECT 2 FROM *"
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
containers = []
|
|
|
|
|
|
|
|
for _ in range(request.param):
|
|
|
|
cont_id = create_container(
|
|
|
|
default_wallet,
|
|
|
|
shell=self.shell,
|
|
|
|
endpoint=self.cluster.default_rpc_endpoint,
|
|
|
|
rule=placement_rule,
|
|
|
|
basic_acl=PUBLIC_ACL,
|
|
|
|
)
|
2023-05-15 09:59:33 +00:00
|
|
|
wallet = WalletInfo(path=default_wallet)
|
2023-05-03 08:45:44 +00:00
|
|
|
storage_cont_info = StorageContainerInfo(id=cont_id, wallet_file=wallet)
|
|
|
|
containers.append(
|
2023-10-12 07:30:13 +00:00
|
|
|
StorageContainer(storage_container_info=storage_cont_info, shell=self.shell, cluster=self.cluster)
|
2023-05-03 08:45:44 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return containers
|
|
|
|
|
|
|
|
@allure.step("Create object and delete after test")
|
|
|
|
@pytest.fixture(scope="class")
|
|
|
|
def storage_objects(
|
2023-06-09 06:21:56 +00:00
|
|
|
self,
|
|
|
|
request: FixtureRequest,
|
|
|
|
containers: list[StorageContainer],
|
2023-08-02 11:54:03 +00:00
|
|
|
simple_object_size: ObjectSize,
|
|
|
|
complex_object_size: ObjectSize,
|
2023-05-03 08:45:44 +00:00
|
|
|
) -> StorageObjectInfo:
|
2023-06-09 06:21:56 +00:00
|
|
|
count_object = request.param
|
2023-08-02 11:54:03 +00:00
|
|
|
object_sizes = [simple_object_size, complex_object_size]
|
2023-11-22 14:26:59 +00:00
|
|
|
object_list: list[StorageObjectInfo] = []
|
2023-05-03 08:45:44 +00:00
|
|
|
for cont in containers:
|
2023-06-09 06:21:56 +00:00
|
|
|
for _ in range(count_object):
|
2023-08-02 11:54:03 +00:00
|
|
|
object_list.append(cont.generate_object(size=random.choice(object_sizes).value))
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
for storage_object in object_list:
|
|
|
|
os.remove(storage_object.file_path)
|
|
|
|
|
2023-06-19 04:43:48 +00:00
|
|
|
yield object_list
|
|
|
|
|
2023-05-03 08:45:44 +00:00
|
|
|
@allure.step("Select random node to stop and start it after test")
|
|
|
|
@pytest.fixture
|
2023-06-09 06:21:56 +00:00
|
|
|
def node_to_stop(
|
|
|
|
self, node_under_test: ClusterNode, cluster_state_controller: ClusterStateController
|
|
|
|
) -> ClusterNode:
|
|
|
|
yield node_under_test
|
|
|
|
with allure.step(f"start {node_under_test.storage_node}"):
|
|
|
|
cluster_state_controller.start_stopped_hosts()
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
@allure.step("Upload object with nodes and compare")
|
|
|
|
def get_corrupted_objects_list(
|
|
|
|
self, nodes: list[StorageNode], storage_objects: list[StorageObjectInfo]
|
|
|
|
) -> list[StorageObjectInfo]:
|
|
|
|
corrupted_objects = []
|
|
|
|
for node in nodes:
|
|
|
|
for storage_object in storage_objects:
|
|
|
|
got_file_path = get_object(
|
|
|
|
storage_object.wallet_file_path,
|
|
|
|
storage_object.cid,
|
|
|
|
storage_object.oid,
|
|
|
|
endpoint=node.get_rpc_endpoint(),
|
|
|
|
shell=self.shell,
|
|
|
|
timeout="60s",
|
|
|
|
)
|
|
|
|
if storage_object.file_hash != get_file_hash(got_file_path):
|
|
|
|
corrupted_objects.append(storage_object)
|
|
|
|
os.remove(got_file_path)
|
|
|
|
|
|
|
|
return corrupted_objects
|
|
|
|
|
|
|
|
def check_objects_replication(
|
|
|
|
self, storage_objects: list[StorageObjectInfo], storage_nodes: list[StorageNode]
|
|
|
|
) -> None:
|
|
|
|
for storage_object in storage_objects:
|
|
|
|
wait_object_replication(
|
|
|
|
storage_object.cid,
|
|
|
|
storage_object.oid,
|
|
|
|
2,
|
|
|
|
shell=self.shell,
|
|
|
|
nodes=storage_nodes,
|
|
|
|
sleep_interval=45,
|
|
|
|
attempts=60,
|
|
|
|
)
|
|
|
|
|
|
|
|
@allure.title("Full shutdown node")
|
2023-06-09 06:21:56 +00:00
|
|
|
@pytest.mark.parametrize("containers, storage_objects", [(5, 10)], indirect=True)
|
2023-05-03 08:45:44 +00:00
|
|
|
def test_complete_node_shutdown(
|
|
|
|
self,
|
|
|
|
storage_objects: list[StorageObjectInfo],
|
|
|
|
node_to_stop: ClusterNode,
|
2023-06-09 06:21:56 +00:00
|
|
|
cluster_state_controller: ClusterStateController,
|
2023-05-03 08:45:44 +00:00
|
|
|
):
|
|
|
|
|
|
|
|
with allure.step(f"Remove {node_to_stop} from the list of nodes"):
|
|
|
|
alive_nodes = list(set(self.cluster.cluster_nodes) - {node_to_stop})
|
|
|
|
|
|
|
|
storage_nodes = [cluster.storage_node for cluster in alive_nodes]
|
|
|
|
|
2023-11-22 14:26:59 +00:00
|
|
|
with allure.step("Tick epoch and wait for 2 blocks"):
|
|
|
|
self.tick_epochs(1, storage_nodes[0], wait_block=2)
|
2023-05-03 08:45:44 +00:00
|
|
|
|
2023-11-22 14:26:59 +00:00
|
|
|
with allure.step(f"Stop node"):
|
|
|
|
cluster_state_controller.stop_node_host(node=node_to_stop, mode="hard")
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
with allure.step("Verify that there are no corrupted objects"):
|
|
|
|
corrupted_objects_list = self.get_corrupted_objects_list(storage_nodes, storage_objects)
|
|
|
|
|
|
|
|
assert not corrupted_objects_list
|
|
|
|
|
|
|
|
with allure.step(f"check {node_to_stop.storage_node} in map"):
|
2023-10-12 07:30:13 +00:00
|
|
|
self.wait_node_in_map(node_to_stop.storage_node, self.shell, alive_node=storage_nodes[0])
|
2023-05-03 08:45:44 +00:00
|
|
|
|
2023-10-12 07:30:13 +00:00
|
|
|
count_tick_epoch = int(alive_nodes[0].ir_node.get_netmap_cleaner_threshold()) + 4
|
2023-05-19 12:26:15 +00:00
|
|
|
|
2023-11-22 14:26:59 +00:00
|
|
|
with allure.step(f"Tick {count_tick_epoch} epochs and wait for 2 blocks"):
|
|
|
|
self.tick_epochs(count_tick_epoch, wait_block=2)
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
with allure.step(f"Check {node_to_stop} in not map"):
|
2023-10-12 07:30:13 +00:00
|
|
|
self.wait_node_not_in_map(node_to_stop.storage_node, self.shell, alive_node=storage_nodes[0])
|
2023-05-03 08:45:44 +00:00
|
|
|
|
2023-10-12 07:30:13 +00:00
|
|
|
with allure.step(f"Verify that there are no corrupted objects after {count_tick_epoch} epoch"):
|
2023-05-03 08:45:44 +00:00
|
|
|
corrupted_objects_list = self.get_corrupted_objects_list(storage_nodes, storage_objects)
|
|
|
|
assert not corrupted_objects_list
|
|
|
|
|
|
|
|
@allure.title("Temporarily disable a node")
|
2023-06-09 06:21:56 +00:00
|
|
|
@pytest.mark.parametrize("containers, storage_objects", [(5, 10)], indirect=True)
|
2023-05-03 08:45:44 +00:00
|
|
|
def test_temporarily_disable_a_node(
|
|
|
|
self,
|
|
|
|
storage_objects: list[StorageObjectInfo],
|
2023-06-09 06:21:56 +00:00
|
|
|
node_to_stop: ClusterNode,
|
|
|
|
cluster_state_controller: ClusterStateController,
|
2023-05-03 08:45:44 +00:00
|
|
|
):
|
|
|
|
with allure.step(f"Remove {node_to_stop} from the list of nodes"):
|
|
|
|
storage_nodes = list(set(self.cluster.storage_nodes) - {node_to_stop.storage_node})
|
|
|
|
|
2023-11-22 14:26:59 +00:00
|
|
|
with allure.step("Tick epoch and wait for 2 blocks"):
|
|
|
|
self.tick_epochs(1, storage_nodes[0], wait_block=2)
|
2023-05-03 08:45:44 +00:00
|
|
|
|
2023-11-22 14:26:59 +00:00
|
|
|
with allure.step(f"Stop node"):
|
|
|
|
cluster_state_controller.stop_node_host(node=node_to_stop, mode="hard")
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
with allure.step("Verify that there are no corrupted objects"):
|
|
|
|
corrupted_objects_list = self.get_corrupted_objects_list(storage_nodes, storage_objects)
|
|
|
|
assert not corrupted_objects_list
|
|
|
|
|
|
|
|
with allure.step(f"Check {node_to_stop} in map"):
|
2023-10-12 07:30:13 +00:00
|
|
|
self.wait_node_in_map(node_to_stop.storage_node, self.shell, alive_node=storage_nodes[0])
|
2023-05-03 08:45:44 +00:00
|
|
|
|
2023-06-09 06:21:56 +00:00
|
|
|
cluster_state_controller.start_node_host(node_to_stop)
|
2023-05-03 08:45:44 +00:00
|
|
|
|
|
|
|
with allure.step("Verify that there are no corrupted objects"):
|
|
|
|
corrupted_objects_list = self.get_corrupted_objects_list(storage_nodes, storage_objects)
|
|
|
|
assert not corrupted_objects_list
|