Skip to content

Commit 64ecb97

Browse files
committed
Scope controllers for a logical cluster
1 parent 23cd6a1 commit 64ecb97

File tree

10 files changed

+70
-30
lines changed

10 files changed

+70
-30
lines changed

pkg/builder/controller.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ import (
3636
"sigs.k8s.io/controller-runtime/pkg/predicate"
3737
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3838
"sigs.k8s.io/controller-runtime/pkg/source"
39+
"sigs.k8s.io/logical-cluster"
3940
)
4041

4142
// Supporting mocking out functions for testing.
42-
var newController = controller.New
43+
var newController = controller.NewUnmanaged
4344
var getGvk = apiutil.GVKForObject
4445

4546
// project represents other forms that the we can use to
@@ -60,6 +61,7 @@ type Builder struct {
6061
watchesInput []WatchesInput
6162
mgr manager.Manager
6263
cluster cluster.Cluster
64+
logicalName logical.Name
6365
globalPredicates []predicate.Predicate
6466
ctrl controller.Controller
6567
ctrlOptions controller.Options
@@ -69,6 +71,7 @@ type Builder struct {
6971
func (blder *Builder) clone() *Builder {
7072
clone := *blder
7173
clone.cluster = nil
74+
clone.logicalName = ""
7275
clone.ctrl = nil
7376
return &clone
7477
}
@@ -244,9 +247,10 @@ func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, erro
244247
return nil, blder.forInput.err
245248
}
246249

247-
if err := blder.mgr.AddRunnableBuilder(func(cl cluster.Cluster) (manager.Runnable, error) {
250+
if err := blder.mgr.AddLogicalRunnableBuilder(func(name logical.Name, cl cluster.Cluster) (manager.Runnable, error) {
248251
cloned := blder.clone()
249252
cloned.cluster = cl
253+
cloned.logicalName = name
250254
if err := cloned.do(r); err != nil {
251255
return nil, err
252256
}
@@ -351,6 +355,14 @@ func (blder *Builder) doWatch() error {
351355
return err
352356
}
353357
srckind.Type = typeForSrc
358+
} else if !ok {
359+
// If we're building a logical controller, raw watches are not allowed
360+
// given that the cache cannot be validated to be coming from the same cluter.
361+
// In the future, we could consider allowing this by satisfying a new interface
362+
// that sets and uses the cluster.
363+
if blder.logicalName != "" {
364+
return fmt.Errorf("when using a logical adapter, custom raw watches %T are not allowed", w.src)
365+
}
354366
}
355367

356368
if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil {
@@ -378,6 +390,10 @@ func (blder *Builder) doController(r reconcile.Reconciler) error {
378390
ctrlOptions.Reconciler = r
379391
}
380392

393+
if blder.logicalName != "" {
394+
ctrlOptions.LogicalCluster = blder.logicalName
395+
}
396+
381397
// Retrieve the GVK from the object we're reconciling
382398
// to prepopulate logger information, and to optionally generate a default name.
383399
var gvk schema.GroupVersionKind

pkg/builder/controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (l *testLogger) WithName(name string) logr.LogSink {
7979

8080
var _ = Describe("application", func() {
8181
BeforeEach(func() {
82-
newController = controller.New
82+
newController = controller.NewUnmanaged
8383
})
8484

8585
noop := reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) {
@@ -623,7 +623,7 @@ var _ = Describe("application", func() {
623623
},
624624
},
625625
}
626-
cluster1, err := mgr.LogicalClusterGetter()("cluster1")
626+
cluster1, err := mgr.GetCluster("cluster1")
627627
Expect(err).NotTo(HaveOccurred())
628628
Expect(cluster1.GetClient().Create(ctx, dep)).To(Succeed())
629629

pkg/cluster/cluster.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import (
3838
intrec "sigs.k8s.io/controller-runtime/pkg/internal/recorder"
3939
)
4040

41-
// LogicalGetterFunc is a function that returns a client for a given logical cluster name.
41+
// LogicalGetterFunc is a function that returns a cluster for a given logical cluster name.
4242
type LogicalGetterFunc func(logical.Name) (Cluster, error)
4343

4444
// Cluster provides various methods to interact with a cluster.

pkg/controller/controller.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
3434
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3535
"sigs.k8s.io/controller-runtime/pkg/source"
36+
"sigs.k8s.io/logical-cluster"
3637
)
3738

3839
// Options are the arguments for creating a new Controller.
@@ -50,6 +51,10 @@ type Options struct {
5051
// LogConstructor is used to construct a logger used for this controller and passed
5152
// to each reconciliation via the context field.
5253
LogConstructor func(request *reconcile.Request) logr.Logger
54+
55+
// LogicalCluster is populated when the controller was created for a logical cluster.
56+
// This is used to determine if watch events without a logical.Name should be ignored.
57+
LogicalCluster logical.Name
5358
}
5459

5560
// Controller implements a Kubernetes API. A Controller manages a work queue fed reconcile.Requests
@@ -76,9 +81,21 @@ type Controller interface {
7681
GetLogger() logr.Logger
7782
}
7883

79-
// New returns a new Controller based on the Manager, the caller is responsible
80-
// for adding the controller to the manager as a Runnable.
84+
// New returns a new Controller registered with the Manager. The Manager will ensure that shared Caches have
85+
// been synced before the Controller is Started.
8186
func New(name string, mgr manager.Manager, options Options) (Controller, error) {
87+
c, err := NewUnmanaged(name, mgr, options)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
// Add the controller as a Manager components
93+
return c, mgr.Add(c)
94+
}
95+
96+
// NewUnmanaged returns a new Controller based on the Manager, the caller is responsible
97+
// for adding the controller to the manager as a Runnable.
98+
func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller, error) {
8299
if options.Reconciler == nil {
83100
return nil, fmt.Errorf("must specify Reconciler")
84101
}
@@ -121,7 +138,8 @@ func New(name string, mgr manager.Manager, options Options) (Controller, error)
121138

122139
// Create controller with dependencies set
123140
return &controller.Controller{
124-
Do: options.Reconciler,
141+
Cluster: options.LogicalCluster,
142+
Do: options.Reconciler,
125143
MakeQueue: func() workqueue.RateLimitingInterface {
126144
return workqueue.NewNamedRateLimitingQueue(options.RateLimiter, name)
127145
},

pkg/controller/controller_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ var _ = Describe("controller.Controller", func() {
7575
It("should not leak goroutines when stopped", func() {
7676
currentGRs := goleak.IgnoreCurrent()
7777

78-
ctx, cancel := context.WithCancel(context.Background())
7978
watchChan := make(chan event.GenericEvent, 1)
8079
watch := &source.Channel{Source: watchChan}
8180
watchChan <- event.GenericEvent{Object: &corev1.Pod{}}
@@ -102,20 +101,21 @@ var _ = Describe("controller.Controller", func() {
102101
Expect(c.Watch(watch, &handler.EnqueueRequestForObject{})).To(Succeed())
103102
Expect(err).NotTo(HaveOccurred())
104103

104+
ctx, cancel := context.WithCancel(context.Background())
105105
go func() {
106106
defer GinkgoRecover()
107107
Expect(m.Start(ctx)).To(Succeed())
108108
close(controllerFinished)
109109
}()
110110

111-
<-reconcileStarted
111+
Eventually(reconcileStarted).Should(BeClosed())
112112
cancel()
113-
<-controllerFinished
113+
Eventually(controllerFinished).Should(BeClosed())
114114

115115
// force-close keep-alive connections. These'll time anyway (after
116116
// like 30s or so) but force it to speed up the tests.
117117
clientTransport.CloseIdleConnections()
118-
Eventually(func() error { return goleak.Find(currentGRs) }).Should(Succeed())
118+
Eventually(func() error { return goleak.Find(currentGRs) }, 10*time.Second).Should(Succeed())
119119
})
120120

121121
It("should not create goroutines if never started", func() {

pkg/internal/controller/controller.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type Controller struct {
4242
// Name is used to uniquely identify a Controller in tracing, logging and monitoring. Name is required.
4343
Name string
4444

45+
// Cluster is the logical cluster that this controller is running against.
46+
// +optional
47+
Cluster logical.Name
48+
4549
// MaxConcurrentReconciles is the maximum number of concurrent Reconciles which can be run. Defaults to 1.
4650
MaxConcurrentReconciles int
4751

@@ -317,7 +321,10 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
317321
ctx = addReconcileID(ctx, reconcileID)
318322

319323
// Set the Cluster on the request if it is set on the context.
320-
req.Cluster = logical.FromContext(ctx)
324+
if req.Cluster != c.Cluster {
325+
panic(fmt.Sprintf("controller was setup for logical cluster %q, got a request for a cluster %q, not allowed!", c.Cluster, req.Cluster))
326+
}
327+
req.Cluster = c.Cluster
321328

322329
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
323330
// resource to be synced.

pkg/internal/testing/process/process_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ var _ = Describe("Start method", func() {
5050
HealthCheck: HealthCheck{
5151
URL: getServerURL(server),
5252
},
53+
StopTimeout: 2 * time.Second,
5354
}
54-
processState.Path = "bash"
5555
processState.Args = simpleBashScript
5656

5757
})

pkg/manager/internal.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const (
6464

6565
var _ Runnable = &controllerManager{}
6666
var _ Manager = &controllerManager{}
67+
var _ cluster.LogicalGetterFunc = (&controllerManager{}).GetCluster
6768

6869
type logicalCluster struct {
6970
cluster.Cluster
@@ -81,7 +82,7 @@ type controllerManager struct {
8182
errChan chan error
8283
runnables *runnables
8384

84-
runnableBuilders []func(cl cluster.Cluster) (Runnable, error)
85+
runnableBuilders []func(logical.Name, cluster.Cluster) (Runnable, error)
8586

8687
// defaultCluster holds a variety of methods to interact with a defaultCluster. Required.
8788
defaultCluster cluster.Cluster
@@ -256,6 +257,10 @@ func (cm *controllerManager) AddReadyzCheck(name string, check healthz.Checker)
256257
return nil
257258
}
258259

260+
func (cm *controllerManager) GetCluster(name logical.Name) (cluster.Cluster, error) {
261+
return cm.getLogicalCluster(name)
262+
}
263+
259264
func (cm *controllerManager) GetHTTPClient() *http.Client {
260265
return cm.defaultCluster.GetHTTPClient()
261266
}
@@ -292,12 +297,6 @@ func (cm *controllerManager) GetAPIReader() client.Reader {
292297
return cm.defaultCluster.GetAPIReader()
293298
}
294299

295-
func (cm *controllerManager) LogicalClusterGetter() cluster.LogicalGetterFunc {
296-
return func(name logical.Name) (cluster.Cluster, error) {
297-
return cm.getLogicalCluster(name)
298-
}
299-
}
300-
301300
func (cm *controllerManager) syncClusterAwareRunnables() {
302301
cm.Lock()
303302
defer cm.Unlock()
@@ -319,7 +318,7 @@ func (cm *controllerManager) syncClusterAwareRunnables() {
319318
}
320319

321320
// Build the runnable.
322-
runnable, err := build(cluster)
321+
runnable, err := build(name, cluster)
323322
if err != nil {
324323
cluster.runnableBuilds = append(cluster.runnableBuilds, err)
325324
cm.logger.Error(err, "failed to build cluster aware runnable, won't retry", "clusterName", name)
@@ -410,7 +409,7 @@ func (cm *controllerManager) removeLogicalCluster(name logical.Name) error {
410409
return nil
411410
}
412411

413-
func (cm *controllerManager) AddRunnableBuilder(fn func(cl cluster.Cluster) (Runnable, error)) error {
412+
func (cm *controllerManager) AddLogicalRunnableBuilder(fn func(name logical.Name, cl cluster.Cluster) (Runnable, error)) error {
414413
cm.Lock()
415414
defer cm.Unlock()
416415
cm.runnableBuilders = append(cm.runnableBuilders, fn)

pkg/manager/manager.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ type Manager interface {
5353
// Cluster holds a variety of methods to interact with a cluster.
5454
cluster.Cluster
5555

56-
// LogicalClientGetter can be set on reconcilers to retrieve a Cluster from a logical.Name.
57-
LogicalClusterGetter() cluster.LogicalGetterFunc
58-
5956
// Add will set requested dependencies on the component, and cause the component to be
6057
// started when Start is called.
6158
// Depending on if a Runnable implements LeaderElectionRunnable interface, a Runnable can be run in either
@@ -67,9 +64,9 @@ type Manager interface {
6764
// election was configured.
6865
Elected() <-chan struct{}
6966

70-
// AddRunnableBuilder adds a controller builder to the manager, which is used to build
67+
// AddLogicalRunnableBuilder adds a controller builder to the manager, which is used to build
7168
// controllers for a given cluster. This is useful when the Manager is running against many logical clusters.
72-
AddRunnableBuilder(func(cluster.Cluster) (Runnable, error)) error
69+
AddLogicalRunnableBuilder(func(logical.Name, cluster.Cluster) (Runnable, error)) error
7370

7471
// AddMetricsExtraHandler adds an extra handler served on path to the http server that serves metrics.
7572
// Might be useful to register some diagnostic endpoints e.g. pprof. Note that these endpoints meant to be
@@ -92,6 +89,9 @@ type Manager interface {
9289
// lock was lost.
9390
Start(ctx context.Context) error
9491

92+
// GetCluster retrieves a Cluster from a given logical name.
93+
GetCluster(logical.Name) (cluster.Cluster, error)
94+
9595
// GetWebhookServer returns a webhook.Server
9696
GetWebhookServer() *webhook.Server
9797

pkg/manager/manager_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,7 +1691,7 @@ var _ = Describe("manger.Manager", func() {
16911691

16921692
var built atomic.Bool
16931693
var started atomic.Bool
1694-
err = m.AddRunnableBuilder(func(cl cluster.Cluster) (Runnable, error) {
1694+
err = m.AddLogicalRunnableBuilder(func(name logical.Name, cl cluster.Cluster) (Runnable, error) {
16951695
built.Store(true)
16961696
return RunnableFunc(func(ctx context.Context) error {
16971697
Expect(logical.FromContext(ctx)).To(Equal(logical.Name("test-cluster")))
@@ -1735,7 +1735,7 @@ var _ = Describe("manger.Manager", func() {
17351735
var started atomic.Int64
17361736
var completed atomic.Int64
17371737
removedCh := make(chan struct{})
1738-
err = m.AddRunnableBuilder(func(cl cluster.Cluster) (Runnable, error) {
1738+
err = m.AddLogicalRunnableBuilder(func(name logical.Name, cl cluster.Cluster) (Runnable, error) {
17391739
built.Add(1)
17401740
return RunnableFunc(func(ctx context.Context) error {
17411741
defer completed.Add(1)

0 commit comments

Comments
 (0)