From a827f607b51839a0ee79fb1890d5b180d6bddc6c Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Fri, 11 Sep 2026 16:40:52 +0800 Subject: [PATCH] update fallbackFull in restore finalizer Signed-off-by: Lyndon-Li --- internal/volume/volumes_information.go | 7 -- internal/volume/volumes_information_test.go | 100 ++++++++++++++++-- pkg/builder/data_download_builder.go | 6 ++ pkg/builder/volume_builder_test.go | 12 +++ .../restore_finalizer_controller.go | 1 + .../restore_finalizer_controller_test.go | 63 ++++++++--- 6 files changed, 155 insertions(+), 34 deletions(-) diff --git a/internal/volume/volumes_information.go b/internal/volume/volumes_information.go index 7d4ec5ead..3aa22f154 100644 --- a/internal/volume/volumes_information.go +++ b/internal/volume/volumes_information.go @@ -300,12 +300,6 @@ type PodVolumeBackupInfo struct { // This field will be empty when the struct is used to represent a podvolumerestore. NodeName string `json:"nodeName,omitempty"` - // FallbackFull indicates whether the incremental backup has fallen back to full backup - FallbackFull bool `json:"fallbackFull,omitempty"` - - // BackupType indicates the type of the backup, incremental or full. - BackupType string `json:"backupType,omitempty"` - // The PVB's Status.Phase value Phase velerov1api.PodVolumeBackupPhase `json:"phase,omitempty"` } @@ -355,7 +349,6 @@ func newPodVolumeInfoFromPVB(pvb *velerov1api.PodVolumeBackup) *PodVolumeBackupI PodNamespace: pvb.Spec.Pod.Namespace, NodeName: pvb.Spec.Node, Phase: pvb.Status.Phase, - FallbackFull: pvb.Status.FallbackFull, } } diff --git a/internal/volume/volumes_information_test.go b/internal/volume/volumes_information_test.go index c4c891e45..c7bcc0035 100644 --- a/internal/volume/volumes_information_test.go +++ b/internal/volume/volumes_information_test.go @@ -730,6 +730,79 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) { }, }, }, + { + name: "PVB's volume has a PVC with fallback to full", + pvMap: map[string]pvcPvInfo{ + "testPV": { + PVCName: "testPVC", + PVCNamespace: "velero", + PV: corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPV", + Labels: map[string]string{"a": "b"}, + }, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete, + }, + }, + }, + }, + pvb: func() *velerov1api.PodVolumeBackup { + pvb := builder.ForPodVolumeBackup("velero", "testPVB"). + PodName("testPod"). + PodNamespace("velero"). + StartTimestamp(&now). + CompletionTimestamp(&now). + Phase(velerov1api.PodVolumeBackupPhaseCompleted). + TotalBytes(1024). + IncrementalBytes(512). + Result() + pvb.Status.FallbackFull = true + return pvb + }(), + pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{ + Name: "test", + VolumeMounts: []corev1api.VolumeMount{ + { + Name: "testVolume", + MountPath: "/data", + }, + }, + }).Volumes( + &corev1api.Volume{ + Name: "", + VolumeSource: corev1api.VolumeSource{ + PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{ + ClaimName: "testPVC", + }, + }, + }, + ).Result(), + expectedVolumeInfos: []*BackupVolumeInfo{ + { + PVCName: "testPVC", + PVCNamespace: "velero", + PVName: "testPV", + BackupMethod: PodVolumeBackup, + BackupType: velerov1api.BackupTypeIncremental, + FallbackFull: true, + StartTimestamp: &now, + CompletionTimestamp: &now, + Result: VolumeResultSucceeded, + PVBInfo: &PodVolumeBackupInfo{ + PodName: "testPod", + PodNamespace: "velero", + Phase: velerov1api.PodVolumeBackupPhaseCompleted, + Size: 1024, + IncrementalSize: ptr.To(int64(512)), + }, + PVInfo: &PVInfo{ + ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete), + Labels: map[string]string{"a": "b"}, + }, + }, + }, + }, } for _, tc := range tests { @@ -1095,6 +1168,7 @@ func TestRestoreVolumeInfoResult(t *testing.T) { PVName: "testPV2", RestoreMethod: PodVolumeRestore, SnapshotDataMoved: false, + RestoreType: "Incremental", PVRInfo: &PodVolumeRestoreInfo{ SnapshotHandle: "pvr-snap-001", PodName: "testPod", @@ -1557,17 +1631,21 @@ func TestNewPodVolumeInfoFromPVB(t *testing.T) { }{ { name: "all fields populated including incremental bytes", - pvb: builder.ForPodVolumeBackup("velero", "pvb-1"). - SnapshotID("snap-1"). - Volume("vol-1"). - PodName("pod-1"). - PodNamespace("ns-1"). - Node("node-1"). - UploaderType("kopia"). - Phase(velerov1api.PodVolumeBackupPhaseCompleted). - TotalBytes(2048). - IncrementalBytes(512). - Result(), + pvb: func() *velerov1api.PodVolumeBackup { + pvb := builder.ForPodVolumeBackup("velero", "pvb-1"). + SnapshotID("snap-1"). + Volume("vol-1"). + PodName("pod-1"). + PodNamespace("ns-1"). + Node("node-1"). + UploaderType("kopia"). + Phase(velerov1api.PodVolumeBackupPhaseCompleted). + TotalBytes(2048). + IncrementalBytes(512). + Result() + pvb.Status.FallbackFull = true + return pvb + }(), expected: &PodVolumeBackupInfo{ SnapshotHandle: "snap-1", Size: 2048, diff --git a/pkg/builder/data_download_builder.go b/pkg/builder/data_download_builder.go index ed05303c6..4c9db9049 100644 --- a/pkg/builder/data_download_builder.go +++ b/pkg/builder/data_download_builder.go @@ -160,6 +160,12 @@ func (d *DataDownloadBuilder) IncrementalBytes(incrementalBytes int64) *DataDown return d } +// FallbackFull sets the DataDownload's FallbackFull status. +func (d *DataDownloadBuilder) FallbackFull(fallbackFull bool) *DataDownloadBuilder { + d.object.Status.FallbackFull = fallbackFull + return d +} + // Node sets the DataDownload's Node. func (d *DataDownloadBuilder) Node(node string) *DataDownloadBuilder { d.object.Status.Node = node diff --git a/pkg/builder/volume_builder_test.go b/pkg/builder/volume_builder_test.go index 6b3291c2d..4821d6868 100644 --- a/pkg/builder/volume_builder_test.go +++ b/pkg/builder/volume_builder_test.go @@ -36,6 +36,18 @@ func TestDataDownloadBuilder_Bytes(t *testing.T) { assert.Equal(t, int64(512), *dd.Status.IncrementalBytes) } +func TestDataDownloadBuilder_FallbackFull(t *testing.T) { + dd1 := ForDataDownload("velero", "dd-1"). + FallbackFull(true). + Result() + assert.True(t, dd1.Status.FallbackFull) + + dd2 := ForDataDownload("velero", "dd-2"). + FallbackFull(false). + Result() + assert.False(t, dd2.Status.FallbackFull) +} + func TestPodVolumeBackupBuilder_ProgressAndBytes(t *testing.T) { pvb1 := ForPodVolumeBackup("velero", "pvb-1"). Progress(shared.DataMoveOperationProgress{ diff --git a/pkg/controller/restore_finalizer_controller.go b/pkg/controller/restore_finalizer_controller.go index c423b42c4..3a06db022 100644 --- a/pkg/controller/restore_finalizer_controller.go +++ b/pkg/controller/restore_finalizer_controller.go @@ -603,6 +603,7 @@ func (ctx *finalizerContext) updateVolumeInfos() (errs results.Result) { ctx.restoreVolumeInfos[index].SnapshotDataMovementInfo != nil { ctx.restoreVolumeInfos[index].SnapshotDataMovementInfo.Size = dataDownload.Status.Progress.TotalBytes ctx.restoreVolumeInfos[index].SnapshotDataMovementInfo.IncrementalSize = dataDownload.Status.IncrementalBytes + ctx.restoreVolumeInfos[index].FallbackFull = dataDownload.Status.FallbackFull ctx.restoreVolumeInfos[index].SnapshotDataMovementInfo.Phase = dataDownload.Status.Phase } } diff --git a/pkg/controller/restore_finalizer_controller_test.go b/pkg/controller/restore_finalizer_controller_test.go index e2695b0bf..d96f9e645 100644 --- a/pkg/controller/restore_finalizer_controller_test.go +++ b/pkg/controller/restore_finalizer_controller_test.go @@ -1199,17 +1199,18 @@ func TestCleanupStubVGSC(t *testing.T) { func TestUpdateVolumeInfos(t *testing.T) { tests := []struct { - name string - restore *velerov1api.Restore - restoreVolumeInfos []*volume.RestoreVolumeInfo - dataDownloads []*velerov2alpha1.DataDownload - listErr error - putErr error - expectedSize int64 - expectedIncrSize *int64 - expectedPhase velerov2alpha1.DataDownloadPhase - expectErrs bool - expectErrMsg string + name string + restore *velerov1api.Restore + restoreVolumeInfos []*volume.RestoreVolumeInfo + dataDownloads []*velerov2alpha1.DataDownload + listErr error + putErr error + expectedSize int64 + expectedIncrSize *int64 + expectedPhase velerov2alpha1.DataDownloadPhase + expectedFallbackFull bool + expectErrs bool + expectErrMsg string }{ { name: "successful update of restore volume infos from data downloads", @@ -1238,6 +1239,16 @@ func TestUpdateVolumeInfos(t *testing.T) { Phase: velerov2alpha1.DataDownloadPhaseCompleted, }, }, + { + PVCName: "pvc-4", + PVCNamespace: "ns-4", + FallbackFull: true, + SnapshotDataMovementInfo: &volume.RestoreSnapshotDataMovementInfo{ + DataMover: "velero", + Size: 0, + Phase: "", + }, + }, }, dataDownloads: []*velerov2alpha1.DataDownload{ builder.ForDataDownload("velero", "dd-1"). @@ -1246,6 +1257,7 @@ func TestUpdateVolumeInfos(t *testing.T) { TotalBytes(4096). IncrementalBytes(1024). Phase(velerov2alpha1.DataDownloadPhaseCompleted). + FallbackFull(true). Result(), builder.ForDataDownload("velero", "dd-2"). ObjectMeta(builder.WithLabelsMap(map[string]string{velerov1api.RestoreNameLabel: "restore-1"})). @@ -1253,6 +1265,7 @@ func TestUpdateVolumeInfos(t *testing.T) { TotalBytes(2048). IncrementalBytes(512). Phase(velerov2alpha1.DataDownloadPhaseCompleted). + FallbackFull(true). Result(), builder.ForDataDownload("velero", "dd-other-restore"). ObjectMeta(builder.WithLabelsMap(map[string]string{velerov1api.RestoreNameLabel: "restore-other"})). @@ -1260,12 +1273,22 @@ func TestUpdateVolumeInfos(t *testing.T) { TotalBytes(9999). IncrementalBytes(8888). Phase(velerov2alpha1.DataDownloadPhaseFailed). + FallbackFull(true). + Result(), + builder.ForDataDownload("velero", "dd-4"). + ObjectMeta(builder.WithLabelsMap(map[string]string{velerov1api.RestoreNameLabel: "restore-1"})). + TargetVolume(velerov2alpha1.TargetVolumeSpec{PVC: "pvc-4", Namespace: "ns-4"}). + TotalBytes(1024). + IncrementalBytes(256). + Phase(velerov2alpha1.DataDownloadPhaseCompleted). + FallbackFull(false). Result(), }, - expectedSize: 4096, - expectedIncrSize: ptr.To(int64(1024)), - expectedPhase: velerov2alpha1.DataDownloadPhaseCompleted, - expectErrs: false, + expectedSize: 4096, + expectedIncrSize: ptr.To(int64(1024)), + expectedPhase: velerov2alpha1.DataDownloadPhaseCompleted, + expectedFallbackFull: true, + expectErrs: false, }, { name: "failed to list data downloads", @@ -1350,10 +1373,18 @@ func TestUpdateVolumeInfos(t *testing.T) { assert.Equal(t, tc.expectedSize, ctx.restoreVolumeInfos[0].SnapshotDataMovementInfo.Size) assert.Equal(t, tc.expectedIncrSize, ctx.restoreVolumeInfos[0].SnapshotDataMovementInfo.IncrementalSize) assert.Equal(t, tc.expectedPhase, ctx.restoreVolumeInfos[0].SnapshotDataMovementInfo.Phase) - // pvc-2 had nil SnapshotDataMovementInfo and should remain nil + assert.Equal(t, tc.expectedFallbackFull, ctx.restoreVolumeInfos[0].FallbackFull) + // pvc-2 had nil SnapshotDataMovementInfo and should remain nil, FallbackFull should remain false assert.Nil(t, ctx.restoreVolumeInfos[1].SnapshotDataMovementInfo) + assert.False(t, ctx.restoreVolumeInfos[1].FallbackFull) // pvc-3 belonged to another restore and should be untouched assert.Equal(t, int64(100), ctx.restoreVolumeInfos[2].SnapshotDataMovementInfo.Size) + assert.False(t, ctx.restoreVolumeInfos[2].FallbackFull) + // pvc-4 had FallbackFull updated to false from data download + assert.Equal(t, int64(1024), ctx.restoreVolumeInfos[3].SnapshotDataMovementInfo.Size) + assert.Equal(t, ptr.To(int64(256)), ctx.restoreVolumeInfos[3].SnapshotDataMovementInfo.IncrementalSize) + assert.Equal(t, velerov2alpha1.DataDownloadPhaseCompleted, ctx.restoreVolumeInfos[3].SnapshotDataMovementInfo.Phase) + assert.False(t, ctx.restoreVolumeInfos[3].FallbackFull) // Verify the content uploaded to backup store can be decoded and matches require.NotEmpty(t, uploadedData)