...

Source file src/sigs.k8s.io/controller-runtime/pkg/controller/controller_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/controller

     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  })
   381  

View as plain text