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 90e3a8f1cf..2987d6a00c 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, @@ -345,8 +348,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. @@ -354,13 +388,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, @@ -429,7 +463,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", @@ -438,7 +475,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: @@ -448,7 +485,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", @@ -459,7 +499,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() @@ -490,6 +530,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, ): @@ -505,7 +546,7 @@ def vm_for_cclm_windows_with_instance_type( cert_configmap_name=remote_cluster_artifactory_config_map_scope_class.name, ), size=Images.Windows.CONTAINER_DISK_DV_SIZE, - storage_class=py_config["default_storage_class"], + storage_class=remote_cluster_source_storage_class, ) dv.to_dict() dv.res["metadata"].pop("namespace", None) diff --git a/tests/storage/cross_cluster_live_migration/test_cclm.py b/tests/storage/cross_cluster_live_migration/test_cclm.py index ab0442b2ce..8b29a22726 100644 --- a/tests/storage/cross_cluster_live_migration/test_cclm.py +++ b/tests/storage/cross_cluster_live_migration/test_cclm.py @@ -1,6 +1,17 @@ +""" +Cross-cluster live migration tests. + +Jira: https://redhat.atlassian.net/browse/CNV-50823 # +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + 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, @@ -11,8 +22,14 @@ from tests.storage.utils import check_file_in_vm from utilities.constants import TIMEOUT_10MIN, TIMEOUT_50MIN +if TYPE_CHECKING: + from kubernetes.dynamic import DynamicClient + + from utilities.virt import VirtualMachineForTests + 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 +42,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,7 +57,19 @@ ], indirect=True, ) +@pytest.mark.usefixtures("remote_cluster_source_storage_class", "local_cluster_target_storage_class") class TestCCLMSeveralVMs: + """ + Tests for cross-cluster live migration of multiple VMs. + + Preconditions: + - Two OpenShift clusters (source and target) with live migration network configured + - MTV installed on the target cluster with Provider, StorageMap, and NetworkMap configured + - Several running VMs on the source cluster, accessible via console + - Test file written to the source VMs before migration + - Boot IDs recorded for all source VMs before migration + """ + @pytest.mark.polarion("CNV-11995") @pytest.mark.dependency(name=f"{TESTS_CLASS_NAME_SEVERAL_VMS}::test_migrate_vm_from_remote_to_local_cluster") def test_migrate_vm_from_remote_to_local_cluster( @@ -47,6 +78,15 @@ def test_migrate_vm_from_remote_to_local_cluster( vms_boot_id_before_cclm, mtv_migration, ): + """ + Test that multiple VMs can be live migrated from the source cluster to the target cluster. + + Steps: + 1. Wait for the MTV migration to reach Succeeded condition + + Expected: + - Migration succeeds for all VMs + """ mtv_migration.wait_for_condition( condition=mtv_migration.Condition.Type.SUCCEEDED, status=mtv_migration.Condition.Status.TRUE, @@ -57,6 +97,19 @@ def test_migrate_vm_from_remote_to_local_cluster( @pytest.mark.dependency(depends=[f"{TESTS_CLASS_NAME_SEVERAL_VMS}::test_migrate_vm_from_remote_to_local_cluster"]) @pytest.mark.polarion("CNV-11910") def test_verify_vms_not_rebooted_after_migration(self, local_vms_after_cclm_migration, vms_boot_id_before_cclm): + """ + Test that VMs are not rebooted during cross-cluster live migration. + + Preconditions: + - Source VMs successfully migrated to the target cluster + + Steps: + 1. Read boot ID from each migrated VM on the target cluster + 2. Compare current boot IDs with boot IDs recorded before migration + + Expected: + - Boot IDs are unchanged for all migrated VMs + """ verify_vms_boot_id_after_cross_cluster_live_migration( local_vms=local_vms_after_cclm_migration, initial_boot_id=vms_boot_id_before_cclm ) @@ -64,6 +117,18 @@ def test_verify_vms_not_rebooted_after_migration(self, local_vms_after_cclm_migr @pytest.mark.dependency(depends=[f"{TESTS_CLASS_NAME_SEVERAL_VMS}::test_migrate_vm_from_remote_to_local_cluster"]) @pytest.mark.polarion("CNV-14332") def test_verify_file_persisted_after_migration(self, local_vms_after_cclm_migration): + """ + Test that files written before migration are preserved after cross-cluster live migration. + + Preconditions: + - Source VMs successfully migrated to the target cluster + + Steps: + 1. Read the test file from each migrated VM on the target cluster + + Expected: + - File content on each migrated VM equals the content written before migration + """ for vm in local_vms_after_cclm_migration: check_file_in_vm( vm=vm, @@ -76,34 +141,90 @@ def test_verify_file_persisted_after_migration(self, local_vms_after_cclm_migrat @pytest.mark.dependency(depends=[f"{TESTS_CLASS_NAME_SEVERAL_VMS}::test_migrate_vm_from_remote_to_local_cluster"]) @pytest.mark.polarion("CNV-14333") def test_source_vms_are_stopped_after_cclm(self, vms_for_cclm): + """ + Test that source VMs on the source cluster are stopped after cross-cluster live migration. + + Preconditions: + - Source VMs successfully migrated to the target cluster + + Steps: + 1. Check the status of each source VM on the source cluster + + Expected: + - All source VMs are in "Stopped" state + """ assert_vms_are_stopped(vms=vms_for_cclm) @pytest.mark.dependency(depends=[f"{TESTS_CLASS_NAME_SEVERAL_VMS}::test_migrate_vm_from_remote_to_local_cluster"]) @pytest.mark.polarion("CNV-12038") - 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) + def test_compute_live_migrate_vms_after_cclm( + self, admin_client: DynamicClient, local_vms_after_cclm_migration: list[VirtualMachineForTests] + ): + """ + Test that VMs can be compute live migrated within the target cluster after cross-cluster live migration. + + Preconditions: + - Source VMs successfully migrated to the target cluster + + Steps: + 1. Trigger intra-cluster live migration for each migrated VM on the target cluster + 2. Wait for each migration to complete + + Expected: + - All migrated VMs are successfully live migrated within the target cluster + """ + verify_compute_live_migration_after_cclm(client=admin_client, local_vms=local_vms_after_cclm_migration) @pytest.mark.polarion("CNV-14334") def test_source_vms_can_be_deleted(self, vms_for_cclm): + """ + Test that source VMs on the source cluster can be deleted after cross-cluster live migration. + + Steps: + 1. Delete each source VM on the source cluster + + Expected: + - All source VMs are successfully deleted + """ assert_vms_can_be_deleted(vms=vms_for_cclm) @pytest.mark.polarion("CNV-15237") def test_target_vms_can_be_deleted(self, local_vms_after_cclm_migration): + """ + Test that migrated VMs on the target cluster can be deleted after cross-cluster live migration. + + Steps: + 1. Delete each migrated VM on the target cluster + + Expected: + - All migrated VMs are successfully deleted + """ assert_vms_can_be_deleted(vms=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: + """ + Tests for cross-cluster live migration of a Windows VM with vTPM. + + Preconditions: + - Two OpenShift clusters (source and target) with live migration network configured + - MTV installed on the target cluster with Provider, StorageMap, and NetworkMap configured + - Running Windows VM on the source cluster + """ + @pytest.mark.dependency(name=f"{TESTS_CLASS_NAME_WINDOWS_VM}::test_migrate_windows_vm_from_remote_to_local_cluster") @pytest.mark.polarion("CNV-11999") def test_migrate_windows_vm_from_remote_to_local_cluster( @@ -111,6 +232,15 @@ def test_migrate_windows_vm_from_remote_to_local_cluster( booted_vms_for_cclm, mtv_migration, ): + """ + Test that a Windows VM can be live migrated from the source cluster to the target cluster. + + Steps: + 1. Wait for the MTV migration to reach Succeeded condition + + Expected: + - Migration succeeds for the Windows VM + """ mtv_migration.wait_for_condition( condition=mtv_migration.Condition.Type.SUCCEEDED, status=mtv_migration.Condition.Status.TRUE, @@ -123,19 +253,233 @@ def test_migrate_windows_vm_from_remote_to_local_cluster( ) @pytest.mark.polarion("CNV-14335") def test_source_vms_are_stopped_after_cclm(self, vms_for_cclm): + """ + Test that the source Windows VM on the source cluster is stopped after cross-cluster live migration. + + Preconditions: + - Source VM successfully migrated to the target cluster + + Steps: + 1. Wait for the source VM on the source cluster to reach Stopped state + + Expected: + - Source VM is in "Stopped" state + """ wait_for_vms_to_be_stopped(vms=vms_for_cclm) @pytest.mark.dependency( depends=[f"{TESTS_CLASS_NAME_WINDOWS_VM}::test_migrate_windows_vm_from_remote_to_local_cluster"] ) @pytest.mark.polarion("CNV-12474") - def test_compute_live_migrate_windows_vms_after_cclm(self, local_vms_after_cclm_migration): - verify_compute_live_migration_after_cclm(local_vms=local_vms_after_cclm_migration) + def test_compute_live_migrate_windows_vms_after_cclm( + self, admin_client: DynamicClient, local_vms_after_cclm_migration: list[VirtualMachineForTests] + ): + """ + Test that a Windows VM can be compute live migrated within the target cluster after cross-cluster live migration. + + Preconditions: + - Source VM successfully migrated to the target cluster + + Steps: + 1. Trigger intra-cluster live migration for the migrated Windows VM on the target cluster + 2. Wait for migration to complete + + Expected: + - Migrated Windows VM is successfully live migrated within the target cluster + """ + verify_compute_live_migration_after_cclm(client=admin_client, local_vms=local_vms_after_cclm_migration) @pytest.mark.polarion("CNV-14336") def test_source_vms_can_be_deleted(self, vms_for_cclm): + """ + Test that the source Windows VM on the source cluster can be deleted after cross-cluster live migration. + + Steps: + 1. Delete the source VM on the source cluster + + Expected: + - Source VM is successfully deleted + """ assert_vms_can_be_deleted(vms=vms_for_cclm) @pytest.mark.polarion("CNV-15236") def test_target_vms_can_be_deleted(self, local_vms_after_cclm_migration): + """ + Test that the migrated Windows VM on the target cluster can be deleted after cross-cluster live migration. + + Steps: + 1. Delete the migrated VM on the target cluster + + Expected: + - Migrated VM is successfully deleted + """ + 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: + """ + Tests for cross-cluster live migration of a VM across different storage classes. + + Preconditions: + - Two OpenShift clusters (source and target) with live migration network configured + - MTV installed on the target cluster with Provider, StorageMap, and NetworkMap configured + - Running VM on the source cluster, accessible via console + - Different storage classes used on source and target clusters + - Test file written to the source VM before migration + - Boot ID recorded for the source VM before migration + """ + + @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, + ): + """ + Test that a VM can be cross-cluster live migrated when source and target storage classes are different. + + Steps: + 1. Wait for the MTV migration to reach Succeeded condition + + Expected: + - Migration succeeds for the VM + """ + 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): + """ + Test that VM is not rebooted during cross-cluster live migration. + + Preconditions: + - Source VM successfully migrated to the target cluster + + Steps: + 1. Read boot ID from the migrated VM on the target cluster + 2. Compare current boot ID with boot ID recorded before migration + + Expected: + - Boot ID is unchanged for the migrated VM + """ + 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): + """ + Test that files written before migration are preserved after cross-cluster live migration. + + Preconditions: + - Source VM successfully migrated to the target cluster + + Steps: + 1. Read the test file from the migrated VM on the target cluster + + Expected: + - File content on the migrated VM equals the content written before 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): + """ + Test that source VM on the source cluster is stopped after cross-cluster live migration. + + Preconditions: + - Source VM successfully migrated to the target cluster + + Steps: + 1. Check the status of the source VM on the source cluster + + Expected: + - Source VM is in "Stopped" state + """ + 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, admin_client: DynamicClient, local_vms_after_cclm_migration: list[VirtualMachineForTests] + ): + """ + Test that a VM can be compute live migrated within the target cluster after cross-cluster live migration. + + Preconditions: + - Source VM successfully migrated to the target cluster + + Steps: + 1. Trigger intra-cluster live migration for the migrated VM on the target cluster + 2. Wait for migration to complete + + Expected: + - Migrated VM is successfully live migrated within the target cluster + """ + verify_compute_live_migration_after_cclm(client=admin_client, local_vms=local_vms_after_cclm_migration) + + @pytest.mark.polarion("CNV-15959") + def test_source_vms_can_be_deleted(self, vms_for_cclm): + """ + Test that source VM on the source cluster can be deleted after cross-cluster live migration. + + Steps: + 1. Delete the source VM on the source cluster + + Expected: + - Source VM is successfully deleted + """ + 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): + """ + Test that migrated VM on the target cluster can be deleted after cross-cluster live migration. + + Steps: + 1. Delete the migrated VM on the target cluster + + Expected: + - Migrated VM is successfully deleted + """ assert_vms_can_be_deleted(vms=local_vms_after_cclm_migration) diff --git a/tests/storage/cross_cluster_live_migration/utils.py b/tests/storage/cross_cluster_live_migration/utils.py index 93d05a3274..a6191496bf 100644 --- a/tests/storage/cross_cluster_live_migration/utils.py +++ b/tests/storage/cross_cluster_live_migration/utils.py @@ -74,11 +74,12 @@ def configure_hco_live_migration_network( ) -def verify_compute_live_migration_after_cclm(local_vms: list[VirtualMachineForTests]) -> None: +def verify_compute_live_migration_after_cclm(client: DynamicClient, local_vms: list[VirtualMachineForTests]) -> None: """ Verify compute live migration for VMs after Cross-Cluster Live Migration (CCLM). Args: + client: DynamicClient used to create migration resources. local_vms: List of VirtualMachineForTests objects in the local cluster Raises: @@ -87,7 +88,7 @@ def verify_compute_live_migration_after_cclm(local_vms: list[VirtualMachineForTe vms_failed_migration = {} for vm in local_vms: try: - migrate_vm_and_verify(vm=vm, check_ssh_connectivity=True) + migrate_vm_and_verify(vm=vm, client=client, check_ssh_connectivity=True) except Exception as migration_exception: vms_failed_migration[vm.name] = migration_exception assert not vms_failed_migration, f"Failed VM migrations: {vms_failed_migration}" diff --git a/tests/storage/storage_migration/conftest.py b/tests/storage/storage_migration/conftest.py index ef601720dd..2632c05094 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 bbd8a556c1..794ae6219e 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 + ) + )