Files
velero/pkg/backup/actions/backup_pv_action_test.go
Joseph db2193c53a Make update
Signed-off-by: Joseph <jvaikath@redhat.com>
2025-12-19 20:17:46 +07:00

325 lines
12 KiB
Go

/*
Copyright 2017 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 actions
import (
"testing"
"github.com/stretchr/testify/assert"
"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"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
func TestBackupPVAction(t *testing.T) {
pvc := &unstructured.Unstructured{
Object: map[string]any{
"spec": map[string]any{},
"status": map[string]any{},
},
}
backup := &v1.Backup{}
a := NewPVCAction(velerotest.NewLogger())
// no spec.volumeName should result in no error
// and no additional items
_, additional, err := a.Execute(pvc, backup)
require.NoError(t, err)
assert.Empty(t, additional)
// empty spec.volumeName should result in no error
// and no additional items
pvc.Object["spec"].(map[string]any)["volumeName"] = ""
_, additional, err = a.Execute(pvc, backup)
require.NoError(t, err)
assert.Empty(t, additional)
// Action should clean the spec.Selector when the StorageClassName is not set.
input := builder.ForPersistentVolumeClaim("abc", "abc").VolumeName("pv").Selector(&metav1.LabelSelector{MatchLabels: map[string]string{"abc": "abc"}}).Phase(corev1api.ClaimBound).Result()
inputUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(input)
require.NoError(t, err)
item, additional, err := a.Execute(&unstructured.Unstructured{Object: inputUnstructured}, backup)
require.NoError(t, err)
require.Len(t, additional, 1)
modifiedPVC := new(corev1api.PersistentVolumeClaim)
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(item.UnstructuredContent(), modifiedPVC))
require.Nil(t, modifiedPVC.Spec.Selector)
// Action should clean the spec.Selector when the StorageClassName is set to specific StorageClass
input2 := builder.ForPersistentVolumeClaim("abc", "abc").VolumeName("pv").StorageClass("sc1").Selector(&metav1.LabelSelector{MatchLabels: map[string]string{"abc": "abc"}}).Phase(corev1api.ClaimBound).Result()
inputUnstructured2, err2 := runtime.DefaultUnstructuredConverter.ToUnstructured(input2)
require.NoError(t, err2)
item2, additional2, err2 := a.Execute(&unstructured.Unstructured{Object: inputUnstructured2}, backup)
require.NoError(t, err2)
require.Len(t, additional2, 1)
modifiedPVC2 := new(corev1api.PersistentVolumeClaim)
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(item2.UnstructuredContent(), modifiedPVC2))
require.Nil(t, modifiedPVC2.Spec.Selector)
// Action should keep the spec.Selector when the StorageClassName is set to ""
input3 := builder.ForPersistentVolumeClaim("abc", "abc").StorageClass("").Selector(&metav1.LabelSelector{MatchLabels: map[string]string{"abc": "abc"}}).VolumeName("pv").Phase(corev1api.ClaimBound).Result()
inputUnstructured3, err3 := runtime.DefaultUnstructuredConverter.ToUnstructured(input3)
require.NoError(t, err3)
item3, additional3, err3 := a.Execute(&unstructured.Unstructured{Object: inputUnstructured3}, backup)
require.NoError(t, err3)
require.Len(t, additional3, 1)
modifiedPVC3 := new(corev1api.PersistentVolumeClaim)
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(item3.UnstructuredContent(), modifiedPVC3))
require.Equal(t, input3.Spec.Selector, modifiedPVC3.Spec.Selector)
// Action should delete label started with"velero.io/" from the spec.Selector when the StorageClassName is set to ""
input4 := builder.ForPersistentVolumeClaim("abc", "abc").StorageClass("").Selector(&metav1.LabelSelector{MatchLabels: map[string]string{"velero.io/abc": "abc", "abc": "abc"}}).VolumeName("pv").Phase(corev1api.ClaimBound).Result()
inputUnstructured4, err4 := runtime.DefaultUnstructuredConverter.ToUnstructured(input4)
require.NoError(t, err4)
item4, additional4, err4 := a.Execute(&unstructured.Unstructured{Object: inputUnstructured4}, backup)
require.NoError(t, err4)
require.Len(t, additional4, 1)
modifiedPVC4 := new(corev1api.PersistentVolumeClaim)
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(item4.UnstructuredContent(), modifiedPVC4))
require.Equal(t, &metav1.LabelSelector{MatchLabels: map[string]string{"abc": "abc"}}, modifiedPVC4.Spec.Selector)
// Action should clean the spec.Selector when the StorageClassName has value
input5 := builder.ForPersistentVolumeClaim("abc", "abc").StorageClass("sc1").Selector(&metav1.LabelSelector{MatchLabels: map[string]string{"velero.io/abc": "abc", "abc": "abc"}}).VolumeName("pv").Phase(corev1api.ClaimBound).Result()
inputUnstructured5, err5 := runtime.DefaultUnstructuredConverter.ToUnstructured(input5)
require.NoError(t, err5)
item5, additional5, err5 := a.Execute(&unstructured.Unstructured{Object: inputUnstructured5}, backup)
require.NoError(t, err5)
require.Len(t, additional5, 1)
modifiedPVC5 := new(corev1api.PersistentVolumeClaim)
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(item5.UnstructuredContent(), modifiedPVC5))
require.Nil(t, modifiedPVC5.Spec.Selector)
// non-empty spec.volumeName when status.phase is empty
// should result in no error and no additional items
pvc.Object["spec"].(map[string]any)["volumeName"] = "myVolume"
_, additional, err = a.Execute(pvc, backup)
require.NoError(t, err)
require.Empty(t, additional)
// non-empty spec.volumeName when status.phase is 'Pending'
// should result in no error and no additional items
pvc.Object["status"].(map[string]any)["phase"] = corev1api.ClaimPending
_, additional, err = a.Execute(pvc, backup)
require.NoError(t, err)
require.Empty(t, additional)
// non-empty spec.volumeName when status.phase is 'Lost'
// should result in no error and no additional items
pvc.Object["status"].(map[string]any)["phase"] = corev1api.ClaimLost
_, additional, err = a.Execute(pvc, backup)
require.NoError(t, err)
require.Empty(t, additional)
// non-empty spec.volumeName when status.phase is 'Bound'
// should result in no error and one additional item for the PV
pvc.Object["status"].(map[string]any)["phase"] = corev1api.ClaimBound
_, additional, err = a.Execute(pvc, backup)
require.NoError(t, err)
require.Len(t, additional, 1)
assert.Equal(t, velero.ResourceIdentifier{GroupResource: kuberesource.PersistentVolumes, Name: "myVolume"}, additional[0])
// empty spec.volumeName when status.phase is 'Bound' should
// result in no error and no additional items
pvc.Object["spec"].(map[string]any)["volumeName"] = ""
_, additional, err = a.Execute(pvc, backup)
require.NoError(t, err)
assert.Empty(t, additional)
}
func TestCleanupStaleVeleroLabels(t *testing.T) {
tests := []struct {
name string
inputPVC *corev1api.PersistentVolumeClaim
backup *v1.Backup
expectedLabels map[string]string
expectedSelector *metav1.LabelSelector
}{
{
name: "removes restore-name labels",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
Labels: map[string]string{
"velero.io/restore-name": "old-restore",
"app": "myapp",
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: map[string]string{
"app": "myapp",
},
},
{
name: "removes backup-name labels that don't match current backup",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
Labels: map[string]string{
"velero.io/backup-name": "old-backup",
"app": "myapp",
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: map[string]string{
"app": "myapp",
},
},
{
name: "keeps backup-name labels that match current backup",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
Labels: map[string]string{
"velero.io/backup-name": "current-backup",
"app": "myapp",
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: map[string]string{
"velero.io/backup-name": "current-backup",
"app": "myapp",
},
},
{
name: "removes volume-snapshot-name labels",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
Labels: map[string]string{
"velero.io/volume-snapshot-name": "old-snapshot",
"app": "myapp",
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: map[string]string{
"app": "myapp",
},
},
{
name: "removes velero labels from selector match labels",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
},
Spec: corev1api.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"velero.io/restore-name": "old-restore",
"velero.io/backup-name": "old-backup",
"app": "myapp",
},
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: nil,
expectedSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "myapp",
},
},
},
{
name: "handles PVC with no labels",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: nil,
},
{
name: "handles PVC with no selector",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
Labels: map[string]string{
"app": "myapp",
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: map[string]string{
"app": "myapp",
},
expectedSelector: nil,
},
{
name: "removes multiple stale velero labels",
inputPVC: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc",
Labels: map[string]string{
"velero.io/restore-name": "old-restore",
"velero.io/backup-name": "old-backup",
"velero.io/volume-snapshot-name": "old-snapshot",
"app": "myapp",
"env": "prod",
},
},
Spec: corev1api.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"velero.io/restore-name": "old-restore",
"app": "myapp",
},
},
},
},
backup: &v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "current-backup"}},
expectedLabels: map[string]string{
"app": "myapp",
"env": "prod",
},
expectedSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "myapp",
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
action := NewPVCAction(velerotest.NewLogger())
// Create a copy of the input PVC to avoid modifying the test case
pvcCopy := tc.inputPVC.DeepCopy()
action.cleanupStaleVeleroLabels(pvcCopy, tc.backup)
assert.Equal(t, tc.expectedLabels, pvcCopy.Labels, "Labels should match expected values")
assert.Equal(t, tc.expectedSelector, pvcCopy.Spec.Selector, "Selector should match expected values")
})
}
}