diff --git a/changelogs/unreleased/6875-Lyndon-Li b/changelogs/unreleased/6875-Lyndon-Li new file mode 100644 index 0000000000..8d11bca2c9 --- /dev/null +++ b/changelogs/unreleased/6875-Lyndon-Li @@ -0,0 +1 @@ +Fix issue #6859, move plugin depending podvolume functions to util pkg, so as to remove the dependencies to unnecessary repository packages like kopia, azure, etc. \ No newline at end of file diff --git a/pkg/apis/velero/v1/labels_annotations.go b/pkg/apis/velero/v1/labels_annotations.go index e16d947efb..675546ef95 100644 --- a/pkg/apis/velero/v1/labels_annotations.go +++ b/pkg/apis/velero/v1/labels_annotations.go @@ -89,6 +89,14 @@ const ( // ResourceUsageLabel is the label key to explain the Velero resource usage. ResourceUsageLabel = "velero.io/resource-usage" + + // VolumesToBackupAnnotation is the annotation on a pod whose mounted volumes + // need to be backed up using pod volume backup. + VolumesToBackupAnnotation = "backup.velero.io/backup-volumes" + + // VolumesToExcludeAnnotation is the annotation on a pod whose mounted volumes + // should be excluded from pod volume backup. + VolumesToExcludeAnnotation = "backup.velero.io/backup-volumes-excludes" ) type AsyncOperationIDPrefix string diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index f1cafbfcad..ddee9a0cb2 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -52,6 +52,7 @@ import ( vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/podvolume" "github.com/vmware-tanzu/velero/pkg/util/boolptr" + pdvolumeutil "github.com/vmware-tanzu/velero/pkg/util/podvolume" "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -200,7 +201,7 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti // Get the list of volumes to back up using pod volume backup from the pod's annotations. Remove from this list // any volumes that use a PVC that we've already backed up (this would be in a read-write-many scenario, // where it's been backed up from another pod), since we don't need >1 backup per PVC. - includedVolumes, optedOutVolumes := podvolume.GetVolumesByPod(pod, boolptr.IsSetToTrue(ib.backupRequest.Spec.DefaultVolumesToFsBackup)) + includedVolumes, optedOutVolumes := pdvolumeutil.GetVolumesByPod(pod, boolptr.IsSetToTrue(ib.backupRequest.Spec.DefaultVolumesToFsBackup)) for _, volume := range includedVolumes { // track the volumes that are PVCs using the PVC snapshot tracker, so that when we backup PVCs/PVs // via an item action in the next step, we don't snapshot PVs that will have their data backed up diff --git a/pkg/podvolume/util.go b/pkg/podvolume/util.go index 15e2e6012f..6d09a5a4f3 100644 --- a/pkg/podvolume/util.go +++ b/pkg/podvolume/util.go @@ -37,14 +37,6 @@ const ( // TODO(2.0): remove podAnnotationPrefix = "snapshot.velero.io/" - // VolumesToBackupAnnotation is the annotation on a pod whose mounted volumes - // need to be backed up using pod volume backup. - VolumesToBackupAnnotation = "backup.velero.io/backup-volumes" - - // VolumesToExcludeAnnotation is the annotation on a pod whose mounted volumes - // should be excluded from pod volume backup. - VolumesToExcludeAnnotation = "backup.velero.io/backup-volumes-excludes" - // DefaultVolumesToFsBackup specifies whether pod volume backup should be used, by default, to // take backup of all pod volumes. DefaultVolumesToFsBackup = false @@ -216,85 +208,3 @@ func getPodSnapshotAnnotations(obj metav1.Object) map[string]string { return res } - -// GetVolumesToBackup returns a list of volume names to backup for -// the provided pod. -// Deprecated: Use GetVolumesByPod instead. -func GetVolumesToBackup(obj metav1.Object) []string { - annotations := obj.GetAnnotations() - if annotations == nil { - return nil - } - - backupsValue := annotations[VolumesToBackupAnnotation] - if backupsValue == "" { - return nil - } - - return strings.Split(backupsValue, ",") -} - -func getVolumesToExclude(obj metav1.Object) []string { - annotations := obj.GetAnnotations() - if annotations == nil { - return nil - } - - return strings.Split(annotations[VolumesToExcludeAnnotation], ",") -} - -func contains(list []string, k string) bool { - for _, i := range list { - if i == k { - return true - } - } - return false -} - -// GetVolumesByPod returns a list of volume names to backup for the provided pod. -func GetVolumesByPod(pod *corev1api.Pod, defaultVolumesToFsBackup bool) ([]string, []string) { - // tracks the volumes that have been explicitly opted out of backup via the annotation in the pod - optedOutVolumes := make([]string, 0) - - if !defaultVolumesToFsBackup { - return GetVolumesToBackup(pod), optedOutVolumes - } - - volsToExclude := getVolumesToExclude(pod) - podVolumes := []string{} - for _, pv := range pod.Spec.Volumes { - // cannot backup hostpath volumes as they are not mounted into /var/lib/kubelet/pods - // and therefore not accessible to the node agent daemon set. - if pv.HostPath != nil { - continue - } - // don't backup volumes mounting secrets. Secrets will be backed up separately. - if pv.Secret != nil { - continue - } - // don't backup volumes mounting config maps. Config maps will be backed up separately. - if pv.ConfigMap != nil { - continue - } - // don't backup volumes mounted as projected volumes, all data in those come from kube state. - if pv.Projected != nil { - continue - } - // don't backup DownwardAPI volumes, all data in those come from kube state. - if pv.DownwardAPI != nil { - continue - } - // don't backup volumes that are included in the exclude list. - if contains(volsToExclude, pv.Name) { - optedOutVolumes = append(optedOutVolumes, pv.Name) - continue - } - // don't include volumes that mount the default service account token. - if strings.HasPrefix(pv.Name, "default-token") { - continue - } - podVolumes = append(podVolumes, pv.Name) - } - return podVolumes, optedOutVolumes -} diff --git a/pkg/podvolume/util_test.go b/pkg/podvolume/util_test.go index 32b0a43c78..79e68450cf 100644 --- a/pkg/podvolume/util_test.go +++ b/pkg/podvolume/util_test.go @@ -17,12 +17,10 @@ limitations under the License. package podvolume import ( - "sort" "testing" "github.com/stretchr/testify/assert" corev1api "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" @@ -303,322 +301,3 @@ func TestVolumeHasNonRestorableSource(t *testing.T) { } } - -func TestGetVolumesToBackup(t *testing.T) { - tests := []struct { - name string - annotations map[string]string - expected []string - }{ - { - name: "nil annotations", - annotations: nil, - expected: nil, - }, - { - name: "no volumes to backup", - annotations: map[string]string{"foo": "bar"}, - expected: nil, - }, - { - name: "one volume to backup", - annotations: map[string]string{"foo": "bar", VolumesToBackupAnnotation: "volume-1"}, - expected: []string{"volume-1"}, - }, - { - name: "multiple volumes to backup", - annotations: map[string]string{"foo": "bar", VolumesToBackupAnnotation: "volume-1,volume-2,volume-3"}, - expected: []string{"volume-1", "volume-2", "volume-3"}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - pod := &corev1api.Pod{} - pod.Annotations = test.annotations - - res := GetVolumesToBackup(pod) - - // sort to ensure good compare of slices - sort.Strings(test.expected) - sort.Strings(res) - - assert.Equal(t, test.expected, res) - }) - } -} - -func TestGetVolumesByPod(t *testing.T) { - testCases := []struct { - name string - pod *corev1api.Pod - expected struct { - included []string - optedOut []string - } - defaultVolumesToFsBackup bool - }{ - { - name: "should get PVs from VolumesToBackupAnnotation when defaultVolumesToFsBackup is false", - defaultVolumesToFsBackup: false, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToBackupAnnotation: "pvbPV1,pvbPV2,pvbPV3", - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{}, - }, - }, - { - name: "should get all pod volumes when defaultVolumesToFsBackup is true and no PVs are excluded", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - // PVB Volumes - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{}, - }, - }, - { - name: "should get all pod volumes except ones excluded when defaultVolumesToFsBackup is true", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", - }, - }, - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - // PVB Volumes - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - /// Excluded from PVB through annotation - {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, - }, - }, - { - name: "should exclude default service account token from pod volume backup", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - // PVB Volumes - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - /// Excluded from PVB because colume mounting default service account token - {Name: "default-token-5xq45"}, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{}, - }, - }, - { - name: "should exclude host path volumes from pod volume backups", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", - }, - }, - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - // PVB Volumes - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - /// Excluded from pod volume backup through annotation - {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, - // Excluded from pod volume backup because hostpath - {Name: "hostPath1", VolumeSource: corev1api.VolumeSource{HostPath: &corev1api.HostPathVolumeSource{Path: "/hostpathVol"}}}, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, - }, - }, - { - name: "should exclude volumes mounting secrets", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", - }, - }, - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - // PVB Volumes - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - /// Excluded from pod volume backup through annotation - {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, - // Excluded from pod volume backup because hostpath - {Name: "superSecret", VolumeSource: corev1api.VolumeSource{Secret: &corev1api.SecretVolumeSource{SecretName: "super-secret"}}}, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, - }, - }, - { - name: "should exclude volumes mounting config maps", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", - }, - }, - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - // PVB Volumes - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - /// Excluded from pod volume backup through annotation - {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, - // Excluded from pod volume backup because hostpath - {Name: "appCOnfig", VolumeSource: corev1api.VolumeSource{ConfigMap: &corev1api.ConfigMapVolumeSource{LocalObjectReference: corev1api.LocalObjectReference{Name: "app-config"}}}}, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, - }, - }, - { - name: "should exclude projected volumes", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", - }, - }, - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - { - Name: "projected", - VolumeSource: corev1api.VolumeSource{ - Projected: &corev1api.ProjectedVolumeSource{ - Sources: []corev1api.VolumeProjection{{ - Secret: &corev1api.SecretProjection{ - LocalObjectReference: corev1api.LocalObjectReference{}, - Items: nil, - Optional: nil, - }, - DownwardAPI: nil, - ConfigMap: nil, - ServiceAccountToken: nil, - }}, - DefaultMode: nil, - }, - }, - }, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{}, - }, - }, - { - name: "should exclude DownwardAPI volumes", - defaultVolumesToFsBackup: true, - pod: &corev1api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", - }, - }, - Spec: corev1api.PodSpec{ - Volumes: []corev1api.Volume{ - {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, - { - Name: "downwardAPI", - VolumeSource: corev1api.VolumeSource{ - DownwardAPI: &corev1api.DownwardAPIVolumeSource{ - Items: []corev1api.DownwardAPIVolumeFile{ - { - Path: "labels", - FieldRef: &corev1api.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.labels", - }, - }, - }, - }, - }, - }, - }, - }, - }, - expected: struct { - included []string - optedOut []string - }{ - included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, - optedOut: []string{}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actualIncluded, actualOptedOut := GetVolumesByPod(tc.pod, tc.defaultVolumesToFsBackup) - - sort.Strings(tc.expected.included) - sort.Strings(actualIncluded) - assert.Equal(t, tc.expected.included, actualIncluded) - - sort.Strings(tc.expected.optedOut) - sort.Strings(actualOptedOut) - assert.Equal(t, tc.expected.optedOut, actualOptedOut) - }) - } -} diff --git a/pkg/util/podvolume/pod_volume.go b/pkg/util/podvolume/pod_volume.go new file mode 100644 index 0000000000..542e152977 --- /dev/null +++ b/pkg/util/podvolume/pod_volume.go @@ -0,0 +1,108 @@ +/* +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 podvolume + +import ( + "strings" + + corev1api "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" +) + +// GetVolumesByPod returns a list of volume names to backup for the provided pod. +func GetVolumesByPod(pod *corev1api.Pod, defaultVolumesToFsBackup bool) ([]string, []string) { + // tracks the volumes that have been explicitly opted out of backup via the annotation in the pod + optedOutVolumes := make([]string, 0) + + if !defaultVolumesToFsBackup { + return GetVolumesToBackup(pod), optedOutVolumes + } + + volsToExclude := getVolumesToExclude(pod) + podVolumes := []string{} + for _, pv := range pod.Spec.Volumes { + // cannot backup hostpath volumes as they are not mounted into /var/lib/kubelet/pods + // and therefore not accessible to the node agent daemon set. + if pv.HostPath != nil { + continue + } + // don't backup volumes mounting secrets. Secrets will be backed up separately. + if pv.Secret != nil { + continue + } + // don't backup volumes mounting config maps. Config maps will be backed up separately. + if pv.ConfigMap != nil { + continue + } + // don't backup volumes mounted as projected volumes, all data in those come from kube state. + if pv.Projected != nil { + continue + } + // don't backup DownwardAPI volumes, all data in those come from kube state. + if pv.DownwardAPI != nil { + continue + } + // don't backup volumes that are included in the exclude list. + if contains(volsToExclude, pv.Name) { + optedOutVolumes = append(optedOutVolumes, pv.Name) + continue + } + // don't include volumes that mount the default service account token. + if strings.HasPrefix(pv.Name, "default-token") { + continue + } + podVolumes = append(podVolumes, pv.Name) + } + return podVolumes, optedOutVolumes +} + +// GetVolumesToBackup returns a list of volume names to backup for +// the provided pod. +// Deprecated: Use GetVolumesByPod instead. +func GetVolumesToBackup(obj metav1.Object) []string { + annotations := obj.GetAnnotations() + if annotations == nil { + return nil + } + + backupsValue := annotations[velerov1api.VolumesToBackupAnnotation] + if backupsValue == "" { + return nil + } + + return strings.Split(backupsValue, ",") +} + +func getVolumesToExclude(obj metav1.Object) []string { + annotations := obj.GetAnnotations() + if annotations == nil { + return nil + } + + return strings.Split(annotations[velerov1api.VolumesToExcludeAnnotation], ",") +} + +func contains(list []string, k string) bool { + for _, i := range list { + if i == k { + return true + } + } + return false +} diff --git a/pkg/util/podvolume/pod_volume_test.go b/pkg/util/podvolume/pod_volume_test.go new file mode 100644 index 0000000000..67ce9bb578 --- /dev/null +++ b/pkg/util/podvolume/pod_volume_test.go @@ -0,0 +1,347 @@ +/* +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 podvolume + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + corev1api "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" +) + +func TestGetVolumesToBackup(t *testing.T) { + tests := []struct { + name string + annotations map[string]string + expected []string + }{ + { + name: "nil annotations", + annotations: nil, + expected: nil, + }, + { + name: "no volumes to backup", + annotations: map[string]string{"foo": "bar"}, + expected: nil, + }, + { + name: "one volume to backup", + annotations: map[string]string{"foo": "bar", velerov1api.VolumesToBackupAnnotation: "volume-1"}, + expected: []string{"volume-1"}, + }, + { + name: "multiple volumes to backup", + annotations: map[string]string{"foo": "bar", velerov1api.VolumesToBackupAnnotation: "volume-1,volume-2,volume-3"}, + expected: []string{"volume-1", "volume-2", "volume-3"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pod := &corev1api.Pod{} + pod.Annotations = test.annotations + + res := GetVolumesToBackup(pod) + + // sort to ensure good compare of slices + sort.Strings(test.expected) + sort.Strings(res) + + assert.Equal(t, test.expected, res) + }) + } +} + +func TestGetVolumesByPod(t *testing.T) { + testCases := []struct { + name string + pod *corev1api.Pod + expected struct { + included []string + optedOut []string + } + defaultVolumesToFsBackup bool + }{ + { + name: "should get PVs from VolumesToBackupAnnotation when defaultVolumesToFsBackup is false", + defaultVolumesToFsBackup: false, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToBackupAnnotation: "pvbPV1,pvbPV2,pvbPV3", + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{}, + }, + }, + { + name: "should get all pod volumes when defaultVolumesToFsBackup is true and no PVs are excluded", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + // PVB Volumes + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{}, + }, + }, + { + name: "should get all pod volumes except ones excluded when defaultVolumesToFsBackup is true", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", + }, + }, + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + // PVB Volumes + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + /// Excluded from PVB through annotation + {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, + }, + }, + { + name: "should exclude default service account token from pod volume backup", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + // PVB Volumes + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + /// Excluded from PVB because colume mounting default service account token + {Name: "default-token-5xq45"}, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{}, + }, + }, + { + name: "should exclude host path volumes from pod volume backups", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", + }, + }, + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + // PVB Volumes + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + /// Excluded from pod volume backup through annotation + {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, + // Excluded from pod volume backup because hostpath + {Name: "hostPath1", VolumeSource: corev1api.VolumeSource{HostPath: &corev1api.HostPathVolumeSource{Path: "/hostpathVol"}}}, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, + }, + }, + { + name: "should exclude volumes mounting secrets", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", + }, + }, + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + // PVB Volumes + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + /// Excluded from pod volume backup through annotation + {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, + // Excluded from pod volume backup because hostpath + {Name: "superSecret", VolumeSource: corev1api.VolumeSource{Secret: &corev1api.SecretVolumeSource{SecretName: "super-secret"}}}, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, + }, + }, + { + name: "should exclude volumes mounting config maps", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", + }, + }, + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + // PVB Volumes + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + /// Excluded from pod volume backup through annotation + {Name: "nonPvbPV1"}, {Name: "nonPvbPV2"}, {Name: "nonPvbPV3"}, + // Excluded from pod volume backup because hostpath + {Name: "appCOnfig", VolumeSource: corev1api.VolumeSource{ConfigMap: &corev1api.ConfigMapVolumeSource{LocalObjectReference: corev1api.LocalObjectReference{Name: "app-config"}}}}, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{"nonPvbPV1", "nonPvbPV2", "nonPvbPV3"}, + }, + }, + { + name: "should exclude projected volumes", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", + }, + }, + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + { + Name: "projected", + VolumeSource: corev1api.VolumeSource{ + Projected: &corev1api.ProjectedVolumeSource{ + Sources: []corev1api.VolumeProjection{{ + Secret: &corev1api.SecretProjection{ + LocalObjectReference: corev1api.LocalObjectReference{}, + Items: nil, + Optional: nil, + }, + DownwardAPI: nil, + ConfigMap: nil, + ServiceAccountToken: nil, + }}, + DefaultMode: nil, + }, + }, + }, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{}, + }, + }, + { + name: "should exclude DownwardAPI volumes", + defaultVolumesToFsBackup: true, + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumesToExcludeAnnotation: "nonPvbPV1,nonPvbPV2,nonPvbPV3", + }, + }, + Spec: corev1api.PodSpec{ + Volumes: []corev1api.Volume{ + {Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"}, + { + Name: "downwardAPI", + VolumeSource: corev1api.VolumeSource{ + DownwardAPI: &corev1api.DownwardAPIVolumeSource{ + Items: []corev1api.DownwardAPIVolumeFile{ + { + Path: "labels", + FieldRef: &corev1api.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.labels", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: struct { + included []string + optedOut []string + }{ + included: []string{"pvbPV1", "pvbPV2", "pvbPV3"}, + optedOut: []string{}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actualIncluded, actualOptedOut := GetVolumesByPod(tc.pod, tc.defaultVolumesToFsBackup) + + sort.Strings(tc.expected.included) + sort.Strings(actualIncluded) + assert.Equal(t, tc.expected.included, actualIncluded) + + sort.Strings(tc.expected.optedOut) + sort.Strings(actualOptedOut) + assert.Equal(t, tc.expected.optedOut, actualOptedOut) + }) + } +}