@@ -38,9 +38,14 @@ import (
3838 "k8s.io/client-go/tools/record"
3939 "k8s.io/component-helpers/storage/volume"
4040 ctrl "sigs.k8s.io/controller-runtime"
41+ "sigs.k8s.io/controller-runtime/pkg/builder"
4142 "sigs.k8s.io/controller-runtime/pkg/client"
4243 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
44+ "sigs.k8s.io/controller-runtime/pkg/event"
45+ "sigs.k8s.io/controller-runtime/pkg/handler"
4346 "sigs.k8s.io/controller-runtime/pkg/log"
47+ "sigs.k8s.io/controller-runtime/pkg/predicate"
48+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
4449
4550 appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
4651 dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
@@ -83,6 +88,8 @@ type pvcRestoreDecision struct {
8388
8489// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/status,verbs=get;update;patch
8590// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/finalizers,verbs=update
91+ // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=restores,verbs=get;list;watch
92+ // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusters,verbs=get;list;watch
8693// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=components,verbs=get;list;watch
8794// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=componentdefinitions,verbs=get;list;watch
8895
@@ -133,9 +140,195 @@ func (r *VolumePopulatorReconciler) handleSyncPVCError(reqCtx intctrlutil.Reques
133140func (r * VolumePopulatorReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
134141 return intctrlutil .NewControllerManagedBy (mgr ).
135142 For (& corev1.PersistentVolumeClaim {}).
143+ Watches (& dpv1alpha1.Restore {}, handler .EnqueueRequestsFromMapFunc (r .mapRestoreToPVCs ),
144+ builder .WithPredicates (restoreDependencyPredicate ())).
145+ Watches (& appsv1.Component {}, handler .EnqueueRequestsFromMapFunc (r .mapComponentToPVCs ),
146+ builder .WithPredicates (componentDependencyPredicate ())).
147+ Watches (& appsv1.Cluster {}, handler .EnqueueRequestsFromMapFunc (r .mapClusterToPVCs ),
148+ builder .WithPredicates (clusterDependencyPredicate ())).
136149 Complete (r )
137150}
138151
152+ func (r * VolumePopulatorReconciler ) mapRestoreToPVCs (ctx context.Context , obj client.Object ) []reconcile.Request {
153+ restore , ok := obj .(* dpv1alpha1.Restore )
154+ if ! ok || restore .Labels [dprestore .DataProtectionRestoreLabelKey ] != restore .Name {
155+ return nil
156+ }
157+
158+ if owner := exactOwnerReference (restore .OwnerReferences , corev1 .SchemeGroupVersion .String (), "PersistentVolumeClaim" ); owner != nil {
159+ pvc := & corev1.PersistentVolumeClaim {}
160+ key := types.NamespacedName {Namespace : restore .Namespace , Name : owner .Name }
161+ if err := r .Client .Get (ctx , key , pvc ); err != nil || pvc .UID != owner .UID ||
162+ ! isClusterRestorePVC (pvc ) || restore .Name != getPopulatePVCName (pvc .UID ) {
163+ return nil
164+ }
165+ return []reconcile.Request {{NamespacedName : key }}
166+ }
167+
168+ owner := exactOwnerReference (restore .OwnerReferences , appsv1 .GroupVersion .String (), "Component" )
169+ if owner == nil {
170+ return nil
171+ }
172+ comp := & appsv1.Component {}
173+ key := types.NamespacedName {Namespace : restore .Namespace , Name : owner .Name }
174+ if err := r .Client .Get (ctx , key , comp ); err != nil || comp .UID != owner .UID ||
175+ restore .Name != postReadyRestoreName (comp .UID ) {
176+ return nil
177+ }
178+ clusterName := comp .Labels [constant .AppInstanceLabelKey ]
179+ componentName := restore .Labels [constant .KBAppComponentLabelKey ]
180+ ownerComponentName := comp .Labels [constant .KBAppComponentLabelKey ]
181+ if clusterName == "" || componentName == "" || ownerComponentName == "" ||
182+ restore .Labels [constant .AppInstanceLabelKey ] != clusterName {
183+ return nil
184+ }
185+ // A postReady Restore is owned by its target Component, while its labels
186+ // identify only the first source PVC that created it. Other Components in
187+ // the Cluster can wait on the same Restore through postReady redirection.
188+ return r .mapRestorePVCs (ctx , restore .Namespace , client.MatchingLabels {
189+ constant .AppInstanceLabelKey : clusterName ,
190+ })
191+ }
192+
193+ func (r * VolumePopulatorReconciler ) mapComponentToPVCs (ctx context.Context , obj client.Object ) []reconcile.Request {
194+ comp , ok := obj .(* appsv1.Component )
195+ if ! ok {
196+ return nil
197+ }
198+ clusterName := comp .Labels [constant .AppInstanceLabelKey ]
199+ componentName := comp .Labels [constant .KBAppComponentLabelKey ]
200+ if clusterName == "" || componentName == "" {
201+ return nil
202+ }
203+ // A PVC can depend on another Component through a redirected postReady
204+ // Restore. That relationship is derived from Backup status and is not
205+ // represented on the Component, so a Component event must fan out to all
206+ // active restore PVCs in its Cluster.
207+ return r .mapRestorePVCs (ctx , comp .Namespace , client.MatchingLabels {
208+ constant .AppInstanceLabelKey : clusterName ,
209+ })
210+ }
211+
212+ func (r * VolumePopulatorReconciler ) mapClusterToPVCs (ctx context.Context , obj client.Object ) []reconcile.Request {
213+ cluster , ok := obj .(* appsv1.Cluster )
214+ if ! ok || cluster .Name == "" {
215+ return nil
216+ }
217+ return r .mapRestorePVCs (ctx , cluster .Namespace , client.MatchingLabels {
218+ constant .AppInstanceLabelKey : cluster .Name ,
219+ })
220+ }
221+
222+ func (r * VolumePopulatorReconciler ) mapRestorePVCs (ctx context.Context , namespace string ,
223+ labels client.MatchingLabels ) []reconcile.Request {
224+ list := & corev1.PersistentVolumeClaimList {}
225+ if err := r .Client .List (ctx , list , client .InNamespace (namespace ), labels ); err != nil {
226+ return nil
227+ }
228+ requests := make ([]reconcile.Request , 0 , len (list .Items ))
229+ for i := range list .Items {
230+ pvc := & list .Items [i ]
231+ if ! isClusterRestorePVC (pvc ) || pvcRestoreTerminal (pvc ) {
232+ continue
233+ }
234+ requests = append (requests , reconcile.Request {NamespacedName : client .ObjectKeyFromObject (pvc )})
235+ }
236+ return requests
237+ }
238+
239+ func isClusterRestorePVC (pvc * corev1.PersistentVolumeClaim ) bool {
240+ ref := pvc .Spec .DataSourceRef
241+ if ref == nil || ref .APIGroup == nil || * ref .APIGroup != dptypes .DataprotectionAPIGroup || ref .Name == "" ||
242+ (ref .Kind != dptypes .BackupKind && ref .Kind != dptypes .RestoreKind ) {
243+ return false
244+ }
245+ if pvc .Labels [constant .AppInstanceLabelKey ] == "" || pvc .Labels [constant .KBAppComponentLabelKey ] == "" {
246+ return false
247+ }
248+ for _ , key := range []string {
249+ constant .RestoreSourceAPIGroupAnnotationKey ,
250+ constant .RestoreSourceKindAnnotationKey ,
251+ constant .RestoreSourceNameAnnotationKey ,
252+ constant .RestoreComponentAnnotationKey ,
253+ constant .RestoreVolumeTemplateAnnotationKey ,
254+ } {
255+ if pvc .Annotations [key ] == "" {
256+ return false
257+ }
258+ }
259+ restoreComponent := pvc .Annotations [constant .RestoreComponentAnnotationKey ]
260+ return restoreComponent == pvc .Labels [constant .KBAppComponentLabelKey ] ||
261+ restoreComponent == pvc .Labels [constant .KBAppShardingNameLabelKey ] ||
262+ restoreComponent == pvc .Labels [constant .KBAppShardTemplateLabelKey ]
263+ }
264+
265+ func pvcRestoreTerminal (pvc * corev1.PersistentVolumeClaim ) bool {
266+ condition := findPVCConditionByType (pvc , appsv1 .ConditionTypeRestore )
267+ return condition != nil && (condition .Status == corev1 .ConditionTrue || condition .Status == corev1 .ConditionFalse )
268+ }
269+
270+ func exactOwnerReference (refs []metav1.OwnerReference , apiVersion , kind string ) * metav1.OwnerReference {
271+ for i := range refs {
272+ if refs [i ].APIVersion == apiVersion && refs [i ].Kind == kind && refs [i ].Name != "" && refs [i ].UID != "" {
273+ return & refs [i ]
274+ }
275+ }
276+ return nil
277+ }
278+
279+ func restoreDependencyPredicate () predicate.Predicate {
280+ return predicate.Funcs {
281+ CreateFunc : func (event.CreateEvent ) bool { return true },
282+ DeleteFunc : func (event.DeleteEvent ) bool { return true },
283+ GenericFunc : func (event.GenericEvent ) bool { return false },
284+ UpdateFunc : func (e event.UpdateEvent ) bool {
285+ oldRestore , oldOK := e .ObjectOld .(* dpv1alpha1.Restore )
286+ newRestore , newOK := e .ObjectNew .(* dpv1alpha1.Restore )
287+ return oldOK && newOK && (oldRestore .Status .Phase != newRestore .Status .Phase ||
288+ ! reflect .DeepEqual (oldRestore .DeletionTimestamp , newRestore .DeletionTimestamp ))
289+ },
290+ }
291+ }
292+
293+ func componentDependencyPredicate () predicate.Predicate {
294+ return predicate.Funcs {
295+ CreateFunc : func (event.CreateEvent ) bool { return true },
296+ DeleteFunc : func (event.DeleteEvent ) bool { return true },
297+ GenericFunc : func (event.GenericEvent ) bool { return false },
298+ UpdateFunc : func (e event.UpdateEvent ) bool {
299+ oldComp , oldOK := e .ObjectOld .(* appsv1.Component )
300+ newComp , newOK := e .ObjectNew .(* appsv1.Component )
301+ return oldOK && newOK && (oldComp .Status .Phase != newComp .Status .Phase ||
302+ ! reflect .DeepEqual (oldComp .DeletionTimestamp , newComp .DeletionTimestamp ) ||
303+ ! reflect .DeepEqual (postProvisionCondition (oldComp ), postProvisionCondition (newComp )))
304+ },
305+ }
306+ }
307+
308+ func clusterDependencyPredicate () predicate.Predicate {
309+ return predicate.Funcs {
310+ CreateFunc : func (event.CreateEvent ) bool { return true },
311+ DeleteFunc : func (event.DeleteEvent ) bool { return true },
312+ GenericFunc : func (event.GenericEvent ) bool { return false },
313+ UpdateFunc : func (e event.UpdateEvent ) bool {
314+ oldCluster , oldOK := e .ObjectOld .(* appsv1.Cluster )
315+ newCluster , newOK := e .ObjectNew .(* appsv1.Cluster )
316+ return oldOK && newOK && (oldCluster .Status .Phase != newCluster .Status .Phase ||
317+ ! reflect .DeepEqual (oldCluster .DeletionTimestamp , newCluster .DeletionTimestamp ))
318+ },
319+ }
320+ }
321+
322+ func postProvisionCondition (comp * appsv1.Component ) * metav1.Condition {
323+ for i := range comp .Status .Conditions {
324+ condition := & comp .Status .Conditions [i ]
325+ if condition .Type == appsv1 .ComponentConditionProgressing && condition .Reason == "PostProvision" {
326+ return condition
327+ }
328+ }
329+ return nil
330+ }
331+
139332func (r * VolumePopulatorReconciler ) MatchToPopulate (pvc * corev1.PersistentVolumeClaim ) (bool , error ) {
140333 dataSourceRef := pvc .Spec .DataSourceRef
141334 if dataSourceRef == nil {
0 commit comments