support cancel for PVB/PVR in backups/restores

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
This commit is contained in:
Lyndon-Li
2025-06-17 15:46:45 +08:00
parent acff99621a
commit e4caab4086
9 changed files with 182 additions and 4 deletions

View File

@@ -0,0 +1 @@
Fix issue #8965, support PVB/PVR's cancel state in the backup/restore

View File

@@ -823,7 +823,8 @@ func (kb *kubernetesBackupper) waitUntilPVBsProcessed(ctx context.Context, log l
}
for _, pvb := range pvbs {
pvbMap[pvb] = pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseCompleted ||
pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed
pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed ||
pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseCanceled
}
}
@@ -840,7 +841,8 @@ func (kb *kubernetesBackupper) waitUntilPVBsProcessed(ctx context.Context, log l
continue
}
if updatedPVB.Status.Phase == velerov1api.PodVolumeBackupPhaseCompleted ||
updatedPVB.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed {
updatedPVB.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed ||
updatedPVB.Status.Phase == velerov1api.PodVolumeBackupPhaseCanceled {
pvbMap[pvb] = true
continue
}

View File

@@ -786,7 +786,11 @@ func describePodVolumeBackups(d *Describer, details bool, podVolumeBackups []vel
for _, phase := range []string{
string(velerov1api.PodVolumeBackupPhaseCompleted),
string(velerov1api.PodVolumeBackupPhaseFailed),
string(velerov1api.PodVolumeBackupPhaseCanceled),
"In Progress",
string(velerov1api.PodVolumeBackupPhaseCanceling),
string(velerov1api.PodVolumeBackupPhasePrepared),
string(velerov1api.PodVolumeBackupPhaseAccepted),
string(velerov1api.PodVolumeBackupPhaseNew),
} {
if len(backupsByPhase[phase]) == 0 {
@@ -822,7 +826,11 @@ func groupByPhase(backups []velerov1api.PodVolumeBackup) map[string][]velerov1ap
phaseToGroup := map[velerov1api.PodVolumeBackupPhase]string{
velerov1api.PodVolumeBackupPhaseCompleted: string(velerov1api.PodVolumeBackupPhaseCompleted),
velerov1api.PodVolumeBackupPhaseFailed: string(velerov1api.PodVolumeBackupPhaseFailed),
velerov1api.PodVolumeBackupPhaseCanceled: string(velerov1api.PodVolumeBackupPhaseCanceled),
velerov1api.PodVolumeBackupPhaseInProgress: "In Progress",
velerov1api.PodVolumeBackupPhaseCanceling: string(velerov1api.PodVolumeBackupPhaseCanceling),
velerov1api.PodVolumeBackupPhasePrepared: string(velerov1api.PodVolumeBackupPhasePrepared),
velerov1api.PodVolumeBackupPhaseAccepted: string(velerov1api.PodVolumeBackupPhaseAccepted),
velerov1api.PodVolumeBackupPhaseNew: string(velerov1api.PodVolumeBackupPhaseNew),
"": string(velerov1api.PodVolumeBackupPhaseNew),
}

View File

@@ -572,6 +572,54 @@ func TestDescribePodVolumeBackups(t *testing.T) {
PodName("pod-2").
PodNamespace("pod-ns-1").
SnapshotID("snap-2").Result()
pvb3 := builder.ForPodVolumeBackup("test-ns1", "test-pvb3").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseFailed).
BackupStorageLocation("bsl-1").
Volume("vol-3").
PodName("pod-3").
PodNamespace("pod-ns-1").
SnapshotID("snap-3").Result()
pvb4 := builder.ForPodVolumeBackup("test-ns1", "test-pvb4").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseCanceled).
BackupStorageLocation("bsl-1").
Volume("vol-4").
PodName("pod-4").
PodNamespace("pod-ns-1").
SnapshotID("snap-4").Result()
pvb5 := builder.ForPodVolumeBackup("test-ns1", "test-pvb5").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseInProgress).
BackupStorageLocation("bsl-1").
Volume("vol-5").
PodName("pod-5").
PodNamespace("pod-ns-1").
SnapshotID("snap-5").Result()
pvb6 := builder.ForPodVolumeBackup("test-ns1", "test-pvb6").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseCanceling).
BackupStorageLocation("bsl-1").
Volume("vol-6").
PodName("pod-6").
PodNamespace("pod-ns-1").
SnapshotID("snap-6").Result()
pvb7 := builder.ForPodVolumeBackup("test-ns1", "test-pvb7").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhasePrepared).
BackupStorageLocation("bsl-1").
Volume("vol-7").
PodName("pod-7").
PodNamespace("pod-ns-1").
SnapshotID("snap-7").Result()
pvb8 := builder.ForPodVolumeBackup("test-ns1", "test-pvb6").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseAccepted).
BackupStorageLocation("bsl-1").
Volume("vol-8").
PodName("pod-8").
PodNamespace("pod-ns-1").
SnapshotID("snap-8").Result()
testcases := []struct {
name string
@@ -602,6 +650,28 @@ func TestDescribePodVolumeBackups(t *testing.T) {
Completed:
pod-ns-1/pod-1: vol-1
pod-ns-1/pod-2: vol-2
`,
},
{
name: "all phases with details",
inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2, *pvb3, *pvb4, *pvb5, *pvb6, *pvb7, *pvb8},
inputDetails: true,
expect: ` Pod Volume Backups - kopia:
Completed:
pod-ns-1/pod-1: vol-1
pod-ns-1/pod-2: vol-2
Failed:
pod-ns-1/pod-3: vol-3
Canceled:
pod-ns-1/pod-4: vol-4
In Progress:
pod-ns-1/pod-5: vol-5
Canceling:
pod-ns-1/pod-6: vol-6
Prepared:
pod-ns-1/pod-7: vol-7
Accepted:
pod-ns-1/pod-8: vol-8
`,
},
}

View File

@@ -499,7 +499,11 @@ func describePodVolumeBackupsInSF(backups []velerov1api.PodVolumeBackup, details
for _, phase := range []string{
string(velerov1api.PodVolumeBackupPhaseCompleted),
string(velerov1api.PodVolumeBackupPhaseFailed),
string(velerov1api.PodVolumeBackupPhaseCanceled),
"In Progress",
string(velerov1api.PodVolumeBackupPhaseCanceling),
string(velerov1api.PodVolumeBackupPhasePrepared),
string(velerov1api.PodVolumeBackupPhaseAccepted),
string(velerov1api.PodVolumeBackupPhaseNew),
} {
if len(backupsByPhase[phase]) == 0 {

View File

@@ -240,6 +240,55 @@ func TestDescribePodVolumeBackupsInSF(t *testing.T) {
PodNamespace("pod-ns-1").
SnapshotID("snap-2").Result()
pvb3 := builder.ForPodVolumeBackup("test-ns1", "test-pvb3").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseFailed).
BackupStorageLocation("bsl-1").
Volume("vol-3").
PodName("pod-3").
PodNamespace("pod-ns-1").
SnapshotID("snap-3").Result()
pvb4 := builder.ForPodVolumeBackup("test-ns1", "test-pvb4").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseCanceled).
BackupStorageLocation("bsl-1").
Volume("vol-4").
PodName("pod-4").
PodNamespace("pod-ns-1").
SnapshotID("snap-4").Result()
pvb5 := builder.ForPodVolumeBackup("test-ns1", "test-pvb5").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseInProgress).
BackupStorageLocation("bsl-1").
Volume("vol-5").
PodName("pod-5").
PodNamespace("pod-ns-1").
SnapshotID("snap-5").Result()
pvb6 := builder.ForPodVolumeBackup("test-ns1", "test-pvb6").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseCanceling).
BackupStorageLocation("bsl-1").
Volume("vol-6").
PodName("pod-6").
PodNamespace("pod-ns-1").
SnapshotID("snap-6").Result()
pvb7 := builder.ForPodVolumeBackup("test-ns1", "test-pvb7").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhasePrepared).
BackupStorageLocation("bsl-1").
Volume("vol-7").
PodName("pod-7").
PodNamespace("pod-ns-1").
SnapshotID("snap-7").Result()
pvb8 := builder.ForPodVolumeBackup("test-ns1", "test-pvb6").
UploaderType("kopia").
Phase(velerov1api.PodVolumeBackupPhaseAccepted).
BackupStorageLocation("bsl-1").
Volume("vol-8").
PodName("pod-8").
PodNamespace("pod-ns-1").
SnapshotID("snap-8").Result()
testcases := []struct {
name string
inputPVBList []velerov1api.PodVolumeBackup
@@ -268,6 +317,40 @@ func TestDescribePodVolumeBackupsInSF(t *testing.T) {
},
},
},
{
name: "all phases",
inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2, *pvb3, *pvb4, *pvb5, *pvb6, *pvb7, *pvb8},
inputDetails: true,
expect: map[string]any{
"podVolumeBackups": map[string]any{
"podVolumeBackupsDetails": map[string]any{
"Completed": []map[string]string{
{"pod-ns-1/pod-1": "vol-1"},
{"pod-ns-1/pod-2": "vol-2"},
},
"Failed": []map[string]string{
{"pod-ns-1/pod-3": "vol-3"},
},
"Canceled": []map[string]string{
{"pod-ns-1/pod-4": "vol-4"},
},
"In Progress": []map[string]string{
{"pod-ns-1/pod-5": "vol-5"},
},
"Canceling": []map[string]string{
{"pod-ns-1/pod-6": "vol-6"},
},
"Prepared": []map[string]string{
{"pod-ns-1/pod-7": "vol-7"},
},
"Accepted": []map[string]string{
{"pod-ns-1/pod-8": "vol-8"},
},
},
"uploderType": "kopia",
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(tt *testing.T) {

View File

@@ -362,8 +362,11 @@ func describePodVolumeRestores(d *Describer, restores []velerov1api.PodVolumeRes
// go through phases in a specific order
for _, phase := range []string{
string(velerov1api.PodVolumeRestorePhaseCompleted),
string(velerov1api.PodVolumeRestorePhaseCanceled),
string(velerov1api.PodVolumeRestorePhaseFailed),
"In Progress",
string(velerov1api.PodVolumeRestorePhasePrepared),
string(velerov1api.PodVolumeRestorePhaseAccepted),
string(velerov1api.PodVolumeRestorePhaseNew),
} {
if len(restoresByPhase[phase]) == 0 {
@@ -442,8 +445,11 @@ func groupRestoresByPhase(restores []velerov1api.PodVolumeRestore) map[string][]
phaseToGroup := map[velerov1api.PodVolumeRestorePhase]string{
velerov1api.PodVolumeRestorePhaseCompleted: string(velerov1api.PodVolumeRestorePhaseCompleted),
velerov1api.PodVolumeRestorePhaseCanceled: string(velerov1api.PodVolumeRestorePhaseCanceled),
velerov1api.PodVolumeRestorePhaseFailed: string(velerov1api.PodVolumeRestorePhaseFailed),
velerov1api.PodVolumeRestorePhaseInProgress: "In Progress",
velerov1api.PodVolumeRestorePhasePrepared: string(velerov1api.PodVolumeRestorePhasePrepared),
velerov1api.PodVolumeRestorePhaseAccepted: string(velerov1api.PodVolumeRestorePhaseAccepted),
velerov1api.PodVolumeRestorePhaseNew: string(velerov1api.PodVolumeRestorePhaseNew),
"": string(velerov1api.PodVolumeRestorePhaseNew),
}

View File

@@ -426,8 +426,10 @@ func (b *backupper) WaitAllPodVolumesProcessed(log logrus.FieldLogger) []*velero
continue
}
podVolumeBackups = append(podVolumeBackups, pvb)
if pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseFailed || pvb.Status.Phase == velerov1api.PodVolumeBackupPhaseCanceled {
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)
}
}
}

View File

@@ -228,8 +228,10 @@ ForEachVolume:
errs = append(errs, errors.New("timed out waiting for all PodVolumeRestores to complete"))
break ForEachVolume
case res := <-resultsChan:
if res.Status.Phase == velerov1api.PodVolumeRestorePhaseFailed || res.Status.Phase == velerov1api.PodVolumeRestorePhaseCanceled {
if res.Status.Phase == velerov1api.PodVolumeRestorePhaseFailed {
errs = append(errs, errors.Errorf("pod volume restore failed: %s", res.Status.Message))
} else if res.Status.Phase == velerov1api.PodVolumeRestorePhaseCanceled {
errs = append(errs, errors.Errorf("pod volume restore canceled: %s", res.Status.Message))
}
tracker.TrackPodVolume(res)
case err := <-r.nodeAgentCheck: