From c63ad8c66b4bcd0b1d6f85b418e92b51b85ea6df Mon Sep 17 00:00:00 2001 From: Adam Cinko Date: Wed, 15 Jul 2026 17:13:22 +0200 Subject: [PATCH 1/5] [Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571) Using `validation-os-images` namespace, we import the Windows image only once and it can be used by other modules because it's session scoped. This PR also makes changes in Hotplug and CDI Clone modules to take advantage of this new approach. Other modules will follow. This creates session scoped Data Source fixture that imports the image only once if needed or uses a golden image already on a cluster. It also makes CDI Clone and Hotplug modules utilising to fixture as a proof of concept. Co-Authored: Claude Code https://redhat.atlassian.net/browse/CNV-51351 --------- Signed-off-by: Adam Cinko Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- conftest.py | 7 + tests/fixtures/images/__init__.py | 0 tests/fixtures/images/validation_os_images.py | 156 ++++++++++++++++++ tests/storage/cdi_clone/conftest.py | 27 ++- tests/storage/cdi_clone/test_clone.py | 104 ++++++------ tests/storage/conftest.py | 6 +- tests/storage/test_hotplug.py | 58 ++++++- tests/utils.py | 54 ++++++ utilities/constants.py | 1 + utilities/storage.py | 49 ++++++ 10 files changed, 406 insertions(+), 56 deletions(-) create mode 100644 tests/fixtures/images/__init__.py create mode 100644 tests/fixtures/images/validation_os_images.py diff --git a/conftest.py b/conftest.py index a31a2c8346..faf7b84e87 100644 --- a/conftest.py +++ b/conftest.py @@ -63,6 +63,13 @@ stop_if_run_in_progress, ) +pytest_plugins = [ + "tests.fixtures.network.l2_bridge", + "tests.fixtures.network.cluster", + "tests.fixtures.images.validation_os_images", + "tests.fixtures.network.multiarch", +] + LOGGER = logging.getLogger(__name__) BASIC_LOGGER = logging.getLogger("basic") diff --git a/tests/fixtures/images/__init__.py b/tests/fixtures/images/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/images/validation_os_images.py b/tests/fixtures/images/validation_os_images.py new file mode 100644 index 0000000000..032e57cb16 --- /dev/null +++ b/tests/fixtures/images/validation_os_images.py @@ -0,0 +1,156 @@ +import pytest +from ocp_resources.cluster_role import ClusterRole +from ocp_resources.data_source import DataSource +from ocp_resources.datavolume import DataVolume +from ocp_resources.namespace import Namespace +from ocp_resources.role_binding import RoleBinding +from ocp_resources.utils.constants import TIMEOUT_1MINUTE +from pytest_testconfig import config as py_config + +from utilities.artifactory import ( + cleanup_artifactory_secret_and_config_map, + get_artifactory_config_map, + get_artifactory_secret, + get_test_artifact_server_url, +) +from utilities.constants import BIND_IMMEDIATE_ANNOTATION, REGISTRY_STR, TIMEOUT_10MIN, TIMEOUT_50MIN, WIN_2K22, Images +from utilities.os_utils import get_windows_container_disk_path +from utilities.storage import construct_datavolume_source_dict, generate_data_source_dict + + +@pytest.fixture(scope="session") +def validation_os_images_namespace(admin_client): + validation_os_images_namespace = Namespace( + name="validation-os-images", + client=admin_client, + ) + if validation_os_images_namespace.exists: + yield validation_os_images_namespace + else: + with validation_os_images_namespace as ns: + yield ns + + +@pytest.fixture(scope="session") +def validation_os_images_role_binding(admin_client, validation_os_images_namespace): + """Grants view permissions in the namespace so unprivileged clients can clone from it.""" + role_binding = RoleBinding( + client=admin_client, + name="validation-os-images-view", + namespace=validation_os_images_namespace.name, + subjects_kind="Group", + subjects_name="system:authenticated", + role_ref_kind=ClusterRole.kind, + role_ref_name="view", + ) + + if role_binding.exists: + subjects = next(iter(role_binding.instance.subjects)) + assert subjects.kind == "Group", ( + f"RoleBinding {role_binding.name} subjects kind is {subjects.kind}, expected Group" + ) + assert subjects.name == "system:authenticated", ( + f"RoleBinding {role_binding.name} subjects name is {subjects.name}, expected system:authenticated" + ) + role_ref = role_binding.instance.roleRef + assert role_ref.kind == ClusterRole.kind, ( + f"RoleBinding {role_binding.name} roleRef kind is {role_ref.kind}, expected {ClusterRole.kind}" + ) + assert role_ref.name == "view", ( + f"RoleBinding {role_binding.name} roleRef name is {role_ref.name}, expected view" + ) + yield role_binding + return + + with role_binding as rb: + yield rb + + +@pytest.fixture(scope="session") +def windows_validation_os_images_data_volume_scope_session( + validation_os_images_role_binding, + conformance_tests, +): + """Provides the DV backing the Windows Server 2022 image in the validation-os-images namespace. + + Resolution order: + 1. DataVolume exists — waits for success, yields it. + 2. DataVolume does not exist — imports via Artifactory (fails on conformance runs), yields the new DataVolume. + + Yields: + DataVolume: The DV containing the Windows 2022 image. + """ + + win_dv = DataVolume( + name=WIN_2K22, + namespace=validation_os_images_role_binding.namespace, + client=validation_os_images_role_binding.client, + ) + + if win_dv.exists: + win_dv.wait_for_dv_success(timeout=TIMEOUT_1MINUTE) + yield win_dv + return + + assert not conformance_tests, ( + f"Windows image {win_dv.name} does not exist in namespace {validation_os_images_role_binding.namespace}." + " Self-validation requires the Windows image to be pre-created." + ) + + artifactory_secret = get_artifactory_secret( + namespace=validation_os_images_role_binding.namespace, client=validation_os_images_role_binding.client + ) + artifactory_config_map = get_artifactory_config_map( + namespace=validation_os_images_role_binding.namespace, client=validation_os_images_role_binding.client + ) + + win_dv.storage_class = py_config["default_storage_class"] + win_dv.source_dict = construct_datavolume_source_dict( + source=REGISTRY_STR, + url=f"{get_test_artifact_server_url(schema=REGISTRY_STR)}/{get_windows_container_disk_path(os_value=WIN_2K22)}", + secret_name=artifactory_secret.name, + cert_configmap_name=artifactory_config_map.name, + ) + win_dv.size = Images.Windows.CONTAINER_DISK_DV_SIZE + win_dv.api_name = "storage" + win_dv.annotations = BIND_IMMEDIATE_ANNOTATION + + with win_dv as wdv: + wdv.wait_for_dv_success(timeout=TIMEOUT_50MIN) + yield wdv + cleanup_artifactory_secret_and_config_map( + artifactory_secret=artifactory_secret, + artifactory_config_map=artifactory_config_map, + ) + + +@pytest.fixture(scope="session") +def windows_validation_os_images_data_source_scope_session( + admin_client, windows_validation_os_images_data_volume_scope_session +): + win_data_source = DataSource( + name=windows_validation_os_images_data_volume_scope_session.name, + namespace=windows_validation_os_images_data_volume_scope_session.namespace, + client=admin_client, + ) + if win_data_source.exists: + source_pvc = win_data_source.instance.spec.source.pvc + assert source_pvc.name == windows_validation_os_images_data_volume_scope_session.name, ( + f"DataSource {win_data_source.name} source PVC name is {source_pvc.name}, " + f"expected {windows_validation_os_images_data_volume_scope_session.name}" + ) + assert source_pvc.namespace == windows_validation_os_images_data_volume_scope_session.pvc.namespace, ( + f"DataSource {win_data_source.name} source PVC namespace is {source_pvc.namespace}, " + f"expected {windows_validation_os_images_data_volume_scope_session.namespace}" + ) + yield win_data_source + return + + win_data_source._source = generate_data_source_dict(dv=windows_validation_os_images_data_volume_scope_session) + with win_data_source as wds: + wds.wait_for_condition( + condition=wds.Condition.READY, + status=wds.Condition.Status.TRUE, + timeout=TIMEOUT_10MIN, + ) + yield wds diff --git a/tests/storage/cdi_clone/conftest.py b/tests/storage/cdi_clone/conftest.py index 7647297a4d..d5d831f915 100644 --- a/tests/storage/cdi_clone/conftest.py +++ b/tests/storage/cdi_clone/conftest.py @@ -2,8 +2,8 @@ from ocp_resources.datavolume import DataVolume from tests.storage.constants import QUAY_FEDORA_CONTAINER_IMAGE -from utilities.constants import REGISTRY_STR, Images -from utilities.storage import create_dv, data_volume +from utilities.constants import Images, REGISTRY_STR, TIMEOUT_40MIN, WIN_2K22 +from utilities.storage import create_dv, data_volume, get_dv_size_from_datasource @pytest.fixture() @@ -59,3 +59,26 @@ def fedora_dv_with_block_volume_mode( ) as dv: dv.wait_for_dv_success() yield dv + + +@pytest.fixture(scope="class") +def cloned_windows_dv_multi_storage_scope_class( + unprivileged_client, + namespace, + storage_class_name_scope_class, + windows_validation_os_images_data_source_scope_session, +): + with create_dv( + client=unprivileged_client, + dv_name=f"dv-target-{WIN_2K22}-clone", + namespace=namespace.name, + size=get_dv_size_from_datasource(windows_validation_os_images_data_source_scope_session), + storage_class=storage_class_name_scope_class, + source_ref={ + "kind": windows_validation_os_images_data_source_scope_session.kind, + "name": windows_validation_os_images_data_source_scope_session.name, + "namespace": windows_validation_os_images_data_source_scope_session.namespace, + }, + ) as cdv: + cdv.wait_for_dv_success(timeout=TIMEOUT_40MIN) + yield cdv diff --git a/tests/storage/cdi_clone/test_clone.py b/tests/storage/cdi_clone/test_clone.py index 4f8f4e901b..89d2c05467 100644 --- a/tests/storage/cdi_clone/test_clone.py +++ b/tests/storage/cdi_clone/test_clone.py @@ -5,11 +5,10 @@ import pytest from ocp_resources.datavolume import DataVolume -from tests.os_params import FEDORA_LATEST, WINDOWS_11, WINDOWS_11_TEMPLATE_LABELS +from tests.os_params import FEDORA_LATEST from tests.storage.utils import ( assert_pvc_snapshot_clone_annotation, assert_use_populator, - create_windows_vm_validate_guest_agent_info, ) from utilities.constants import ( OS_FLAVOR_FEDORA, @@ -32,8 +31,6 @@ running_vm, ) -WINDOWS_CLONE_TIMEOUT = TIMEOUT_40MIN - def create_vm_from_clone_dv_template( vm_name, @@ -142,50 +139,65 @@ def test_successful_vm_restart_with_cloned_dv( @pytest.mark.tier3 -@pytest.mark.parametrize( - ("data_volume_multi_storage_scope_function", "vm_params"), - [ - pytest.param( - { - "dv_name": "dv-source", - "source": "http", - "image": f"{Images.Windows.DIR}/{Images.Windows.WIN11_IMG}", - "dv_size": Images.Windows.DEFAULT_DV_SIZE, - }, - { - "vm_name": f"vm-win-{WINDOWS_11.get('os_version')}", - "template_labels": WINDOWS_11_TEMPLATE_LABELS, - "os_version": WINDOWS_11.get("os_version"), - "ssh": True, - }, - marks=pytest.mark.polarion("CNV-3638"), - ), - ], - indirect=["data_volume_multi_storage_scope_function"], -) -def test_successful_vm_from_cloned_dv_windows( - unprivileged_client, - data_volume_multi_storage_scope_function, - vm_params, - namespace, -): - with create_dv( - client=unprivileged_client, - source="pvc", - dv_name="dv-target", - namespace=data_volume_multi_storage_scope_function.namespace, - size=data_volume_multi_storage_scope_function.size, - source_pvc=data_volume_multi_storage_scope_function.name, - storage_class=data_volume_multi_storage_scope_function.storage_class, - ) as cdv: - cdv.wait_for_dv_success(timeout=WINDOWS_CLONE_TIMEOUT) - create_windows_vm_validate_guest_agent_info( - dv=cdv, - namespace=namespace, - unprivileged_client=unprivileged_client, - vm_params=vm_params, +@pytest.mark.incremental +class TestWindowsClonedDv: + """ + Tests for Windows 2022 DV cloning, and VM creation with vTPM. + + Preconditions: + - Windows Server 2022 DataVolume + - Cloned DataVolume created from the source DataVolume (PVC clone) + """ + + @pytest.mark.polarion("CNV-1892") + def test_clone_dv_windows(self, cloned_windows_dv_multi_storage_scope_class): + """ + Test that a large image can be cloned. + + Preconditions: + - Cloned DataVolume created from the source DataVolume (PVC clone) + + Steps: + 1. Verify the cloned DataVolume status + + Expected: + - Cloned DataVolume status is "Succeeded" + """ + assert cloned_windows_dv_multi_storage_scope_class.status == DataVolume.Status.SUCCEEDED, ( + f"Cloned DV status is {cloned_windows_dv_multi_storage_scope_class.status}, expected {DataVolume.Status.SUCCEEDED}" ) + @pytest.mark.polarion("CNV-3638") + def test_vm_from_cloned_dv_windows( + self, + unprivileged_client, + namespace, + modern_cpu_for_migration, + cloned_windows_dv_multi_storage_scope_class, + ): + """ + Test that a Windows 2022 VM with vTPM boots from a cloned DataVolume. + + Preconditions: + - Cloned DataVolume created from the source DataVolume (PVC clone) + + Steps: + 1. Create a Windows 2022 VM with vTPM from the cloned DataVolume using instance type and preference + 2. Wait for the VM to reach Running state + 3. Wait for Windows OS to be ready inside the VM + + Expected: + - VM OS info reported by VMI matches the expected Windows OS parameters + """ + with create_windows2022_vm_using_existing_dv( + namespace=namespace.name, + client=unprivileged_client, + vm_name=f"vm-{WIN_2K22}", + cpu_model=modern_cpu_for_migration, + existing_data_volume=cloned_windows_dv_multi_storage_scope_class, + ) as vm: + validate_os_info_vmi_vs_windows_os(vm=vm) + @pytest.mark.parametrize( "data_volume_snapshot_capable_storage_scope_function", diff --git a/tests/storage/conftest.py b/tests/storage/conftest.py index 9df024ccd5..b79d251931 100644 --- a/tests/storage/conftest.py +++ b/tests/storage/conftest.py @@ -67,7 +67,11 @@ INTERNAL_HTTP_SERVER_ADDRESS, ExecCommandOnPod, ) -from utilities.storage import data_volume_template_with_source_ref_dict, get_downloaded_artifact, write_file_via_ssh +from utilities.storage import ( + data_volume_template_with_source_ref_dict, + get_downloaded_artifact, + write_file_via_ssh, +) from utilities.virt import VirtualMachineForTests, running_vm LOGGER = logging.getLogger(__name__) diff --git a/tests/storage/test_hotplug.py b/tests/storage/test_hotplug.py index 0185a7d7ea..67803deb49 100644 --- a/tests/storage/test_hotplug.py +++ b/tests/storage/test_hotplug.py @@ -10,15 +10,17 @@ from ocp_resources.kubevirt import KubeVirt from ocp_resources.storage_profile import StorageProfile -from tests.os_params import WINDOWS_LATEST, WINDOWS_LATEST_LABELS -from utilities.constants import HOTPLUG_DISK_SERIAL, Images +from tests.storage.utils import assert_disk_bus +from tests.utils import create_windows2022_vm_with_data_volume_template +from utilities.constants.storage import HOTPLUG_DISK_SCSI_BUS, HOTPLUG_DISK_SERIAL, HOTPLUG_DISK_VIRTIO_BUS +from utilities.constants.virt import WIN_2K22 from utilities.hco import ResourceEditorValidateHCOReconcile from utilities.jira import is_jira_open from utilities.storage import ( assert_disk_serial, assert_hotplugvolume_nonexist, create_dv, - data_volume, + data_volume_template_with_source_ref_dict, virtctl_volume, wait_for_vm_volume_ready, ) @@ -59,12 +61,12 @@ def enabled_feature_gate_for_declarative_hotplug_volumes( @pytest.fixture(scope="class") def hotplug_volume_windows_scope_class( - request, namespace, vm_instance_from_template_multi_storage_scope_class, blank_disk_dv_multi_storage_scope_class + request, namespace, vm_instance_multi_storage_scope_class, blank_disk_dv_multi_storage_scope_class ): with virtctl_volume( action="add", namespace=namespace.name, - vm_name=vm_instance_from_template_multi_storage_scope_class.name, + vm_name=vm_instance_multi_storage_scope_class.name, volume_name=blank_disk_dv_multi_storage_scope_class.name, **request.param, ) as res: @@ -74,6 +76,7 @@ def hotplug_volume_windows_scope_class( @pytest.fixture(scope="class") +<<<<<<< HEAD def vm_instance_from_template_multi_storage_scope_class( request, unprivileged_client, @@ -119,6 +122,27 @@ def data_volume_multi_storage_scope_class( storage_class_matrix=storage_class_matrix__class__, client=namespace.client, ) +======= +def vm_instance_multi_storage_scope_class( + unprivileged_client, + namespace, + modern_cpu_for_migration, + windows_validation_os_images_data_source_scope_session, + storage_class_name_scope_class, +): + """Creates a Windows 2022 VM with vTPM from the session-scoped Windows DataSource.""" + with create_windows2022_vm_with_data_volume_template( + dv_template=data_volume_template_with_source_ref_dict( + data_source=windows_validation_os_images_data_source_scope_session, + storage_class=storage_class_name_scope_class, + ), + namespace=namespace.name, + client=unprivileged_client, + vm_name=f"vm-{WIN_2K22}-hotplug", + cpu_model=modern_cpu_for_migration, + ) as vm: + yield vm +>>>>>>> f9fb41bc ([Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571)) @pytest.fixture(scope="class") @@ -285,22 +309,32 @@ class TestHotPlugWindows: def test_windows_hotplug( self, blank_disk_dv_multi_storage_scope_class, +<<<<<<< HEAD data_volume_multi_storage_scope_class, vm_instance_from_template_multi_storage_scope_class, started_windows_vm_scope_class, hotplug_volume_windows_scope_class, ): wait_for_vm_volume_ready(vm=vm_instance_from_template_multi_storage_scope_class) +======= + vm_instance_multi_storage_scope_class, + ): + wait_for_vm_volume_ready( + vm=vm_instance_multi_storage_scope_class, + volume_name=blank_disk_dv_multi_storage_scope_class.name, + ) +>>>>>>> f9fb41bc ([Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571)) assert_disk_serial( command=shlex.split("wmic diskdrive get SerialNumber"), - vm=vm_instance_from_template_multi_storage_scope_class, + vm=vm_instance_multi_storage_scope_class, ) - assert_hotplugvolume_nonexist(vm=vm_instance_from_template_multi_storage_scope_class) + assert_hotplugvolume_nonexist(vm=vm_instance_multi_storage_scope_class) @pytest.mark.polarion("CNV-11391") @pytest.mark.dependency(depends=["test_windows_hotplug"]) def test_windows_hotplug_migrate( self, +<<<<<<< HEAD unprivileged_client, blank_disk_dv_multi_storage_scope_class, data_volume_multi_storage_scope_class, @@ -311,5 +345,15 @@ def test_windows_hotplug_migrate( if is_dv_migratable(dv=blank_disk_dv_multi_storage_scope_class): migrate_vm_and_verify( vm=vm_instance_from_template_multi_storage_scope_class, +======= + admin_client: DynamicClient, + blank_disk_dv_multi_storage_scope_class: DataVolume, + vm_instance_multi_storage_scope_class: VirtualMachineForTests, + ): + if is_dv_migratable(dv=blank_disk_dv_multi_storage_scope_class): + migrate_vm_and_verify( + vm=vm_instance_multi_storage_scope_class, + client=admin_client, +>>>>>>> f9fb41bc ([Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571)) check_ssh_connectivity=True, ) diff --git a/tests/utils.py b/tests/utils.py index 48b0a22474..0efa61e137 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -19,6 +19,8 @@ from ocp_resources.resource import ResourceEditor from ocp_resources.storage_profile import StorageProfile from ocp_resources.virtual_machine import VirtualMachine +from ocp_resources.virtual_machine_cluster_instancetype import VirtualMachineClusterInstancetype +from ocp_resources.virtual_machine_cluster_preference import VirtualMachineClusterPreference from ocp_resources.virtual_machine_instance_migration import VirtualMachineInstanceMigration from pyhelper_utils.shell import run_ssh_commands from pytest_testconfig import config as py_config @@ -45,6 +47,9 @@ TIMEOUT_15SEC, TIMEOUT_30MIN, Images, + OS_FLAVOR_WIN_CONTAINER_DISK, + U1_LARGE, + WINDOWS_2K22_PREFERENCE, ) from utilities.data_collector import get_data_collector_dir, write_to_file from utilities.exceptions import ResourceValueError @@ -685,3 +690,52 @@ def verify_rwx_default_storage(client: DynamicClient) -> None: f"Default storage class '{storage_class}' doesn't support RWX mode " f"(required: RWX, found: {found_mode or 'none'})" ) + + + +@contextmanager +def create_windows2022_vm_using_existing_dv( + namespace: str, + client: DynamicClient, + vm_name: str, + cpu_model: str | None = None, + existing_data_volume: DataVolume | None = None, +) -> Generator[VirtualMachineForTests, None, None]: + """Creates a Windows Server 2022 VM with vTPM using existing DataVolume.""" + with VirtualMachineForTests( + name=vm_name, + namespace=namespace, + client=client, + os_flavor=OS_FLAVOR_WIN_CONTAINER_DISK, + vm_instance_type=VirtualMachineClusterInstancetype(name=U1_LARGE, client=client), + vm_preference=VirtualMachineClusterPreference(name=WINDOWS_2K22_PREFERENCE, client=client), + data_volume=existing_data_volume, + cpu_model=cpu_model, + ) as vm: + running_vm(vm=vm) + wait_for_windows_vm(vm=vm, version="2022") + yield vm + + +@contextmanager +def create_windows2022_vm_with_data_volume_template( + namespace: str, + client: DynamicClient, + vm_name: str, + cpu_model: str | None = None, + dv_template: dict | None = None, +) -> Generator[VirtualMachineForTests, None, None]: + """Creates a Windows Server 2022 VM with vTPM with dv template.""" + with VirtualMachineForTests( + name=vm_name, + namespace=namespace, + client=client, + os_flavor=OS_FLAVOR_WIN_CONTAINER_DISK, + vm_instance_type=VirtualMachineClusterInstancetype(name=U1_LARGE, client=client), + vm_preference=VirtualMachineClusterPreference(name=WINDOWS_2K22_PREFERENCE, client=client), + data_volume_template=dv_template, + cpu_model=cpu_model, + ) as vm: + running_vm(vm=vm) + wait_for_windows_vm(vm=vm, version="2022") + yield vm diff --git a/utilities/constants.py b/utilities/constants.py index b799dace86..3aa642971e 100644 --- a/utilities/constants.py +++ b/utilities/constants.py @@ -792,6 +792,7 @@ class NamespacesNames: RHEL8_PREFERENCE = "rhel.8" RHEL9_PREFERENCE = "rhel.9" RHEL10_PREFERENCE = "rhel.10" +WINDOWS_2K22_PREFERENCE = "windows.2k22" U1_SMALL = "u1.small" U1_LARGE = "u1.large" PROMETHEUS_K8S = "prometheus-k8s" diff --git a/utilities/storage.py b/utilities/storage.py index f3b5d0f939..f53f93bb95 100644 --- a/utilities/storage.py +++ b/utilities/storage.py @@ -677,6 +677,55 @@ def generate_data_source_dict(dv): return {"pvc": {"name": dv.name, "namespace": dv.namespace}} +def construct_datavolume_source_dict( + source: str, + url: str | None = None, + secret_name: str | None = None, + cert_configmap_name: str | None = None, + source_pvc_name: str | None = None, + source_pvc_namespace: str | None = None, +) -> dict[str, Any]: + """ + Build a DataVolume source_dict. + + Args: + source: Source type ("http", "registry", "pvc", "blank", "upload"). + url: URL for http/registry sources. + secret_name: Optional Secret name for authentication (http/registry sources). + cert_configmap_name: Optional ConfigMap name for TLS certificates (http/registry sources). + source_pvc_name: PVC name for pvc source type. + source_pvc_namespace: Namespace of the source PVC. + + Returns: + dict[str, Any]: The constructed source_dict for DataVolume. + """ + if source == "http": + if not utilities.infra.url_excluded_from_validation(url): + validate_file_exists_in_url(url=url) + source_spec: dict[str, Any] = {"http": {"url": url}} + elif source == "registry": + source_spec = {"registry": {"url": url}} + elif source == "pvc": + pvc_spec: dict[str, Any] = {"name": source_pvc_name} + if source_pvc_namespace is not None: + pvc_spec["namespace"] = source_pvc_namespace + source_spec = {"pvc": pvc_spec} + elif source == "blank": + source_spec = {"blank": {}} + elif source == "upload": + source_spec = {"upload": {}} + else: + raise ValueError(f"Unsupported source type: {source}") + + if source in ("http", "registry"): + if secret_name: + source_spec[source]["secretRef"] = secret_name + if cert_configmap_name: + source_spec[source]["certConfigMap"] = cert_configmap_name + + return source_spec + + def create_or_update_data_source(admin_client, dv): """ Create or updates a data source referencing a provided DV. From 4ef06bc0ca9e46aadd034d377c7acf3576ce0f2b Mon Sep 17 00:00:00 2001 From: Adam Cinko Date: Thu, 16 Jul 2026 10:10:40 +0200 Subject: [PATCH 2/5] backport session scoped win dv into 4.21 Signed-off-by: Adam Cinko --- tests/storage/cdi_clone/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/storage/cdi_clone/conftest.py b/tests/storage/cdi_clone/conftest.py index d5d831f915..b6d63ec93d 100644 --- a/tests/storage/cdi_clone/conftest.py +++ b/tests/storage/cdi_clone/conftest.py @@ -2,7 +2,7 @@ from ocp_resources.datavolume import DataVolume from tests.storage.constants import QUAY_FEDORA_CONTAINER_IMAGE -from utilities.constants import Images, REGISTRY_STR, TIMEOUT_40MIN, WIN_2K22 +from utilities.constants import REGISTRY_STR, TIMEOUT_40MIN, WIN_2K22, Images from utilities.storage import create_dv, data_volume, get_dv_size_from_datasource From 0ffba918052dd51f0d46ec3c4f7daa655ffb1279 Mon Sep 17 00:00:00 2001 From: Adam Cinko Date: Thu, 16 Jul 2026 10:10:40 +0200 Subject: [PATCH 3/5] backport session scoped win dv into 4.21 Signed-off-by: Adam Cinko --- conftest.py | 3 - tests/storage/cdi_clone/test_clone.py | 39 ++-------- tests/storage/test_hotplug.py | 102 ++------------------------ tests/utils.py | 6 +- 4 files changed, 14 insertions(+), 136 deletions(-) diff --git a/conftest.py b/conftest.py index faf7b84e87..180b2ba865 100644 --- a/conftest.py +++ b/conftest.py @@ -64,10 +64,7 @@ ) pytest_plugins = [ - "tests.fixtures.network.l2_bridge", - "tests.fixtures.network.cluster", "tests.fixtures.images.validation_os_images", - "tests.fixtures.network.multiarch", ] LOGGER = logging.getLogger(__name__) diff --git a/tests/storage/cdi_clone/test_clone.py b/tests/storage/cdi_clone/test_clone.py index 89d2c05467..ca097ba4a0 100644 --- a/tests/storage/cdi_clone/test_clone.py +++ b/tests/storage/cdi_clone/test_clone.py @@ -10,13 +10,15 @@ assert_pvc_snapshot_clone_annotation, assert_use_populator, ) +from tests.utils import create_windows2022_vm_using_existing_dv from utilities.constants import ( OS_FLAVOR_FEDORA, OS_FLAVOR_WINDOWS, TIMEOUT_1MIN, - TIMEOUT_40MIN, + WIN_2K22, Images, ) +from utilities.ssp import validate_os_info_vmi_vs_windows_os from utilities.storage import ( check_disk_count_in_vm, create_dv, @@ -60,38 +62,6 @@ def create_vm_from_clone_dv_template( running_vm(vm=vm) -@pytest.mark.tier3 -@pytest.mark.parametrize( - "data_volume_multi_storage_scope_function", - [ - pytest.param( - { - "dv_name": "dv-source", - "image": f"{Images.Windows.DIR}/{Images.Windows.WIN11_IMG}", - "dv_size": Images.Windows.DEFAULT_DV_SIZE, - }, - marks=(pytest.mark.polarion("CNV-1892")), - ), - ], - indirect=True, -) -@pytest.mark.s390x -def test_successful_clone_of_large_image( - namespace, - data_volume_multi_storage_scope_function, -): - with create_dv( - source="pvc", - dv_name="dv-target", - namespace=namespace.name, - size=data_volume_multi_storage_scope_function.size, - source_pvc=data_volume_multi_storage_scope_function.name, - storage_class=data_volume_multi_storage_scope_function.storage_class, - client=namespace.client, - ) as cdv: - cdv.wait_for_dv_success(timeout=WINDOWS_CLONE_TIMEOUT) - - @pytest.mark.sno @pytest.mark.polarion("CNV-2148") @pytest.mark.gating() @@ -164,7 +134,8 @@ def test_clone_dv_windows(self, cloned_windows_dv_multi_storage_scope_class): - Cloned DataVolume status is "Succeeded" """ assert cloned_windows_dv_multi_storage_scope_class.status == DataVolume.Status.SUCCEEDED, ( - f"Cloned DV status is {cloned_windows_dv_multi_storage_scope_class.status}, expected {DataVolume.Status.SUCCEEDED}" + f"Cloned DV status is {cloned_windows_dv_multi_storage_scope_class.status}," + f" expected {DataVolume.Status.SUCCEEDED}" ) @pytest.mark.polarion("CNV-3638") diff --git a/tests/storage/test_hotplug.py b/tests/storage/test_hotplug.py index 67803deb49..3e8a9cc662 100644 --- a/tests/storage/test_hotplug.py +++ b/tests/storage/test_hotplug.py @@ -10,10 +10,8 @@ from ocp_resources.kubevirt import KubeVirt from ocp_resources.storage_profile import StorageProfile -from tests.storage.utils import assert_disk_bus from tests.utils import create_windows2022_vm_with_data_volume_template -from utilities.constants.storage import HOTPLUG_DISK_SCSI_BUS, HOTPLUG_DISK_SERIAL, HOTPLUG_DISK_VIRTIO_BUS -from utilities.constants.virt import WIN_2K22 +from utilities.constants import HOTPLUG_DISK_SERIAL, WIN_2K22, Images from utilities.hco import ResourceEditorValidateHCOReconcile from utilities.jira import is_jira_open from utilities.storage import ( @@ -29,8 +27,6 @@ fedora_vm_body, migrate_vm_and_verify, running_vm, - vm_instance_from_template, - wait_for_windows_vm, ) LOGGER = logging.getLogger(__name__) @@ -76,53 +72,6 @@ def hotplug_volume_windows_scope_class( @pytest.fixture(scope="class") -<<<<<<< HEAD -def vm_instance_from_template_multi_storage_scope_class( - request, - unprivileged_client, - namespace, - data_volume_multi_storage_scope_class, - cpu_for_migration, -): - """Calls vm_instance_from_template contextmanager - - Creates a VM from template and starts it (if requested). - """ - with vm_instance_from_template( - request=request, - unprivileged_client=unprivileged_client, - namespace=namespace, - existing_data_volume=data_volume_multi_storage_scope_class, - vm_cpu_model=cpu_for_migration if request.param.get("set_vm_common_cpu") else None, - ) as vm: - yield vm - - -@pytest.fixture(scope="class") -def started_windows_vm_scope_class( - request, - vm_instance_from_template_multi_storage_scope_class, -): - wait_for_windows_vm( - vm=vm_instance_from_template_multi_storage_scope_class, - version=request.param["os_version"], - ) - - -@pytest.fixture(scope="class") -def data_volume_multi_storage_scope_class( - request, - unprivileged_client, - namespace, - storage_class_matrix__class__, -): - yield from data_volume( - request=request, - namespace=namespace, - storage_class_matrix=storage_class_matrix__class__, - client=namespace.client, - ) -======= def vm_instance_multi_storage_scope_class( unprivileged_client, namespace, @@ -142,7 +91,6 @@ def vm_instance_multi_storage_scope_class( cpu_model=modern_cpu_for_migration, ) as vm: yield vm ->>>>>>> f9fb41bc ([Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571)) @pytest.fixture(scope="class") @@ -281,24 +229,9 @@ def test_hotplug_volume_with_serial_and_persist_migrate( @pytest.mark.parametrize( - "data_volume_multi_storage_scope_class," - "vm_instance_from_template_multi_storage_scope_class," - "started_windows_vm_scope_class," "hotplug_volume_windows_scope_class", [ - pytest.param( - { - "dv_name": "dv-windows", - "image": WINDOWS_LATEST.get("image_path"), - "dv_size": WINDOWS_LATEST.get("dv_size"), - }, - { - "vm_name": f"vm-win-{WINDOWS_LATEST.get('os_version')}", - "template_labels": WINDOWS_LATEST_LABELS, - }, - {"os_version": WINDOWS_LATEST.get("os_version")}, - {"persist": True, "serial": HOTPLUG_DISK_SERIAL}, - ), + pytest.param({"persist": True, "serial": HOTPLUG_DISK_SERIAL}), ], indirect=True, ) @@ -309,21 +242,10 @@ class TestHotPlugWindows: def test_windows_hotplug( self, blank_disk_dv_multi_storage_scope_class, -<<<<<<< HEAD - data_volume_multi_storage_scope_class, - vm_instance_from_template_multi_storage_scope_class, - started_windows_vm_scope_class, - hotplug_volume_windows_scope_class, - ): - wait_for_vm_volume_ready(vm=vm_instance_from_template_multi_storage_scope_class) -======= vm_instance_multi_storage_scope_class, + hotplug_volume_windows_scope_class, ): - wait_for_vm_volume_ready( - vm=vm_instance_multi_storage_scope_class, - volume_name=blank_disk_dv_multi_storage_scope_class.name, - ) ->>>>>>> f9fb41bc ([Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571)) + wait_for_vm_volume_ready(vm=vm_instance_multi_storage_scope_class) assert_disk_serial( command=shlex.split("wmic diskdrive get SerialNumber"), vm=vm_instance_multi_storage_scope_class, @@ -334,26 +256,14 @@ def test_windows_hotplug( @pytest.mark.dependency(depends=["test_windows_hotplug"]) def test_windows_hotplug_migrate( self, -<<<<<<< HEAD - unprivileged_client, + admin_client, blank_disk_dv_multi_storage_scope_class, - data_volume_multi_storage_scope_class, - vm_instance_from_template_multi_storage_scope_class, - started_windows_vm_scope_class, + vm_instance_multi_storage_scope_class, hotplug_volume_windows_scope_class, - ): - if is_dv_migratable(dv=blank_disk_dv_multi_storage_scope_class): - migrate_vm_and_verify( - vm=vm_instance_from_template_multi_storage_scope_class, -======= - admin_client: DynamicClient, - blank_disk_dv_multi_storage_scope_class: DataVolume, - vm_instance_multi_storage_scope_class: VirtualMachineForTests, ): if is_dv_migratable(dv=blank_disk_dv_multi_storage_scope_class): migrate_vm_and_verify( vm=vm_instance_multi_storage_scope_class, client=admin_client, ->>>>>>> f9fb41bc ([Storage] Windows Session Scoped Data Source + CDI Clone and Hotplug implementation of it (#4571)) check_ssh_connectivity=True, ) diff --git a/tests/utils.py b/tests/utils.py index 0efa61e137..f9b771a339 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -35,6 +35,7 @@ from utilities.constants import ( DISK_SERIAL, NODE_HUGE_PAGES_1GI_KEY, + OS_FLAVOR_WIN_CONTAINER_DISK, OS_FLAVOR_WINDOWS, RHSM_SECRET_NAME, TCP_TIMEOUT_30SEC, @@ -46,10 +47,9 @@ TIMEOUT_10SEC, TIMEOUT_15SEC, TIMEOUT_30MIN, - Images, - OS_FLAVOR_WIN_CONTAINER_DISK, U1_LARGE, WINDOWS_2K22_PREFERENCE, + Images, ) from utilities.data_collector import get_data_collector_dir, write_to_file from utilities.exceptions import ResourceValueError @@ -65,6 +65,7 @@ running_vm, wait_for_migration_finished, wait_for_ssh_connectivity, + wait_for_windows_vm, ) NUM_TEST_VMS = 3 @@ -692,7 +693,6 @@ def verify_rwx_default_storage(client: DynamicClient) -> None: ) - @contextmanager def create_windows2022_vm_using_existing_dv( namespace: str, From 415898755fbce42db5a477cfc13bde899e57f4d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 06:01:51 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/storage/test_hotplug.py | 2 +- tests/utils.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/storage/test_hotplug.py b/tests/storage/test_hotplug.py index f3310dc552..59f9226bca 100644 --- a/tests/storage/test_hotplug.py +++ b/tests/storage/test_hotplug.py @@ -11,7 +11,7 @@ from ocp_resources.storage_profile import StorageProfile from tests.utils import create_windows2022_vm_with_data_volume_template -from utilities.constants import HOTPLUG_DISK_SERIAL, WIN_2K22, Images +from utilities.constants import HOTPLUG_DISK_SERIAL, WIN_2K22 from utilities.hco import ResourceEditorValidateHCOReconcile from utilities.storage import ( assert_disk_serial, diff --git a/tests/utils.py b/tests/utils.py index 3cfa0d9686..aec8b50fbc 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -27,12 +27,10 @@ from timeout_sampler import TimeoutExpiredError, TimeoutSampler, retry from utilities.artifactory import ( - cleanup_artifactory_secret_and_config_map, get_artifactory_config_map, get_artifactory_header, get_artifactory_secret, get_http_image_url, - get_test_artifact_server_url, ) from utilities.constants import ( DISK_SERIAL, @@ -51,7 +49,6 @@ TIMEOUT_15SEC, TIMEOUT_30MIN, U1_LARGE, - WIN_2K22, WINDOWS_2K22_PREFERENCE, Images, ) @@ -61,7 +58,6 @@ from utilities.infra import ( ExecCommandOnPod, ) -from utilities.os_utils import get_windows_container_disk_path from utilities.virt import ( VirtualMachineForTests, fedora_vm_body, From 9f5189f7a0d882c11c39764051bade68867b9a92 Mon Sep 17 00:00:00 2001 From: Adam Cinko Date: Thu, 13 Aug 2026 10:40:47 +0200 Subject: [PATCH 5/5] move validation os images to storage conftest Signed-off-by: Adam Cinko --- conftest.py | 4 - tests/fixtures/images/__init__.py | 0 tests/fixtures/images/validation_os_images.py | 156 ----------------- tests/storage/conftest.py | 158 +++++++++++++++++- 4 files changed, 157 insertions(+), 161 deletions(-) delete mode 100644 tests/fixtures/images/__init__.py delete mode 100644 tests/fixtures/images/validation_os_images.py diff --git a/conftest.py b/conftest.py index 47ef10d776..9a74535c54 100644 --- a/conftest.py +++ b/conftest.py @@ -65,10 +65,6 @@ stop_if_run_in_progress, ) -pytest_plugins = [ - "tests.fixtures.images.validation_os_images", -] - LOGGER = logging.getLogger(__name__) BASIC_LOGGER = logging.getLogger("basic") diff --git a/tests/fixtures/images/__init__.py b/tests/fixtures/images/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/images/validation_os_images.py b/tests/fixtures/images/validation_os_images.py deleted file mode 100644 index 032e57cb16..0000000000 --- a/tests/fixtures/images/validation_os_images.py +++ /dev/null @@ -1,156 +0,0 @@ -import pytest -from ocp_resources.cluster_role import ClusterRole -from ocp_resources.data_source import DataSource -from ocp_resources.datavolume import DataVolume -from ocp_resources.namespace import Namespace -from ocp_resources.role_binding import RoleBinding -from ocp_resources.utils.constants import TIMEOUT_1MINUTE -from pytest_testconfig import config as py_config - -from utilities.artifactory import ( - cleanup_artifactory_secret_and_config_map, - get_artifactory_config_map, - get_artifactory_secret, - get_test_artifact_server_url, -) -from utilities.constants import BIND_IMMEDIATE_ANNOTATION, REGISTRY_STR, TIMEOUT_10MIN, TIMEOUT_50MIN, WIN_2K22, Images -from utilities.os_utils import get_windows_container_disk_path -from utilities.storage import construct_datavolume_source_dict, generate_data_source_dict - - -@pytest.fixture(scope="session") -def validation_os_images_namespace(admin_client): - validation_os_images_namespace = Namespace( - name="validation-os-images", - client=admin_client, - ) - if validation_os_images_namespace.exists: - yield validation_os_images_namespace - else: - with validation_os_images_namespace as ns: - yield ns - - -@pytest.fixture(scope="session") -def validation_os_images_role_binding(admin_client, validation_os_images_namespace): - """Grants view permissions in the namespace so unprivileged clients can clone from it.""" - role_binding = RoleBinding( - client=admin_client, - name="validation-os-images-view", - namespace=validation_os_images_namespace.name, - subjects_kind="Group", - subjects_name="system:authenticated", - role_ref_kind=ClusterRole.kind, - role_ref_name="view", - ) - - if role_binding.exists: - subjects = next(iter(role_binding.instance.subjects)) - assert subjects.kind == "Group", ( - f"RoleBinding {role_binding.name} subjects kind is {subjects.kind}, expected Group" - ) - assert subjects.name == "system:authenticated", ( - f"RoleBinding {role_binding.name} subjects name is {subjects.name}, expected system:authenticated" - ) - role_ref = role_binding.instance.roleRef - assert role_ref.kind == ClusterRole.kind, ( - f"RoleBinding {role_binding.name} roleRef kind is {role_ref.kind}, expected {ClusterRole.kind}" - ) - assert role_ref.name == "view", ( - f"RoleBinding {role_binding.name} roleRef name is {role_ref.name}, expected view" - ) - yield role_binding - return - - with role_binding as rb: - yield rb - - -@pytest.fixture(scope="session") -def windows_validation_os_images_data_volume_scope_session( - validation_os_images_role_binding, - conformance_tests, -): - """Provides the DV backing the Windows Server 2022 image in the validation-os-images namespace. - - Resolution order: - 1. DataVolume exists — waits for success, yields it. - 2. DataVolume does not exist — imports via Artifactory (fails on conformance runs), yields the new DataVolume. - - Yields: - DataVolume: The DV containing the Windows 2022 image. - """ - - win_dv = DataVolume( - name=WIN_2K22, - namespace=validation_os_images_role_binding.namespace, - client=validation_os_images_role_binding.client, - ) - - if win_dv.exists: - win_dv.wait_for_dv_success(timeout=TIMEOUT_1MINUTE) - yield win_dv - return - - assert not conformance_tests, ( - f"Windows image {win_dv.name} does not exist in namespace {validation_os_images_role_binding.namespace}." - " Self-validation requires the Windows image to be pre-created." - ) - - artifactory_secret = get_artifactory_secret( - namespace=validation_os_images_role_binding.namespace, client=validation_os_images_role_binding.client - ) - artifactory_config_map = get_artifactory_config_map( - namespace=validation_os_images_role_binding.namespace, client=validation_os_images_role_binding.client - ) - - win_dv.storage_class = py_config["default_storage_class"] - win_dv.source_dict = construct_datavolume_source_dict( - source=REGISTRY_STR, - url=f"{get_test_artifact_server_url(schema=REGISTRY_STR)}/{get_windows_container_disk_path(os_value=WIN_2K22)}", - secret_name=artifactory_secret.name, - cert_configmap_name=artifactory_config_map.name, - ) - win_dv.size = Images.Windows.CONTAINER_DISK_DV_SIZE - win_dv.api_name = "storage" - win_dv.annotations = BIND_IMMEDIATE_ANNOTATION - - with win_dv as wdv: - wdv.wait_for_dv_success(timeout=TIMEOUT_50MIN) - yield wdv - cleanup_artifactory_secret_and_config_map( - artifactory_secret=artifactory_secret, - artifactory_config_map=artifactory_config_map, - ) - - -@pytest.fixture(scope="session") -def windows_validation_os_images_data_source_scope_session( - admin_client, windows_validation_os_images_data_volume_scope_session -): - win_data_source = DataSource( - name=windows_validation_os_images_data_volume_scope_session.name, - namespace=windows_validation_os_images_data_volume_scope_session.namespace, - client=admin_client, - ) - if win_data_source.exists: - source_pvc = win_data_source.instance.spec.source.pvc - assert source_pvc.name == windows_validation_os_images_data_volume_scope_session.name, ( - f"DataSource {win_data_source.name} source PVC name is {source_pvc.name}, " - f"expected {windows_validation_os_images_data_volume_scope_session.name}" - ) - assert source_pvc.namespace == windows_validation_os_images_data_volume_scope_session.pvc.namespace, ( - f"DataSource {win_data_source.name} source PVC namespace is {source_pvc.namespace}, " - f"expected {windows_validation_os_images_data_volume_scope_session.namespace}" - ) - yield win_data_source - return - - win_data_source._source = generate_data_source_dict(dv=windows_validation_os_images_data_volume_scope_session) - with win_data_source as wds: - wds.wait_for_condition( - condition=wds.Condition.READY, - status=wds.Condition.Status.TRUE, - timeout=TIMEOUT_10MIN, - ) - yield wds diff --git a/tests/storage/conftest.py b/tests/storage/conftest.py index 69a152b2da..da15a36adb 100644 --- a/tests/storage/conftest.py +++ b/tests/storage/conftest.py @@ -13,13 +13,18 @@ import shortuuid from kubernetes.dynamic.exceptions import ResourceNotFoundError from ocp_resources.cdi import CDI +from ocp_resources.cluster_role import ClusterRole from ocp_resources.config_map import ConfigMap from ocp_resources.csi_driver import CSIDriver from ocp_resources.data_source import DataSource +from ocp_resources.datavolume import DataVolume from ocp_resources.deployment import Deployment from ocp_resources.exceptions import ExecOnPodError +from ocp_resources.namespace import Namespace +from ocp_resources.role_binding import RoleBinding from ocp_resources.route import Route from ocp_resources.secret import Secret +from ocp_resources.utils.constants import TIMEOUT_1MINUTE from ocp_resources.virtual_machine_cluster_instancetype import ( VirtualMachineClusterInstancetype, ) @@ -44,19 +49,29 @@ is_hpp_cr_legacy, ) from tests.utils import create_cirros_vm -from utilities.artifactory import get_artifactory_config_map, get_artifactory_secret +from utilities.artifactory import ( + cleanup_artifactory_secret_and_config_map, + get_artifactory_config_map, + get_artifactory_secret, + get_test_artifact_server_url, +) from utilities.constants import ( + BIND_IMMEDIATE_ANNOTATION, CDI_OPERATOR, CDI_UPLOADPROXY, CNV_TEST_SERVICE_ACCOUNT, OS_FLAVOR_FEDORA, OS_FLAVOR_RHEL, + REGISTRY_STR, RHEL10_PREFERENCE, SECURITY_CONTEXT, TIMEOUT_1MIN, TIMEOUT_5SEC, + TIMEOUT_10MIN, TIMEOUT_30MIN, + TIMEOUT_50MIN, U1_SMALL, + WIN_2K22, Images, ) from utilities.hco import ( @@ -67,8 +82,11 @@ INTERNAL_HTTP_SERVER_ADDRESS, ExecCommandOnPod, ) +from utilities.os_utils import get_windows_container_disk_path from utilities.storage import ( + construct_datavolume_source_dict, data_volume_template_with_source_ref_dict, + generate_data_source_dict, get_downloaded_artifact, write_file_via_ssh, ) @@ -548,3 +566,141 @@ def unique_suffix(): @pytest.fixture(scope="class") def dv_wait_timeout(request): return request.param.get("dv_wait_timeout") if hasattr(request, "param") else TIMEOUT_30MIN + + +@pytest.fixture(scope="session") +def validation_os_images_namespace(admin_client): + validation_os_images_namespace = Namespace( + name="validation-os-images", + client=admin_client, + ) + if validation_os_images_namespace.exists: + yield validation_os_images_namespace + else: + with validation_os_images_namespace as ns: + yield ns + + +@pytest.fixture(scope="session") +def validation_os_images_role_binding(admin_client, validation_os_images_namespace): + """Grants view permissions in the namespace so unprivileged clients can clone from it.""" + role_binding = RoleBinding( + client=admin_client, + name="validation-os-images-view", + namespace=validation_os_images_namespace.name, + subjects_kind="Group", + subjects_name="system:authenticated", + role_ref_kind=ClusterRole.kind, + role_ref_name="view", + ) + + if role_binding.exists: + subjects = next(iter(role_binding.instance.subjects)) + assert subjects.kind == "Group", ( + f"RoleBinding {role_binding.name} subjects kind is {subjects.kind}, expected Group" + ) + assert subjects.name == "system:authenticated", ( + f"RoleBinding {role_binding.name} subjects name is {subjects.name}, expected system:authenticated" + ) + role_ref = role_binding.instance.roleRef + assert role_ref.kind == ClusterRole.kind, ( + f"RoleBinding {role_binding.name} roleRef kind is {role_ref.kind}, expected {ClusterRole.kind}" + ) + assert role_ref.name == "view", ( + f"RoleBinding {role_binding.name} roleRef name is {role_ref.name}, expected view" + ) + yield role_binding + return + + with role_binding as rb: + yield rb + + +@pytest.fixture(scope="session") +def windows_validation_os_images_data_volume_scope_session( + validation_os_images_role_binding, + conformance_tests, +): + """Provides the DV backing the Windows Server 2022 image in the validation-os-images namespace. + + Resolution order: + 1. DataVolume exists — waits for success, yields it. + 2. DataVolume does not exist — imports via Artifactory (fails on conformance runs), yields the new DataVolume. + + Yields: + DataVolume: The DV containing the Windows 2022 image. + """ + + win_dv = DataVolume( + name=WIN_2K22, + namespace=validation_os_images_role_binding.namespace, + client=validation_os_images_role_binding.client, + ) + + if win_dv.exists: + win_dv.wait_for_dv_success(timeout=TIMEOUT_1MINUTE) + yield win_dv + return + + assert not conformance_tests, ( + f"Windows image {win_dv.name} does not exist in namespace {validation_os_images_role_binding.namespace}." + " Self-validation requires the Windows image to be pre-created." + ) + + artifactory_secret = get_artifactory_secret( + namespace=validation_os_images_role_binding.namespace, client=validation_os_images_role_binding.client + ) + artifactory_config_map = get_artifactory_config_map( + namespace=validation_os_images_role_binding.namespace, client=validation_os_images_role_binding.client + ) + + win_dv.storage_class = py_config["default_storage_class"] + win_dv.source_dict = construct_datavolume_source_dict( + source=REGISTRY_STR, + url=f"{get_test_artifact_server_url(schema=REGISTRY_STR)}/{get_windows_container_disk_path(os_value=WIN_2K22)}", + secret_name=artifactory_secret.name, + cert_configmap_name=artifactory_config_map.name, + ) + win_dv.size = Images.Windows.CONTAINER_DISK_DV_SIZE + win_dv.api_name = "storage" + win_dv.annotations = BIND_IMMEDIATE_ANNOTATION + + with win_dv as wdv: + wdv.wait_for_dv_success(timeout=TIMEOUT_50MIN) + yield wdv + cleanup_artifactory_secret_and_config_map( + artifactory_secret=artifactory_secret, + artifactory_config_map=artifactory_config_map, + ) + + +@pytest.fixture(scope="session") +def windows_validation_os_images_data_source_scope_session( + admin_client, windows_validation_os_images_data_volume_scope_session +): + win_data_source = DataSource( + name=windows_validation_os_images_data_volume_scope_session.name, + namespace=windows_validation_os_images_data_volume_scope_session.namespace, + client=admin_client, + ) + if win_data_source.exists: + source_pvc = win_data_source.instance.spec.source.pvc + assert source_pvc.name == windows_validation_os_images_data_volume_scope_session.name, ( + f"DataSource {win_data_source.name} source PVC name is {source_pvc.name}, " + f"expected {windows_validation_os_images_data_volume_scope_session.name}" + ) + assert source_pvc.namespace == windows_validation_os_images_data_volume_scope_session.pvc.namespace, ( + f"DataSource {win_data_source.name} source PVC namespace is {source_pvc.namespace}, " + f"expected {windows_validation_os_images_data_volume_scope_session.namespace}" + ) + yield win_data_source + return + + win_data_source._source = generate_data_source_dict(dv=windows_validation_os_images_data_volume_scope_session) + with win_data_source as wds: + wds.wait_for_condition( + condition=wds.Condition.READY, + status=wds.Condition.Status.TRUE, + timeout=TIMEOUT_10MIN, + ) + yield wds