Skip to content

Commit 5695b27

Browse files
committed
wire in permissionClaims context store
1 parent fd11270 commit 5695b27

1 file changed

Lines changed: 121 additions & 64 deletions

File tree

pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go

Lines changed: 121 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ func (r *reconciler) ensureControllers(ctx context.Context, namespace, name stri
154154
contexts := r.syncStore.ListPrefixed(exportKey)
155155
for _, c := range contexts {
156156
schemaName := strings.TrimPrefix(string(c.Key()), string(exportKey)+".") // schemaName will be processed schema names.
157+
// Skip permission claim controllers - they are handled separately in ensureControllersForPermissionClaims
158+
if strings.HasPrefix(schemaName, "claim.") {
159+
continue
160+
}
157161
if !processedSchemas[schemaName] {
158162
logger.V(1).Info("Stopping APIServiceExport resource sync", "key", c.Key(), "reason", "Schema no longer referenced")
159163
r.syncStore.Delete(c.Key())
@@ -299,100 +303,153 @@ func (r *reconciler) ensureControllersForPermissionClaims(
299303
) error {
300304
logger := klog.FromContext(ctx)
301305

302-
//ctxWithCancel, cancel := context.WithCancel(ctx)
303-
304-
dynamicProviderClient := dynamicclient.NewForConfigOrDie(r.providerConfig)
305-
dynamicConsumerClient := dynamicclient.NewForConfigOrDie(r.consumerConfig)
306-
307-
// We will create a new filtered informer factory for each claim that has a label selector.
308-
// We still need a default one for claims without a selector.
309-
defaultConsumerInf := dynamicinformer.NewDynamicSharedInformerFactory(dynamicConsumerClient, time.Minute*30)
306+
// Track processed claims for cleanup
307+
processedClaims := make(map[string]bool)
308+
var errs []error
310309

311-
// PermissionClaims sync controller
312-
claimControllers := make([]func(context.Context, int), len(binding.Spec.PermissionClaims))
310+
// Process each permission claim
313311
for _, claim := range binding.Spec.PermissionClaims {
314312
claimGVR, err := kubebindv1alpha2.ResolveClaimableAPI(claim)
315313
if err != nil {
316314
logger.Info("skipping unsupported claim", "claim", claim, "error", err)
317315
continue
318316
}
319-
// Define a function to tweak list options based on the claim's LabelSelector.
320-
tweakListOptions := func(options *metav1.ListOptions) {
321-
if claim.Selector.LabelSelector != nil {
322-
// Convert the LabelSelector struct to a string that the API server understands.
323-
selector, err := metav1.LabelSelectorAsSelector(claim.Selector.LabelSelector)
324-
if err == nil {
325-
options.LabelSelector = selector.String()
326-
logger.V(2).Info("Applying label selector to informer", "selector", options.LabelSelector, "resource", claim.Resource)
327-
} else {
328-
logger.Error(err, "failed to parse label selector", "selector", claim.Selector.LabelSelector)
329-
}
317+
318+
// Create unique key for this claim controller
319+
claimKey := contextstore.NewKey(export.Namespace, export.Name, "claim", claimGVR.String())
320+
processedClaims[claimKey.String()] = true
321+
322+
// Check if controller already exists with correct generation
323+
c, found := r.syncStore.Get(claimKey)
324+
if found {
325+
if c.Generation == binding.Generation {
326+
continue // controller is up to date
330327
}
328+
// Generation changed, stop old controller
329+
logger.V(1).Info("Stopping permission claim controller", "key", claimKey, "reason", "GenerationChanged")
330+
r.syncStore.Delete(claimKey)
331331
}
332332

333-
var providerInf multinsinformer.GetterInformer
334-
if isClusterScoped {
335-
factory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicProviderClient, time.Minute*30)
336-
factory.ForResource(claimGVR).Lister() // wire the GVR up in the informer factory
337-
providerInf = multinsinformer.GetterInformerWrapper{
338-
GVR: claimGVR,
339-
Delegate: factory,
340-
}
341-
} else {
342-
providerInf, err = multinsinformer.NewDynamicMultiNamespaceInformer(
343-
claimGVR,
344-
r.providerNamespace,
345-
r.providerConfig,
346-
r.serviceNamespaceInformer,
347-
)
348-
if err != nil {
349-
logger.Info("aborting", "error", err)
350-
return err
351-
}
333+
// Start new controller for this claim
334+
if err := r.ensureControllerForPermissionClaim(ctx, binding, claim, claimGVR, isClusterScoped, claimKey); err != nil {
335+
errs = append(errs, err)
336+
}
337+
}
338+
339+
// Cleanup controllers for claims that are no longer present
340+
claimPrefix := contextstore.NewKey(export.Namespace, export.Name, "claim")
341+
contexts := r.syncStore.ListPrefixed(claimPrefix)
342+
for _, c := range contexts {
343+
if !processedClaims[c.Key().String()] {
344+
logger.V(1).Info("Stopping permission claim controller", "key", c.Key(), "reason", "Claim no longer present")
345+
r.syncStore.Delete(c.Key())
352346
}
347+
}
348+
349+
return utilerrors.NewAggregate(errs)
350+
}
353351

352+
func (r *reconciler) ensureControllerForPermissionClaim(
353+
ctx context.Context,
354+
binding *kubebindv1alpha2.APIServiceBinding,
355+
claim kubebindv1alpha2.PermissionClaim,
356+
claimGVR runtimeschema.GroupVersionResource,
357+
isClusterScoped bool,
358+
claimKey contextstore.Key,
359+
) error {
360+
logger := klog.FromContext(ctx)
361+
362+
ctxWithCancel, cancel := context.WithCancel(ctx)
363+
364+
dynamicProviderClient := dynamicclient.NewForConfigOrDie(r.providerConfig)
365+
dynamicConsumerClient := dynamicclient.NewForConfigOrDie(r.consumerConfig)
366+
367+
// Define a function to tweak list options based on the claim's LabelSelector.
368+
tweakListOptions := func(options *metav1.ListOptions) {
354369
if claim.Selector.LabelSelector != nil {
355-
defaultConsumerInf = dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicConsumerClient, time.Minute*30, metav1.NamespaceAll, tweakListOptions)
370+
// Convert the LabelSelector struct to a string that the API server understands.
371+
selector, err := metav1.LabelSelectorAsSelector(claim.Selector.LabelSelector)
372+
if err == nil {
373+
options.LabelSelector = selector.String()
374+
logger.V(2).Info("Applying label selector to informer", "selector", options.LabelSelector, "resource", claim.Resource)
375+
} else {
376+
logger.Error(err, "failed to parse label selector", "selector", claim.Selector.LabelSelector)
377+
}
356378
}
379+
}
357380

358-
claimedCtrl, err := claimedresources.NewController(
381+
// Create consumer informer factory
382+
var defaultConsumerInf dynamicinformer.DynamicSharedInformerFactory
383+
if claim.Selector.LabelSelector != nil {
384+
defaultConsumerInf = dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicConsumerClient, time.Minute*30, metav1.NamespaceAll, tweakListOptions)
385+
} else {
386+
defaultConsumerInf = dynamicinformer.NewDynamicSharedInformerFactory(dynamicConsumerClient, time.Minute*30)
387+
}
388+
389+
var providerInf multinsinformer.GetterInformer
390+
if isClusterScoped {
391+
factory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicProviderClient, time.Minute*30)
392+
factory.ForResource(claimGVR).Lister() // wire the GVR up in the informer factory
393+
providerInf = multinsinformer.GetterInformerWrapper{
394+
GVR: claimGVR,
395+
Delegate: factory,
396+
}
397+
} else {
398+
var err error
399+
providerInf, err = multinsinformer.NewDynamicMultiNamespaceInformer(
359400
claimGVR,
360-
claim,
361401
r.providerNamespace,
362-
r.consumerConfig,
363402
r.providerConfig,
364-
defaultConsumerInf.ForResource(claimGVR),
365-
providerInf,
366403
r.serviceNamespaceInformer,
367404
)
368-
369405
if err != nil {
370-
runtime.HandleError(err)
371-
return nil // nothing we can do here
406+
logger.Info("aborting", "error", err)
407+
cancel()
408+
return err
372409
}
373-
logger.Info("creating claim reconciler", "gvr", claimGVR)
374-
375-
claimControllers = append(claimControllers, func(ctx context.Context, i int) {
376-
providerInf.Start(ctx)
410+
}
377411

378-
providerSynced := providerInf.WaitForCacheSync(ctx.Done())
379-
logger.V(2).Info("Synced informers", "provider", slices.Collect(maps.Keys(providerSynced)))
412+
claimedCtrl, err := claimedresources.NewController(
413+
claimGVR,
414+
claim,
415+
r.providerNamespace,
416+
r.consumerConfig,
417+
r.providerConfig,
418+
defaultConsumerInf.ForResource(claimGVR),
419+
providerInf,
420+
r.serviceNamespaceInformer,
421+
)
380422

381-
claimedCtrl.Start(ctx, i)
382-
})
423+
if err != nil {
424+
cancel()
425+
return err
383426
}
384-
defaultConsumerInf.Start(ctx.Done())
385427

428+
logger.Info("creating claim reconciler", "gvr", claimGVR, "key", claimKey)
429+
430+
// Start the informers and controllers in a goroutine
386431
go func() {
387-
// to not block the main thread
388-
consumerSynced := defaultConsumerInf.WaitForCacheSync(ctx.Done())
389-
logger.V(2).Info("Synced informers", "consumer", slices.Collect(maps.Keys(consumerSynced)))
432+
defaultConsumerInf.Start(ctxWithCancel.Done())
390433

391-
for _, ctrl := range claimControllers {
392-
go ctrl(ctx, 1)
393-
}
434+
// Wait for consumer informers to sync
435+
consumerSynced := defaultConsumerInf.WaitForCacheSync(ctxWithCancel.Done())
436+
logger.V(2).Info("Synced consumer informers", "consumer", slices.Collect(maps.Keys(consumerSynced)), "key", claimKey)
437+
438+
// Start provider informer and wait for sync
439+
providerInf.Start(ctxWithCancel)
440+
providerSynced := providerInf.WaitForCacheSync(ctxWithCancel.Done())
441+
logger.V(2).Info("Synced provider informers", "provider", slices.Collect(maps.Keys(providerSynced)), "key", claimKey)
442+
443+
// Start the claimed resources controller
444+
claimedCtrl.Start(ctxWithCancel, 1)
394445
}()
395446

447+
// Store the controller context for tracking
448+
r.syncStore.Set(claimKey, contextstore.SyncContext{
449+
Generation: binding.Generation,
450+
Cancel: cancel,
451+
})
452+
396453
return nil
397454
}
398455

0 commit comments

Comments
 (0)