Skip to content

Commit e4e6159

Browse files
committed
fix(dataprotection): recover PV handoff on bound PVC retries
1 parent b4a1316 commit e4e6159

2 files changed

Lines changed: 51 additions & 10 deletions

File tree

controllers/dataprotection/volumepopulator_controller.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,24 @@ func (r *VolumePopulatorReconciler) completeBoundPVCIfNeeded(reqCtx intctrlutil.
12001200
break
12011201
}
12021202
if !populateReleased {
1203+
// A target PVC can be bound elsewhere while our PV-first handoff is in
1204+
// progress. Recover that handoff before validation makes the failure terminal.
1205+
populatePVC := &corev1.PersistentVolumeClaim{}
1206+
populateKey := types.NamespacedName{Namespace: pvc.Namespace, Name: getPopulatePVCName(pvc.UID)}
1207+
if err := r.Client.Get(reqCtx.Ctx, populateKey, populatePVC); err != nil {
1208+
if !apierrors.IsNotFound(err) {
1209+
return intctrlutil.NewRequeueError(reconcileInterval,
1210+
fmt.Sprintf("waiting to read helper PVC %s: %v", populateKey, err))
1211+
}
1212+
} else if populatePVC.Spec.VolumeName != "" && populatePVC.Spec.VolumeName != pvc.Spec.VolumeName {
1213+
rebound, err := r.rebindPVCAndPV(reqCtx, populatePVC, pvc)
1214+
if err != nil {
1215+
return err
1216+
}
1217+
if !rebound {
1218+
return intctrlutil.NewRequeueError(reconcileInterval, "waiting to recover interrupted PV handoff")
1219+
}
1220+
}
12031221
if err := r.validateBoundTargetPV(reqCtx, pvc); err != nil {
12041222
return err
12051223
}

controllers/dataprotection/volumepopulator_controller_test.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3057,20 +3057,38 @@ func TestRebindPVCAndPVResumesAfterPVHandoff(t *testing.T) {
30573057
require.Equal(t, pv.Name, currentPVC.Spec.VolumeName)
30583058
}
30593059

3060-
func TestRebindPVCAndPVDoesNotOverwriteConcurrentTargetPVCBinding(t *testing.T) {
3060+
func TestReconcileRecoversPVHandoffAfterConcurrentTargetBinding(t *testing.T) {
30613061
scheme := runtime.NewScheme()
30623062
require.NoError(t, corev1.AddToScheme(scheme))
3063+
require.NoError(t, dpv1alpha1.AddToScheme(scheme))
3064+
apiGroup := dptypes.DataprotectionAPIGroup
30633065
stalePVC := &corev1.PersistentVolumeClaim{
30643066
ObjectMeta: metav1.ObjectMeta{
30653067
Namespace: "default", Name: "target", UID: "target-uid", ResourceVersion: "1",
3068+
Finalizers: []string{dptypes.DataProtectionFinalizerName},
3069+
},
3070+
Spec: corev1.PersistentVolumeClaimSpec{DataSourceRef: &corev1.TypedObjectReference{
3071+
APIGroup: &apiGroup, Kind: dptypes.RestoreKind, Name: "source-restore",
3072+
}},
3073+
}
3074+
backup := newBackupForRestoreDecision([]string{"data"}, nil)
3075+
backup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
3076+
backup.Status.BackupMethod.SnapshotVolumes = ptr.To(true)
3077+
sourceRestore := &dpv1alpha1.Restore{
3078+
ObjectMeta: metav1.ObjectMeta{Namespace: stalePVC.Namespace, Name: stalePVC.Spec.DataSourceRef.Name},
3079+
Spec: dpv1alpha1.RestoreSpec{
3080+
Backup: dpv1alpha1.BackupRef{Namespace: backup.Namespace, Name: backup.Name},
3081+
ServiceAccountName: "worker",
3082+
PrepareDataConfig: &dpv1alpha1.PrepareDataConfig{
3083+
DataSourceRef: &dpv1alpha1.VolumeConfig{VolumeSource: "data"},
3084+
},
30663085
},
3067-
Spec: corev1.PersistentVolumeClaimSpec{DataSourceRef: &corev1.TypedObjectReference{Name: "backup"}},
30683086
}
30693087
livePVC := stalePVC.DeepCopy()
30703088
livePVC.ResourceVersion = "2"
30713089
livePVC.Spec.VolumeName = "other-pv"
30723090
populatePVC := &corev1.PersistentVolumeClaim{
3073-
ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "populate", UID: "populate-uid"},
3091+
ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: getPopulatePVCName(stalePVC.UID), UID: "populate-uid"},
30743092
Spec: corev1.PersistentVolumeClaimSpec{VolumeName: "restored-pv"},
30753093
}
30763094
helperPV := &corev1.PersistentVolume{
@@ -3085,8 +3103,9 @@ func TestRebindPVCAndPVDoesNotOverwriteConcurrentTargetPVCBinding(t *testing.T)
30853103
Namespace: livePVC.Namespace, Name: livePVC.Name, UID: livePVC.UID,
30863104
}},
30873105
}
3088-
liveClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(livePVC, helperPV, otherPV).Build()
3089-
reconciler := &VolumePopulatorReconciler{Client: liveClient}
3106+
liveClient := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(livePVC).
3107+
WithObjects(livePVC, populatePVC, helperPV, otherPV, backup, sourceRestore).Build()
3108+
reconciler := &VolumePopulatorReconciler{Client: liveClient, Scheme: scheme, Recorder: record.NewFakeRecorder(10)}
30903109

30913110
rebound, err := reconciler.rebindPVCAndPV(
30923111
intctrlutil.RequestCtx{Ctx: context.Background()}, populatePVC, stalePVC)
@@ -3101,15 +3120,19 @@ func TestRebindPVCAndPVDoesNotOverwriteConcurrentTargetPVCBinding(t *testing.T)
31013120
require.NoError(t, liveClient.Get(context.Background(), client.ObjectKeyFromObject(helperPV), halfCompletedPV))
31023121
require.True(t, pvClaimRefMatchesPVC(halfCompletedPV.Spec.ClaimRef, stalePVC))
31033122

3104-
rebound, err = reconciler.rebindPVCAndPV(
3105-
intctrlutil.RequestCtx{Ctx: context.Background()}, populatePVC, currentPVC)
3106-
require.False(t, rebound)
3107-
require.Error(t, err)
3108-
require.True(t, intctrlutil.IsTargetError(err, intctrlutil.ErrorTypeFatal), err)
3123+
_, err = reconciler.Reconcile(context.Background(), reconcile.Request{NamespacedName: client.ObjectKeyFromObject(currentPVC)})
3124+
require.NoError(t, err)
31093125
currentHelperPV := &corev1.PersistentVolume{}
31103126
require.NoError(t, liveClient.Get(context.Background(), client.ObjectKeyFromObject(helperPV), currentHelperPV))
31113127
require.True(t, pvClaimRefMatchesPVC(currentHelperPV.Spec.ClaimRef, populatePVC))
31123128
require.Empty(t, currentHelperPV.Annotations[AnnPopulateFrom])
3129+
require.NoError(t, liveClient.Get(context.Background(), client.ObjectKeyFromObject(livePVC), currentPVC))
3130+
require.Equal(t, "other-pv", currentPVC.Spec.VolumeName)
3131+
require.Contains(t, currentPVC.Finalizers, dptypes.DataProtectionFinalizerName)
3132+
require.NoError(t, liveClient.Get(context.Background(), client.ObjectKeyFromObject(populatePVC), &corev1.PersistentVolumeClaim{}))
3133+
restoreCondition := findPVCConditionByType(currentPVC, kbappsv1.ConditionTypeRestore)
3134+
require.NotNil(t, restoreCondition)
3135+
require.Equal(t, corev1.ConditionFalse, restoreCondition.Status)
31133136
}
31143137

31153138
func TestRebindPVCAndPVDoesNotOverwriteConcurrentClaimRefChange(t *testing.T) {

0 commit comments

Comments
 (0)