sigs.k8s.io/controller-runtime@v0.18.2/pkg/controller/controller_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // Make sure reconciliation takes a moment and is not quicker than the controllers 91 // shutdown. 92 time.Sleep(50 * time.Millisecond) 93 // Explicitly test this on top of the leakdetection, as the latter uses Eventually 94 // so might succeed even when the controller does not wait for all reconciliations 95 // to finish. 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 // force-close keep-alive connections. These'll time anyway (after 118 // like 30s or so) but force it to speed up the tests. 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 // force-close keep-alive connections. These'll time anyway (after 133 // like 30s or so) but force it to speed up the tests. 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 })