Skip to content
This repository was archived by the owner on Apr 17, 2025. It is now read-only.

Commit c566204

Browse files
authored
Merge pull request #180 from adrianludwin/enqueue
Decouple reconcilers from each other
2 parents 459ab1d + 75a2052 commit c566204

File tree

15 files changed

+324
-347
lines changed

15 files changed

+324
-347
lines changed

cmd/manager/main.go

+6-11
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ func startControllers(mgr ctrl.Manager, certsReady chan struct{}) {
289289
// certs are all in place.
290290
setupLog.Info("Waiting for certificate generation to complete")
291291
<-certsReady
292+
setupLog.Info("Certs ready")
292293

293294
if testLog {
294295
stats.StartLoggingActivity()
@@ -298,18 +299,12 @@ func startControllers(mgr ctrl.Manager, certsReady chan struct{}) {
298299
// other components.
299300
f := forest.NewForest()
300301

301-
// Create all validating and mutating admission controllers.
302-
if !noWebhooks {
303-
setupLog.Info("Registering validating webhook (won't work when running locally; use --no-webhooks)")
304-
setup.CreateWebhooks(mgr, f)
305-
}
306-
307-
// Create all reconciling controllers
308-
setupLog.Info("Creating controllers", "maxReconciles", maxReconciles)
309-
if err := setup.CreateReconcilers(mgr, f, maxReconciles, webhooksOnly, false); err != nil {
310-
setupLog.Error(err, "cannot create controllers")
311-
os.Exit(1)
302+
opts := setup.Options{
303+
NoWebhooks: noWebhooks,
304+
MaxReconciles: maxReconciles,
305+
ReadOnly: webhooksOnly,
312306
}
307+
setup.Create(setupLog, mgr, f, opts)
313308

314309
setupLog.Info("All controllers started; setup complete")
315310
}

internal/anchor/reconciler.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ type Reconciler struct {
4747

4848
Forest *forest.Forest
4949

50-
// Affected is a channel of event.GenericEvent (see "Watching Channels" in
50+
// affected is a channel of event.GenericEvent (see "Watching Channels" in
5151
// https://book-v1.book.kubebuilder.io/beyond_basics/controller_watches.html) that is used to
5252
// enqueue additional objects that need updating.
53-
Affected chan event.GenericEvent
53+
affected chan event.GenericEvent
5454

5555
// ReadOnly disables writebacks
5656
ReadOnly bool
@@ -319,16 +319,22 @@ func (r *Reconciler) updateState(log logr.Logger, inst *api.SubnamespaceAnchor,
319319
}
320320
}
321321

322-
// Enqueue enqueues a subnamespace anchor for later reconciliation. This occurs in a goroutine
323-
// so the caller doesn't block; since the reconciler is never garbage-collected, this is safe.
324-
func (r *Reconciler) Enqueue(log logr.Logger, nm, pnm, reason string) {
322+
// OnChangeNamespace enqueues a subnamespace anchor for later reconciliation. This occurs in a
323+
// goroutine so the caller doesn't block; since the reconciler is never garbage-collected, this is
324+
// safe.
325+
func (r *Reconciler) OnChangeNamespace(log logr.Logger, ns *forest.Namespace) {
326+
if ns == nil || !ns.IsSub {
327+
return
328+
}
329+
nm := ns.Name()
330+
pnm := ns.Parent().Name()
325331
go func() {
326332
// The watch handler doesn't care about anything except the metadata.
327333
inst := &api.SubnamespaceAnchor{}
328334
inst.ObjectMeta.Name = nm
329335
inst.ObjectMeta.Namespace = pnm
330-
log.V(1).Info("Enqueuing for reconciliation", "affected", pnm+"/"+nm, "reason", reason)
331-
r.Affected <- event.GenericEvent{Object: inst}
336+
log.V(1).Info("Enqueuing for reconciliation", "affected", pnm+"/"+nm)
337+
r.affected <- event.GenericEvent{Object: inst}
332338
}()
333339
}
334340

@@ -423,6 +429,8 @@ func (r *Reconciler) deleteNamespace(ctx context.Context, log logr.Logger, inst
423429
}
424430

425431
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
432+
r.affected = make(chan event.GenericEvent)
433+
426434
// Maps an subnamespace to its anchor in the parent namespace.
427435
nsMapFn := func(obj client.Object) []reconcile.Request {
428436
if obj.GetAnnotations()[api.SubnamespaceOf] == "" {
@@ -437,7 +445,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
437445
}
438446
return ctrl.NewControllerManagedBy(mgr).
439447
For(&api.SubnamespaceAnchor{}).
440-
Watches(&source.Channel{Source: r.Affected}, &handler.EnqueueRequestForObject{}).
448+
Watches(&source.Channel{Source: r.affected}, &handler.EnqueueRequestForObject{}).
441449
Watches(&source.Kind{Type: &corev1.Namespace{}}, handler.EnqueueRequestsFromMapFunc(nsMapFn)).
442450
Complete(r)
443451
}

internal/forest/forest.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ type Forest struct {
3030
// We can also move the lock out of the forest and pass it to all reconcilers that need the lock.
3131
// In that way, we don't need to put the list in the forest.
3232
types []TypeSyncer
33+
34+
// nsListeners is a list of listeners
35+
listeners []NamespaceListener
3336
}
3437

3538
type namedNamespaces map[string]*Namespace
3639

3740
// TypeSyncer syncs objects of a specific type. Reconcilers implement the interface so that they can be
3841
// called by the HierarchyReconciler if the hierarchy changes.
3942
type TypeSyncer interface {
40-
// SyncNamespace syncs objects of a namespace for a specific type.
41-
SyncNamespace(context.Context, logr.Logger, string) error
42-
4343
// Provides the GVK that is handled by the reconciler who implements the interface.
4444
GetGVK() schema.GroupVersionKind
4545

@@ -55,6 +55,12 @@ type TypeSyncer interface {
5555
GetNumPropagatedObjects() int
5656
}
5757

58+
// NamespaceListener has methods that get called whenever a namespace changes.
59+
type NamespaceListener interface {
60+
// OnChangeNamespace is called whenever a namespace changes.
61+
OnChangeNamespace(logr.Logger, *Namespace)
62+
}
63+
5864
func NewForest() *Forest {
5965
return &Forest{
6066
namespaces: namedNamespaces{},
@@ -148,3 +154,13 @@ func (f *Forest) GetTypeSyncers() []TypeSyncer {
148154
copy(types, f.types)
149155
return types
150156
}
157+
158+
func (f *Forest) AddListener(l NamespaceListener) {
159+
f.listeners = append(f.listeners, l)
160+
}
161+
162+
func (f *Forest) OnChangeNamespace(log logr.Logger, ns *Namespace) {
163+
for _, l := range f.listeners {
164+
l.OnChangeNamespace(log, ns)
165+
}
166+
}

internal/forest/namespace.go

+3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ func (ns *Namespace) Name() string {
7171

7272
// Parent returns a pointer to the parent namespace.
7373
func (ns *Namespace) Parent() *Namespace {
74+
if ns == nil {
75+
return nil
76+
}
7477
return ns.parent
7578
}
7679

internal/forest/namespaceobjects.go

+16-15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package forest
33
import (
44
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
55
"k8s.io/apimachinery/pkg/runtime/schema"
6+
"k8s.io/apimachinery/pkg/types"
67
)
78

89
// SetSourceObject updates or creates the source object in forest.namespace.
@@ -35,11 +36,11 @@ func (ns *Namespace) DeleteSourceObject(gvk schema.GroupVersionKind, nm string)
3536
}
3637
}
3738

38-
// GetSourceObjects returns all source objects in the namespace.
39-
func (ns *Namespace) GetSourceObjects(gvk schema.GroupVersionKind) []*unstructured.Unstructured {
40-
o := []*unstructured.Unstructured{}
39+
// GetSourceNames returns all source objects in the namespace.
40+
func (ns *Namespace) GetSourceNames(gvk schema.GroupVersionKind) []types.NamespacedName {
41+
o := []types.NamespacedName{}
4142
for _, obj := range ns.sourceObjects[gvk] {
42-
o = append(o, obj)
43+
o = append(o, types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()})
4344
}
4445
return o
4546
}
@@ -50,10 +51,10 @@ func (ns *Namespace) GetNumSourceObjects(gvk schema.GroupVersionKind) int {
5051
return len(ns.sourceObjects[gvk])
5152
}
5253

53-
// GetAncestorSourceObjects returns all source objects with the specified name
54+
// GetAncestorSourceNames returns all source objects with the specified name
5455
// in the ancestors (including itself) from top down. If the name is not
5556
// specified, all the source objects in the ancestors will be returned.
56-
func (ns *Namespace) GetAncestorSourceObjects(gvk schema.GroupVersionKind, name string) []*unstructured.Unstructured {
57+
func (ns *Namespace) GetAncestorSourceNames(gvk schema.GroupVersionKind, name string) []types.NamespacedName {
5758
// The namespace could be nil when we use this function on "ns.Parent()" to
5859
// get the source objects of the ancestors excluding itself without caring if
5960
// the "ns.Parent()" is nil.
@@ -62,22 +63,22 @@ func (ns *Namespace) GetAncestorSourceObjects(gvk schema.GroupVersionKind, name
6263
}
6364

6465
// Get the source objects in the ancestors from top down.
65-
objs := []*unstructured.Unstructured{}
66-
ans := ns.AncestryNames()
67-
for _, n := range ans {
68-
nsObjs := ns.forest.Get(n).GetSourceObjects(gvk)
66+
allNNMs := []types.NamespacedName{}
67+
ancs := ns.AncestryNames()
68+
for _, anc := range ancs {
69+
nnms := ns.forest.Get(anc).GetSourceNames(gvk)
6970
if name == "" {
7071
// Get all the source objects if the name is not specified.
71-
objs = append(objs, ns.forest.Get(n).GetSourceObjects(gvk)...)
72+
allNNMs = append(allNNMs, nnms...)
7273
} else {
7374
// If a name is specified, return the matching objects.
74-
for _, o := range nsObjs {
75-
if o.GetName() == name {
76-
objs = append(objs, o)
75+
for _, o := range nnms {
76+
if o.Name == name {
77+
allNNMs = append(allNNMs, o)
7778
}
7879
}
7980
}
8081
}
8182

82-
return objs
83+
return allNNMs
8384
}

0 commit comments

Comments
 (0)