diff --git a/tests/storage/constants.py b/tests/storage/constants.py index 7b110c3e17..4b223f9452 100644 --- a/tests/storage/constants.py +++ b/tests/storage/constants.py @@ -22,3 +22,15 @@ TEST_FILE_NAME = "test-file.txt" TEST_FILE_CONTENT = "test-content" + +STORAGE_CLASS_A = "storage_class_a" +STORAGE_CLASS_B = "storage_class_b" + +NO_STORAGE_CLASS_FAILURE_MESSAGE = ( + f"Test failed: {'{storage_class}'} storage class is not deployed. " + f"Available storage classes: {'{cluster_storage_classes_names}'}. " + "Ensure the correct storage_class is set in the global_config, " + "or override it with the pytest params: " + f"--tc={STORAGE_CLASS_A}: " + f"--tc={STORAGE_CLASS_B}:" +) diff --git a/tests/storage/cross_cluster_live_migration/conftest.py b/tests/storage/cross_cluster_live_migration/conftest.py index e62305a044..613fb58352 100644 --- a/tests/storage/cross_cluster_live_migration/conftest.py +++ b/tests/storage/cross_cluster_live_migration/conftest.py @@ -6,6 +6,7 @@ import pytest import requests import yaml +from kubernetes.dynamic import DynamicClient from kubernetes.dynamic.exceptions import NotFoundError from ocp_resources.data_source import DataSource from ocp_resources.datavolume import DataVolume @@ -18,6 +19,7 @@ from ocp_resources.resource import get_client from ocp_resources.route import Route from ocp_resources.secret import Secret +from ocp_resources.storage_class import StorageClass from ocp_resources.storage_map import StorageMap from ocp_resources.virtual_machine_cluster_instancetype import ( VirtualMachineClusterInstancetype, @@ -33,6 +35,7 @@ configure_hco_live_migration_network, get_vm_boot_id_via_console, ) +from tests.storage.utils import get_storage_class_for_storage_migration from utilities.artifactory import ( get_artifactory_config_map, get_artifactory_secret, @@ -344,8 +347,39 @@ def local_cluster_mtv_provider_for_local_cluster(admin_client, mtv_namespace): @pytest.fixture(scope="module") +def remote_cluster_storage_classes_names(remote_admin_client: DynamicClient) -> list[str]: + """Get list of all storage class names available in the remote cluster.""" + return [sc.name for sc in list(StorageClass.get(client=remote_admin_client))] + + +@pytest.fixture(scope="class") +def remote_cluster_source_storage_class( + request: pytest.FixtureRequest, remote_cluster_storage_classes_names: list[str] +) -> str: + """Storage class for creating VMs in the remote cluster before migration.""" + return get_storage_class_for_storage_migration( + storage_class=request.param["source_storage_class"], + cluster_storage_classes_names=remote_cluster_storage_classes_names, + ) + + +@pytest.fixture(scope="class") +def local_cluster_target_storage_class(request: pytest.FixtureRequest, cluster_storage_classes_names: list[str]) -> str: + """Storage class for migrated VMs in the local cluster.""" + return get_storage_class_for_storage_migration( + storage_class=request.param["target_storage_class"], + cluster_storage_classes_names=cluster_storage_classes_names, + ) + + +@pytest.fixture(scope="class") def local_cluster_mtv_storage_map( - admin_client, local_cluster_mtv_provider_for_local_cluster, local_cluster_mtv_provider_for_remote_cluster + admin_client, + local_cluster_mtv_provider_for_local_cluster, + local_cluster_mtv_provider_for_remote_cluster, + unique_suffix, + remote_cluster_source_storage_class, + local_cluster_target_storage_class, ): """ Create a StorageMap resource for MTV migration. @@ -353,13 +387,13 @@ def local_cluster_mtv_storage_map( """ mapping = [ { - "source": {"name": py_config["default_storage_class"]}, - "destination": {"storageClass": py_config["default_storage_class"]}, + "source": {"name": remote_cluster_source_storage_class}, + "destination": {"storageClass": local_cluster_target_storage_class}, } ] with StorageMap( client=admin_client, - name="storage-map", + name=f"storage-map-{unique_suffix}", namespace=local_cluster_mtv_provider_for_local_cluster.namespace, source_provider_name=local_cluster_mtv_provider_for_remote_cluster.name, source_provider_namespace=local_cluster_mtv_provider_for_remote_cluster.namespace, @@ -428,7 +462,10 @@ def remote_cluster_rhel10_data_source(remote_admin_client, remote_cluster_golden @pytest.fixture(scope="class") def vm_for_cclm_from_template_with_data_source( - remote_admin_client, remote_cluster_source_test_namespace, remote_cluster_rhel10_data_source + remote_admin_client, + remote_cluster_source_test_namespace, + remote_cluster_rhel10_data_source, + remote_cluster_source_storage_class, ): with VirtualMachineForTests( name="vm-from-template-and-data-source", @@ -437,7 +474,7 @@ def vm_for_cclm_from_template_with_data_source( os_flavor=OS_FLAVOR_RHEL, data_volume_template=data_volume_template_with_source_ref_dict( data_source=remote_cluster_rhel10_data_source, - storage_class=py_config["default_storage_class"], + storage_class=remote_cluster_source_storage_class, ), memory_guest=Images.Rhel.DEFAULT_MEMORY_SIZE, ) as vm: @@ -447,7 +484,10 @@ def vm_for_cclm_from_template_with_data_source( @pytest.fixture(scope="class") def vm_for_cclm_with_instance_type( - remote_admin_client, remote_cluster_source_test_namespace, remote_cluster_rhel10_data_source + remote_admin_client, + remote_cluster_source_test_namespace, + remote_cluster_rhel10_data_source, + remote_cluster_source_storage_class, ): with VirtualMachineForTests( name="vm-with-instance-type", @@ -458,7 +498,7 @@ def vm_for_cclm_with_instance_type( vm_preference=VirtualMachineClusterPreference(name=RHEL10_PREFERENCE, client=remote_admin_client), data_volume_template=data_volume_template_with_source_ref_dict( data_source=remote_cluster_rhel10_data_source, - storage_class=py_config["default_storage_class"], + storage_class=remote_cluster_source_storage_class, ), ) as vm: vm.start() @@ -489,6 +529,7 @@ def remote_cluster_artifactory_config_map_scope_class(remote_admin_client, remot def vm_for_cclm_windows_with_instance_type( remote_admin_client, remote_cluster_source_test_namespace, + remote_cluster_source_storage_class, remote_cluster_artifactory_secret_scope_class, remote_cluster_artifactory_config_map_scope_class, ): @@ -499,7 +540,7 @@ def vm_for_cclm_windows_with_instance_type( api_name="storage", source="registry", size=Images.Windows.CONTAINER_DISK_DV_SIZE, - storage_class=py_config["default_storage_class"], + storage_class=remote_cluster_source_storage_class, url=f"{get_test_artifact_server_url(schema='registry')}/{WINDOWS_2022[CONTAINER_DISK_IMAGE_PATH_STR]}", secret=remote_cluster_artifactory_secret_scope_class, cert_configmap=remote_cluster_artifactory_config_map_scope_class.name, diff --git a/tests/storage/cross_cluster_live_migration/test_cclm.py b/tests/storage/cross_cluster_live_migration/test_cclm.py index ab0442b2ce..ae988b91a5 100644 --- a/tests/storage/cross_cluster_live_migration/test_cclm.py +++ b/tests/storage/cross_cluster_live_migration/test_cclm.py @@ -1,6 +1,13 @@ +""" +Cross-cluster live migration tests. + +Jira epic: https://redhat.atlassian.net/browse/CNV-50823 # +""" + import pytest +from pytest_testconfig import config as py_config -from tests.storage.constants import TEST_FILE_CONTENT, TEST_FILE_NAME +from tests.storage.constants import STORAGE_CLASS_A, STORAGE_CLASS_B, TEST_FILE_CONTENT, TEST_FILE_NAME from tests.storage.cross_cluster_live_migration.utils import ( assert_vms_are_stopped, assert_vms_can_be_deleted, @@ -13,6 +20,7 @@ TESTS_CLASS_NAME_SEVERAL_VMS = "TestCCLMSeveralVMs" TESTS_CLASS_NAME_WINDOWS_VM = "TestCCLMWindowsWithVTPM" +TESTS_CLASS_NAME_STORAGE_A_TO_B = "TestCCLMFromStorageAtoB" pytestmark = [ pytest.mark.cclm, @@ -25,9 +33,11 @@ @pytest.mark.parametrize( - "vms_for_cclm", + "remote_cluster_source_storage_class, local_cluster_target_storage_class, vms_for_cclm", [ pytest.param( + {"source_storage_class": py_config[STORAGE_CLASS_B]}, + {"target_storage_class": py_config[STORAGE_CLASS_B]}, { "vms_fixtures": [ "vm_for_cclm_from_template_with_data_source", @@ -38,6 +48,7 @@ ], indirect=True, ) +@pytest.mark.usefixtures("remote_cluster_source_storage_class", "local_cluster_target_storage_class") class TestCCLMSeveralVMs: @pytest.mark.polarion("CNV-11995") @pytest.mark.dependency(name=f"{TESTS_CLASS_NAME_SEVERAL_VMS}::test_migrate_vm_from_remote_to_local_cluster") @@ -93,16 +104,18 @@ def test_target_vms_can_be_deleted(self, local_vms_after_cclm_migration): @pytest.mark.parametrize( - "dv_wait_timeout, vms_for_cclm", + "remote_cluster_source_storage_class, local_cluster_target_storage_class, dv_wait_timeout, vms_for_cclm", [ pytest.param( + {"source_storage_class": py_config[STORAGE_CLASS_B]}, + {"target_storage_class": py_config[STORAGE_CLASS_B]}, {"dv_wait_timeout": TIMEOUT_50MIN}, {"vms_fixtures": ["vm_for_cclm_windows_with_instance_type"]}, ) ], indirect=True, ) -@pytest.mark.usefixtures("dv_wait_timeout") +@pytest.mark.usefixtures("remote_cluster_source_storage_class", "local_cluster_target_storage_class", "dv_wait_timeout") class TestCCLMWindowsWithVTPM: @pytest.mark.dependency(name=f"{TESTS_CLASS_NAME_WINDOWS_VM}::test_migrate_windows_vm_from_remote_to_local_cluster") @pytest.mark.polarion("CNV-11999") @@ -139,3 +152,81 @@ def test_source_vms_can_be_deleted(self, vms_for_cclm): @pytest.mark.polarion("CNV-15236") def test_target_vms_can_be_deleted(self, local_vms_after_cclm_migration): assert_vms_can_be_deleted(vms=local_vms_after_cclm_migration) + + +@pytest.mark.parametrize( + "remote_cluster_source_storage_class, local_cluster_target_storage_class, vms_for_cclm", + [ + pytest.param( + {"source_storage_class": py_config[STORAGE_CLASS_A]}, + {"target_storage_class": py_config[STORAGE_CLASS_B]}, + { + "vms_fixtures": [ + "vm_for_cclm_with_instance_type", + ] + }, + ) + ], + indirect=True, +) +@pytest.mark.usefixtures("remote_cluster_source_storage_class", "local_cluster_target_storage_class") +class TestCCLMFromStorageAtoB: + @pytest.mark.polarion("CNV-15955") + @pytest.mark.dependency(name=f"{TESTS_CLASS_NAME_STORAGE_A_TO_B}::test_migrate_vm_from_remote_to_local_cluster") + def test_migrate_vm_from_remote_to_local_cluster( + self, + written_file_to_vms_before_cclm, + vms_boot_id_before_cclm, + mtv_migration, + ): + mtv_migration.wait_for_condition( + condition=mtv_migration.Condition.Type.SUCCEEDED, + status=mtv_migration.Condition.Status.TRUE, + timeout=TIMEOUT_10MIN, + stop_condition=mtv_migration.Status.FAILED, + ) + + @pytest.mark.dependency( + depends=[f"{TESTS_CLASS_NAME_STORAGE_A_TO_B}::test_migrate_vm_from_remote_to_local_cluster"] + ) + @pytest.mark.polarion("CNV-15956") + def test_verify_vms_not_rebooted_after_migration(self, local_vms_after_cclm_migration, vms_boot_id_before_cclm): + verify_vms_boot_id_after_cross_cluster_live_migration( + local_vms=local_vms_after_cclm_migration, initial_boot_id=vms_boot_id_before_cclm + ) + + @pytest.mark.dependency( + depends=[f"{TESTS_CLASS_NAME_STORAGE_A_TO_B}::test_migrate_vm_from_remote_to_local_cluster"] + ) + @pytest.mark.polarion("CNV-15957") + def test_verify_file_persisted_after_migration(self, local_vms_after_cclm_migration): + for vm in local_vms_after_cclm_migration: + check_file_in_vm( + vm=vm, + file_name=TEST_FILE_NAME, + file_content=TEST_FILE_CONTENT, + username=vm.username, + password=vm.password, + ) + + @pytest.mark.dependency( + depends=[f"{TESTS_CLASS_NAME_STORAGE_A_TO_B}::test_migrate_vm_from_remote_to_local_cluster"] + ) + @pytest.mark.polarion("CNV-15958") + def test_source_vms_are_stopped_after_cclm(self, vms_for_cclm): + assert_vms_are_stopped(vms=vms_for_cclm) + + @pytest.mark.dependency( + depends=[f"{TESTS_CLASS_NAME_STORAGE_A_TO_B}::test_migrate_vm_from_remote_to_local_cluster"] + ) + @pytest.mark.polarion("CNV-15954") + def test_compute_live_migrate_vms_after_cclm(self, local_vms_after_cclm_migration): + verify_compute_live_migration_after_cclm(local_vms=local_vms_after_cclm_migration) + + @pytest.mark.polarion("CNV-15959") + def test_source_vms_can_be_deleted(self, vms_for_cclm): + assert_vms_can_be_deleted(vms=vms_for_cclm) + + @pytest.mark.polarion("CNV-15960") + def test_target_vms_can_be_deleted(self, local_vms_after_cclm_migration): + assert_vms_can_be_deleted(vms=local_vms_after_cclm_migration) diff --git a/tests/storage/storage_migration/conftest.py b/tests/storage/storage_migration/conftest.py index ae877c0d4b..3e78012b03 100644 --- a/tests/storage/storage_migration/conftest.py +++ b/tests/storage/storage_migration/conftest.py @@ -21,10 +21,9 @@ ) from tests.storage.storage_migration.utils import ( build_namespaces_spec_for_storage_migration, - get_storage_class_for_storage_migration, wait_for_storage_migration_completed, ) -from tests.storage.utils import create_windows_directory +from tests.storage.utils import create_windows_directory, get_storage_class_for_storage_migration from utilities.artifactory import get_http_image_url from utilities.constants import ( OS_FLAVOR_FEDORA, diff --git a/tests/storage/storage_migration/constants.py b/tests/storage/storage_migration/constants.py index 976e991f5f..965639523a 100644 --- a/tests/storage/storage_migration/constants.py +++ b/tests/storage/storage_migration/constants.py @@ -4,17 +4,5 @@ WINDOWS_FILE_BEFORE_STORAGE_MIGRATION = f"{FILE_BEFORE_STORAGE_MIGRATION}.txt" WINDOWS_FILE_WITH_PATH = f"{WINDOWS_TEST_DIRECTORY_PATH}\\{WINDOWS_FILE_BEFORE_STORAGE_MIGRATION}" -STORAGE_CLASS_A = "storage_class_a" -STORAGE_CLASS_B = "storage_class_b" - -NO_STORAGE_CLASS_FAILURE_MESSAGE = ( - f"Test failed: {'{storage_class}'} storage class is not deployed. " - f"Available storage classes: {'{cluster_storage_classes_names}'}. " - "Ensure the correct storage_class is set in the global_config, " - "or override it with the pytest params: " - f"--tc={STORAGE_CLASS_A}: " - f"--tc={STORAGE_CLASS_B}:" -) - HOTPLUGGED_DEVICE = "/dev/sda" MOUNT_HOTPLUGGED_DEVICE_PATH = "/mnt/hotplug" diff --git a/tests/storage/storage_migration/test_storage_class_migration.py b/tests/storage/storage_migration/test_storage_class_migration.py index 874166b98a..e19e8b252b 100644 --- a/tests/storage/storage_migration/test_storage_class_migration.py +++ b/tests/storage/storage_migration/test_storage_class_migration.py @@ -2,11 +2,10 @@ from pytest_testconfig import config as py_config from tests.os_params import FEDORA_LATEST, FEDORA_LATEST_LABELS +from tests.storage.constants import STORAGE_CLASS_A, STORAGE_CLASS_B from tests.storage.storage_migration.constants import ( CONTENT, FILE_BEFORE_STORAGE_MIGRATION, - STORAGE_CLASS_A, - STORAGE_CLASS_B, WINDOWS_FILE_WITH_PATH, ) from tests.storage.storage_migration.utils import ( diff --git a/tests/storage/storage_migration/utils.py b/tests/storage/storage_migration/utils.py index 7ae133d447..414ad9b73d 100644 --- a/tests/storage/storage_migration/utils.py +++ b/tests/storage/storage_migration/utils.py @@ -1,6 +1,5 @@ import shlex -import pytest from ocp_resources.multi_namespace_virtual_machine_storage_migration import MultiNamespaceVirtualMachineStorageMigration from ocp_resources.persistent_volume_claim import PersistentVolumeClaim from pyhelper_utils.shell import run_ssh_commands @@ -10,7 +9,6 @@ CONTENT, FILE_BEFORE_STORAGE_MIGRATION, MOUNT_HOTPLUGGED_DEVICE_PATH, - NO_STORAGE_CLASS_FAILURE_MESSAGE, ) from tests.storage.utils import check_file_in_vm from utilities.constants import TIMEOUT_2MIN, TIMEOUT_5SEC, TIMEOUT_10MIN, TIMEOUT_10SEC @@ -76,17 +74,6 @@ def verify_storage_migration_succeeded( verify_vm_storage_class_updated(vm=vm, target_storage_class=target_storage_class) -def get_storage_class_for_storage_migration(storage_class: str, cluster_storage_classes_names: list[str]) -> str: - if storage_class in cluster_storage_classes_names: - return storage_class - else: - pytest.fail( - NO_STORAGE_CLASS_FAILURE_MESSAGE.format( - storage_class=storage_class, cluster_storage_classes_names=cluster_storage_classes_names - ) - ) - - def verify_file_in_hotplugged_disk(vm: VirtualMachineForTests, file_name: str, file_content: str) -> None: output = run_ssh_commands( host=vm.ssh_exec, diff --git a/tests/storage/utils.py b/tests/storage/utils.py index 4754b7a8d6..1be2a0deb2 100644 --- a/tests/storage/utils.py +++ b/tests/storage/utils.py @@ -4,6 +4,7 @@ from collections.abc import Generator from contextlib import contextmanager +import pytest import requests from kubernetes.dynamic import DynamicClient from ocp_resources.cluster_role import ClusterRole @@ -23,6 +24,7 @@ from pytest_testconfig import config as py_config from timeout_sampler import TimeoutExpiredError, TimeoutSampler +from tests.storage.constants import NO_STORAGE_CLASS_FAILURE_MESSAGE from utilities import console from utilities.artifactory import ( cleanup_artifactory_secret_and_config_map, @@ -504,3 +506,26 @@ def check_file_in_vm( vm_console.expect(pattern=file_name, timeout=TIMEOUT_20SEC) vm_console.sendline(f"cat {file_name}") vm_console.expect(pattern=file_content, timeout=TIMEOUT_20SEC) + + +def get_storage_class_for_storage_migration(storage_class: str, cluster_storage_classes_names: list[str]) -> str: + """Validate that the requested storage class exists in the cluster. + + Args: + storage_class: Name of the storage class to validate. + cluster_storage_classes_names: List of available storage class names in the cluster. + + Returns: + The validated storage class name if it exists. + + Raises: + pytest.Failed: If the storage class is not found in the cluster. + """ + if storage_class in cluster_storage_classes_names: + return storage_class + + pytest.fail( + NO_STORAGE_CLASS_FAILURE_MESSAGE.format( + storage_class=storage_class, cluster_storage_classes_names=cluster_storage_classes_names + ) + )