1
16
17 package controller_test
18
19 import (
20 "context"
21 "time"
22
23 . "github.com/onsi/ginkgo/v2"
24 . "github.com/onsi/gomega"
25 "go.uber.org/goleak"
26 corev1 "k8s.io/api/core/v1"
27 "k8s.io/client-go/util/workqueue"
28 "k8s.io/utils/ptr"
29
30 "sigs.k8s.io/controller-runtime/pkg/config"
31 "sigs.k8s.io/controller-runtime/pkg/controller"
32 "sigs.k8s.io/controller-runtime/pkg/event"
33 "sigs.k8s.io/controller-runtime/pkg/handler"
34 internalcontroller "sigs.k8s.io/controller-runtime/pkg/internal/controller"
35 "sigs.k8s.io/controller-runtime/pkg/manager"
36 "sigs.k8s.io/controller-runtime/pkg/ratelimiter"
37 "sigs.k8s.io/controller-runtime/pkg/reconcile"
38 "sigs.k8s.io/controller-runtime/pkg/source"
39 )
40
41 var _ = Describe("controller.Controller", func() {
42 rec := reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) {
43 return reconcile.Result{}, nil
44 })
45
46 Describe("New", func() {
47 It("should return an error if Name is not Specified", func() {
48 m, err := manager.New(cfg, manager.Options{})
49 Expect(err).NotTo(HaveOccurred())
50 c, err := controller.New("", m, controller.Options{Reconciler: rec})
51 Expect(c).To(BeNil())
52 Expect(err.Error()).To(ContainSubstring("must specify Name for Controller"))
53 })
54
55 It("should return an error if Reconciler is not Specified", func() {
56 m, err := manager.New(cfg, manager.Options{})
57 Expect(err).NotTo(HaveOccurred())
58
59 c, err := controller.New("foo", m, controller.Options{})
60 Expect(c).To(BeNil())
61 Expect(err.Error()).To(ContainSubstring("must specify Reconciler"))
62 })
63
64 It("should not return an error if two controllers are registered with different names", func() {
65 m, err := manager.New(cfg, manager.Options{})
66 Expect(err).NotTo(HaveOccurred())
67
68 c1, err := controller.New("c1", m, controller.Options{Reconciler: rec})
69 Expect(err).NotTo(HaveOccurred())
70 Expect(c1).ToNot(BeNil())
71
72 c2, err := controller.New("c2", m, controller.Options{Reconciler: rec})
73 Expect(err).NotTo(HaveOccurred())
74 Expect(c2).ToNot(BeNil())
75 })
76
77 It("should not leak goroutines when stopped", func() {
78 currentGRs := goleak.IgnoreCurrent()
79
80 ctx, cancel := context.WithCancel(context.Background())
81 watchChan := make(chan event.GenericEvent, 1)
82 watch := source.Channel(watchChan, &handler.EnqueueRequestForObject{})
83 watchChan <- event.GenericEvent{Object: &corev1.Pod{}}
84
85 reconcileStarted := make(chan struct{})
86 controllerFinished := make(chan struct{})
87 rec := reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) {
88 defer GinkgoRecover()
89 close(reconcileStarted)
90
91
92 time.Sleep(50 * time.Millisecond)
93
94
95
96 Expect(controllerFinished).NotTo(BeClosed())
97 return reconcile.Result{}, nil
98 })
99
100 m, err := manager.New(cfg, manager.Options{})
101 Expect(err).NotTo(HaveOccurred())
102
103 c, err := controller.New("new-controller", m, controller.Options{Reconciler: rec})
104 Expect(c.Watch(watch)).To(Succeed())
105 Expect(err).NotTo(HaveOccurred())
106
107 go func() {
108 defer GinkgoRecover()
109 Expect(m.Start(ctx)).To(Succeed())
110 close(controllerFinished)
111 }()
112
113 <-reconcileStarted
114 cancel()
115 <-controllerFinished
116
117
118
119 clientTransport.CloseIdleConnections()
120 Eventually(func() error { return goleak.Find(currentGRs) }).Should(Succeed())
121 })
122
123 It("should not create goroutines if never started", func() {
124 currentGRs := goleak.IgnoreCurrent()
125
126 m, err := manager.New(cfg, manager.Options{})
127 Expect(err).NotTo(HaveOccurred())
128
129 _, err = controller.New("new-controller", m, controller.Options{Reconciler: rec})
130 Expect(err).NotTo(HaveOccurred())
131
132
133
134 clientTransport.CloseIdleConnections()
135 Eventually(func() error { return goleak.Find(currentGRs) }).Should(Succeed())
136 })
137
138 It("should default RateLimiter and NewQueue if not specified", func() {
139 m, err := manager.New(cfg, manager.Options{})
140 Expect(err).NotTo(HaveOccurred())
141
142 c, err := controller.New("new-controller", m, controller.Options{
143 Reconciler: reconcile.Func(nil),
144 })
145 Expect(err).NotTo(HaveOccurred())
146
147 ctrl, ok := c.(*internalcontroller.Controller)
148 Expect(ok).To(BeTrue())
149
150 Expect(ctrl.RateLimiter).NotTo(BeNil())
151 Expect(ctrl.NewQueue).NotTo(BeNil())
152 })
153
154 It("should not override RateLimiter and NewQueue if specified", func() {
155 m, err := manager.New(cfg, manager.Options{})
156 Expect(err).NotTo(HaveOccurred())
157
158 customRateLimiter := workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second)
159 customNewQueueCalled := false
160 customNewQueue := func(controllerName string, rateLimiter ratelimiter.RateLimiter) workqueue.RateLimitingInterface {
161 customNewQueueCalled = true
162 return nil
163 }
164
165 c, err := controller.New("new-controller", m, controller.Options{
166 Reconciler: reconcile.Func(nil),
167 RateLimiter: customRateLimiter,
168 NewQueue: customNewQueue,
169 })
170 Expect(err).NotTo(HaveOccurred())
171
172 ctrl, ok := c.(*internalcontroller.Controller)
173 Expect(ok).To(BeTrue())
174
175 Expect(ctrl.RateLimiter).To(BeIdenticalTo(customRateLimiter))
176 ctrl.NewQueue("controller1", nil)
177 Expect(customNewQueueCalled).To(BeTrue(), "Expected customNewQueue to be called")
178 })
179
180 It("should default RecoverPanic from the manager", func() {
181 m, err := manager.New(cfg, manager.Options{Controller: config.Controller{RecoverPanic: ptr.To(true)}})
182 Expect(err).NotTo(HaveOccurred())
183
184 c, err := controller.New("new-controller", m, controller.Options{
185 Reconciler: reconcile.Func(nil),
186 })
187 Expect(err).NotTo(HaveOccurred())
188
189 ctrl, ok := c.(*internalcontroller.Controller)
190 Expect(ok).To(BeTrue())
191
192 Expect(ctrl.RecoverPanic).NotTo(BeNil())
193 Expect(*ctrl.RecoverPanic).To(BeTrue())
194 })
195
196 It("should not override RecoverPanic on the controller", func() {
197 m, err := manager.New(cfg, manager.Options{Controller: config.Controller{RecoverPanic: ptr.To(true)}})
198 Expect(err).NotTo(HaveOccurred())
199
200 c, err := controller.New("new-controller", m, controller.Options{
201 RecoverPanic: ptr.To(false),
202 Reconciler: reconcile.Func(nil),
203 })
204 Expect(err).NotTo(HaveOccurred())
205
206 ctrl, ok := c.(*internalcontroller.Controller)
207 Expect(ok).To(BeTrue())
208
209 Expect(ctrl.RecoverPanic).NotTo(BeNil())
210 Expect(*ctrl.RecoverPanic).To(BeFalse())
211 })
212
213 It("should default NeedLeaderElection from the manager", func() {
214 m, err := manager.New(cfg, manager.Options{Controller: config.Controller{NeedLeaderElection: ptr.To(true)}})
215 Expect(err).NotTo(HaveOccurred())
216
217 c, err := controller.New("new-controller", m, controller.Options{
218 Reconciler: reconcile.Func(nil),
219 })
220 Expect(err).NotTo(HaveOccurred())
221
222 ctrl, ok := c.(*internalcontroller.Controller)
223 Expect(ok).To(BeTrue())
224
225 Expect(ctrl.NeedLeaderElection()).To(BeTrue())
226 })
227
228 It("should not override NeedLeaderElection on the controller", func() {
229 m, err := manager.New(cfg, manager.Options{Controller: config.Controller{NeedLeaderElection: ptr.To(true)}})
230 Expect(err).NotTo(HaveOccurred())
231
232 c, err := controller.New("new-controller", m, controller.Options{
233 NeedLeaderElection: ptr.To(false),
234 Reconciler: reconcile.Func(nil),
235 })
236 Expect(err).NotTo(HaveOccurred())
237
238 ctrl, ok := c.(*internalcontroller.Controller)
239 Expect(ok).To(BeTrue())
240
241 Expect(ctrl.NeedLeaderElection()).To(BeFalse())
242 })
243
244 It("Should default MaxConcurrentReconciles from the manager if set", func() {
245 m, err := manager.New(cfg, manager.Options{Controller: config.Controller{MaxConcurrentReconciles: 5}})
246 Expect(err).NotTo(HaveOccurred())
247
248 c, err := controller.New("new-controller", m, controller.Options{
249 Reconciler: reconcile.Func(nil),
250 })
251 Expect(err).NotTo(HaveOccurred())
252
253 ctrl, ok := c.(*internalcontroller.Controller)
254 Expect(ok).To(BeTrue())
255
256 Expect(ctrl.MaxConcurrentReconciles).To(BeEquivalentTo(5))
257 })
258
259 It("Should default MaxConcurrentReconciles to 1 if unset", func() {
260 m, err := manager.New(cfg, manager.Options{})
261 Expect(err).NotTo(HaveOccurred())
262
263 c, err := controller.New("new-controller", m, controller.Options{
264 Reconciler: reconcile.Func(nil),
265 })
266 Expect(err).NotTo(HaveOccurred())
267
268 ctrl, ok := c.(*internalcontroller.Controller)
269 Expect(ok).To(BeTrue())
270
271 Expect(ctrl.MaxConcurrentReconciles).To(BeEquivalentTo(1))
272 })
273
274 It("Should leave MaxConcurrentReconciles if set", func() {
275 m, err := manager.New(cfg, manager.Options{})
276 Expect(err).NotTo(HaveOccurred())
277
278 c, err := controller.New("new-controller", m, controller.Options{
279 Reconciler: reconcile.Func(nil),
280 MaxConcurrentReconciles: 5,
281 })
282 Expect(err).NotTo(HaveOccurred())
283
284 ctrl, ok := c.(*internalcontroller.Controller)
285 Expect(ok).To(BeTrue())
286
287 Expect(ctrl.MaxConcurrentReconciles).To(BeEquivalentTo(5))
288 })
289
290 It("Should default CacheSyncTimeout from the manager if set", func() {
291 m, err := manager.New(cfg, manager.Options{Controller: config.Controller{CacheSyncTimeout: 5}})
292 Expect(err).NotTo(HaveOccurred())
293
294 c, err := controller.New("new-controller", m, controller.Options{
295 Reconciler: reconcile.Func(nil),
296 })
297 Expect(err).NotTo(HaveOccurred())
298
299 ctrl, ok := c.(*internalcontroller.Controller)
300 Expect(ok).To(BeTrue())
301
302 Expect(ctrl.CacheSyncTimeout).To(BeEquivalentTo(5))
303 })
304
305 It("Should default CacheSyncTimeout to 2 minutes if unset", func() {
306 m, err := manager.New(cfg, manager.Options{})
307 Expect(err).NotTo(HaveOccurred())
308
309 c, err := controller.New("new-controller", m, controller.Options{
310 Reconciler: reconcile.Func(nil),
311 })
312 Expect(err).NotTo(HaveOccurred())
313
314 ctrl, ok := c.(*internalcontroller.Controller)
315 Expect(ok).To(BeTrue())
316
317 Expect(ctrl.CacheSyncTimeout).To(BeEquivalentTo(2 * time.Minute))
318 })
319
320 It("Should leave CacheSyncTimeout if set", func() {
321 m, err := manager.New(cfg, manager.Options{})
322 Expect(err).NotTo(HaveOccurred())
323
324 c, err := controller.New("new-controller", m, controller.Options{
325 Reconciler: reconcile.Func(nil),
326 CacheSyncTimeout: 5,
327 })
328 Expect(err).NotTo(HaveOccurred())
329
330 ctrl, ok := c.(*internalcontroller.Controller)
331 Expect(ok).To(BeTrue())
332
333 Expect(ctrl.CacheSyncTimeout).To(BeEquivalentTo(5))
334 })
335
336 It("should default NeedLeaderElection on the controller to true", func() {
337 m, err := manager.New(cfg, manager.Options{})
338 Expect(err).NotTo(HaveOccurred())
339
340 c, err := controller.New("new-controller", m, controller.Options{
341 Reconciler: rec,
342 })
343 Expect(err).NotTo(HaveOccurred())
344
345 ctrl, ok := c.(*internalcontroller.Controller)
346 Expect(ok).To(BeTrue())
347
348 Expect(ctrl.NeedLeaderElection()).To(BeTrue())
349 })
350
351 It("should allow for setting leaderElected to false", func() {
352 m, err := manager.New(cfg, manager.Options{})
353 Expect(err).NotTo(HaveOccurred())
354
355 c, err := controller.New("new-controller", m, controller.Options{
356 NeedLeaderElection: ptr.To(false),
357 Reconciler: rec,
358 })
359 Expect(err).NotTo(HaveOccurred())
360
361 ctrl, ok := c.(*internalcontroller.Controller)
362 Expect(ok).To(BeTrue())
363
364 Expect(ctrl.NeedLeaderElection()).To(BeFalse())
365 })
366
367 It("should implement manager.LeaderElectionRunnable", func() {
368 m, err := manager.New(cfg, manager.Options{})
369 Expect(err).NotTo(HaveOccurred())
370
371 c, err := controller.New("new-controller", m, controller.Options{
372 Reconciler: rec,
373 })
374 Expect(err).NotTo(HaveOccurred())
375
376 _, ok := c.(manager.LeaderElectionRunnable)
377 Expect(ok).To(BeTrue())
378 })
379 })
380 })
381
View as plain text