Identify the backed-up volume by CSI volume handle in the in-place restore pre-flight check (#10530)

The pre-flight check that verifies the existing PVC is still bound to the
backed-up volume compared PV names. A block data mover restore of a file
system volume recreates the PV under a new name (the volumeMode field is
immutable), so a second in-place restore of the same workload failed the
check even though the PVC was bound to the very same volume.

Record the CSI volume handle in the backup volume info (PVInfo) and
compare handles when both the backup and the bound PV record one; the
PV name remains the fallback for non-CSI volumes and for backups taken
before the handle was recorded. The handle reaches the PVC CSI RIA
through the same carrier annotation mechanism as the source size.

Signed-off-by: chlins <chlins.zhang@gmail.com>
This commit is contained in:
Chlins Zhang
2026-09-16 16:55:27 -04:00
committed by GitHub
parent 473f7529e1
commit e164dc5984
12 changed files with 302 additions and 62 deletions
+1 -1
View File
@@ -247,7 +247,7 @@ func (p *pvcRestoreItemAction) executeWithDataMove(logger *logrus.Entry, input *
restoreType := input.Restore.Spec.ExistingVolumeDataPolicy
if pvcExists {
// Pre-flight checks must pass before any side effect on the existing PVC/PV.
if err := inplace.CheckPVCBoundToBackedUpPV(existingPVC, pvcFromBackup.Spec.VolumeName, pvcFromBackup.Namespace); err != nil {
if err := inplace.CheckPVCBoundToBackedUpVolume(ctx, p.crClient, existingPVC, pvcFromBackup.Spec.VolumeName, pvc.Annotations[velerov1api.InplaceRestoreVolumeHandleAnnotation], pvcFromBackup.Namespace); err != nil {
return nil, errors.WithStack(err)
}
if err := inplace.CheckPVCCapacity(existingPVC, sourceSizeFromCarrier(pvc)); err != nil {
+30 -6
View File
@@ -843,12 +843,14 @@ func TestExecuteInplaceRestorePreflight(t *testing.T) {
}
tests := []struct {
name string
pod *corev1api.Pod
backedUpPVName string
sourceSize string // carried on the PVC item by the restore engine
pvcCapacity string
expectBlock string
name string
pod *corev1api.Pod
backedUpPVName string
backedUpHandle string // carried on the PVC item by the restore engine
existingPVHandle string // CSI volume handle of the PV bound to the existing PVC
sourceSize string // carried on the PVC item by the restore engine
pvcCapacity string
expectBlock string
}{
{
name: "checks pass, restore proceeds",
@@ -865,6 +867,22 @@ func TestExecuteInplaceRestorePreflight(t *testing.T) {
backedUpPVName: "backupPV",
expectBlock: "was bound to PV backupPV at backup time",
},
{
// The PV was recreated under a new name by a previous in-place
// restore (block data mover on a file system volume) but is the
// same CSI volume.
name: "PVC bound to the backed-up volume under a recreated PV name proceeds",
backedUpPVName: "backupPV",
backedUpHandle: "vol-1",
existingPVHandle: "vol-1",
},
{
name: "PVC bound to a different CSI volume under the same PV name blocks the restore",
backedUpPVName: "testPV",
backedUpHandle: "vol-1",
existingPVHandle: "vol-other",
expectBlock: "is bound to volume vol-other (PV testPV), but was bound to volume vol-1 (PV testPV) at backup time",
},
{
// Backed-up PV unknown so the same-volume skip does not apply.
name: "PVC smaller than the source volume blocks the restore",
@@ -887,6 +905,9 @@ func TestExecuteInplaceRestorePreflight(t *testing.T) {
existingPVC.Status.Capacity = corev1api.ResourceList{corev1api.ResourceStorage: resource.MustParse(tc.pvcCapacity)}
}
existingPV := builder.ForPersistentVolume("testPV").Result()
if tc.existingPVHandle != "" {
existingPV.Spec.CSI = &corev1api.CSIPersistentVolumeSource{Driver: "fake.csi", VolumeHandle: tc.existingPVHandle}
}
backup := builder.ForBackup("velero", "testBackup").SnapshotMoveData(true).Result()
restore := builder.ForRestore("velero", "testRestore").Backup("testBackup").
ObjectMeta(builder.WithUID("uid")).ExistingVolumeDataPolicy("full").Result()
@@ -920,6 +941,9 @@ func TestExecuteInplaceRestorePreflight(t *testing.T) {
if tc.sourceSize != "" {
item.Annotations[velerov1api.InplaceRestoreSourceSizeAnnotation] = tc.sourceSize
}
if tc.backedUpHandle != "" {
item.Annotations[velerov1api.InplaceRestoreVolumeHandleAnnotation] = tc.backedUpHandle
}
pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
require.NoError(t, err)
pvcFromBackupMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pvcFromBackup)