889 lines
23 KiB
Go
889 lines
23 KiB
Go
/*
|
|
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"
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
|
)
|
|
|
|
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
|
|
backupExcludePVC 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 ConfigMaps",
|
|
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{},
|
|
},
|
|
},
|
|
{
|
|
name: "should exclude PVC volume when backup excludes PVC resource",
|
|
defaultVolumesToFsBackup: true,
|
|
backupExcludePVC: 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{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "testPVC",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
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, tc.backupExcludePVC, []string{})
|
|
|
|
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)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsPVCDefaultToFSBackup(t *testing.T) {
|
|
objs := []runtime.Object{
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod2",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
"backup.velero.io/backup-volumes": "csi-vol1",
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod3",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
EmptyDir: &v1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "awesome-pod-1",
|
|
Namespace: "awesome-ns",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "awesome-csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "awesome-pod-2",
|
|
Namespace: "awesome-ns",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "awesome-csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod1",
|
|
Namespace: "uploader-ns",
|
|
Annotations: map[string]string{
|
|
"backup.velero.io/backup-volumes": "csi-vol1",
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod2",
|
|
Namespace: "uploader-ns",
|
|
Annotations: map[string]string{
|
|
"backup.velero.io/backup-volumes": "csi-vol1",
|
|
},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, objs...)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
inPVCNamespace string
|
|
inPVCName string
|
|
expectedIsFSUploaderUsed bool
|
|
defaultVolumesToFSBackup bool
|
|
}{
|
|
{
|
|
name: "2 pods using PVC, 1 pod using uploader",
|
|
inPVCNamespace: "default",
|
|
inPVCName: "csi-pvc1",
|
|
expectedIsFSUploaderUsed: true,
|
|
defaultVolumesToFSBackup: false,
|
|
},
|
|
{
|
|
name: "2 pods using PVC, 2 pods using uploader",
|
|
inPVCNamespace: "uploader-ns",
|
|
inPVCName: "csi-pvc1",
|
|
expectedIsFSUploaderUsed: true,
|
|
defaultVolumesToFSBackup: false,
|
|
},
|
|
{
|
|
name: "2 pods using PVC, 0 pods using uploader",
|
|
inPVCNamespace: "awesome-ns",
|
|
inPVCName: "awesome-csi-pvc1",
|
|
expectedIsFSUploaderUsed: false,
|
|
defaultVolumesToFSBackup: false,
|
|
},
|
|
{
|
|
name: "0 pods using PVC",
|
|
inPVCNamespace: "default",
|
|
inPVCName: "does-not-exist",
|
|
expectedIsFSUploaderUsed: false,
|
|
defaultVolumesToFSBackup: false,
|
|
},
|
|
{
|
|
name: "2 pods using PVC, using uploader by default",
|
|
inPVCNamespace: "awesome-ns",
|
|
inPVCName: "awesome-csi-pvc1",
|
|
expectedIsFSUploaderUsed: true,
|
|
defaultVolumesToFSBackup: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualIsFSUploaderUsed, _ := IsPVCDefaultToFSBackup(tc.inPVCNamespace, tc.inPVCName, fakeClient, tc.defaultVolumesToFSBackup)
|
|
assert.Equal(t, tc.expectedIsFSUploaderUsed, actualIsFSUploaderUsed)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPodVolumeNameForPVC(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
pod v1.Pod
|
|
pvcName string
|
|
expectError bool
|
|
expectedVolumeName string
|
|
}{
|
|
{
|
|
name: "should get volume name for pod with multuple PVCs",
|
|
pod: v1.Pod{
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "csi-vol2",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc2",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "csi-vol3",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc3",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
pvcName: "csi-pvc2",
|
|
expectedVolumeName: "csi-vol2",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "should get volume name from pod using exactly one PVC",
|
|
pod: v1.Pod{
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
pvcName: "csi-pvc1",
|
|
expectedVolumeName: "csi-vol1",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "should return error for pod with no PVCs",
|
|
pod: v1.Pod{
|
|
Spec: v1.PodSpec{},
|
|
},
|
|
pvcName: "csi-pvc2",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "should return error for pod with no matching PVC",
|
|
pod: v1.Pod{
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
pvcName: "mismatch-pvc",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualVolumeName, err := getPodVolumeNameForPVC(tc.pod, tc.pvcName)
|
|
if tc.expectError && err == nil {
|
|
assert.NotNil(t, err, "Want error; Got nil error")
|
|
return
|
|
}
|
|
assert.Equalf(t, tc.expectedVolumeName, actualVolumeName, "unexpected podVolumename returned. Want %s; Got %s", tc.expectedVolumeName, actualVolumeName)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPodsUsingPVC(t *testing.T) {
|
|
objs := []runtime.Object{
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod2",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod3",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
EmptyDir: &v1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod1",
|
|
Namespace: "awesome-ns",
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Volumes: []v1.Volume{
|
|
{
|
|
Name: "csi-vol1",
|
|
VolumeSource: v1.VolumeSource{
|
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "csi-pvc1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, objs...)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
pvcNamespace string
|
|
pvcName string
|
|
expectedPodCount int
|
|
}{
|
|
{
|
|
name: "should find exactly 2 pods using the PVC",
|
|
pvcNamespace: "default",
|
|
pvcName: "csi-pvc1",
|
|
expectedPodCount: 2,
|
|
},
|
|
{
|
|
name: "should find exactly 1 pod using the PVC",
|
|
pvcNamespace: "awesome-ns",
|
|
pvcName: "csi-pvc1",
|
|
expectedPodCount: 1,
|
|
},
|
|
{
|
|
name: "should find 0 pods using the PVC",
|
|
pvcNamespace: "default",
|
|
pvcName: "unused-pvc",
|
|
expectedPodCount: 0,
|
|
},
|
|
{
|
|
name: "should find 0 pods in non-existent namespace",
|
|
pvcNamespace: "does-not-exist",
|
|
pvcName: "csi-pvc1",
|
|
expectedPodCount: 0,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualPods, err := GetPodsUsingPVC(tc.pvcNamespace, tc.pvcName, fakeClient)
|
|
assert.Nilf(t, err, "Want error=nil; Got error=%v", err)
|
|
assert.Lenf(t, actualPods, tc.expectedPodCount, "unexpected number of pods in result; Want: %d; Got: %d", tc.expectedPodCount, len(actualPods))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetVolumesToProcess(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
volumes []corev1api.Volume
|
|
volsToProcessByLegacyApproach []string
|
|
expectedVolumes []corev1api.Volume
|
|
}{
|
|
{
|
|
name: "pod has 2 volumes empty volsToProcessByLegacyApproach list return 2 volumes",
|
|
volumes: []corev1api.Volume{
|
|
{
|
|
Name: "sample-volume-1",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-1",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "sample-volume-2",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
volsToProcessByLegacyApproach: []string{},
|
|
expectedVolumes: []corev1api.Volume{
|
|
{
|
|
Name: "sample-volume-1",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-1",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "sample-volume-2",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod has 2 volumes non-empty volsToProcessByLegacyApproach list returns 1 volumes",
|
|
volumes: []corev1api.Volume{
|
|
{
|
|
Name: "sample-volume-1",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-1",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "sample-volume-2",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
volsToProcessByLegacyApproach: []string{"sample-volume-2"},
|
|
expectedVolumes: []corev1api.Volume{
|
|
{
|
|
Name: "sample-volume-2",
|
|
VolumeSource: corev1api.VolumeSource{
|
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
|
ClaimName: "sample-pvc-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "empty case, return empty list",
|
|
volumes: []corev1api.Volume{},
|
|
volsToProcessByLegacyApproach: []string{},
|
|
expectedVolumes: []corev1api.Volume{},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualVolumes := GetVolumesToProcess(tc.volumes, tc.volsToProcessByLegacyApproach)
|
|
assert.Equal(t, tc.expectedVolumes, actualVolumes, "Want Volumes List %v; Got Volumes List %v", tc.expectedVolumes, actualVolumes)
|
|
})
|
|
}
|
|
}
|