/* Copyright the Velero contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package volumehelper import ( "testing" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" corev1api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "github.com/vmware-tanzu/velero/internal/volumehelper" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/kuberesource" velerotest "github.com/vmware-tanzu/velero/pkg/test" ) func TestShouldPerformSnapshotWithBackup(t *testing.T) { tests := []struct { name string pvc *corev1api.PersistentVolumeClaim pv *corev1api.PersistentVolume backup *velerov1api.Backup wantSnapshot bool wantError bool }{ { name: "Returns true when snapshotVolumes not set", pvc: &corev1api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pvc", Namespace: "default", }, Spec: corev1api.PersistentVolumeClaimSpec{ VolumeName: "test-pv", }, Status: corev1api.PersistentVolumeClaimStatus{ Phase: corev1api.ClaimBound, }, }, pv: &corev1api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pv", }, Spec: corev1api.PersistentVolumeSpec{ PersistentVolumeSource: corev1api.PersistentVolumeSource{ CSI: &corev1api.CSIPersistentVolumeSource{ Driver: "test-driver", }, }, ClaimRef: &corev1api.ObjectReference{ Namespace: "default", Name: "test-pvc", }, }, }, backup: &velerov1api.Backup{ ObjectMeta: metav1.ObjectMeta{ Name: "test-backup", Namespace: "velero", }, }, wantSnapshot: true, wantError: false, }, { name: "Returns false when snapshotVolumes is false", pvc: &corev1api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pvc", Namespace: "default", }, Spec: corev1api.PersistentVolumeClaimSpec{ VolumeName: "test-pv", }, Status: corev1api.PersistentVolumeClaimStatus{ Phase: corev1api.ClaimBound, }, }, pv: &corev1api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pv", }, Spec: corev1api.PersistentVolumeSpec{ PersistentVolumeSource: corev1api.PersistentVolumeSource{ CSI: &corev1api.CSIPersistentVolumeSource{ Driver: "test-driver", }, }, ClaimRef: &corev1api.ObjectReference{ Namespace: "default", Name: "test-pvc", }, }, }, backup: &velerov1api.Backup{ ObjectMeta: metav1.ObjectMeta{ Name: "test-backup", Namespace: "velero", }, Spec: velerov1api.BackupSpec{ SnapshotVolumes: boolPtr(false), }, }, wantSnapshot: false, wantError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create fake client with PV and PVC client := velerotest.NewFakeControllerRuntimeClient(t, tt.pv, tt.pvc) // Convert PVC to unstructured pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tt.pvc) require.NoError(t, err) unstructuredPVC := &unstructured.Unstructured{Object: pvcMap} logger := logrus.New() // Call the function under test - this is the wrapper for third-party plugins result, err := ShouldPerformSnapshotWithBackup( unstructuredPVC, kuberesource.PersistentVolumeClaims, *tt.backup, client, logger, ) if tt.wantError { require.Error(t, err) } else { require.NoError(t, err) require.Equal(t, tt.wantSnapshot, result) } }) } } func boolPtr(b bool) *bool { return &b } func TestShouldPerformSnapshotWithVolumeHelper(t *testing.T) { tests := []struct { name string pvc *corev1api.PersistentVolumeClaim pv *corev1api.PersistentVolume backup *velerov1api.Backup wantSnapshot bool wantError bool }{ { name: "Returns true with nil VolumeHelper when snapshotVolumes not set", pvc: &corev1api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pvc", Namespace: "default", }, Spec: corev1api.PersistentVolumeClaimSpec{ VolumeName: "test-pv", }, Status: corev1api.PersistentVolumeClaimStatus{ Phase: corev1api.ClaimBound, }, }, pv: &corev1api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pv", }, Spec: corev1api.PersistentVolumeSpec{ PersistentVolumeSource: corev1api.PersistentVolumeSource{ CSI: &corev1api.CSIPersistentVolumeSource{ Driver: "test-driver", }, }, ClaimRef: &corev1api.ObjectReference{ Namespace: "default", Name: "test-pvc", }, }, }, backup: &velerov1api.Backup{ ObjectMeta: metav1.ObjectMeta{ Name: "test-backup", Namespace: "velero", }, }, wantSnapshot: true, wantError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create fake client with PV client := velerotest.NewFakeControllerRuntimeClient(t, tt.pv, tt.pvc) // Convert PVC to unstructured pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tt.pvc) require.NoError(t, err) unstructuredPVC := &unstructured.Unstructured{Object: pvcMap} logger := logrus.New() // Call the function under test with nil VolumeHelper // This exercises the fallback path that creates a new VolumeHelper per call result, err := ShouldPerformSnapshotWithVolumeHelper( unstructuredPVC, kuberesource.PersistentVolumeClaims, *tt.backup, client, logger, nil, // Pass nil for VolumeHelper - exercises fallback path ) if tt.wantError { require.Error(t, err) } else { require.NoError(t, err) require.Equal(t, tt.wantSnapshot, result) } }) } } // TestShouldPerformSnapshotWithNonNilVolumeHelper tests the ShouldPerformSnapshotWithVolumeHelper // function when a pre-created VolumeHelper is passed. This exercises the cached path used // by BIA plugins for better performance. func TestShouldPerformSnapshotWithNonNilVolumeHelper(t *testing.T) { pvc := &corev1api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pvc", Namespace: "default", }, Spec: corev1api.PersistentVolumeClaimSpec{ VolumeName: "test-pv", }, Status: corev1api.PersistentVolumeClaimStatus{ Phase: corev1api.ClaimBound, }, } pv := &corev1api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pv", }, Spec: corev1api.PersistentVolumeSpec{ PersistentVolumeSource: corev1api.PersistentVolumeSource{ CSI: &corev1api.CSIPersistentVolumeSource{ Driver: "test-driver", }, }, ClaimRef: &corev1api.ObjectReference{ Namespace: "default", Name: "test-pvc", }, }, } backup := &velerov1api.Backup{ ObjectMeta: metav1.ObjectMeta{ Name: "test-backup", Namespace: "velero", }, Spec: velerov1api.BackupSpec{ IncludedNamespaces: []string{"default"}, }, } // Create fake client with PV and PVC client := velerotest.NewFakeControllerRuntimeClient(t, pv, pvc) logger := logrus.New() // Create VolumeHelper using the internal function with namespace caching vh, err := volumehelper.NewVolumeHelperImplWithNamespaces( nil, // no resource policies for this test nil, // snapshotVolumes not set logger, client, false, // defaultVolumesToFSBackup true, // backupExcludePVC []string{"default"}, ) require.NoError(t, err) require.NotNil(t, vh) // Convert PVC to unstructured pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pvc) require.NoError(t, err) unstructuredPVC := &unstructured.Unstructured{Object: pvcMap} // Call with non-nil VolumeHelper - exercises the cached path result, err := ShouldPerformSnapshotWithVolumeHelper( unstructuredPVC, kuberesource.PersistentVolumeClaims, *backup, client, logger, vh, // Pass non-nil VolumeHelper - exercises cached path ) require.NoError(t, err) require.True(t, result, "Should return true for snapshot when snapshotVolumes not set") }