Skip to content

Commit 7c61439

Browse files
committed
refactor(dataprotection): clarify population completion and PV rebind flow
1 parent dee0833 commit 7c61439

2 files changed

Lines changed: 241 additions & 209 deletions

File tree

controllers/dataprotection/volumepopulator_controller.go

Lines changed: 107 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import (
6060
viper "github.com/apecloud/kubeblocks/pkg/viperx"
6161
)
6262

63-
// VolumePopulatorReconciler reconciles Backup dataSource PVCs.
63+
// VolumePopulatorReconciler reconciles PVCs with Backup or Restore data sources.
6464
type VolumePopulatorReconciler struct {
6565
client.Client
6666
Scheme *runtime.Scheme
@@ -389,8 +389,7 @@ func (r *VolumePopulatorReconciler) syncPVC(reqCtx intctrlutil.RequestCtx, pvc *
389389
// dispatchUnboundPVC routes an unbound PVC to either Populate or ProvisionOnly.
390390
// When mode is RestoreData but PrepareDataBackupSets is empty, it checks
391391
// PostReadyBackupSets: if postReady actions exist, fall back to ProvisionOnly
392-
// (data arrives via postReady); if neither stage exists, fail the invalid
393-
// restore contract rather than silently completing with an empty PVC.
392+
// (data arrives via postReady); if neither stage exists, fail the restore.
394393
func (r *VolumePopulatorReconciler) dispatchUnboundPVC(reqCtx intctrlutil.RequestCtx, pvc *corev1.PersistentVolumeClaim, restoreCtx *pvcRestoreContext) error {
395394
if restoreCtx.mode == pvcRestoreModeRestoreData {
396395
if len(restoreCtx.restoreMgr.PrepareDataBackupSets) == 0 {
@@ -1190,55 +1189,17 @@ func (r *VolumePopulatorReconciler) ProvisionOnly(reqCtx intctrlutil.RequestCtx,
11901189
func (r *VolumePopulatorReconciler) completeBoundPVCIfNeeded(reqCtx intctrlutil.RequestCtx,
11911190
pvc *corev1.PersistentVolumeClaim,
11921191
restoreCtx *pvcRestoreContext) error {
1193-
populateReleased := pvcPopulateReleased(pvc)
1194-
for i := range pvc.Status.Conditions {
1195-
condition := pvc.Status.Conditions[i]
1196-
if string(condition.Type) != appsv1.ConditionTypeRestore {
1197-
continue
1198-
}
1199-
if condition.Status == corev1.ConditionFalse {
1200-
return nil
1201-
}
1202-
break
1192+
if condition := findPVCConditionByType(pvc, appsv1.ConditionTypeRestore); condition != nil && condition.Status == corev1.ConditionFalse {
1193+
return nil
12031194
}
1204-
if !populateReleased {
1205-
// A target PVC can be bound elsewhere while our PV-first handoff is in
1206-
// progress. Recover that handoff before validation makes the failure terminal.
1207-
populatePVC := &corev1.PersistentVolumeClaim{}
1208-
populateKey := types.NamespacedName{Namespace: pvc.Namespace, Name: getPopulatePVCName(pvc.UID)}
1209-
if err := r.Client.Get(reqCtx.Ctx, populateKey, populatePVC); err != nil {
1210-
if !apierrors.IsNotFound(err) {
1211-
return err
1212-
}
1213-
} else if populatePVC.Spec.VolumeName != "" && populatePVC.Spec.VolumeName != pvc.Spec.VolumeName {
1214-
rebound, err := r.rebindPVCAndPV(reqCtx, populatePVC, pvc)
1215-
if err != nil {
1216-
return err
1217-
}
1218-
if !rebound {
1219-
return intctrlutil.NewRequeueError(reconcileInterval, "waiting to recover interrupted PV handoff")
1220-
}
1221-
}
1222-
if err := r.validateBoundTargetPV(reqCtx, pvc); err != nil {
1223-
return err
1224-
}
1225-
if !pvcBindingCompleted(pvc) {
1226-
return intctrlutil.NewRequeueError(reconcileInterval, "waiting for Kubernetes to complete target PVC binding")
1227-
}
1228-
// Release the target PVC after prepareData and PV rebind. PostReady
1229-
// actions may need the workload pod to start, which cannot happen while
1230-
// the populate PVC still owns the restored PV or while the target PVC is
1231-
// still marked as being populated.
1232-
if err := r.releasePopulateResources(reqCtx, pvc); err != nil {
1233-
return err
1234-
}
1235-
reason := ReasonPopulatingSucceed
1236-
message := "Populator finished"
1237-
if restoreCtx.mode == pvcRestoreModeProvisionOnly {
1238-
reason = ReasonPopulatingProvisioned
1239-
message = "PVC provisioned without data restore"
1240-
}
1241-
if err := r.updatePVCPopulatingCondition(reqCtx, pvc, reason, message); err != nil {
1195+
reason := ReasonPopulatingSucceed
1196+
message := "Populator finished"
1197+
if restoreCtx.mode == pvcRestoreModeProvisionOnly {
1198+
reason = ReasonPopulatingProvisioned
1199+
message = "PVC provisioned without data restore"
1200+
}
1201+
if !pvcPopulateReleased(pvc) {
1202+
if err := r.completePVCPopulation(reqCtx, pvc, reason, message); err != nil {
12421203
return err
12431204
}
12441205
}
@@ -1249,17 +1210,51 @@ func (r *VolumePopulatorReconciler) completeBoundPVCIfNeeded(reqCtx intctrlutil.
12491210
if !postReadyCompleted {
12501211
return intctrlutil.NewRequeueError(reconcileInterval, "waiting for postReady restore")
12511212
}
1252-
reason := ReasonPopulatingSucceed
1253-
message := "Populator finished"
1254-
if restoreCtx.mode == pvcRestoreModeProvisionOnly {
1255-
reason = ReasonPopulatingProvisioned
1256-
message = "PVC provisioned without data restore"
1257-
}
12581213
return r.UpdatePVCConditions(reqCtx, pvc, reason, message)
12591214
}
12601215

1261-
// validateBoundTargetPV verifies that an already-bound target PVC points to
1262-
// the PV rebound by this populator.
1216+
// completePVCPopulation verifies the target binding, releases helper resources,
1217+
// and records population completion. PostReady completion is checked separately.
1218+
func (r *VolumePopulatorReconciler) completePVCPopulation(reqCtx intctrlutil.RequestCtx,
1219+
pvc *corev1.PersistentVolumeClaim, reason, message string) error {
1220+
if err := r.recoverInterruptedPVRebind(reqCtx, pvc); err != nil {
1221+
return err
1222+
}
1223+
if err := r.validateBoundTargetPV(reqCtx, pvc); err != nil {
1224+
return err
1225+
}
1226+
if !pvcBindingCompleted(pvc) {
1227+
return intctrlutil.NewRequeueError(reconcileInterval, "waiting for Kubernetes to complete target PVC binding")
1228+
}
1229+
if err := r.releasePopulateResources(reqCtx, pvc); err != nil {
1230+
return err
1231+
}
1232+
return r.updatePVCPopulatingCondition(reqCtx, pvc, reason, message)
1233+
}
1234+
1235+
// recoverInterruptedPVRebind reconciles the helper PV when the target PVC
1236+
// references a different PV, before target-binding validation can report failure.
1237+
func (r *VolumePopulatorReconciler) recoverInterruptedPVRebind(reqCtx intctrlutil.RequestCtx,
1238+
pvc *corev1.PersistentVolumeClaim) error {
1239+
populatePVC := &corev1.PersistentVolumeClaim{}
1240+
populateKey := types.NamespacedName{Namespace: pvc.Namespace, Name: getPopulatePVCName(pvc.UID)}
1241+
if err := r.Client.Get(reqCtx.Ctx, populateKey, populatePVC); err != nil {
1242+
return client.IgnoreNotFound(err)
1243+
}
1244+
if populatePVC.Spec.VolumeName == "" || populatePVC.Spec.VolumeName == pvc.Spec.VolumeName {
1245+
return nil
1246+
}
1247+
rebound, err := r.rebindPVCAndPV(reqCtx, populatePVC, pvc)
1248+
if err != nil {
1249+
return err
1250+
}
1251+
if !rebound {
1252+
return intctrlutil.NewRequeueError(reconcileInterval, "waiting to recover interrupted PV handoff")
1253+
}
1254+
return nil
1255+
}
1256+
1257+
// validateBoundTargetPV checks the target PV's claimRef and population-source annotation.
12631258
func (r *VolumePopulatorReconciler) validateBoundTargetPV(reqCtx intctrlutil.RequestCtx,
12641259
pvc *corev1.PersistentVolumeClaim) error {
12651260
if pvc.Spec.VolumeName == "" {
@@ -1862,8 +1857,8 @@ func postReadyRestoreName(componentUID types.UID) string {
18621857
return constant.ShortenKubeName(fmt.Sprintf("restore-%s-post-ready", componentUID), constant.KubeNameMaxLength)
18631858
}
18641859

1865-
// releasePopulateResources releases only the temporary PVC and the target PVC
1866-
// finalizer after population has succeeded.
1860+
// releasePopulateResources deletes the helper PVC and removes the target PVC's
1861+
// population finalizer after target binding is verified.
18671862
func (r *VolumePopulatorReconciler) releasePopulateResources(reqCtx intctrlutil.RequestCtx, pvc *corev1.PersistentVolumeClaim) error {
18681863
if err := r.deletePopulatePVC(reqCtx, pvc); err != nil {
18691864
return err
@@ -2042,7 +2037,7 @@ func pvClaimRefComplete(claimRef *corev1.ObjectReference) bool {
20422037

20432038
func (r *VolumePopulatorReconciler) rebindPVCAndPV(reqCtx intctrlutil.RequestCtx, populatePVC, pvc *corev1.PersistentVolumeClaim) (bool, error) {
20442039
if populatePVC == nil {
2045-
return false, intctrlutil.NewFatalError(fmt.Sprintf("populate PVC is nil for target PVC %s/%s; restoreData path entered without prepareData backup set", pvc.Namespace, pvc.Name))
2040+
return false, intctrlutil.NewFatalError(fmt.Sprintf("populate PVC is nil for target PVC %s/%s", pvc.Namespace, pvc.Name))
20462041
}
20472042
if pvc.Spec.DataSourceRef == nil || pvc.Spec.DataSourceRef.Name == "" {
20482043
return false, intctrlutil.NewFatalError(fmt.Sprintf(
@@ -2062,48 +2057,61 @@ func (r *VolumePopulatorReconciler) rebindPVCAndPV(reqCtx intctrlutil.RequestCtx
20622057
if !apierrors.IsNotFound(err) {
20632058
return false, err
20642059
}
2065-
// We'll get called again later when the PV exists
2060+
// Wait until the helper PV is visible in the cache.
20662061
return false, nil
20672062
}
2068-
// Examine the claimref for the PV and see if it's bound to the correct PVC
2069-
claimRef := pv.Spec.ClaimRef
2070-
if pvClaimRefMatchesPVC(claimRef, pvc) {
2071-
if pv.Annotations[AnnPopulateFrom] != pvc.Spec.DataSourceRef.Name {
2072-
return false, intctrlutil.NewFatalError(fmt.Sprintf(
2073-
"PV %s already identifies target PVC %s/%s without expected population provenance %s=%q",
2074-
pv.Name, pvc.Namespace, pvc.Name, AnnPopulateFrom, pvc.Spec.DataSourceRef.Name))
2075-
}
2076-
if pvc.Spec.VolumeName != "" && pvc.Spec.VolumeName != pv.Name {
2077-
if err := r.restoreHelperPVBinding(reqCtx, pv, populatePVC); err != nil {
2078-
return false, err
2079-
}
2080-
if err := r.validateBoundTargetPV(reqCtx, pvc); err != nil {
2081-
return false, err
2082-
}
2083-
return true, nil
2084-
}
2085-
if err := r.bindTargetPVCToPV(reqCtx, pvc, pv.Name); err != nil {
2086-
return false, err
2087-
}
2088-
return true, nil
2063+
if pvClaimRefMatchesPVC(pv.Spec.ClaimRef, pvc) {
2064+
err := r.resumePVRebind(reqCtx, pv, populatePVC, pvc)
2065+
return err == nil, err
20892066
}
2090-
if !pvClaimRefMatchesPVC(claimRef, populatePVC) {
2091-
if !pvClaimRefComplete(claimRef) {
2092-
return false, intctrlutil.NewRequeueError(reconcileInterval, fmt.Sprintf(
2093-
"waiting for PV %s claimRef to identify helper PVC %s/%s",
2094-
pv.Name, populatePVC.Namespace, populatePVC.Name))
2095-
}
2096-
return false, intctrlutil.NewFatalError(fmt.Sprintf(
2097-
"refusing to rebind PV %s for target PVC %s/%s: claimRef does not identify helper PVC %s/%s UID %s",
2098-
pv.Name, pvc.Namespace, pvc.Name, populatePVC.Namespace, populatePVC.Name, populatePVC.UID))
2067+
if err := validateHelperPVClaimRef(pv, populatePVC, pvc); err != nil {
2068+
return false, err
20992069
}
21002070
if pvc.Spec.VolumeName != "" {
2101-
if err := r.validateBoundTargetPV(reqCtx, pvc); err != nil {
2102-
return false, err
2071+
err := r.validateBoundTargetPV(reqCtx, pvc)
2072+
return err == nil, err
2073+
}
2074+
if err := r.rebindPVToTarget(reqCtx, pv, pvc); err != nil {
2075+
return false, err
2076+
}
2077+
err := r.bindTargetPVCToPV(reqCtx, pvc, pv.Name)
2078+
return err == nil, err
2079+
}
2080+
2081+
// resumePVRebind completes a PV assignment to the target PVC, or returns the PV
2082+
// to the helper if a concurrent binding assigned a different PV to the target.
2083+
func (r *VolumePopulatorReconciler) resumePVRebind(reqCtx intctrlutil.RequestCtx,
2084+
pv *corev1.PersistentVolume, populatePVC, pvc *corev1.PersistentVolumeClaim) error {
2085+
if pv.Annotations[AnnPopulateFrom] != pvc.Spec.DataSourceRef.Name {
2086+
return intctrlutil.NewFatalError(fmt.Sprintf(
2087+
"PV %s already identifies target PVC %s/%s without expected population provenance %s=%q",
2088+
pv.Name, pvc.Namespace, pvc.Name, AnnPopulateFrom, pvc.Spec.DataSourceRef.Name))
2089+
}
2090+
if pvc.Spec.VolumeName != "" && pvc.Spec.VolumeName != pv.Name {
2091+
if err := r.restoreHelperPVBinding(reqCtx, pv, populatePVC); err != nil {
2092+
return err
21032093
}
2104-
return true, nil
2094+
return r.validateBoundTargetPV(reqCtx, pvc)
2095+
}
2096+
return r.bindTargetPVCToPV(reqCtx, pvc, pv.Name)
2097+
}
2098+
2099+
func validateHelperPVClaimRef(pv *corev1.PersistentVolume, populatePVC, pvc *corev1.PersistentVolumeClaim) error {
2100+
if pvClaimRefMatchesPVC(pv.Spec.ClaimRef, populatePVC) {
2101+
return nil
2102+
}
2103+
if !pvClaimRefComplete(pv.Spec.ClaimRef) {
2104+
return intctrlutil.NewRequeueError(reconcileInterval, fmt.Sprintf(
2105+
"waiting for PV %s claimRef to identify helper PVC %s/%s",
2106+
pv.Name, populatePVC.Namespace, populatePVC.Name))
21052107
}
2106-
// Make new PV with strategic patch values to perform the PV rebind
2108+
return intctrlutil.NewFatalError(fmt.Sprintf(
2109+
"refusing to rebind PV %s for target PVC %s/%s: claimRef does not identify helper PVC %s/%s UID %s",
2110+
pv.Name, pvc.Namespace, pvc.Name, populatePVC.Namespace, populatePVC.Name, populatePVC.UID))
2111+
}
2112+
2113+
func (r *VolumePopulatorReconciler) rebindPVToTarget(reqCtx intctrlutil.RequestCtx,
2114+
pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) error {
21072115
patchPV := client.MergeFromWithOptions(pv.DeepCopy(), client.MergeFromWithOptimisticLock{})
21082116
pv.Spec.ClaimRef = &corev1.ObjectReference{
21092117
Namespace: pvc.Namespace,
@@ -2115,13 +2123,7 @@ func (r *VolumePopulatorReconciler) rebindPVCAndPV(reqCtx intctrlutil.RequestCtx
21152123
pv.Annotations = map[string]string{}
21162124
}
21172125
pv.Annotations[AnnPopulateFrom] = pvc.Spec.DataSourceRef.Name
2118-
if err := r.Client.Patch(reqCtx.Ctx, pv, patchPV); err != nil {
2119-
return false, err
2120-
}
2121-
if err := r.bindTargetPVCToPV(reqCtx, pvc, pv.Name); err != nil {
2122-
return false, err
2123-
}
2124-
return true, nil
2126+
return r.Client.Patch(reqCtx.Ctx, pv, patchPV)
21252127
}
21262128

21272129
func (r *VolumePopulatorReconciler) restoreHelperPVBinding(reqCtx intctrlutil.RequestCtx,
@@ -2195,7 +2197,7 @@ func (r *VolumePopulatorReconciler) UpdatePVCConditions(reqCtx intctrlutil.Reque
21952197
continue
21962198
}
21972199
if v.Reason == ReasonPopulatingSucceed {
2198-
// ignore succeed condition
2200+
// Preserve completed population while updating the Restore condition.
21992201
if pvcConditionMatches(pvc.Status.Conditions, restoreCondition) {
22002202
return nil
22012203
}

0 commit comments

Comments
 (0)