Skip volume restores from projected sources (#3877)

In #3863, it was discovered that volumes from projected sources were
being backed up by restic when they should have been skipped. Restoring
these volumes triggers a known bug in restic.

In #3866, we started skipping volumes from a projected source, however
there will exist backups that were taken before this fix was introduced.
This change modifies the restore logic to skip the restore of any volume
that came from a projected source, allowing backups taken before #3866
to be restored successfully.

Signed-off-by: Bridget McErlean <bmcerlean@vmware.com>
pull/3845/head
Bridget McErlean 2021-06-17 14:00:37 -04:00 committed by GitHub
parent c21b66166a
commit f727e070cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 131 additions and 3 deletions

View File

@ -0,0 +1 @@
Skip the restore of volumes that originally came from a projected volume when using restic.

View File

@ -100,9 +100,20 @@ func isPVBMatchPod(pvb *velerov1api.PodVolumeBackup, podName string, namespace s
return podName == pvb.Spec.Pod.Name && namespace == pvb.Spec.Pod.Namespace
}
// volumeIsProjected checks if the given volume exists in the list of podVolumes
// and returns true if the volume has a projected source
func volumeIsProjected(volumeName string, podVolumes []corev1api.Volume) bool {
for _, volume := range podVolumes {
if volume.Name == volumeName && volume.Projected != nil {
return true
}
}
return false
}
// GetVolumeBackupsForPod returns a map, of volume name -> snapshot id,
// of the PodVolumeBackups that exist for the provided pod.
func GetVolumeBackupsForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, pod metav1.Object, sourcePodNs string) map[string]string {
func GetVolumeBackupsForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, pod *corev1api.Pod, sourcePodNs string) map[string]string {
volumes := make(map[string]string)
for _, pvb := range podVolumeBackups {
@ -116,6 +127,13 @@ func GetVolumeBackupsForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, pod
continue
}
// If the volume came from a projected source, skip its restore.
// This allows backups affected by https://github.com/vmware-tanzu/velero/issues/3863
// to be restored successfully.
if volumeIsProjected(pvb.Spec.Volume, pod.Spec.Volumes) {
continue
}
volumes[pvb.Spec.Volume] = pvb.Status.SnapshotID
}

View File

@ -37,6 +37,7 @@ func TestGetVolumeBackupsForPod(t *testing.T) {
tests := []struct {
name string
podVolumeBackups []*velerov1api.PodVolumeBackup
podVolumes []corev1api.Volume
podAnnotations map[string]string
podName string
sourcePodNs string
@ -127,6 +128,30 @@ func TestGetVolumeBackupsForPod(t *testing.T) {
sourcePodNs: "TestNS",
expected: map[string]string{"pvbtest1-foo": "snapshot1"},
},
{
name: "volumes from PVBs that correspond to a pod volume from a projected source are not returned",
podVolumeBackups: []*velerov1api.PodVolumeBackup{
builder.ForPodVolumeBackup("velero", "pvb-1").PodName("TestPod").PodNamespace("TestNS").SnapshotID("snapshot1").Volume("pvb-non-projected").Result(),
builder.ForPodVolumeBackup("velero", "pvb-1").PodName("TestPod").PodNamespace("TestNS").SnapshotID("snapshot2").Volume("pvb-projected").Result(),
},
podVolumes: []corev1api.Volume{
{
Name: "pvb-non-projected",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
},
{
Name: "pvb-projected",
VolumeSource: corev1api.VolumeSource{
Projected: &corev1api.ProjectedVolumeSource{},
},
},
},
podName: "TestPod",
sourcePodNs: "TestNS",
expected: map[string]string{"pvb-non-projected": "snapshot1"},
},
}
for _, test := range tests {
@ -134,6 +159,7 @@ func TestGetVolumeBackupsForPod(t *testing.T) {
pod := &corev1api.Pod{}
pod.Annotations = test.podAnnotations
pod.Name = test.podName
pod.Spec.Volumes = test.podVolumes
res := GetVolumeBackupsForPod(test.podVolumeBackups, pod, test.sourcePodNs)
assert.Equal(t, test.expected, res)
@ -629,3 +655,78 @@ func TestIsPVBMatchPod(t *testing.T) {
}
}
func TestVolumeIsProjected(t *testing.T) {
testCases := []struct {
name string
volumeName string
podVolumes []corev1api.Volume
expected bool
}{
{
name: "volume name not in list of volumes",
volumeName: "missing-volume",
podVolumes: []corev1api.Volume{
{
Name: "non-projected",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
},
{
Name: "projected",
VolumeSource: corev1api.VolumeSource{
Projected: &corev1api.ProjectedVolumeSource{},
},
},
},
expected: false,
},
{
name: "volume name in list of volumes but not projected",
volumeName: "non-projected",
podVolumes: []corev1api.Volume{
{
Name: "non-projected",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
},
{
Name: "projected",
VolumeSource: corev1api.VolumeSource{
Projected: &corev1api.ProjectedVolumeSource{},
},
},
},
expected: false,
},
{
name: "volume name in list of volumes and projected",
volumeName: "projected",
podVolumes: []corev1api.Volume{
{
Name: "non-projected",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
},
{
Name: "projected",
VolumeSource: corev1api.VolumeSource{
Projected: &corev1api.ProjectedVolumeSource{},
},
},
},
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := volumeIsProjected(tc.volumeName, tc.podVolumes)
assert.Equal(t, tc.expected, actual)
})
}
}

View File

@ -1232,8 +1232,16 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
return warnings, errs
}
if groupResource == kuberesource.Pods && len(restic.GetVolumeBackupsForPod(ctx.podVolumeBackups, obj, originalNamespace)) > 0 {
restorePodVolumeBackups(ctx, createdObj, originalNamespace)
if groupResource == kuberesource.Pods {
pod := new(v1.Pod)
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
errs.Add(namespace, err)
return warnings, errs
}
if len(restic.GetVolumeBackupsForPod(ctx.podVolumeBackups, pod, originalNamespace)) > 0 {
restorePodVolumeBackups(ctx, createdObj, originalNamespace)
}
}
if groupResource == kuberesource.Pods {