@@ -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 .
6464type 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.
394393func (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,
11901189func (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.
12631258func (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 .
18671862func (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
20432038func (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
21272129func (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