mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-29 03:05:54 +00:00
Fail the in-place restore pre-flight check when the backed-up pod already exists on the file system restore path (#10550)
PodVolumeRestores are only created for pods that Velero creates, so when the pod already exists in the cluster the PVC-not-in-use pre-flight check never runs and the volume data restore is skipped silently, while the existing pod keeps consuming the PVC. Report an explicit pre-flight error for such pods, aligned with the PVC CSI RIA behavior. Signed-off-by: chlins <chlins.zhang@gmail.com>
This commit is contained in:
@@ -1945,6 +1945,31 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
itemStatus := ctx.restoredItems[itemKey]
|
||||
itemStatus.itemExists = itemExists
|
||||
ctx.restoredItems[itemKey] = itemStatus
|
||||
|
||||
// PodVolumeRestores are only created for pods Velero creates, so an
|
||||
// existing pod silently skips the volume data restore. For an in-place
|
||||
// restore this fails the pre-flight check: the pod is still consuming
|
||||
// the PVCs that were supposed to be restored in place. Otherwise it is
|
||||
// only worth a warning.
|
||||
if newGR == kuberesource.Pods {
|
||||
pod := new(corev1api.Pod)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
if len(podvolume.GetVolumeBackupsForPod(ctx.podVolumeBackups, pod, originalNamespace)) > 0 {
|
||||
if ctx.restore.IsVolumeDataInplaceRestore() {
|
||||
err := errors.Errorf("in-place restore pre-flight check failed, skipping volume data restore: pod %s already exists and is still using the backed-up volumes: delete the pod and its owning workload and retry", kube.NamespaceAndName(obj))
|
||||
restoreLogger.Error(err.Error())
|
||||
errs.Add(namespace, err)
|
||||
} else {
|
||||
err := errors.Errorf("skipping volume data restore: pod %s already exists, its PodVolumeBackups will not be restored", kube.NamespaceAndName(obj))
|
||||
restoreLogger.Warn(err.Error())
|
||||
warnings.Add(namespace, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove insubstantial metadata.
|
||||
fromCluster, err = resetMetadataAndStatus(fromCluster)
|
||||
if err != nil {
|
||||
|
||||
@@ -4333,6 +4333,99 @@ func TestRestoreWithPodVolume(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestoreInplaceExistingPodWithPodVolumeBackups verifies that an in-place
|
||||
// restore reports an error when the backed-up pod has PodVolumeBackups to
|
||||
// restore but already exists in the cluster: the PodVolumeRestores are never
|
||||
// created for an existing pod, so the volume data restore must not be skipped
|
||||
// silently. A regular restore keeps the plain "already exists" warning.
|
||||
func TestRestoreInplaceExistingPodWithPodVolumeBackups(t *testing.T) {
|
||||
pvbs := []*velerov1api.PodVolumeBackup{
|
||||
builder.ForPodVolumeBackup("velero", "pvb-1").PodName("pod-1").PodNamespace("ns-1").Volume("data").SnapshotID("foo").Result(),
|
||||
}
|
||||
backedUpPod := builder.ForPod("ns-1", "pod-1").
|
||||
Volumes(builder.ForVolume("data").PersistentVolumeClaimSource("pvc-1").Result()).
|
||||
Result()
|
||||
existingPod := backedUpPod.DeepCopy()
|
||||
existingPod.Spec.NodeName = "node-1"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
restore *velerov1api.Restore
|
||||
pvbs []*velerov1api.PodVolumeBackup
|
||||
wantErrs bool
|
||||
wantWarnings bool
|
||||
wantPVBSkip bool
|
||||
}{
|
||||
{
|
||||
name: "in-place restore with an existing pod consuming the backed-up volumes fails the pre-flight check",
|
||||
restore: defaultRestore().ExistingVolumeDataPolicy(string(velerov1api.VolumeDataPolicyTypeFull)).Result(),
|
||||
pvbs: pvbs,
|
||||
wantErrs: true,
|
||||
},
|
||||
{
|
||||
name: "in-place restore with existingResourcePolicy=update and an existing pod still fails the pre-flight check",
|
||||
restore: defaultRestore().ExistingVolumeDataPolicy(string(velerov1api.VolumeDataPolicyTypeFull)).ExistingResourcePolicy(string(velerov1api.ResourcePolicyTypeUpdate)).Result(),
|
||||
pvbs: pvbs,
|
||||
wantErrs: true,
|
||||
},
|
||||
{
|
||||
name: "in-place restore with an existing pod without PodVolumeBackups only warns",
|
||||
restore: defaultRestore().ExistingVolumeDataPolicy(string(velerov1api.VolumeDataPolicyTypeIncremental)).Result(),
|
||||
pvbs: nil,
|
||||
wantWarnings: true,
|
||||
},
|
||||
{
|
||||
name: "regular restore with an existing pod warns that the PodVolumeBackups are not restored",
|
||||
restore: defaultRestore().Result(),
|
||||
pvbs: pvbs,
|
||||
wantWarnings: true,
|
||||
wantPVBSkip: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
restorer := new(uploadermocks.Restorer)
|
||||
defer restorer.AssertExpectations(t)
|
||||
h.restorer.podVolumeRestorerFactory = &fakePodVolumeRestorerFactory{restorer: restorer}
|
||||
|
||||
h.AddItems(t, test.Pods(existingPod))
|
||||
|
||||
tarball := test.NewTarWriter(t)
|
||||
tarball.AddItems("pods", backedUpPod)
|
||||
|
||||
warnings, errs := h.restorer.Restore(
|
||||
&Request{
|
||||
Log: h.log,
|
||||
Restore: tc.restore,
|
||||
Backup: defaultBackup().Result(),
|
||||
PodVolumeBackups: tc.pvbs,
|
||||
BackupReader: tarball.Done(),
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
if tc.wantErrs {
|
||||
require.Len(t, errs.Namespaces["ns-1"], 1)
|
||||
assert.Contains(t, errs.Namespaces["ns-1"][0], "in-place restore pre-flight check failed")
|
||||
assert.Contains(t, errs.Namespaces["ns-1"][0], "pod ns-1/pod-1 already exists")
|
||||
} else {
|
||||
assert.Empty(t, errs.Namespaces)
|
||||
}
|
||||
if tc.wantPVBSkip {
|
||||
require.Len(t, warnings.Namespaces["ns-1"], 2)
|
||||
assert.Contains(t, warnings.Namespaces["ns-1"][0], "PodVolumeBackups will not be restored")
|
||||
assert.Contains(t, warnings.Namespaces["ns-1"][1], "already exists")
|
||||
} else if tc.wantWarnings {
|
||||
require.Len(t, warnings.Namespaces["ns-1"], 1)
|
||||
assert.Contains(t, warnings.Namespaces["ns-1"][0], "already exists")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetMetadata(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user