From 933d477a7d00d4d3beb3f3a52505c11f3e2aa287 Mon Sep 17 00:00:00 2001 From: Emanuele Prella Date: Tue, 11 Aug 2026 09:24:19 +0200 Subject: [PATCH] [4.21][Storage] Manual Cherry-Pick: Add Velero backup hooks opt-out tests (#5924) ##### What this PR does / why we need it: This is a **manual** cherry-pick of https://github.com/RedHatQE/openshift-virtualization-tests/pull/5478. ##### Which issue(s) this PR fixes: ##### Special notes for reviewer: ##### jira-ticket: https://redhat.atlassian.net/browse/CNV-88655 ## Summary by CodeRabbit - **New Features** - Added support for opting virtual machines out of Velero backup hooks using an annotation. - Added coverage for running and paused virtual machines to ensure backup hooks are not injected when opted out. - **Tests** - Added automated validation for namespace-scoped backups and backup-hook behavior. - Added checks confirming expected backup-hook annotations are absent. --------- Signed-off-by: Emanuele Prella --- tests/data_protection/oadp/conftest.py | 63 ++++++++++++- .../oadp/test_velero_backup_hooks.py | 91 +++++++++++++++++++ tests/data_protection/oadp/utils.py | 36 ++++++++ utilities/constants.py | 11 +++ 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 tests/data_protection/oadp/test_velero_backup_hooks.py diff --git a/tests/data_protection/oadp/conftest.py b/tests/data_protection/oadp/conftest.py index 669ad86cb9..b3d009d16d 100644 --- a/tests/data_protection/oadp/conftest.py +++ b/tests/data_protection/oadp/conftest.py @@ -1,6 +1,8 @@ import pytest from ocp_resources.datavolume import DataVolume from ocp_resources.namespace import Namespace +from ocp_resources.virtual_machine_cluster_instancetype import VirtualMachineClusterInstancetype +from ocp_resources.virtual_machine_cluster_preference import VirtualMachineClusterPreference from tests.data_protection.oadp.utils import ( VeleroRestore, @@ -10,9 +12,12 @@ BACKUP_STORAGE_LOCATION, FILE_NAME_FOR_BACKUP, OS_FLAVOR_RHEL, + RHEL10_PREFERENCE, + SKIP_BACKUP_HOOKS_ANNOTATION, TEXT_TO_TEST, TIMEOUT_8MIN, TIMEOUT_15MIN, + U1_SMALL, Images, ) from utilities.infra import create_ns @@ -24,11 +29,12 @@ check_upload_virtctl_result, create_dv, create_vm_from_dv, + data_volume_template_with_source_ref_dict, get_downloaded_artifact, virtctl_upload_dv, write_file, ) -from utilities.virt import running_vm +from utilities.virt import VirtualMachineForTests, running_vm @pytest.fixture() @@ -254,3 +260,58 @@ def velero_restore_second_namespace_with_datamover( timeout=TIMEOUT_15MIN, ) as restore: yield restore + + +@pytest.fixture() +def namespace_for_hooks_backup(admin_client, unprivileged_client): + """Namespace for hooks opt-out tests, created with unprivileged RBAC.""" + yield from create_ns(admin_client=admin_client, unprivileged_client=unprivileged_client, name="velero-hooks-ns") + + +@pytest.fixture() +def rhel_vm_with_hooks_opt_out( + unprivileged_client, + rhel10_data_source_scope_session, + snapshot_storage_class_name_scope_module, + namespace_for_hooks_backup, +): + """Running RHEL VM with kubevirt.io/skip-backup-hooks annotation set to 'true'. + + Creates a RHEL VM with the skip-backup-hooks annotation, waits for it to + reach a running state, and verifies the annotation is present before yielding. + + Yields: + VirtualMachineForTests: Running VM with backup hooks opt-out annotation. + """ + with VirtualMachineForTests( + name="vm-hooks-opt-out", + namespace=namespace_for_hooks_backup.name, + client=unprivileged_client, + os_flavor=OS_FLAVOR_RHEL, + vm_instance_type=VirtualMachineClusterInstancetype(client=unprivileged_client, name=U1_SMALL), + vm_preference=VirtualMachineClusterPreference(client=unprivileged_client, name=RHEL10_PREFERENCE), + data_volume_template=data_volume_template_with_source_ref_dict( + data_source=rhel10_data_source_scope_session, + storage_class=snapshot_storage_class_name_scope_module, + ), + annotations={SKIP_BACKUP_HOOKS_ANNOTATION: "true"}, + ) as vm: + running_vm(vm=vm) + assert vm.instance.metadata.annotations[SKIP_BACKUP_HOOKS_ANNOTATION] == "true", ( + f"VM {vm.name} missing {SKIP_BACKUP_HOOKS_ANNOTATION} annotation" + ) + yield vm + + +@pytest.fixture() +def paused_rhel_vm_with_hooks_opt_out(rhel_vm_with_hooks_opt_out): + """Paused RHEL VM with kubevirt.io/skip-backup-hooks annotation set to 'true'. + + Pauses the running VM from rhel_vm_with_hooks_opt_out and yields it + in the paused state. + + Yields: + VirtualMachineForTests: Paused VM with backup hooks opt-out annotation. + """ + rhel_vm_with_hooks_opt_out.vmi.pause(wait=True) + yield rhel_vm_with_hooks_opt_out diff --git a/tests/data_protection/oadp/test_velero_backup_hooks.py b/tests/data_protection/oadp/test_velero_backup_hooks.py new file mode 100644 index 0000000000..3577f9c6c1 --- /dev/null +++ b/tests/data_protection/oadp/test_velero_backup_hooks.py @@ -0,0 +1,91 @@ +""" +Velero Backup Hook Opt-Out Tests + +STP: +https://github.com/RedHatQE/openshift-virtualization-tests-design-docs/blob/main/stps/ +sig-storage/remove-velero-hooks-stp.md +Jira: https://redhat.atlassian.net/browse/CNV-79727 # +""" + +import logging + +import pytest + +from tests.data_protection.oadp.utils import assert_velero_backup_hooks_not_injected +from utilities.oadp import VeleroBackup + +LOGGER = logging.getLogger(__name__) + + +class TestVeleroBackupHookOptOut: + """ + Tests for Velero backup hook opt-out. + + The skip-backup-hooks annotation is intended for metadata-only backup workflows where + third-party solutions handle the actual data protection. These tests verify that + freeze/unfreeze hooks are not injected and that Velero backup completes when the + annotation is set. + + Preconditions: + - Under-test VM with ``kubevirt.io/skip-backup-hooks`` set to ``"true"`` + """ + + @pytest.mark.polarion("CNV-16267") + def test_backup_paused_vm_hooks_disabled( + self, + admin_client, + namespace_for_hooks_backup, + paused_rhel_vm_with_hooks_opt_out, + ): + """ + Test that backup of paused VM completes with hooks disabled. + + Preconditions: + - Under-test VM with ``kubevirt.io/skip-backup-hooks`` set to ``"true"``, paused + + Steps: + 1. Run Velero backup + 2. Inspect virt-launcher pod for Velero hook annotations + + Expected: + - No freeze/unfreeze hooks are injected on the virt-launcher pod + - Backup completes successfully + """ + with VeleroBackup( + name="backup-paused-optout", + client=admin_client, + included_namespaces=[namespace_for_hooks_backup.name], + teardown=True, + ) as backup: + assert_velero_backup_hooks_not_injected(vm=paused_rhel_vm_with_hooks_opt_out, admin_client=admin_client) + LOGGER.info(f"Backup {backup.name} completed for paused VM with opt-out annotation") + + @pytest.mark.polarion("CNV-16268") + def test_backup_running_vm_hooks_disabled( + self, + admin_client, + namespace_for_hooks_backup, + rhel_vm_with_hooks_opt_out, + ): + """ + Test that backup of a running VM completes with hooks disabled. + + Preconditions: + - Under-test VM with ``kubevirt.io/skip-backup-hooks`` set to ``"true"``, running + + Steps: + 1. Run Velero backup + 2. Inspect virt-launcher pod for Velero hook annotations + + Expected: + - No freeze/unfreeze hooks are injected on the virt-launcher pod + - Backup completes successfully + """ + with VeleroBackup( + name="backup-hooks-opt-out", + client=admin_client, + included_namespaces=[namespace_for_hooks_backup.name], + teardown=True, + ) as backup: + assert_velero_backup_hooks_not_injected(vm=rhel_vm_with_hooks_opt_out, admin_client=admin_client) + LOGGER.info(f"Backup {backup.name} completed for running VM with opt-out annotation") diff --git a/tests/data_protection/oadp/utils.py b/tests/data_protection/oadp/utils.py index d688989573..e266891767 100644 --- a/tests/data_protection/oadp/utils.py +++ b/tests/data_protection/oadp/utils.py @@ -1,8 +1,10 @@ import logging +from typing import TYPE_CHECKING from ocp_resources.persistent_volume_claim import PersistentVolumeClaim from ocp_resources.restore import Restore from ocp_resources.storage_profile import StorageProfile +from ocp_resources.virtual_machine_instance import VirtualMachineInstance from utilities import console from utilities.constants import ( @@ -14,12 +16,18 @@ TIMEOUT_10SEC, TIMEOUT_15SEC, TIMEOUT_20SEC, + VELERO_BACKUP_HOOK_ANNOTATIONS, ) from utilities.infra import ( unique_name, ) from utilities.oadp import delete_velero_resource +if TYPE_CHECKING: + from kubernetes.dynamic import DynamicClient + + from utilities.virt import VirtualMachineForTests + LOGGER = logging.getLogger(__name__) @@ -81,3 +89,31 @@ def is_storage_class_support_volume_mode(storage_class_name, requested_volume_mo def wait_for_restored_dv(dv): dv.pvc.wait_for_status(status=PersistentVolumeClaim.Status.BOUND, timeout=TIMEOUT_15SEC) dv.wait_for_dv_success(timeout=TIMEOUT_10SEC) + + +def assert_velero_backup_hooks_not_injected(vm: VirtualMachineForTests, admin_client: DynamicClient) -> None: + """Assert virt-launcher has no Velero freeze/unfreeze hook annotations. + + Absence of these annotations means Velero will not execute filesystem + freeze/unfreeze during backup. + + Args: + vm: VirtualMachine whose virt-launcher pod is checked. + admin_client: Privileged client used to access the virt-launcher pod. + + Raises: + AssertionError: If any Velero hook annotations are found on the virt-launcher pod. + """ + virt_launcher_pod = VirtualMachineInstance( + client=admin_client, + name=vm.name, + namespace=vm.namespace, + ).virt_launcher_pod + pod_annotations = virt_launcher_pod.instance.metadata.annotations or {} + present_hook_annotations = [ + annotation_key for annotation_key in VELERO_BACKUP_HOOK_ANNOTATIONS if annotation_key in pod_annotations + ] + assert not present_hook_annotations, ( + f"VM {vm.name} virt-launcher pod has Velero hook annotations {present_hook_annotations} " + f"but backup hooks should be disabled" + ) diff --git a/utilities/constants.py b/utilities/constants.py index 7e6ea22fdf..bd28d00893 100644 --- a/utilities/constants.py +++ b/utilities/constants.py @@ -953,6 +953,17 @@ class NamespacesNames: FILE_NAME_FOR_BACKUP = "file_before_backup.txt" TEXT_TO_TEST = "text" BACKUP_STORAGE_LOCATION = "dpa-1" +SKIP_BACKUP_HOOKS_ANNOTATION = "kubevirt.io/skip-backup-hooks" + +# Velero hook annotations injected on virt-launcher when backup hooks are enabled. +# See kubevirt pkg/storage/velero and pkg/storage/pod/annotations/generator.go +VELERO_BACKUP_HOOK_ANNOTATIONS = ( + "pre.hook.backup.velero.io/container", + "pre.hook.backup.velero.io/command", + "pre.hook.backup.velero.io/timeout", + "post.hook.backup.velero.io/container", + "post.hook.backup.velero.io/command", +) # AAQ AAQ_NAMESPACE_LABEL = {"application-aware-quota/enable-gating": ""}