mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-07-19 22:42:44 +00:00
Fix PodVolumeBackup metadata loss on fs-backup timeout (#9995)
Run the E2E test on kind / get-go-version (push) Failing after 1m6s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 24s
Run the E2E test on kind / get-go-version (push) Failing after 1m6s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 15s
Main CI / Build (push) Failing after 24s
* Fix PodVolumeBackup metadata loss on fs-backup timeout When a backup hits the fs-backup timeout, WaitAllPodVolumesProcessed returned nil because PVBs were only collected from the indexer in the done branch of the select. This discarded all PVB metadata including already-completed PVBs, making their data unrestorable. Move the PVB collection loop to run after the select so tracked PVBs are always persisted regardless of timeout. Fixes #9986 Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> * Add changelog for PR #9995 Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> * Filter non-completed PVBs in hasPodVolumeBackup After preserving tracked PVBs on timeout, non-completed PVBs (in-progress or with no snapshot ID) would cause hasPodVolumeBackup to return true, leading the restore to skip the original PV and dynamically re-provision it without any data to restore from. Only match PVBs that are Completed with a valid SnapshotID. Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> * Add unit tests for hasPodVolumeBackup phase filtering Verify that hasPodVolumeBackup only matches PVBs that are Completed with a valid SnapshotID, and rejects in-progress, failed, or empty-snapshot PVBs. Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> --------- Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
This commit is contained in:
committed by
GitHub
parent
c825e3c136
commit
e593ba73f9
@@ -0,0 +1 @@
|
||||
Fix PodVolumeBackup metadata loss on fs-backup timeout, which caused all fs-backup volumes to become unrestorable
|
||||
+15
-12
@@ -412,18 +412,21 @@ func (b *backupper) WaitAllPodVolumesProcessed(log logrus.FieldLogger) []*velero
|
||||
case <-b.ctx.Done():
|
||||
log.Error("timed out waiting for all PodVolumeBackups to complete")
|
||||
case <-done:
|
||||
for _, obj := range b.pvbIndexer.List() {
|
||||
pvb, ok := obj.(*velerov1api.PodVolumeBackup)
|
||||
if !ok {
|
||||
log.Errorf("expected PodVolumeBackup, but got %T", obj)
|
||||
continue
|
||||
}
|
||||
podVolumeBackups = append(podVolumeBackups, pvb)
|
||||
if pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed {
|
||||
log.Errorf("pod volume backup failed: %s", pvb.Status.Message)
|
||||
} else if pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseCanceled {
|
||||
log.Errorf("pod volume backup canceled: %s", pvb.Status.Message)
|
||||
}
|
||||
}
|
||||
|
||||
// Collect tracked PVBs regardless of whether we timed out or completed normally.
|
||||
// On timeout, already-completed PVBs must still be persisted so their data remains restorable.
|
||||
for _, obj := range b.pvbIndexer.List() {
|
||||
pvb, ok := obj.(*velerov1api.PodVolumeBackup)
|
||||
if !ok {
|
||||
log.Errorf("expected PodVolumeBackup, but got %T", obj)
|
||||
continue
|
||||
}
|
||||
podVolumeBackups = append(podVolumeBackups, pvb)
|
||||
if pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed {
|
||||
log.Errorf("pod volume backup failed: %s", pvb.Status.Message)
|
||||
} else if pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseCanceled {
|
||||
log.Errorf("pod volume backup canceled: %s", pvb.Status.Message)
|
||||
}
|
||||
}
|
||||
return podVolumeBackups
|
||||
|
||||
@@ -757,16 +757,18 @@ func TestWaitAllPodVolumesProcessed(t *testing.T) {
|
||||
statusToBeUpdated *velerov1api.PodVolumeBackupStatus
|
||||
expectedErr string
|
||||
expectedPVBPhase velerov1api.PodVolumeBackupPhase
|
||||
expectedPVBCount int
|
||||
}{
|
||||
{
|
||||
name: "contains no pvb should report no error",
|
||||
ctx: timeoutCtx,
|
||||
},
|
||||
{
|
||||
name: "context canceled",
|
||||
ctx: timeoutCtx,
|
||||
pvb: pvb,
|
||||
expectedErr: "timed out waiting for all PodVolumeBackups to complete",
|
||||
name: "context canceled should still return tracked pvbs",
|
||||
ctx: timeoutCtx,
|
||||
pvb: pvb,
|
||||
expectedErr: "timed out waiting for all PodVolumeBackups to complete",
|
||||
expectedPVBCount: 1,
|
||||
},
|
||||
{
|
||||
name: "failed pvbs",
|
||||
@@ -834,6 +836,10 @@ func TestWaitAllPodVolumesProcessed(t *testing.T) {
|
||||
assert.Nil(t, logHook.entry)
|
||||
}
|
||||
|
||||
if c.expectedPVBCount > 0 {
|
||||
require.Len(t, pvbs, c.expectedPVBCount)
|
||||
}
|
||||
|
||||
if c.expectedPVBPhase != "" {
|
||||
require.Len(t, pvbs, 1)
|
||||
assert.Equal(t, c.expectedPVBPhase, pvbs[0].Status.Phase)
|
||||
|
||||
@@ -2347,6 +2347,9 @@ func hasPodVolumeBackup(unstructuredPV *unstructured.Unstructured, ctx *restoreC
|
||||
|
||||
var found bool
|
||||
for _, pvb := range ctx.podVolumeBackups {
|
||||
if pvb.Status.Phase != velerov1api.PodVolumeBackupPhaseCompleted || pvb.Status.SnapshotID == "" {
|
||||
continue
|
||||
}
|
||||
if pvb.Spec.Pod.Namespace == pv.Spec.ClaimRef.Namespace && pvb.GetAnnotations()[configs.PVCNameAnnotation] == pv.Spec.ClaimRef.Name {
|
||||
found = true
|
||||
break
|
||||
|
||||
@@ -4246,3 +4246,84 @@ func TestDetermineRestoreStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasPodVolumeBackup(t *testing.T) {
|
||||
pvUnstructured := func() *unstructured.Unstructured {
|
||||
pv := &corev1api.PersistentVolume{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "PersistentVolume"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pv",
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
ClaimRef: &corev1api.ObjectReference{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-pvc",
|
||||
},
|
||||
},
|
||||
}
|
||||
obj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(pv)
|
||||
return &unstructured.Unstructured{Object: obj}
|
||||
}
|
||||
|
||||
makePVB := func(phase velerov1api.PodVolumeBackupPhase, snapshotID string) *velerov1api.PodVolumeBackup {
|
||||
return &velerov1api.PodVolumeBackup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"velero.io/pvc-name": "test-pvc",
|
||||
},
|
||||
},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{
|
||||
Phase: phase,
|
||||
SnapshotID: snapshotID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pvbs []*velerov1api.PodVolumeBackup
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no pvbs",
|
||||
pvbs: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "completed pvb with snapshot ID",
|
||||
pvbs: []*velerov1api.PodVolumeBackup{makePVB(velerov1api.PodVolumeBackupPhaseCompleted, "snap-123")},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "in-progress pvb should not match",
|
||||
pvbs: []*velerov1api.PodVolumeBackup{makePVB(velerov1api.PodVolumeBackupPhaseInProgress, "")},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "completed pvb with empty snapshot ID should not match",
|
||||
pvbs: []*velerov1api.PodVolumeBackup{makePVB(velerov1api.PodVolumeBackupPhaseCompleted, "")},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "failed pvb should not match",
|
||||
pvbs: []*velerov1api.PodVolumeBackup{makePVB(velerov1api.PodVolumeBackupPhaseFailed, "")},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := &restoreContext{
|
||||
podVolumeBackups: tc.pvbs,
|
||||
log: logrus.New(),
|
||||
}
|
||||
result := hasPodVolumeBackup(pvUnstructured(), ctx)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user