-
Notifications
You must be signed in to change notification settings - Fork 68
Storage: Add windows OADP test #4603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,30 @@ | ||
| import pytest | ||
| from ocp_resources.data_protection_application import DataProtectionApplication | ||
| from ocp_resources.datavolume import DataVolume | ||
| from ocp_resources.namespace import Namespace | ||
|
|
||
| from ocp_resources.resource import ResourceEditor | ||
| from pytest_testconfig import config as py_config | ||
|
|
||
| from tests.data_protection.oadp.utils import ( | ||
| OADP_DPA_NAME, | ||
| OADP_VELERO_IMAGE_FQIN_OVERRIDE, | ||
| create_windows_vm_from_dv_template, | ||
| write_file_windows_vm_for_oadp, | ||
| ) | ||
| from utilities.artifactory import get_test_artifact_server_url | ||
| from utilities.constants import ( | ||
| ADP_NAMESPACE, | ||
| BACKUP_STORAGE_LOCATION, | ||
| DV_SIZE_STR, | ||
| FILE_NAME_FOR_BACKUP, | ||
| IMAGE_PATH_STR, | ||
| OS_FLAVOR_RHEL, | ||
| OS_VERSION_STR, | ||
| TEMPLATE_LABELS_STR, | ||
| TEXT_TO_TEST, | ||
| TIMEOUT_8MIN, | ||
| TIMEOUT_15MIN, | ||
| TIMEOUT_60MIN, | ||
| Images, | ||
| ) | ||
| from utilities.infra import create_ns | ||
|
|
@@ -26,7 +42,20 @@ | |
| virtctl_upload_dv, | ||
| write_file, | ||
| ) | ||
| from utilities.virt import running_vm | ||
| from utilities.virt import running_vm, wait_for_windows_vm | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
| def dpa_velero_image_override(admin_client): | ||
| """Temporarily override DPA Velero image, restored on teardown.""" | ||
| dpa = DataProtectionApplication( | ||
| name=OADP_DPA_NAME, | ||
| namespace=ADP_NAMESPACE, | ||
| client=admin_client, | ||
| ) | ||
| patch = {"spec": {"unsupportedOverrides": {"veleroImageFqin": OADP_VELERO_IMAGE_FQIN_OVERRIDE}}} | ||
| with ResourceEditor(patches={dpa: patch}): | ||
| yield dpa | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
|
|
@@ -137,6 +166,69 @@ def rhel_vm_with_data_volume_template( | |
| yield vm | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
| def windows_vm_with_data_volume_template( | ||
| admin_client, | ||
| dpa_velero_image_override, | ||
| namespace_for_backup, | ||
| snapshot_storage_class_name_scope_module, | ||
| modern_cpu_for_migration, | ||
| ): | ||
| """Windows VM in the backup namespace for OADP backup testing.""" | ||
| latest_windows = py_config["latest_windows_os_dict"] | ||
| with create_windows_vm_from_dv_template( | ||
| storage_class=snapshot_storage_class_name_scope_module, | ||
| namespace=namespace_for_backup.name, | ||
| dv_name="oadp-windows-dv", | ||
| vm_name="oadp-windows-vm", | ||
| image_url=f"{get_test_artifact_server_url()}{latest_windows.get(IMAGE_PATH_STR)}", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to guarantee Win VM with vTPM, we can use an explicit version (for example, Win 2022) |
||
| dv_size=latest_windows.get(DV_SIZE_STR), | ||
| template_labels=latest_windows.get(TEMPLATE_LABELS_STR, {}), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason we want to use a template? Why not InstanceType and Preference? |
||
| client=admin_client, | ||
| cpu_model=modern_cpu_for_migration, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are not planning to migrate it, we don't need |
||
| dv_wait_timeout=TIMEOUT_60MIN, | ||
| ) as vm: | ||
| wait_for_windows_vm( | ||
| vm=vm, | ||
| version=latest_windows.get(OS_VERSION_STR), | ||
| timeout=TIMEOUT_60MIN, | ||
| ) | ||
| write_file_windows_vm_for_oadp(vm=vm) | ||
| yield vm | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
| def velero_backup_first_namespace_without_datamover( | ||
| admin_client, | ||
| namespace_for_backup, | ||
| windows_vm_with_data_volume_template, | ||
| ): | ||
| with VeleroBackup( | ||
| client=admin_client, | ||
| included_namespaces=[ | ||
| namespace_for_backup.name, | ||
| ], | ||
| name="backup-windows-dvt-ns", | ||
| ) as backup: | ||
| yield backup | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
| def velero_restore_first_namespace_without_datamover( | ||
| admin_client, | ||
| velero_backup_first_namespace_without_datamover, | ||
| ): | ||
| Namespace(name=velero_backup_first_namespace_without_datamover.included_namespaces[0]).delete(wait=True) | ||
| with VeleroRestore( | ||
| client=admin_client, | ||
| included_namespaces=velero_backup_first_namespace_without_datamover.included_namespaces, | ||
| name="restore-windows-dvt-ns", | ||
| backup_name=velero_backup_first_namespace_without_datamover.name, | ||
| timeout=TIMEOUT_8MIN, | ||
| ) as restore: | ||
| yield restore | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
| def velero_backup_first_namespace_using_datamover(admin_client, namespace_for_backup): | ||
| with VeleroBackup( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,116 @@ | ||
| import logging | ||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Generator | ||
| from contextlib import contextmanager | ||
| from typing import Any | ||
|
|
||
| from kubernetes.dynamic import DynamicClient | ||
| from ocp_resources.datavolume import DataVolume | ||
| from ocp_resources.persistent_volume_claim import PersistentVolumeClaim | ||
| from ocp_resources.template import Template | ||
| from pyhelper_utils.shell import run_ssh_commands | ||
|
|
||
| from utilities.artifactory import ( | ||
| cleanup_artifactory_secret_and_config_map, | ||
| get_artifactory_config_map, | ||
| get_artifactory_secret, | ||
| ) | ||
| from utilities.constants import ( | ||
| TEXT_TO_TEST, | ||
| TIMEOUT_10SEC, | ||
| TIMEOUT_15SEC, | ||
| TIMEOUT_60MIN, | ||
| ) | ||
| from utilities.virt import VirtualMachineForTests, VirtualMachineForTestsFromTemplate, running_vm | ||
|
|
||
| FILE_PATH_FOR_WINDOWS_BACKUP = "C:/oadp_file_before_backup.txt" | ||
|
|
||
| LOGGER = logging.getLogger(__name__) | ||
| OADP_DPA_NAME = "dpa" | ||
| OADP_VELERO_IMAGE_FQIN_OVERRIDE = "quay.io/sseago/velero:csi-quick-poll" | ||
|
dalia-frank marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a comment on why it is used? Will there be the official velero image? Is it tracked somewhere? |
||
|
|
||
|
|
||
| def wait_for_restored_dv(dv): | ||
| def wait_for_restored_dv(dv: DataVolume) -> None: | ||
| dv.pvc.wait_for_status(status=PersistentVolumeClaim.Status.BOUND, timeout=TIMEOUT_15SEC) | ||
| dv.wait_for_dv_success(timeout=TIMEOUT_10SEC) | ||
|
|
||
|
|
||
| def write_file_windows_vm_for_oadp(vm: VirtualMachineForTests) -> None: | ||
| """Write test data to marker file on Windows VM for OADP backup verification.""" | ||
| value = TEXT_TO_TEST.replace("'", "''") | ||
| cmd = [ | ||
| "powershell", | ||
| "-NoProfile", | ||
| "-Command", | ||
| f"Set-Content -LiteralPath '{FILE_PATH_FOR_WINDOWS_BACKUP}' -Value '{value}' -Encoding ascii", | ||
| ] | ||
| run_ssh_commands(host=vm.ssh_exec, commands=cmd) | ||
|
Comment on lines
+39
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have this in another util function, can we reuse the code? |
||
|
|
||
|
|
||
| @contextmanager | ||
| def create_windows_vm_from_dv_template( | ||
| storage_class: str, | ||
| namespace: str, | ||
| dv_name: str, | ||
| vm_name: str, | ||
| image_url: str, | ||
| dv_size: str, | ||
| template_labels: dict[str, Any], | ||
| client: DynamicClient, | ||
| cpu_model: str | None = None, | ||
| wait_running: bool = True, | ||
| dv_wait_timeout: int = TIMEOUT_60MIN, | ||
| ) -> Generator[VirtualMachineForTests, None, None]: | ||
| """ | ||
| Create Windows VM from template with HTTP DataVolume. | ||
|
|
||
| Args: | ||
| storage_class: Storage class for the DataVolume. | ||
| namespace: Target namespace. | ||
| dv_name: DataVolume name. | ||
| vm_name: VirtualMachine name. | ||
| image_url: HTTP URL to Windows image. | ||
| dv_size: DataVolume size. | ||
| template_labels: Labels to identify the Windows template. | ||
| client: Kubernetes dynamic client. | ||
| cpu_model: CPU model for the VM. | ||
| wait_running: Wait for VM to reach Running state. | ||
| dv_wait_timeout: DataVolume import timeout. | ||
|
|
||
| Yields: | ||
| VirtualMachineForTests instance. | ||
| """ | ||
| artifactory_secret = None | ||
| artifactory_config_map = None | ||
|
|
||
| try: | ||
| artifactory_secret = get_artifactory_secret(namespace=namespace) | ||
| artifactory_config_map = get_artifactory_config_map(namespace=namespace) | ||
|
|
||
| dv = DataVolume( | ||
| name=dv_name, | ||
| namespace=namespace, | ||
| storage_class=storage_class, | ||
| source="http", | ||
| url=image_url, | ||
| size=dv_size, | ||
| client=client, | ||
| api_name="storage", | ||
| secret=artifactory_secret, | ||
| cert_configmap=artifactory_config_map.name, | ||
| ) | ||
| dv.to_dict() | ||
| with VirtualMachineForTestsFromTemplate( | ||
| name=vm_name, | ||
| namespace=namespace, | ||
| client=client, | ||
| labels=Template.generate_template_labels(**template_labels), | ||
| cpu_model=cpu_model, | ||
| data_volume_template={"metadata": dv.res["metadata"], "spec": dv.res["spec"]}, | ||
| ) as vm: | ||
| if wait_running: | ||
| running_vm(vm=vm, dv_wait_timeout=dv_wait_timeout) | ||
| yield vm | ||
| finally: | ||
| cleanup_artifactory_secret_and_config_map( | ||
| artifactory_secret=artifactory_secret, artifactory_config_map=artifactory_config_map | ||
| ) | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1725,6 +1725,28 @@ def get_guest_os_info(vmi): | |||||||||
| raise | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def verify_file_in_windows_vm(windows_vm: VirtualMachineForTests, file_name_with_path: str, file_content: str) -> None: | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
| """ | ||||||||||
| Verify that a file on a Windows VM contains the expected content. | ||||||||||
|
|
||||||||||
| Args: | ||||||||||
| windows_vm: The Windows VM to check. | ||||||||||
| file_name_with_path: Full path to the file on the Windows guest (e.g., "C:/test.txt"). | ||||||||||
| file_content: Expected file content. | ||||||||||
|
|
||||||||||
| Raises: | ||||||||||
| AssertionError: If file content does not match expected content. | ||||||||||
| """ | ||||||||||
| cmd = [ | ||||||||||
| "powershell", | ||||||||||
| "-NoProfile", | ||||||||||
| "-Command", | ||||||||||
| f"Get-Content -LiteralPath '{file_name_with_path}'", | ||||||||||
| ] | ||||||||||
| out = run_ssh_commands(host=windows_vm.ssh_exec, commands=cmd)[0].strip() | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's an SSH retry in the original function to avoid flakiness
Suggested change
|
||||||||||
| assert out == file_content, f"'{out}' does not equal '{file_content}'" | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def get_windows_os_dict(windows_version: str) -> dict[str, Any]: | ||||||||||
| """ | ||||||||||
| Returns a dictionary of Windows os information from the system_windows_os_matrix in py_config. | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Images from the artifactory registry are better maintained, so I would suggest using them
url=f"{get_test_artifact_server_url(schema='registry')}/{WINDOWS_2022[CONTAINER_DISK_IMAGE_PATH_STR]}",