Update Restore Exposer and PVC CSI to support in-place restore (#10104)

1. Update Restore Exposer to support exposing with existing PV for in-place restore
2. Update PVC CSI RIA to continue the restore process for in-place restore

Signed-off-by: Wenkai Yin(尹文开) <yinw@vmware.com>
This commit is contained in:
Wenkai Yin(尹文开)
2026-08-14 16:28:29 +08:00
parent dfbd7ef5d5
commit c0d26b9f59
12 changed files with 561 additions and 108 deletions
@@ -936,6 +936,7 @@ func (r *DataDownloadReconciler) setupExposeParam(dd *velerov2alpha1api.DataDown
return exposer.GenericRestoreExposeParam{
TargetPVCName: dd.Spec.TargetVolume.PVC,
TargetPVName: dd.Spec.TargetVolume.PV,
TargetNamespace: dd.Spec.TargetVolume.Namespace,
HostingPodLabels: hostingPodLabels,
HostingPodAnnotations: hostingPodAnnotation,
@@ -61,7 +61,6 @@ func dataDownloadBuilder() *builder.DataDownloadBuilder {
BackupStorageLocation("bsl-loc").
DataMover("velero").
SnapshotID("test-snapshot-id").TargetVolume(velerov2alpha1api.TargetVolumeSpec{
PV: "test-pv",
PVC: "test-pvc",
Namespace: "test-ns",
})
@@ -183,6 +182,7 @@ func TestDataDownloadReconcile(t *testing.T) {
dd *velerov2alpha1api.DataDownload
notCreateDD bool
targetPVC *corev1api.PersistentVolumeClaim
targetPV *corev1api.PersistentVolume
dataMgr *datapath.Manager
needErrs []bool
needCreateFSBR bool
@@ -194,6 +194,7 @@ func TestDataDownloadReconcile(t *testing.T) {
isPeekExposeErr bool
isNilExposer bool
notNilExpose bool
mockExpose bool
notMockCleanUp bool
mockInit bool
mockInitErr error
@@ -351,6 +352,16 @@ func TestDataDownloadReconcile(t *testing.T) {
targetPVC: builder.ForPersistentVolumeClaim("test-ns", "test-pvc").StorageClass("sc").Result(),
expected: dataDownloadBuilder().Finalizers([]string{DataUploadDownloadFinalizer}).Phase(velerov2alpha1api.DataDownloadPhaseAccepted).Result(),
},
{
name: "dd succeeds for accepted with target PV set",
dd: dataDownloadBuilder().Finalizers([]string{DataUploadDownloadFinalizer}).TargetVolume(velerov2alpha1api.TargetVolumeSpec{PVC: "test-pvc", Namespace: "test-ns", PV: "test-pv"}).Result(),
targetPVC: builder.ForPersistentVolumeClaim("test-ns", "test-pvc").StorageClass("sc").Result(),
targetPV: builder.ForPersistentVolume("test-pv").Result(),
expected: dataDownloadBuilder().Finalizers([]string{DataUploadDownloadFinalizer}).TargetVolume(velerov2alpha1api.TargetVolumeSpec{PVC: "test-pvc", Namespace: "test-ns", PV: "test-pv"}).Phase(velerov2alpha1api.DataDownloadPhaseAccepted).Result(),
mockExpose: true,
notMockCleanUp: true,
notNilExpose: true,
},
{
name: "prepare timeout on accepted",
dd: dataDownloadBuilder().Phase(velerov2alpha1api.DataDownloadPhaseAccepted).Finalizers([]string{DataUploadDownloadFinalizer}).AcceptedTimestamp(&metav1.Time{Time: time.Now().Add(-time.Minute * 30)}).Result(),
@@ -487,6 +498,10 @@ func TestDataDownloadReconcile(t *testing.T) {
objects = append(objects, test.targetPVC)
}
if test.targetPV != nil {
objects = append(objects, test.targetPV)
}
r, err := initDataDownloadReconciler(t, objects, test.needErrs...)
require.NoError(t, err)
@@ -543,7 +558,7 @@ func TestDataDownloadReconcile(t *testing.T) {
return asyncBR
}
if test.isExposeErr || test.isGetExposeErr || test.isGetExposeNil || test.isPeekExposeErr || test.isNilExposer || test.notNilExpose {
if test.isExposeErr || test.isGetExposeErr || test.isGetExposeNil || test.isPeekExposeErr || test.isNilExposer || test.notNilExpose || test.mockExpose {
if test.isNilExposer {
r.restoreExposer = nil
} else {
@@ -551,6 +566,8 @@ func TestDataDownloadReconcile(t *testing.T) {
ep := exposermockes.NewGenericRestoreExposer(t)
if test.isExposeErr {
ep.On("Expose", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("Error to expose restore exposer"))
} else if test.mockExpose {
ep.On("Expose", mock.Anything, mock.Anything, mock.Anything).Return(nil)
} else if test.notNilExpose {
hostingPod := builder.ForPod("test-ns", "test-name").Volumes(&corev1api.Volume{Name: "test-pvc"}).Result()
hostingPod.ObjectMeta.SetUID("test-uid")
@@ -1319,6 +1336,7 @@ func TestDataDownloadSetupExposeParam(t *testing.T) {
baseDataDownload := dataDownloadBuilder().Result()
baseDataDownload.Namespace = velerov1api.DefaultNamespace
baseDataDownload.Spec.TargetVolume.PV = "pv-1"
baseDataDownload.Spec.OperationTimeout = metav1.Duration{Duration: time.Minute * 10}
baseDataDownload.Spec.SnapshotSize = 5368709120 // 5Gi
@@ -1428,6 +1446,7 @@ func TestDataDownloadSetupExposeParam(t *testing.T) {
// Core fields
assert.Equal(t, baseDataDownload.Spec.TargetVolume.PVC, got.TargetPVCName)
assert.Equal(t, baseDataDownload.Spec.TargetVolume.PV, got.TargetPVName)
assert.Equal(t, baseDataDownload.Spec.TargetVolume.Namespace, got.TargetNamespace)
assert.Equal(t, baseDataDownload.Spec.DataMover, got.DataMover)