From 6b3e7ef5dd10a62a91c1b74faa8314f65b4d0292 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Thu, 10 Sep 2026 09:43:38 +0000 Subject: [PATCH] add fallback to volume info and backup/restore describe Signed-off-by: Lyndon-Li --- changelogs/unreleased/10517-Lyndon-Li | 1 + .../v1/bases/velero.io_podvolumebackups.yaml | 4 +++ .../v1/bases/velero.io_podvolumerestores.yaml | 4 +++ .../bases/velero.io_datadownloads.yaml | 4 +++ .../v2alpha1/bases/velero.io_datauploads.yaml | 4 +++ internal/volume/volumes_information.go | 6 ++-- pkg/apis/velero/v1/pod_volume_backup_types.go | 2 +- pkg/apis/velero/v1/pod_volume_restore_type.go | 2 +- .../velero/v2alpha1/data_download_types.go | 2 +- pkg/apis/velero/v2alpha1/data_upload_types.go | 2 +- pkg/datamover/backup_micro_service_test.go | 2 +- pkg/datamover/restore_micro_service_test.go | 2 +- pkg/podvolume/backup_micro_service_test.go | 2 +- pkg/podvolume/restore_micro_service_test.go | 2 +- pkg/uploader/block/snapshot.go | 10 ++++-- pkg/uploader/block/snapshot_test.go | 33 +++++++++++++++++-- 16 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/10517-Lyndon-Li diff --git a/changelogs/unreleased/10517-Lyndon-Li b/changelogs/unreleased/10517-Lyndon-Li new file mode 100644 index 000000000..dcfd01fd4 --- /dev/null +++ b/changelogs/unreleased/10517-Lyndon-Li @@ -0,0 +1 @@ +Add fallback full to DU/DD/PVB/PVR CRs, volume info and backup/restore describe \ No newline at end of file diff --git a/config/crd/v1/bases/velero.io_podvolumebackups.yaml b/config/crd/v1/bases/velero.io_podvolumebackups.yaml index 3df50f831..fddc9407c 100644 --- a/config/crd/v1/bases/velero.io_podvolumebackups.yaml +++ b/config/crd/v1/bases/velero.io_podvolumebackups.yaml @@ -204,6 +204,10 @@ spec: format: date-time nullable: true type: string + fallbackFull: + description: FallbackFull indicates whether the incremental backup + has fallen back to full backup + type: boolean incrementalBytes: description: |- IncrementalBytes holds the number of bytes new or changed since the last backup. diff --git a/config/crd/v1/bases/velero.io_podvolumerestores.yaml b/config/crd/v1/bases/velero.io_podvolumerestores.yaml index c8ddb9c87..3631badda 100644 --- a/config/crd/v1/bases/velero.io_podvolumerestores.yaml +++ b/config/crd/v1/bases/velero.io_podvolumerestores.yaml @@ -204,6 +204,10 @@ spec: format: date-time nullable: true type: string + fallbackFull: + description: FallbackFull indicates whether the incremental restore + has fallen back to full restore + type: boolean incrementalBytes: description: IncrementalBytes holds the number of bytes restored incrementally format: int64 diff --git a/config/crd/v2alpha1/bases/velero.io_datadownloads.yaml b/config/crd/v2alpha1/bases/velero.io_datadownloads.yaml index 88fe710e4..63f0c145a 100644 --- a/config/crd/v2alpha1/bases/velero.io_datadownloads.yaml +++ b/config/crd/v2alpha1/bases/velero.io_datadownloads.yaml @@ -213,6 +213,10 @@ spec: format: date-time nullable: true type: string + fallbackFull: + description: FallbackFull indicates whether the incremental restore + has fallen back to full restore + type: boolean incrementalBytes: description: IncrementalBytes holds the number of bytes restored incrementally since the last snapshot diff --git a/config/crd/v2alpha1/bases/velero.io_datauploads.yaml b/config/crd/v2alpha1/bases/velero.io_datauploads.yaml index 19c1069f9..d0162048c 100644 --- a/config/crd/v2alpha1/bases/velero.io_datauploads.yaml +++ b/config/crd/v2alpha1/bases/velero.io_datauploads.yaml @@ -195,6 +195,10 @@ spec: as a result of the DataUpload. nullable: true type: object + fallbackFull: + description: FallbackFull indicates whether the incremental backup + has fallen back to full backup + type: boolean incrementalBytes: description: |- IncrementalBytes holds the number of bytes new or changed since the last backup. diff --git a/internal/volume/volumes_information.go b/internal/volume/volumes_information.go index d76b890f4..1cdc82c26 100644 --- a/internal/volume/volumes_information.go +++ b/internal/volume/volumes_information.go @@ -93,7 +93,7 @@ type BackupVolumeInfo struct { // How volume data is backed up, with possible values including Full and Incremental. BackupType velerov1api.BackupType `json:"backupType,omitempty"` - // FallbackFull indicates whether the incremental backup fallbacks to full backup + // FallbackFull indicates whether the incremental backup has fallen back to full backup FallbackFull bool `json:"fallbackFull,omitempty"` CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` @@ -132,7 +132,7 @@ type RestoreVolumeInfo struct { // Indicates the type of the restore, incremental or full. RestoreType string `json:"restoreType,omitempty"` - // FallbackFull indicates whether the incremental restore fallbacks to full restore + // FallbackFull indicates whether the incremental restore has fallen back to full restore FallbackFull bool `json:"fallbackFull,omitempty"` CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` @@ -300,7 +300,7 @@ 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 fallbacks to full backup + // 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. diff --git a/pkg/apis/velero/v1/pod_volume_backup_types.go b/pkg/apis/velero/v1/pod_volume_backup_types.go index 5b2c03361..b8daead83 100644 --- a/pkg/apis/velero/v1/pod_volume_backup_types.go +++ b/pkg/apis/velero/v1/pod_volume_backup_types.go @@ -142,7 +142,7 @@ type PodVolumeBackupStatus struct { // +nullable AcceptedTimestamp *metav1.Time `json:"acceptedTimestamp,omitempty"` - // FallbackFull indicates whether the incremental backup fallbacks to full backup + // FallbackFull indicates whether the incremental backup has fallen back to full backup FallbackFull bool `json:"fallbackFull,omitempty"` } diff --git a/pkg/apis/velero/v1/pod_volume_restore_type.go b/pkg/apis/velero/v1/pod_volume_restore_type.go index af9b9ecba..d0b537a87 100644 --- a/pkg/apis/velero/v1/pod_volume_restore_type.go +++ b/pkg/apis/velero/v1/pod_volume_restore_type.go @@ -125,7 +125,7 @@ type PodVolumeRestoreStatus struct { // +optional Node string `json:"node,omitempty"` - // FallbackFull indicates whether the incremental restore fallbacks to full restore + // FallbackFull indicates whether the incremental restore has fallen back to full restore FallbackFull bool `json:"fallbackFull,omitempty"` } diff --git a/pkg/apis/velero/v2alpha1/data_download_types.go b/pkg/apis/velero/v2alpha1/data_download_types.go index d54fdb788..f2be18c57 100644 --- a/pkg/apis/velero/v2alpha1/data_download_types.go +++ b/pkg/apis/velero/v2alpha1/data_download_types.go @@ -150,7 +150,7 @@ type DataDownloadStatus struct { // +nullable AcceptedTimestamp *metav1.Time `json:"acceptedTimestamp,omitempty"` - // FallbackFull indicates whether the incremental restore fallbacks to full restore + // FallbackFull indicates whether the incremental restore has fallen back to full restore FallbackFull bool `json:"fallbackFull,omitempty"` } diff --git a/pkg/apis/velero/v2alpha1/data_upload_types.go b/pkg/apis/velero/v2alpha1/data_upload_types.go index d207b151e..af94fd92b 100644 --- a/pkg/apis/velero/v2alpha1/data_upload_types.go +++ b/pkg/apis/velero/v2alpha1/data_upload_types.go @@ -199,7 +199,7 @@ type DataUploadStatus struct { // +nullable AcceptedTimestamp *metav1.Time `json:"acceptedTimestamp,omitempty"` - // FallbackFull indicates whether the incremental backup fallbacks to full backup + // FallbackFull indicates whether the incremental backup has fallen back to full backup FallbackFull bool `json:"fallbackFull,omitempty"` } diff --git a/pkg/datamover/backup_micro_service_test.go b/pkg/datamover/backup_micro_service_test.go index 392781443..af706c3b5 100644 --- a/pkg/datamover/backup_micro_service_test.go +++ b/pkg/datamover/backup_micro_service_test.go @@ -152,7 +152,7 @@ func TestOnDataUploadCompleted(t *testing.T) { { name: "marshal fail", marshalErr: errors.New("fake-marshal-error"), - expectedErr: "Failed to marshal backup result { false { } 0 0}: fake-marshal-error", + expectedErr: "Failed to marshal backup result { false { } 0 0 false}: fake-marshal-error", }, { name: "succeed", diff --git a/pkg/datamover/restore_micro_service_test.go b/pkg/datamover/restore_micro_service_test.go index 63a3e6a5e..9f5da306f 100644 --- a/pkg/datamover/restore_micro_service_test.go +++ b/pkg/datamover/restore_micro_service_test.go @@ -102,7 +102,7 @@ func TestOnDataDownloadCompleted(t *testing.T) { { name: "marshal fail", marshalErr: errors.New("fake-marshal-error"), - expectedErr: "Failed to marshal restore result {{ } 0 0}: fake-marshal-error", + expectedErr: "Failed to marshal restore result {{ } 0 0 false}: fake-marshal-error", }, { name: "succeed", diff --git a/pkg/podvolume/backup_micro_service_test.go b/pkg/podvolume/backup_micro_service_test.go index 8eb5f7cf2..5982dbd16 100644 --- a/pkg/podvolume/backup_micro_service_test.go +++ b/pkg/podvolume/backup_micro_service_test.go @@ -156,7 +156,7 @@ func TestOnDataPathCompleted(t *testing.T) { { name: "marshal fail", marshalErr: errors.New("fake-marshal-error"), - expectedErr: "Failed to marshal backup result { false { } 0 0}: fake-marshal-error", + expectedErr: "Failed to marshal backup result { false { } 0 0 false}: fake-marshal-error", }, { name: "succeed", diff --git a/pkg/podvolume/restore_micro_service_test.go b/pkg/podvolume/restore_micro_service_test.go index 46c8813b6..8fae70f73 100644 --- a/pkg/podvolume/restore_micro_service_test.go +++ b/pkg/podvolume/restore_micro_service_test.go @@ -165,7 +165,7 @@ func TestOnPvrCompleted(t *testing.T) { { name: "marshal fail", marshalErr: errors.New("fake-marshal-error"), - expectedErr: "error marshaling restore result {{ } 0 0}: fake-marshal-error", + expectedErr: "error marshaling restore result {{ } 0 0 false}: fake-marshal-error", }, { name: "succeed", diff --git a/pkg/uploader/block/snapshot.go b/pkg/uploader/block/snapshot.go index cff152805..d5d338e14 100644 --- a/pkg/uploader/block/snapshot.go +++ b/pkg/uploader/block/snapshot.go @@ -128,7 +128,10 @@ func snapshotSource( log.WithError(err).Warnf("Failed to create CBT with source %v", cbtSource) } - fallback := (len(bitmap.Errors()) > 0) + fallback := false + if !forceFull { + fallback = (len(bitmap.Errors()) > 0) + } snap, backupSize, err := u.Backup(source, parentBackup.parentObject, bitmap.Iterator(), uploaderCfg) if err != nil { @@ -251,7 +254,10 @@ func Restore(ctx context.Context, blkUp Uploader, rep udmrepo.BackupRepo, snapsh bitmap.SetFull() } - fallback := (len(bitmap.Errors()) > 0) + fallback := false + if incremental { + fallback = (len(bitmap.Errors()) > 0) + } destPath, err := filepath.Abs(dest) if err != nil { diff --git a/pkg/uploader/block/snapshot_test.go b/pkg/uploader/block/snapshot_test.go index 14f1b5a75..9dcd3b7fe 100644 --- a/pkg/uploader/block/snapshot_test.go +++ b/pkg/uploader/block/snapshot_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "k8s.io/utils/ptr" "github.com/vmware-tanzu/velero/pkg/cbtservice" cbtservicemocks "github.com/vmware-tanzu/velero/pkg/cbtservice/mocks" @@ -126,6 +127,7 @@ func TestBackup(t *testing.T) { assert.Equal(t, int64(8), info.IncrementalSize) assert.Equal(t, int64(2048), info.SnapshotSize) assert.Equal(t, int64(len("test-block-data")), info.SourceSize) + assert.False(t, info.Fallback) }, }, { @@ -149,6 +151,7 @@ func TestBackup(t *testing.T) { assert.Equal(t, int64(8), info.IncrementalSize) assert.Equal(t, int64(len("test-block-data")), info.SnapshotSize) assert.Equal(t, int64(len("test-block-data")), info.SourceSize) + assert.False(t, info.Fallback) }, }, } @@ -210,6 +213,8 @@ func TestSnapshotSource(t *testing.T) { testCases := []struct { name string + forceFull *bool + parentSnapshot string setupMocks func(blkup *mockUploader, repo *udmrepomocks.BackupRepo) expectedErrStr string expectedSnapID string @@ -260,7 +265,7 @@ func TestSnapshotSource(t *testing.T) { expectedSnapID: "snap-success", expectedSize: 512, expectedSnapshotSize: 2048, - expectedFallback: true, + expectedFallback: false, }, { name: "tags from cbtSource and snapshotTags are merged onto snapshot", @@ -278,7 +283,7 @@ func TestSnapshotSource(t *testing.T) { expectedSnapID: "snap-tags", expectedSize: 256, expectedSnapshotSize: 4096, - expectedFallback: true, + expectedFallback: false, }, { name: "success with cbtService getting allocated blocks", @@ -322,6 +327,23 @@ func TestSnapshotSource(t *testing.T) { expectedSnapID: "snap-cbt-fallback", expectedSize: 1024, expectedSnapshotSize: 1024, + expectedFallback: false, + }, + { + name: "incremental backup falls back to full when parent snapshot load fails", + forceFull: ptr.To(false), + parentSnapshot: "snap-parent", + setupMocks: func(blkup *mockUploader, repo *udmrepomocks.BackupRepo) { + repo.On("GetSnapshot", mock.Anything, udmrepo.ID("snap-parent")). + Return(udmrepo.Snapshot{}, errors.New("parent not found")) + blkup.On("Backup", mock.Anything, udmrepo.ID(""), mock.Anything, mock.Anything). + Return(udmrepo.Snapshot{TotalSize: 1024}, int64(1024), nil) + repo.On("SaveSnapshot", mock.Anything, mock.Anything).Return(udmrepo.ID("snap-inc-fallback"), nil) + repo.On("Flush", mock.Anything).Return(nil) + }, + expectedSnapID: "snap-inc-fallback", + expectedSize: 1024, + expectedSnapshotSize: 1024, expectedFallback: true, }, } @@ -342,10 +364,15 @@ func TestSnapshotSource(t *testing.T) { cbtSvc = tc.cbtService(t) } + forceFull := true + if tc.forceFull != nil { + forceFull = *tc.forceFull + } + snapID, size, snapshotSize, fallback, err := snapshotSource( ctx, mockRepo, mockBlkup, baseSource, - true, "", + forceFull, tc.parentSnapshot, cbtSrc, cbtSvc, snapshotTags, map[string]string{}, testLog(), "Block Uploader",