From 142e639ff9e3f52b0ab8f32dd6508f4af4c54331 Mon Sep 17 00:00:00 2001 From: Ahmad Hafe Date: Mon, 13 Jul 2026 12:11:43 +0300 Subject: [PATCH] update: expand storage migration hotplug test to 4-disk conformance (CNV-88911) Signed-off-by: Ahmad Hafe Co-authored-by: Cursor --- tests/storage/storage_migration/conftest.py | 135 ++++++++++-------- tests/storage/storage_migration/constants.py | 5 +- .../test_storage_class_migration.py | 16 +-- tests/storage/storage_migration/utils.py | 30 ++-- 4 files changed, 107 insertions(+), 79 deletions(-) diff --git a/tests/storage/storage_migration/conftest.py b/tests/storage/storage_migration/conftest.py index 7d79adac9d..eed7ae1450 100644 --- a/tests/storage/storage_migration/conftest.py +++ b/tests/storage/storage_migration/conftest.py @@ -1,3 +1,4 @@ +import contextlib import shlex import pytest @@ -14,8 +15,9 @@ from tests.storage.storage_migration.constants import ( CONTENT, FILE_BEFORE_STORAGE_MIGRATION, - HOTPLUGGED_DEVICE, - MOUNT_HOTPLUGGED_DEVICE_PATH, + HOTPLUGGED_DEVICES, + MOUNT_HOTPLUGGED_DEVICE_PATHS, + NUM_HOTPLUG_DISKS, WINDOWS_FILE_WITH_PATH, WINDOWS_TEST_DIRECTORY_PATH, ) @@ -41,7 +43,6 @@ ) from utilities.virt import ( VirtualMachineForTests, - fedora_vm_body, get_vm_boot_time, running_vm, vm_instance_from_template, @@ -276,83 +277,97 @@ def deleted_old_dvs_of_stopped_vms(unprivileged_client, namespace): @pytest.fixture(scope="class") -def blank_disk_dv_for_storage_migration(unprivileged_client, namespace, source_storage_class): - with create_dv( - source="blank", - dv_name="blank-dv-for-hotplug", - client=unprivileged_client, - namespace=namespace.name, - size=DEFAULT_DV_SIZE, - storage_class=source_storage_class, - consume_wffc=False, - ) as dv: - yield dv +def blank_disk_dvs_for_storage_migration(unprivileged_client, namespace, source_storage_class): + with contextlib.ExitStack() as stack: + dvs = [] + for idx in range(NUM_HOTPLUG_DISKS): + dv = stack.enter_context( + cm=create_dv( + source="blank", + dv_name=f"blank-dv-for-hotplug-{idx}", + client=unprivileged_client, + namespace=namespace.name, + size=DEFAULT_DV_SIZE, + storage_class=source_storage_class, + consume_wffc=False, + ) + ) + dvs.append(dv) + yield dvs @pytest.fixture(scope="class") -def fedora_vm_for_hotplug_and_storage_migration(unprivileged_client, namespace, cpu_for_migration): - name = "fedora-volume-hotplug-vm" +def fedora_vm_for_hotplug_and_storage_migration( + unprivileged_client, namespace, fedora_data_source_scope_module, source_storage_class, cpu_for_migration +): with VirtualMachineForTests( - name=name, + name="fedora-volume-hotplug-vm", namespace=namespace.name, - body=fedora_vm_body(name=name), - cpu_model=cpu_for_migration, client=unprivileged_client, + vm_instance_type=VirtualMachineClusterInstancetype(name=U1_SMALL, client=unprivileged_client), + vm_preference=VirtualMachineClusterPreference(name=OS_FLAVOR_FEDORA, client=unprivileged_client), + data_volume_template=data_volume_template_with_source_ref_dict( + data_source=fedora_data_source_scope_module, + storage_class=source_storage_class, + ), + cpu_model=cpu_for_migration, ) as vm: running_vm(vm=vm) yield vm @pytest.fixture(scope="class") -def vm_for_storage_class_migration_with_hotplugged_volume( - namespace, blank_disk_dv_for_storage_migration, fedora_vm_for_hotplug_and_storage_migration +def vm_for_storage_class_migration_with_hotplugged_volumes( + namespace, blank_disk_dvs_for_storage_migration, fedora_vm_for_hotplug_and_storage_migration ): - with virtctl_volume( - action="add", - namespace=namespace.name, - vm_name=fedora_vm_for_hotplug_and_storage_migration.name, - volume_name=blank_disk_dv_for_storage_migration.name, - persist=True, - ) as res: - status, out, err = res - assert status, f"Failed to add volume to VM, out: {out}, err: {err}." - wait_for_vm_volume_ready( - vm=fedora_vm_for_hotplug_and_storage_migration, - volume_name=blank_disk_dv_for_storage_migration.name, - ) + with contextlib.ExitStack() as stack: + for dv in blank_disk_dvs_for_storage_migration: + status, out, err = stack.enter_context( + cm=virtctl_volume( + action="add", + namespace=namespace.name, + vm_name=fedora_vm_for_hotplug_and_storage_migration.name, + volume_name=dv.name, + persist=True, + ) + ) + assert status, f"Failed to add volume {dv.name} to VM, out: {out}, err: {err}." + wait_for_vm_volume_ready( + vm=fedora_vm_for_hotplug_and_storage_migration, + volume_name=dv.name, + ) yield fedora_vm_for_hotplug_and_storage_migration @pytest.fixture(scope="class") -def vm_with_mounted_hotplugged_disk(vm_for_storage_class_migration_with_hotplugged_volume): - # Mount the disk to the VM - run_ssh_commands( - host=vm_for_storage_class_migration_with_hotplugged_volume.ssh_exec, - commands=[ - shlex.split(cmd) - for cmd in [ - f"sudo mkfs.ext4 {HOTPLUGGED_DEVICE}", - f"sudo mkdir {MOUNT_HOTPLUGGED_DEVICE_PATH}", - f"sudo mount {HOTPLUGGED_DEVICE} {MOUNT_HOTPLUGGED_DEVICE_PATH}", - ] - ], - wait_timeout=TIMEOUT_2MIN, - sleep=TIMEOUT_5SEC, - ) - yield vm_for_storage_class_migration_with_hotplugged_volume +def vm_with_mounted_hotplugged_disks(vm_for_storage_class_migration_with_hotplugged_volumes): + for device, mount_path in zip(HOTPLUGGED_DEVICES, MOUNT_HOTPLUGGED_DEVICE_PATHS, strict=True): + run_ssh_commands( + host=vm_for_storage_class_migration_with_hotplugged_volumes.ssh_exec, + commands=[ + shlex.split(cmd) + for cmd in [ + f"sudo mkfs.ext4 {device}", + f"sudo mkdir -p {mount_path}", + f"sudo mount {device} {mount_path}", + ] + ], + wait_timeout=TIMEOUT_2MIN, + sleep=TIMEOUT_5SEC, + ) + yield vm_for_storage_class_migration_with_hotplugged_volumes @pytest.fixture(scope="class") -def written_file_to_the_mounted_hotplugged_disk(vm_with_mounted_hotplugged_disk): - run_ssh_commands( - host=vm_with_mounted_hotplugged_disk.ssh_exec, - commands=shlex.split( - f"echo '{CONTENT}' | sudo tee {MOUNT_HOTPLUGGED_DEVICE_PATH}/{FILE_BEFORE_STORAGE_MIGRATION}" - ), - wait_timeout=TIMEOUT_2MIN, - sleep=TIMEOUT_5SEC, - ) - yield vm_with_mounted_hotplugged_disk +def written_files_to_mounted_hotplugged_disks(vm_with_mounted_hotplugged_disks): + for mount_path in MOUNT_HOTPLUGGED_DEVICE_PATHS: + run_ssh_commands( + host=vm_with_mounted_hotplugged_disks.ssh_exec, + commands=shlex.split(f"echo '{CONTENT}' | sudo tee {mount_path}/{FILE_BEFORE_STORAGE_MIGRATION}"), + wait_timeout=TIMEOUT_2MIN, + sleep=TIMEOUT_5SEC, + ) + yield vm_with_mounted_hotplugged_disks @pytest.fixture(scope="class") diff --git a/tests/storage/storage_migration/constants.py b/tests/storage/storage_migration/constants.py index 965639523a..3d969841e2 100644 --- a/tests/storage/storage_migration/constants.py +++ b/tests/storage/storage_migration/constants.py @@ -4,5 +4,6 @@ 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}" -HOTPLUGGED_DEVICE = "/dev/sda" -MOUNT_HOTPLUGGED_DEVICE_PATH = "/mnt/hotplug" +NUM_HOTPLUG_DISKS = 3 +HOTPLUGGED_DEVICES = [f"/dev/sd{chr(ord('a') + idx)}" for idx in range(NUM_HOTPLUG_DISKS)] +MOUNT_HOTPLUGGED_DEVICE_PATHS = [f"/mnt/hotplug-{idx}" for idx in range(NUM_HOTPLUG_DISKS)] diff --git a/tests/storage/storage_migration/test_storage_class_migration.py b/tests/storage/storage_migration/test_storage_class_migration.py index 403ea545d8..6bc0efe61f 100644 --- a/tests/storage/storage_migration/test_storage_class_migration.py +++ b/tests/storage/storage_migration/test_storage_class_migration.py @@ -13,7 +13,7 @@ WINDOWS_FILE_WITH_PATH, ) from tests.storage.storage_migration.utils import ( - verify_file_in_hotplugged_disk, + verify_files_in_hotplugged_disks, verify_storage_migration_succeeded, verify_vm_storage_class_updated, verify_vms_boot_time_after_storage_migration, @@ -156,12 +156,13 @@ def test_vm_storage_class_migration_b_to_a_with_running_vms( [ pytest.param( {"source_storage_class": py_config[STORAGE_CLASS_A]}, - {"vms_fixtures": ["vm_for_storage_class_migration_with_hotplugged_volume"]}, + {"vms_fixtures": ["vm_for_storage_class_migration_with_hotplugged_volumes"]}, id="mig_volume_hotplug_source_a_target_b", ) ], indirect=True, ) +@pytest.mark.conformance class TestStorageClassMigrationWithVolumeHotplug: @pytest.mark.dependency( name=f"{TESTS_CLASS_NAME_VOLUME_HOTPLUG}::test_vm_storage_class_migration_with_hotplugged_volume" @@ -178,11 +179,11 @@ class TestStorageClassMigrationWithVolumeHotplug: ], indirect=True, ) + @pytest.mark.usefixtures("written_files_to_mounted_hotplugged_disks") def test_vm_storage_class_migration_with_hotplugged_volume( self, source_storage_class, target_storage_class, - written_file_to_the_mounted_hotplugged_disk, written_file_to_vms_before_migration, online_vms_for_storage_class_migration, vms_boot_time_before_storage_migration, @@ -200,12 +201,11 @@ def test_vm_storage_class_migration_with_hotplugged_volume( @pytest.mark.dependency( depends=[f"{TESTS_CLASS_NAME_VOLUME_HOTPLUG}::test_vm_storage_class_migration_with_hotplugged_volume"] ) + @pytest.mark.usefixtures("vms_for_storage_class_migration") @pytest.mark.polarion("CNV-12002") - def test_hotplugged_volume_data_after_storage_migration( - self, vms_for_storage_class_migration, written_file_to_the_mounted_hotplugged_disk - ): - verify_file_in_hotplugged_disk( - vm=written_file_to_the_mounted_hotplugged_disk, + def test_hotplugged_volume_data_after_storage_migration(self, written_files_to_mounted_hotplugged_disks): + verify_files_in_hotplugged_disks( + vm=written_files_to_mounted_hotplugged_disks, file_name=FILE_BEFORE_STORAGE_MIGRATION, file_content=CONTENT, ) diff --git a/tests/storage/storage_migration/utils.py b/tests/storage/storage_migration/utils.py index ae2742dcc1..be61ab7aff 100644 --- a/tests/storage/storage_migration/utils.py +++ b/tests/storage/storage_migration/utils.py @@ -8,7 +8,7 @@ from tests.storage.storage_migration.constants import ( CONTENT, FILE_BEFORE_STORAGE_MIGRATION, - MOUNT_HOTPLUGGED_DEVICE_PATH, + MOUNT_HOTPLUGGED_DEVICE_PATHS, ) from tests.storage.utils import check_file_in_vm from utilities.constants.timeouts import TIMEOUT_2MIN, TIMEOUT_5SEC, TIMEOUT_10MIN, TIMEOUT_10SEC @@ -74,14 +74,26 @@ def verify_storage_migration_succeeded( verify_vm_storage_class_updated(vm=vm, target_storage_class=target_storage_class) -def verify_file_in_hotplugged_disk(vm: VirtualMachineForTests, file_name: str, file_content: str) -> None: - output = run_ssh_commands( - host=vm.ssh_exec, - commands=shlex.split(f"cat {MOUNT_HOTPLUGGED_DEVICE_PATH}/{file_name}"), - wait_timeout=TIMEOUT_2MIN, - sleep=TIMEOUT_5SEC, - )[0] - assert output.strip() == file_content, f"'{output}' does not equal '{file_content}'" +def verify_files_in_hotplugged_disks(vm: VirtualMachineForTests, file_name: str, file_content: str) -> None: + """Verify that a file exists with expected content on all hotplugged disk mount paths. + + Args: + vm: The VM to check. + file_name: Name of the file to verify on each mount path. + file_content: Expected content of the file. + """ + mismatches = {} + for mount_path in MOUNT_HOTPLUGGED_DEVICE_PATHS: + output = run_ssh_commands( + host=vm.ssh_exec, + commands=shlex.split(f"cat {mount_path}/{file_name}"), + wait_timeout=TIMEOUT_2MIN, + sleep=TIMEOUT_5SEC, + )[0] + stripped_output = output.strip() + if stripped_output != file_content: + mismatches[mount_path] = f"'{stripped_output}' does not equal '{file_content}'" + assert not mismatches, f"Data mismatch on hotplugged disk(s): {mismatches}" def wait_for_storage_migration_completed(