Merge pull request #6875 from Lyndon-Li/issue-fix-6859
Issue 6859: move plugin depdending podvolume functions to util pkgpull/6885/head
commit
b6b320c85b
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue