sigs.k8s.io/controller-runtime@v0.18.2/pkg/manager/manager_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 manager 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "net" 25 "net/http" 26 "path" 27 "sync" 28 "sync/atomic" 29 "time" 30 31 "github.com/go-logr/logr" 32 . "github.com/onsi/ginkgo/v2" 33 . "github.com/onsi/gomega" 34 "github.com/prometheus/client_golang/prometheus" 35 "go.uber.org/goleak" 36 coordinationv1 "k8s.io/api/coordination/v1" 37 corev1 "k8s.io/api/core/v1" 38 "k8s.io/apimachinery/pkg/api/meta" 39 "k8s.io/apimachinery/pkg/runtime" 40 "k8s.io/apimachinery/pkg/runtime/schema" 41 "k8s.io/client-go/rest" 42 "k8s.io/client-go/tools/leaderelection/resourcelock" 43 "sigs.k8s.io/controller-runtime/pkg/cache" 44 "sigs.k8s.io/controller-runtime/pkg/cache/informertest" 45 "sigs.k8s.io/controller-runtime/pkg/client" 46 "sigs.k8s.io/controller-runtime/pkg/cluster" 47 intrec "sigs.k8s.io/controller-runtime/pkg/internal/recorder" 48 "sigs.k8s.io/controller-runtime/pkg/leaderelection" 49 fakeleaderelection "sigs.k8s.io/controller-runtime/pkg/leaderelection/fake" 50 "sigs.k8s.io/controller-runtime/pkg/metrics" 51 metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 52 "sigs.k8s.io/controller-runtime/pkg/recorder" 53 "sigs.k8s.io/controller-runtime/pkg/webhook" 54 ) 55 56 var _ = Describe("manger.Manager", func() { 57 Describe("New", func() { 58 It("should return an error if there is no Config", func() { 59 m, err := New(nil, Options{}) 60 Expect(m).To(BeNil()) 61 Expect(err.Error()).To(ContainSubstring("must specify Config")) 62 63 }) 64 65 It("should return an error if it can't create a RestMapper", func() { 66 expected := fmt.Errorf("expected error: RestMapper") 67 m, err := New(cfg, Options{ 68 MapperProvider: func(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) { return nil, expected }, 69 }) 70 Expect(m).To(BeNil()) 71 Expect(err).To(Equal(expected)) 72 73 }) 74 75 It("should return an error it can't create a client.Client", func() { 76 m, err := New(cfg, Options{ 77 NewClient: func(config *rest.Config, options client.Options) (client.Client, error) { 78 return nil, errors.New("expected error") 79 }, 80 }) 81 Expect(m).To(BeNil()) 82 Expect(err).To(HaveOccurred()) 83 Expect(err.Error()).To(ContainSubstring("expected error")) 84 }) 85 86 It("should return an error it can't create a cache.Cache", func() { 87 m, err := New(cfg, Options{ 88 NewCache: func(config *rest.Config, opts cache.Options) (cache.Cache, error) { 89 return nil, fmt.Errorf("expected error") 90 }, 91 }) 92 Expect(m).To(BeNil()) 93 Expect(err).To(HaveOccurred()) 94 Expect(err.Error()).To(ContainSubstring("expected error")) 95 }) 96 97 It("should create a client defined in by the new client function", func() { 98 m, err := New(cfg, Options{ 99 NewClient: func(config *rest.Config, options client.Options) (client.Client, error) { 100 return nil, nil 101 }, 102 }) 103 Expect(m).ToNot(BeNil()) 104 Expect(err).ToNot(HaveOccurred()) 105 Expect(m.GetClient()).To(BeNil()) 106 }) 107 108 It("should return an error it can't create a recorder.Provider", func() { 109 m, err := New(cfg, Options{ 110 newRecorderProvider: func(_ *rest.Config, _ *http.Client, _ *runtime.Scheme, _ logr.Logger, _ intrec.EventBroadcasterProducer) (*intrec.Provider, error) { 111 return nil, fmt.Errorf("expected error") 112 }, 113 }) 114 Expect(m).To(BeNil()) 115 Expect(err).To(HaveOccurred()) 116 Expect(err.Error()).To(ContainSubstring("expected error")) 117 }) 118 119 It("should lazily initialize a webhook server if needed", func() { 120 By("creating a manager with options") 121 m, err := New(cfg, Options{WebhookServer: webhook.NewServer(webhook.Options{Port: 9440, Host: "foo.com"})}) 122 Expect(err).NotTo(HaveOccurred()) 123 Expect(m).NotTo(BeNil()) 124 125 By("checking options are passed to the webhook server") 126 svr := m.GetWebhookServer() 127 Expect(svr).NotTo(BeNil()) 128 Expect(svr.(*webhook.DefaultServer).Options.Port).To(Equal(9440)) 129 Expect(svr.(*webhook.DefaultServer).Options.Host).To(Equal("foo.com")) 130 }) 131 132 It("should not initialize a webhook server if Options.WebhookServer is set", func() { 133 By("creating a manager with options") 134 srv := webhook.NewServer(webhook.Options{Port: 9440}) 135 m, err := New(cfg, Options{WebhookServer: srv}) 136 Expect(err).NotTo(HaveOccurred()) 137 Expect(m).NotTo(BeNil()) 138 139 By("checking the server contains the Port set on the webhook server and not passed to Options") 140 svr := m.GetWebhookServer() 141 Expect(svr).NotTo(BeNil()) 142 Expect(svr).To(Equal(srv)) 143 Expect(svr.(*webhook.DefaultServer).Options.Port).To(Equal(9440)) 144 }) 145 146 It("should allow passing a custom webhook.Server implementation", func() { 147 type customWebhook struct { 148 webhook.Server 149 } 150 m, err := New(cfg, Options{WebhookServer: customWebhook{}}) 151 Expect(err).NotTo(HaveOccurred()) 152 Expect(m).NotTo(BeNil()) 153 154 svr := m.GetWebhookServer() 155 Expect(svr).NotTo(BeNil()) 156 157 _, isCustomWebhook := svr.(customWebhook) 158 Expect(isCustomWebhook).To(BeTrue()) 159 }) 160 161 Context("with leader election enabled", func() { 162 It("should only cancel the leader election after all runnables are done", func() { 163 m, err := New(cfg, Options{ 164 LeaderElection: true, 165 LeaderElectionNamespace: "default", 166 LeaderElectionID: "test-leader-election-id-2", 167 HealthProbeBindAddress: "0", 168 Metrics: metricsserver.Options{BindAddress: "0"}, 169 PprofBindAddress: "0", 170 }) 171 Expect(err).ToNot(HaveOccurred()) 172 gvkcorev1 := schema.GroupVersionKind{Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, Kind: "ConfigMap"} 173 gvkcoordinationv1 := schema.GroupVersionKind{Group: coordinationv1.SchemeGroupVersion.Group, Version: coordinationv1.SchemeGroupVersion.Version, Kind: "Lease"} 174 Expect(m.GetScheme().Recognizes(gvkcorev1)).To(BeTrue()) 175 Expect(m.GetScheme().Recognizes(gvkcoordinationv1)).To(BeTrue()) 176 runnableDone := make(chan struct{}) 177 slowRunnable := RunnableFunc(func(ctx context.Context) error { 178 <-ctx.Done() 179 time.Sleep(100 * time.Millisecond) 180 close(runnableDone) 181 return nil 182 }) 183 Expect(m.Add(slowRunnable)).To(Succeed()) 184 185 cm := m.(*controllerManager) 186 cm.gracefulShutdownTimeout = time.Second 187 leaderElectionDone := make(chan struct{}) 188 cm.onStoppedLeading = func() { 189 close(leaderElectionDone) 190 } 191 192 ctx, cancel := context.WithCancel(context.Background()) 193 mgrDone := make(chan struct{}) 194 go func() { 195 defer GinkgoRecover() 196 Expect(m.Start(ctx)).To(Succeed()) 197 close(mgrDone) 198 }() 199 <-cm.Elected() 200 cancel() 201 select { 202 case <-leaderElectionDone: 203 Expect(errors.New("leader election was cancelled before runnables were done")).ToNot(HaveOccurred()) 204 case <-runnableDone: 205 // Success 206 } 207 // Don't leak routines 208 <-mgrDone 209 210 }) 211 It("should disable gracefulShutdown when stopping to lead", func() { 212 m, err := New(cfg, Options{ 213 LeaderElection: true, 214 LeaderElectionNamespace: "default", 215 LeaderElectionID: "test-leader-election-id-3", 216 HealthProbeBindAddress: "0", 217 Metrics: metricsserver.Options{BindAddress: "0"}, 218 PprofBindAddress: "0", 219 }) 220 Expect(err).ToNot(HaveOccurred()) 221 222 ctx, cancel := context.WithCancel(context.Background()) 223 defer cancel() 224 mgrDone := make(chan struct{}) 225 go func() { 226 defer GinkgoRecover() 227 err := m.Start(ctx) 228 Expect(err).To(HaveOccurred()) 229 Expect(err.Error()).To(ContainSubstring("leader election lost")) 230 close(mgrDone) 231 }() 232 cm := m.(*controllerManager) 233 <-cm.elected 234 235 cm.leaderElectionCancel() 236 <-mgrDone 237 238 Expect(cm.gracefulShutdownTimeout.Nanoseconds()).To(Equal(int64(0))) 239 }) 240 241 It("should prevent leader election when shutting down a non-elected manager", func() { 242 var rl resourcelock.Interface 243 m1, err := New(cfg, Options{ 244 LeaderElection: true, 245 LeaderElectionNamespace: "default", 246 LeaderElectionID: "test-leader-election-id", 247 newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { 248 var err error 249 rl, err = leaderelection.NewResourceLock(config, recorderProvider, options) 250 return rl, err 251 }, 252 HealthProbeBindAddress: "0", 253 Metrics: metricsserver.Options{BindAddress: "0"}, 254 PprofBindAddress: "0", 255 }) 256 Expect(err).ToNot(HaveOccurred()) 257 Expect(m1).ToNot(BeNil()) 258 Expect(rl.Describe()).To(Equal("default/test-leader-election-id")) 259 260 m1cm, ok := m1.(*controllerManager) 261 Expect(ok).To(BeTrue()) 262 m1cm.onStoppedLeading = func() {} 263 264 m2, err := New(cfg, Options{ 265 LeaderElection: true, 266 LeaderElectionNamespace: "default", 267 LeaderElectionID: "test-leader-election-id", 268 newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { 269 var err error 270 rl, err = leaderelection.NewResourceLock(config, recorderProvider, options) 271 return rl, err 272 }, 273 HealthProbeBindAddress: "0", 274 Metrics: metricsserver.Options{BindAddress: "0"}, 275 PprofBindAddress: "0", 276 }) 277 Expect(err).ToNot(HaveOccurred()) 278 Expect(m2).ToNot(BeNil()) 279 Expect(rl.Describe()).To(Equal("default/test-leader-election-id")) 280 281 m1done := make(chan struct{}) 282 Expect(m1.Add(RunnableFunc(func(ctx context.Context) error { 283 defer GinkgoRecover() 284 close(m1done) 285 return nil 286 }))).To(Succeed()) 287 288 ctx1, cancel1 := context.WithCancel(context.Background()) 289 defer cancel1() 290 go func() { 291 defer GinkgoRecover() 292 Expect(m1.Elected()).ShouldNot(BeClosed()) 293 Expect(m1.Start(ctx1)).NotTo(HaveOccurred()) 294 }() 295 <-m1.Elected() 296 <-m1done 297 298 electionRunnable := &needElection{make(chan struct{})} 299 300 Expect(m2.Add(electionRunnable)).To(Succeed()) 301 302 ctx2, cancel2 := context.WithCancel(context.Background()) 303 m2done := make(chan struct{}) 304 go func() { 305 defer GinkgoRecover() 306 Expect(m2.Start(ctx2)).NotTo(HaveOccurred()) 307 close(m2done) 308 }() 309 Consistently(m2.Elected()).ShouldNot(Receive()) 310 311 go func() { 312 defer GinkgoRecover() 313 Consistently(electionRunnable.ch).ShouldNot(Receive()) 314 }() 315 cancel2() 316 <-m2done 317 }) 318 319 It("should default ID to controller-runtime if ID is not set", func() { 320 var rl resourcelock.Interface 321 m1, err := New(cfg, Options{ 322 LeaderElection: true, 323 LeaderElectionNamespace: "default", 324 LeaderElectionID: "test-leader-election-id", 325 newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { 326 var err error 327 rl, err = leaderelection.NewResourceLock(config, recorderProvider, options) 328 return rl, err 329 }, 330 HealthProbeBindAddress: "0", 331 Metrics: metricsserver.Options{BindAddress: "0"}, 332 PprofBindAddress: "0", 333 }) 334 Expect(err).ToNot(HaveOccurred()) 335 Expect(m1).ToNot(BeNil()) 336 Expect(rl.Describe()).To(Equal("default/test-leader-election-id")) 337 338 m1cm, ok := m1.(*controllerManager) 339 Expect(ok).To(BeTrue()) 340 m1cm.onStoppedLeading = func() {} 341 342 m2, err := New(cfg, Options{ 343 LeaderElection: true, 344 LeaderElectionNamespace: "default", 345 LeaderElectionID: "test-leader-election-id", 346 newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { 347 var err error 348 rl, err = leaderelection.NewResourceLock(config, recorderProvider, options) 349 return rl, err 350 }, 351 HealthProbeBindAddress: "0", 352 Metrics: metricsserver.Options{BindAddress: "0"}, 353 PprofBindAddress: "0", 354 }) 355 Expect(err).ToNot(HaveOccurred()) 356 Expect(m2).ToNot(BeNil()) 357 Expect(rl.Describe()).To(Equal("default/test-leader-election-id")) 358 359 m2cm, ok := m2.(*controllerManager) 360 Expect(ok).To(BeTrue()) 361 m2cm.onStoppedLeading = func() {} 362 363 c1 := make(chan struct{}) 364 Expect(m1.Add(RunnableFunc(func(ctx context.Context) error { 365 defer GinkgoRecover() 366 close(c1) 367 return nil 368 }))).To(Succeed()) 369 370 ctx1, cancel1 := context.WithCancel(context.Background()) 371 defer cancel1() 372 go func() { 373 defer GinkgoRecover() 374 Expect(m1.Elected()).ShouldNot(BeClosed()) 375 Expect(m1.Start(ctx1)).NotTo(HaveOccurred()) 376 }() 377 <-m1.Elected() 378 <-c1 379 380 c2 := make(chan struct{}) 381 Expect(m2.Add(RunnableFunc(func(context.Context) error { 382 defer GinkgoRecover() 383 close(c2) 384 return nil 385 }))).To(Succeed()) 386 387 ctx2, cancel := context.WithCancel(context.Background()) 388 m2done := make(chan struct{}) 389 go func() { 390 defer GinkgoRecover() 391 Expect(m2.Start(ctx2)).NotTo(HaveOccurred()) 392 close(m2done) 393 }() 394 Consistently(m2.Elected()).ShouldNot(Receive()) 395 396 Consistently(c2).ShouldNot(Receive()) 397 cancel() 398 <-m2done 399 }) 400 401 It("should return an error if it can't create a ResourceLock", func() { 402 m, err := New(cfg, Options{ 403 newResourceLock: func(_ *rest.Config, _ recorder.Provider, _ leaderelection.Options) (resourcelock.Interface, error) { 404 return nil, fmt.Errorf("expected error") 405 }, 406 }) 407 Expect(m).To(BeNil()) 408 Expect(err).To(MatchError(ContainSubstring("expected error"))) 409 }) 410 411 It("should return an error if namespace not set and not running in cluster", func() { 412 m, err := New(cfg, Options{LeaderElection: true, LeaderElectionID: "controller-runtime"}) 413 Expect(m).To(BeNil()) 414 Expect(err).To(HaveOccurred()) 415 Expect(err.Error()).To(ContainSubstring("unable to find leader election namespace: not running in-cluster, please specify LeaderElectionNamespace")) 416 }) 417 418 // We must keep this default until we are sure all controller-runtime users have upgraded from the original default 419 // ConfigMap lock to a controller-runtime version that has this new default. Many users of controller-runtime skip 420 // versions, so we should be extremely conservative here. 421 It("should default to LeasesResourceLock", func() { 422 m, err := New(cfg, Options{LeaderElection: true, LeaderElectionID: "controller-runtime", LeaderElectionNamespace: "my-ns"}) 423 Expect(m).ToNot(BeNil()) 424 Expect(err).ToNot(HaveOccurred()) 425 cm, ok := m.(*controllerManager) 426 Expect(ok).To(BeTrue()) 427 _, isLeaseLock := cm.resourceLock.(*resourcelock.LeaseLock) 428 Expect(isLeaseLock).To(BeTrue()) 429 430 }) 431 It("should use the specified ResourceLock", func() { 432 m, err := New(cfg, Options{ 433 LeaderElection: true, 434 LeaderElectionResourceLock: resourcelock.LeasesResourceLock, 435 LeaderElectionID: "controller-runtime", 436 LeaderElectionNamespace: "my-ns", 437 }) 438 Expect(m).ToNot(BeNil()) 439 Expect(err).ToNot(HaveOccurred()) 440 cm, ok := m.(*controllerManager) 441 Expect(ok).To(BeTrue()) 442 _, isLeaseLock := cm.resourceLock.(*resourcelock.LeaseLock) 443 Expect(isLeaseLock).To(BeTrue()) 444 }) 445 It("should release lease if ElectionReleaseOnCancel is true", func() { 446 var rl resourcelock.Interface 447 m, err := New(cfg, Options{ 448 LeaderElection: true, 449 LeaderElectionResourceLock: resourcelock.LeasesResourceLock, 450 LeaderElectionID: "controller-runtime", 451 LeaderElectionNamespace: "my-ns", 452 LeaderElectionReleaseOnCancel: true, 453 newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { 454 var err error 455 rl, err = fakeleaderelection.NewResourceLock(config, recorderProvider, options) 456 return rl, err 457 }, 458 }) 459 Expect(err).ToNot(HaveOccurred()) 460 461 ctx, cancel := context.WithCancel(context.Background()) 462 doneCh := make(chan struct{}) 463 go func() { 464 defer GinkgoRecover() 465 defer close(doneCh) 466 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 467 }() 468 <-m.(*controllerManager).elected 469 cancel() 470 <-doneCh 471 472 ctx, cancel = context.WithCancel(context.Background()) 473 defer cancel() 474 record, _, err := rl.Get(ctx) 475 Expect(err).ToNot(HaveOccurred()) 476 Expect(record.HolderIdentity).To(BeEmpty()) 477 }) 478 When("using a custom LeaderElectionResourceLockInterface", func() { 479 It("should use the custom LeaderElectionResourceLockInterface", func() { 480 rl, err := fakeleaderelection.NewResourceLock(nil, nil, leaderelection.Options{}) 481 Expect(err).NotTo(HaveOccurred()) 482 483 m, err := New(cfg, Options{ 484 LeaderElection: true, 485 LeaderElectionResourceLockInterface: rl, 486 newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { 487 return nil, fmt.Errorf("this should not be called") 488 }, 489 }) 490 Expect(m).ToNot(BeNil()) 491 Expect(err).ToNot(HaveOccurred()) 492 cm, ok := m.(*controllerManager) 493 Expect(ok).To(BeTrue()) 494 Expect(cm.resourceLock).To(Equal(rl)) 495 }) 496 }) 497 }) 498 499 It("should create a metrics server if a valid address is provided", func() { 500 var srv metricsserver.Server 501 m, err := New(cfg, Options{ 502 Metrics: metricsserver.Options{BindAddress: ":0"}, 503 newMetricsServer: func(options metricsserver.Options, config *rest.Config, httpClient *http.Client) (metricsserver.Server, error) { 504 var err error 505 srv, err = metricsserver.NewServer(options, config, httpClient) 506 return srv, err 507 }, 508 }) 509 Expect(m).ToNot(BeNil()) 510 Expect(err).ToNot(HaveOccurred()) 511 Expect(srv).ToNot(BeNil()) 512 513 // Triggering the metric server start here manually to test if it works. 514 // Usually this happens later during manager.Start(). 515 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 516 Expect(srv.Start(ctx)).To(Succeed()) 517 cancel() 518 }) 519 520 It("should create a metrics server if a valid address is provided and secure serving is enabled", func() { 521 var srv metricsserver.Server 522 m, err := New(cfg, Options{ 523 Metrics: metricsserver.Options{BindAddress: ":0", SecureServing: true}, 524 newMetricsServer: func(options metricsserver.Options, config *rest.Config, httpClient *http.Client) (metricsserver.Server, error) { 525 var err error 526 srv, err = metricsserver.NewServer(options, config, httpClient) 527 return srv, err 528 }, 529 }) 530 Expect(m).ToNot(BeNil()) 531 Expect(err).ToNot(HaveOccurred()) 532 Expect(srv).ToNot(BeNil()) 533 534 // Triggering the metric server start here manually to test if it works. 535 // Usually this happens later during manager.Start(). 536 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 537 Expect(srv.Start(ctx)).To(Succeed()) 538 cancel() 539 }) 540 541 It("should be able to create a manager with a cache that fails on missing informer", func() { 542 m, err := New(cfg, Options{ 543 Cache: cache.Options{ 544 ReaderFailOnMissingInformer: true, 545 }, 546 }) 547 Expect(m).ToNot(BeNil()) 548 Expect(err).ToNot(HaveOccurred()) 549 }) 550 551 It("should return an error if the metrics bind address is already in use", func() { 552 ln, err := net.Listen("tcp", ":0") //nolint:gosec 553 Expect(err).ShouldNot(HaveOccurred()) 554 555 var srv metricsserver.Server 556 m, err := New(cfg, Options{ 557 Metrics: metricsserver.Options{ 558 BindAddress: ln.Addr().String(), 559 }, 560 newMetricsServer: func(options metricsserver.Options, config *rest.Config, httpClient *http.Client) (metricsserver.Server, error) { 561 var err error 562 srv, err = metricsserver.NewServer(options, config, httpClient) 563 return srv, err 564 }, 565 }) 566 Expect(m).ToNot(BeNil()) 567 Expect(err).ToNot(HaveOccurred()) 568 569 // Triggering the metric server start here manually to test if it works. 570 // Usually this happens later during manager.Start(). 571 Expect(srv.Start(context.Background())).ToNot(Succeed()) 572 573 Expect(ln.Close()).To(Succeed()) 574 }) 575 576 It("should return an error if the metrics bind address is already in use and secure serving enabled", func() { 577 ln, err := net.Listen("tcp", ":0") //nolint:gosec 578 Expect(err).ShouldNot(HaveOccurred()) 579 580 var srv metricsserver.Server 581 m, err := New(cfg, Options{ 582 Metrics: metricsserver.Options{ 583 BindAddress: ln.Addr().String(), 584 SecureServing: true, 585 }, 586 newMetricsServer: func(options metricsserver.Options, config *rest.Config, httpClient *http.Client) (metricsserver.Server, error) { 587 var err error 588 srv, err = metricsserver.NewServer(options, config, httpClient) 589 return srv, err 590 }, 591 }) 592 Expect(m).ToNot(BeNil()) 593 Expect(err).ToNot(HaveOccurred()) 594 595 // Triggering the metric server start here manually to test if it works. 596 // Usually this happens later during manager.Start(). 597 Expect(srv.Start(context.Background())).ToNot(Succeed()) 598 599 Expect(ln.Close()).To(Succeed()) 600 }) 601 602 It("should create a listener for the health probes if a valid address is provided", func() { 603 var listener net.Listener 604 m, err := New(cfg, Options{ 605 HealthProbeBindAddress: ":0", 606 newHealthProbeListener: func(addr string) (net.Listener, error) { 607 var err error 608 listener, err = defaultHealthProbeListener(addr) 609 return listener, err 610 }, 611 }) 612 Expect(m).ToNot(BeNil()) 613 Expect(err).ToNot(HaveOccurred()) 614 Expect(listener).ToNot(BeNil()) 615 Expect(listener.Close()).ToNot(HaveOccurred()) 616 }) 617 618 It("should return an error if the health probes bind address is already in use", func() { 619 ln, err := defaultHealthProbeListener(":0") 620 Expect(err).ShouldNot(HaveOccurred()) 621 622 var listener net.Listener 623 m, err := New(cfg, Options{ 624 HealthProbeBindAddress: ln.Addr().String(), 625 newHealthProbeListener: func(addr string) (net.Listener, error) { 626 var err error 627 listener, err = defaultHealthProbeListener(addr) 628 return listener, err 629 }, 630 }) 631 Expect(m).To(BeNil()) 632 Expect(err).To(HaveOccurred()) 633 Expect(listener).To(BeNil()) 634 635 Expect(ln.Close()).ToNot(HaveOccurred()) 636 }) 637 }) 638 639 Describe("Start", func() { 640 var startSuite = func(options Options, callbacks ...func(Manager)) { 641 It("should Start each Component", func() { 642 m, err := New(cfg, options) 643 Expect(err).NotTo(HaveOccurred()) 644 for _, cb := range callbacks { 645 cb(m) 646 } 647 var wgRunnableStarted sync.WaitGroup 648 wgRunnableStarted.Add(2) 649 Expect(m.Add(RunnableFunc(func(context.Context) error { 650 defer GinkgoRecover() 651 wgRunnableStarted.Done() 652 return nil 653 }))).To(Succeed()) 654 655 Expect(m.Add(RunnableFunc(func(context.Context) error { 656 defer GinkgoRecover() 657 wgRunnableStarted.Done() 658 return nil 659 }))).To(Succeed()) 660 661 ctx, cancel := context.WithCancel(context.Background()) 662 defer cancel() 663 go func() { 664 defer GinkgoRecover() 665 Expect(m.Elected()).ShouldNot(BeClosed()) 666 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 667 }() 668 669 <-m.Elected() 670 wgRunnableStarted.Wait() 671 }) 672 673 It("should not manipulate the provided config", func() { 674 // strip WrapTransport, cause func values are PartialEq, not Eq -- 675 // specifically, for reflect.DeepEqual, for all functions F, 676 // F != nil implies F != F, which means no full equivalence relation. 677 cfg := rest.CopyConfig(cfg) 678 cfg.WrapTransport = nil 679 originalCfg := rest.CopyConfig(cfg) 680 // The options object is shared by multiple tests, copy it 681 // into our scope so we manipulate it for this testcase only 682 options := options 683 options.newResourceLock = nil 684 m, err := New(cfg, options) 685 Expect(err).NotTo(HaveOccurred()) 686 for _, cb := range callbacks { 687 cb(m) 688 } 689 Expect(m.GetConfig()).To(Equal(originalCfg)) 690 }) 691 692 It("should stop when context is cancelled", func() { 693 m, err := New(cfg, options) 694 Expect(err).NotTo(HaveOccurred()) 695 for _, cb := range callbacks { 696 cb(m) 697 } 698 ctx, cancel := context.WithCancel(context.Background()) 699 cancel() 700 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 701 }) 702 703 It("should return an error if it can't start the cache", func() { 704 m, err := New(cfg, options) 705 Expect(err).NotTo(HaveOccurred()) 706 for _, cb := range callbacks { 707 cb(m) 708 } 709 mgr, ok := m.(*controllerManager) 710 Expect(ok).To(BeTrue()) 711 Expect(mgr.Add( 712 &cacheProvider{cache: &informertest.FakeInformers{Error: fmt.Errorf("expected error")}}, 713 )).To(Succeed()) 714 715 ctx, cancel := context.WithCancel(context.Background()) 716 defer cancel() 717 Expect(m.Start(ctx)).To(MatchError(ContainSubstring("expected error"))) 718 }) 719 720 It("should start the cache before starting anything else", func() { 721 fakeCache := &startSignalingInformer{Cache: &informertest.FakeInformers{}} 722 options.NewCache = func(_ *rest.Config, _ cache.Options) (cache.Cache, error) { 723 return fakeCache, nil 724 } 725 m, err := New(cfg, options) 726 Expect(err).NotTo(HaveOccurred()) 727 for _, cb := range callbacks { 728 cb(m) 729 } 730 731 runnableWasStarted := make(chan struct{}) 732 runnable := RunnableFunc(func(ctx context.Context) error { 733 defer GinkgoRecover() 734 if !fakeCache.wasSynced { 735 return errors.New("runnable got started before cache was synced") 736 } 737 close(runnableWasStarted) 738 return nil 739 }) 740 Expect(m.Add(runnable)).To(Succeed()) 741 742 ctx, cancel := context.WithCancel(context.Background()) 743 defer cancel() 744 go func() { 745 defer GinkgoRecover() 746 Expect(m.Start(ctx)).ToNot(HaveOccurred()) 747 }() 748 749 <-runnableWasStarted 750 }) 751 752 It("should start additional clusters before anything else", func() { 753 fakeCache := &startSignalingInformer{Cache: &informertest.FakeInformers{}} 754 options.NewCache = func(_ *rest.Config, _ cache.Options) (cache.Cache, error) { 755 return fakeCache, nil 756 } 757 m, err := New(cfg, options) 758 Expect(err).NotTo(HaveOccurred()) 759 for _, cb := range callbacks { 760 cb(m) 761 } 762 763 additionalClusterCache := &startSignalingInformer{Cache: &informertest.FakeInformers{}} 764 additionalCluster, err := cluster.New(cfg, func(o *cluster.Options) { 765 o.NewCache = func(_ *rest.Config, _ cache.Options) (cache.Cache, error) { 766 return additionalClusterCache, nil 767 } 768 }) 769 Expect(err).NotTo(HaveOccurred()) 770 Expect(m.Add(additionalCluster)).NotTo(HaveOccurred()) 771 772 runnableWasStarted := make(chan struct{}) 773 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 774 defer GinkgoRecover() 775 if !fakeCache.wasSynced { 776 return errors.New("WaitForCacheSyncCalled wasn't called before Runnable got started") 777 } 778 if !additionalClusterCache.wasSynced { 779 return errors.New("the additional clusters WaitForCacheSync wasn't called before Runnable got started") 780 } 781 close(runnableWasStarted) 782 return nil 783 }))).To(Succeed()) 784 785 ctx, cancel := context.WithCancel(context.Background()) 786 defer cancel() 787 go func() { 788 defer GinkgoRecover() 789 Expect(m.Start(ctx)).ToNot(HaveOccurred()) 790 }() 791 792 <-runnableWasStarted 793 }) 794 795 It("should return an error if any Components fail to Start", func() { 796 m, err := New(cfg, options) 797 Expect(err).NotTo(HaveOccurred()) 798 for _, cb := range callbacks { 799 cb(m) 800 } 801 802 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 803 defer GinkgoRecover() 804 <-ctx.Done() 805 return nil 806 }))).To(Succeed()) 807 808 Expect(m.Add(RunnableFunc(func(context.Context) error { 809 defer GinkgoRecover() 810 return fmt.Errorf("expected error") 811 }))).To(Succeed()) 812 813 Expect(m.Add(RunnableFunc(func(context.Context) error { 814 defer GinkgoRecover() 815 return nil 816 }))).To(Succeed()) 817 818 defer GinkgoRecover() 819 ctx, cancel := context.WithCancel(context.Background()) 820 defer cancel() 821 err = m.Start(ctx) 822 Expect(err).To(HaveOccurred()) 823 Expect(err.Error()).To(Equal("expected error")) 824 }) 825 826 It("should start caches added after Manager has started", func() { 827 fakeCache := &startSignalingInformer{Cache: &informertest.FakeInformers{}} 828 options.NewCache = func(_ *rest.Config, _ cache.Options) (cache.Cache, error) { 829 return fakeCache, nil 830 } 831 m, err := New(cfg, options) 832 Expect(err).NotTo(HaveOccurred()) 833 for _, cb := range callbacks { 834 cb(m) 835 } 836 837 runnableWasStarted := make(chan struct{}) 838 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 839 defer GinkgoRecover() 840 if !fakeCache.wasSynced { 841 return errors.New("WaitForCacheSyncCalled wasn't called before Runnable got started") 842 } 843 close(runnableWasStarted) 844 return nil 845 }))).To(Succeed()) 846 847 ctx, cancel := context.WithCancel(context.Background()) 848 defer cancel() 849 go func() { 850 defer GinkgoRecover() 851 Expect(m.Start(ctx)).ToNot(HaveOccurred()) 852 }() 853 854 <-runnableWasStarted 855 856 additionalClusterCache := &startSignalingInformer{Cache: &informertest.FakeInformers{}} 857 fakeCluster := &startClusterAfterManager{informer: additionalClusterCache} 858 859 Expect(err).NotTo(HaveOccurred()) 860 Expect(m.Add(fakeCluster)).NotTo(HaveOccurred()) 861 862 Eventually(func() bool { 863 fakeCluster.informer.mu.Lock() 864 defer fakeCluster.informer.mu.Unlock() 865 return fakeCluster.informer.wasStarted && fakeCluster.informer.wasSynced 866 }).Should(BeTrue()) 867 }) 868 869 It("should wait for runnables to stop", func() { 870 m, err := New(cfg, options) 871 Expect(err).NotTo(HaveOccurred()) 872 for _, cb := range callbacks { 873 cb(m) 874 } 875 876 var lock sync.Mutex 877 var runnableDoneCount int64 878 runnableDoneFunc := func() { 879 lock.Lock() 880 defer lock.Unlock() 881 atomic.AddInt64(&runnableDoneCount, 1) 882 } 883 var wgRunnableRunning sync.WaitGroup 884 wgRunnableRunning.Add(2) 885 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 886 wgRunnableRunning.Done() 887 defer GinkgoRecover() 888 defer runnableDoneFunc() 889 <-ctx.Done() 890 return nil 891 }))).To(Succeed()) 892 893 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 894 wgRunnableRunning.Done() 895 defer GinkgoRecover() 896 defer runnableDoneFunc() 897 <-ctx.Done() 898 time.Sleep(300 * time.Millisecond) // slow closure simulation 899 return nil 900 }))).To(Succeed()) 901 902 defer GinkgoRecover() 903 ctx, cancel := context.WithCancel(context.Background()) 904 905 var wgManagerRunning sync.WaitGroup 906 wgManagerRunning.Add(1) 907 go func() { 908 defer GinkgoRecover() 909 defer wgManagerRunning.Done() 910 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 911 Eventually(func() int64 { 912 return atomic.LoadInt64(&runnableDoneCount) 913 }).Should(BeEquivalentTo(2)) 914 }() 915 wgRunnableRunning.Wait() 916 cancel() 917 918 wgManagerRunning.Wait() 919 }) 920 921 It("should return an error if any Components fail to Start and wait for runnables to stop", func() { 922 m, err := New(cfg, options) 923 Expect(err).NotTo(HaveOccurred()) 924 for _, cb := range callbacks { 925 cb(m) 926 } 927 defer GinkgoRecover() 928 var lock sync.Mutex 929 runnableDoneCount := 0 930 runnableDoneFunc := func() { 931 lock.Lock() 932 defer lock.Unlock() 933 runnableDoneCount++ 934 } 935 936 Expect(m.Add(RunnableFunc(func(context.Context) error { 937 defer GinkgoRecover() 938 defer runnableDoneFunc() 939 return fmt.Errorf("expected error") 940 }))).To(Succeed()) 941 942 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 943 defer GinkgoRecover() 944 defer runnableDoneFunc() 945 <-ctx.Done() 946 return nil 947 }))).To(Succeed()) 948 949 ctx, cancel := context.WithCancel(context.Background()) 950 defer cancel() 951 Expect(m.Start(ctx)).To(HaveOccurred()) 952 Expect(runnableDoneCount).To(Equal(2)) 953 }) 954 955 It("should refuse to add runnable if stop procedure is already engaged", func() { 956 m, err := New(cfg, options) 957 Expect(err).NotTo(HaveOccurred()) 958 for _, cb := range callbacks { 959 cb(m) 960 } 961 defer GinkgoRecover() 962 963 var wgRunnableRunning sync.WaitGroup 964 wgRunnableRunning.Add(1) 965 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 966 wgRunnableRunning.Done() 967 defer GinkgoRecover() 968 <-ctx.Done() 969 return nil 970 }))).To(Succeed()) 971 972 ctx, cancel := context.WithCancel(context.Background()) 973 go func() { 974 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 975 }() 976 wgRunnableRunning.Wait() 977 cancel() 978 time.Sleep(100 * time.Millisecond) // give some time for the stop chan closure to be caught by the manager 979 Expect(m.Add(RunnableFunc(func(context.Context) error { 980 defer GinkgoRecover() 981 return nil 982 }))).NotTo(Succeed()) 983 }) 984 985 It("should return both runnables and stop errors when both error", func() { 986 m, err := New(cfg, options) 987 Expect(err).NotTo(HaveOccurred()) 988 for _, cb := range callbacks { 989 cb(m) 990 } 991 m.(*controllerManager).gracefulShutdownTimeout = 1 * time.Nanosecond 992 Expect(m.Add(RunnableFunc(func(context.Context) error { 993 return runnableError{} 994 }))).To(Succeed()) 995 testDone := make(chan struct{}) 996 defer close(testDone) 997 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 998 <-ctx.Done() 999 timer := time.NewTimer(30 * time.Second) 1000 defer timer.Stop() 1001 select { 1002 case <-testDone: 1003 return nil 1004 case <-timer.C: 1005 return nil 1006 } 1007 }))).To(Succeed()) 1008 ctx, cancel := context.WithCancel(context.Background()) 1009 defer cancel() 1010 err = m.Start(ctx) 1011 Expect(err).To(HaveOccurred()) 1012 eMsg := "[not feeling like that, failed waiting for all runnables to end within grace period of 1ns: context deadline exceeded]" 1013 Expect(err.Error()).To(Equal(eMsg)) 1014 Expect(errors.Is(err, context.DeadlineExceeded)).To(BeTrue()) 1015 Expect(errors.Is(err, runnableError{})).To(BeTrue()) 1016 }) 1017 1018 It("should return only stop errors if runnables dont error", func() { 1019 m, err := New(cfg, options) 1020 Expect(err).NotTo(HaveOccurred()) 1021 for _, cb := range callbacks { 1022 cb(m) 1023 } 1024 m.(*controllerManager).gracefulShutdownTimeout = 1 * time.Nanosecond 1025 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1026 <-ctx.Done() 1027 return nil 1028 }))).To(Succeed()) 1029 testDone := make(chan struct{}) 1030 defer close(testDone) 1031 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1032 <-ctx.Done() 1033 timer := time.NewTimer(30 * time.Second) 1034 defer timer.Stop() 1035 select { 1036 case <-testDone: 1037 return nil 1038 case <-timer.C: 1039 return nil 1040 } 1041 }))).To(Succeed()) 1042 ctx, cancel := context.WithCancel(context.Background()) 1043 managerStopDone := make(chan struct{}) 1044 go func() { err = m.Start(ctx); close(managerStopDone) }() 1045 // Use the 'elected' channel to find out if startup was done, otherwise we stop 1046 // before we started the Runnable and see flakes, mostly in low-CPU envs like CI 1047 <-m.(*controllerManager).elected 1048 cancel() 1049 <-managerStopDone 1050 Expect(err).To(HaveOccurred()) 1051 Expect(err.Error()).To(Equal("failed waiting for all runnables to end within grace period of 1ns: context deadline exceeded")) 1052 Expect(errors.Is(err, context.DeadlineExceeded)).To(BeTrue()) 1053 Expect(errors.Is(err, runnableError{})).ToNot(BeTrue()) 1054 }) 1055 1056 It("should return only runnables error if stop doesn't error", func() { 1057 m, err := New(cfg, options) 1058 Expect(err).NotTo(HaveOccurred()) 1059 for _, cb := range callbacks { 1060 cb(m) 1061 } 1062 Expect(m.Add(RunnableFunc(func(context.Context) error { 1063 return runnableError{} 1064 }))).To(Succeed()) 1065 ctx, cancel := context.WithCancel(context.Background()) 1066 defer cancel() 1067 err = m.Start(ctx) 1068 Expect(err).To(HaveOccurred()) 1069 Expect(err.Error()).To(Equal("not feeling like that")) 1070 Expect(errors.Is(err, context.DeadlineExceeded)).ToNot(BeTrue()) 1071 Expect(errors.Is(err, runnableError{})).To(BeTrue()) 1072 }) 1073 1074 It("should not wait for runnables if gracefulShutdownTimeout is 0", func() { 1075 m, err := New(cfg, options) 1076 Expect(err).NotTo(HaveOccurred()) 1077 for _, cb := range callbacks { 1078 cb(m) 1079 } 1080 m.(*controllerManager).gracefulShutdownTimeout = time.Duration(0) 1081 1082 runnableStopped := make(chan struct{}) 1083 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1084 <-ctx.Done() 1085 time.Sleep(100 * time.Millisecond) 1086 close(runnableStopped) 1087 return nil 1088 }))).ToNot(HaveOccurred()) 1089 1090 ctx, cancel := context.WithCancel(context.Background()) 1091 managerStopDone := make(chan struct{}) 1092 go func() { 1093 defer GinkgoRecover() 1094 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1095 close(managerStopDone) 1096 }() 1097 <-m.Elected() 1098 cancel() 1099 1100 <-managerStopDone 1101 <-runnableStopped 1102 }) 1103 1104 It("should wait forever for runnables if gracefulShutdownTimeout is <0 (-1)", func() { 1105 m, err := New(cfg, options) 1106 Expect(err).NotTo(HaveOccurred()) 1107 for _, cb := range callbacks { 1108 cb(m) 1109 } 1110 m.(*controllerManager).gracefulShutdownTimeout = time.Duration(-1) 1111 1112 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1113 <-ctx.Done() 1114 time.Sleep(100 * time.Millisecond) 1115 return nil 1116 }))).ToNot(HaveOccurred()) 1117 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1118 <-ctx.Done() 1119 time.Sleep(200 * time.Millisecond) 1120 return nil 1121 }))).ToNot(HaveOccurred()) 1122 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1123 <-ctx.Done() 1124 time.Sleep(500 * time.Millisecond) 1125 return nil 1126 }))).ToNot(HaveOccurred()) 1127 Expect(m.Add(RunnableFunc(func(ctx context.Context) error { 1128 <-ctx.Done() 1129 time.Sleep(1500 * time.Millisecond) 1130 return nil 1131 }))).ToNot(HaveOccurred()) 1132 1133 ctx, cancel := context.WithCancel(context.Background()) 1134 managerStopDone := make(chan struct{}) 1135 go func() { 1136 defer GinkgoRecover() 1137 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1138 close(managerStopDone) 1139 }() 1140 <-m.Elected() 1141 cancel() 1142 1143 beforeDone := time.Now() 1144 <-managerStopDone 1145 Expect(time.Since(beforeDone)).To(BeNumerically(">=", 1500*time.Millisecond)) 1146 }) 1147 1148 } 1149 1150 Context("with defaults", func() { 1151 startSuite(Options{}) 1152 }) 1153 1154 Context("with leaderelection enabled", func() { 1155 startSuite( 1156 Options{ 1157 LeaderElection: true, 1158 LeaderElectionID: "controller-runtime", 1159 LeaderElectionNamespace: "default", 1160 newResourceLock: fakeleaderelection.NewResourceLock, 1161 }, 1162 func(m Manager) { 1163 cm, ok := m.(*controllerManager) 1164 Expect(ok).To(BeTrue()) 1165 cm.onStoppedLeading = func() {} 1166 }, 1167 ) 1168 }) 1169 1170 Context("should start serving metrics", func() { 1171 var srv metricsserver.Server 1172 var defaultServer metricsDefaultServer 1173 var opts Options 1174 1175 BeforeEach(func() { 1176 srv = nil 1177 opts = Options{ 1178 Metrics: metricsserver.Options{ 1179 BindAddress: ":0", 1180 }, 1181 newMetricsServer: func(options metricsserver.Options, config *rest.Config, httpClient *http.Client) (metricsserver.Server, error) { 1182 var err error 1183 srv, err = metricsserver.NewServer(options, config, httpClient) 1184 if srv != nil { 1185 defaultServer = srv.(metricsDefaultServer) 1186 } 1187 return srv, err 1188 }, 1189 } 1190 }) 1191 1192 It("should stop serving metrics when stop is called", func() { 1193 m, err := New(cfg, opts) 1194 Expect(err).NotTo(HaveOccurred()) 1195 1196 ctx, cancel := context.WithCancel(context.Background()) 1197 go func() { 1198 defer GinkgoRecover() 1199 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1200 }() 1201 <-m.Elected() 1202 // Note: Wait until metrics server has been started. A finished leader election 1203 // doesn't guarantee that the metrics server is up. 1204 Eventually(func() string { return defaultServer.GetBindAddr() }, 10*time.Second).ShouldNot(BeEmpty()) 1205 1206 // Check the metrics started 1207 endpoint := fmt.Sprintf("http://%s/metrics", defaultServer.GetBindAddr()) 1208 _, err = http.Get(endpoint) 1209 Expect(err).NotTo(HaveOccurred()) 1210 1211 // Shutdown the server 1212 cancel() 1213 1214 // Expect the metrics server to shutdown 1215 Eventually(func() error { 1216 _, err = http.Get(endpoint) 1217 return err 1218 }, 10*time.Second).ShouldNot(Succeed()) 1219 }) 1220 1221 It("should serve metrics endpoint", func() { 1222 m, err := New(cfg, opts) 1223 Expect(err).NotTo(HaveOccurred()) 1224 1225 ctx, cancel := context.WithCancel(context.Background()) 1226 defer cancel() 1227 go func() { 1228 defer GinkgoRecover() 1229 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1230 }() 1231 <-m.Elected() 1232 // Note: Wait until metrics server has been started. A finished leader election 1233 // doesn't guarantee that the metrics server is up. 1234 Eventually(func() string { return defaultServer.GetBindAddr() }, 10*time.Second).ShouldNot(BeEmpty()) 1235 1236 metricsEndpoint := fmt.Sprintf("http://%s/metrics", defaultServer.GetBindAddr()) 1237 resp, err := http.Get(metricsEndpoint) 1238 Expect(err).NotTo(HaveOccurred()) 1239 Expect(resp.StatusCode).To(Equal(200)) 1240 }) 1241 1242 It("should not serve anything other than metrics endpoint by default", func() { 1243 m, err := New(cfg, opts) 1244 Expect(err).NotTo(HaveOccurred()) 1245 1246 ctx, cancel := context.WithCancel(context.Background()) 1247 defer cancel() 1248 go func() { 1249 defer GinkgoRecover() 1250 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1251 }() 1252 <-m.Elected() 1253 // Note: Wait until metrics server has been started. A finished leader election 1254 // doesn't guarantee that the metrics server is up. 1255 Eventually(func() string { return defaultServer.GetBindAddr() }, 10*time.Second).ShouldNot(BeEmpty()) 1256 1257 endpoint := fmt.Sprintf("http://%s/should-not-exist", defaultServer.GetBindAddr()) 1258 resp, err := http.Get(endpoint) 1259 Expect(err).NotTo(HaveOccurred()) 1260 defer resp.Body.Close() 1261 Expect(resp.StatusCode).To(Equal(404)) 1262 }) 1263 1264 It("should serve metrics in its registry", func() { 1265 one := prometheus.NewCounter(prometheus.CounterOpts{ 1266 Name: "test_one", 1267 Help: "test metric for testing", 1268 }) 1269 one.Inc() 1270 err := metrics.Registry.Register(one) 1271 Expect(err).NotTo(HaveOccurred()) 1272 1273 m, err := New(cfg, opts) 1274 Expect(err).NotTo(HaveOccurred()) 1275 1276 ctx, cancel := context.WithCancel(context.Background()) 1277 defer cancel() 1278 go func() { 1279 defer GinkgoRecover() 1280 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1281 }() 1282 <-m.Elected() 1283 // Note: Wait until metrics server has been started. A finished leader election 1284 // doesn't guarantee that the metrics server is up. 1285 Eventually(func() string { return defaultServer.GetBindAddr() }, 10*time.Second).ShouldNot(BeEmpty()) 1286 1287 metricsEndpoint := fmt.Sprintf("http://%s/metrics", defaultServer.GetBindAddr()) 1288 resp, err := http.Get(metricsEndpoint) 1289 Expect(err).NotTo(HaveOccurred()) 1290 defer resp.Body.Close() 1291 Expect(resp.StatusCode).To(Equal(200)) 1292 1293 data, err := io.ReadAll(resp.Body) 1294 Expect(err).NotTo(HaveOccurred()) 1295 Expect(string(data)).To(ContainSubstring("%s\n%s\n%s\n", 1296 `# HELP test_one test metric for testing`, 1297 `# TYPE test_one counter`, 1298 `test_one 1`, 1299 )) 1300 1301 // Unregister will return false if the metric was never registered 1302 ok := metrics.Registry.Unregister(one) 1303 Expect(ok).To(BeTrue()) 1304 }) 1305 1306 It("should serve extra endpoints", func() { 1307 opts.Metrics.ExtraHandlers = map[string]http.Handler{ 1308 "/debug": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1309 _, _ = w.Write([]byte("Some debug info")) 1310 }), 1311 } 1312 m, err := New(cfg, opts) 1313 Expect(err).NotTo(HaveOccurred()) 1314 1315 // Should error when we add another extra endpoint on the already registered path. 1316 err = m.AddMetricsServerExtraHandler("/debug", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1317 _, _ = w.Write([]byte("Another debug info")) 1318 })) 1319 Expect(err).To(HaveOccurred()) 1320 1321 ctx, cancel := context.WithCancel(context.Background()) 1322 defer cancel() 1323 go func() { 1324 defer GinkgoRecover() 1325 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1326 }() 1327 <-m.Elected() 1328 // Note: Wait until metrics server has been started. A finished leader election 1329 // doesn't guarantee that the metrics server is up. 1330 Eventually(func() string { return defaultServer.GetBindAddr() }, 10*time.Second).ShouldNot(BeEmpty()) 1331 1332 endpoint := fmt.Sprintf("http://%s/debug", defaultServer.GetBindAddr()) 1333 resp, err := http.Get(endpoint) 1334 Expect(err).NotTo(HaveOccurred()) 1335 defer resp.Body.Close() 1336 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1337 1338 body, err := io.ReadAll(resp.Body) 1339 Expect(err).NotTo(HaveOccurred()) 1340 Expect(string(body)).To(Equal("Some debug info")) 1341 }) 1342 }) 1343 }) 1344 1345 Context("should start serving health probes", func() { 1346 var listener net.Listener 1347 var opts Options 1348 1349 BeforeEach(func() { 1350 listener = nil 1351 opts = Options{ 1352 newHealthProbeListener: func(addr string) (net.Listener, error) { 1353 var err error 1354 listener, err = defaultHealthProbeListener(addr) 1355 return listener, err 1356 }, 1357 } 1358 }) 1359 1360 AfterEach(func() { 1361 if listener != nil { 1362 listener.Close() 1363 } 1364 }) 1365 1366 It("should stop serving health probes when stop is called", func() { 1367 opts.HealthProbeBindAddress = ":0" 1368 m, err := New(cfg, opts) 1369 Expect(err).NotTo(HaveOccurred()) 1370 1371 ctx, cancel := context.WithCancel(context.Background()) 1372 go func() { 1373 defer GinkgoRecover() 1374 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1375 }() 1376 <-m.Elected() 1377 1378 // Check the health probes started 1379 endpoint := fmt.Sprintf("http://%s", listener.Addr().String()) 1380 _, err = http.Get(endpoint) 1381 Expect(err).NotTo(HaveOccurred()) 1382 1383 // Shutdown the server 1384 cancel() 1385 1386 // Expect the health probes server to shutdown 1387 Eventually(func() error { 1388 _, err = http.Get(endpoint) 1389 return err 1390 }, 10*time.Second).ShouldNot(Succeed()) 1391 }) 1392 1393 It("should serve readiness endpoint", func() { 1394 opts.HealthProbeBindAddress = ":0" 1395 m, err := New(cfg, opts) 1396 Expect(err).NotTo(HaveOccurred()) 1397 1398 res := fmt.Errorf("not ready yet") 1399 namedCheck := "check" 1400 err = m.AddReadyzCheck(namedCheck, func(_ *http.Request) error { return res }) 1401 Expect(err).NotTo(HaveOccurred()) 1402 1403 ctx, cancel := context.WithCancel(context.Background()) 1404 defer cancel() 1405 go func() { 1406 defer GinkgoRecover() 1407 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1408 }() 1409 <-m.Elected() 1410 1411 readinessEndpoint := fmt.Sprint("http://", listener.Addr().String(), defaultReadinessEndpoint) 1412 1413 // Controller is not ready 1414 resp, err := http.Get(readinessEndpoint) 1415 Expect(err).NotTo(HaveOccurred()) 1416 defer resp.Body.Close() 1417 Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError)) 1418 1419 // Controller is ready 1420 res = nil 1421 resp, err = http.Get(readinessEndpoint) 1422 Expect(err).NotTo(HaveOccurred()) 1423 defer resp.Body.Close() 1424 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1425 1426 // Check readiness path without trailing slash without redirect 1427 readinessEndpoint = fmt.Sprint("http://", listener.Addr().String(), defaultReadinessEndpoint) 1428 res = nil 1429 httpClient := http.Client{ 1430 CheckRedirect: func(req *http.Request, via []*http.Request) error { 1431 return http.ErrUseLastResponse // Do not follow redirect 1432 }, 1433 } 1434 resp, err = httpClient.Get(readinessEndpoint) 1435 Expect(err).NotTo(HaveOccurred()) 1436 defer resp.Body.Close() 1437 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1438 1439 // Check readiness path for individual check 1440 readinessEndpoint = fmt.Sprint("http://", listener.Addr().String(), path.Join(defaultReadinessEndpoint, namedCheck)) 1441 res = nil 1442 resp, err = http.Get(readinessEndpoint) 1443 Expect(err).NotTo(HaveOccurred()) 1444 defer resp.Body.Close() 1445 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1446 }) 1447 1448 It("should serve liveness endpoint", func() { 1449 opts.HealthProbeBindAddress = ":0" 1450 m, err := New(cfg, opts) 1451 Expect(err).NotTo(HaveOccurred()) 1452 1453 res := fmt.Errorf("not alive") 1454 namedCheck := "check" 1455 err = m.AddHealthzCheck(namedCheck, func(_ *http.Request) error { return res }) 1456 Expect(err).NotTo(HaveOccurred()) 1457 1458 ctx, cancel := context.WithCancel(context.Background()) 1459 defer cancel() 1460 go func() { 1461 defer GinkgoRecover() 1462 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1463 }() 1464 <-m.Elected() 1465 1466 livenessEndpoint := fmt.Sprint("http://", listener.Addr().String(), defaultLivenessEndpoint) 1467 1468 // Controller is not ready 1469 resp, err := http.Get(livenessEndpoint) 1470 Expect(err).NotTo(HaveOccurred()) 1471 defer resp.Body.Close() 1472 Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError)) 1473 1474 // Controller is ready 1475 res = nil 1476 resp, err = http.Get(livenessEndpoint) 1477 Expect(err).NotTo(HaveOccurred()) 1478 defer resp.Body.Close() 1479 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1480 1481 // Check liveness path without trailing slash without redirect 1482 livenessEndpoint = fmt.Sprint("http://", listener.Addr().String(), defaultLivenessEndpoint) 1483 res = nil 1484 httpClient := http.Client{ 1485 CheckRedirect: func(req *http.Request, via []*http.Request) error { 1486 return http.ErrUseLastResponse // Do not follow redirect 1487 }, 1488 } 1489 resp, err = httpClient.Get(livenessEndpoint) 1490 Expect(err).NotTo(HaveOccurred()) 1491 defer resp.Body.Close() 1492 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1493 1494 // Check readiness path for individual check 1495 livenessEndpoint = fmt.Sprint("http://", listener.Addr().String(), path.Join(defaultLivenessEndpoint, namedCheck)) 1496 res = nil 1497 resp, err = http.Get(livenessEndpoint) 1498 Expect(err).NotTo(HaveOccurred()) 1499 defer resp.Body.Close() 1500 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1501 }) 1502 }) 1503 1504 Context("should start serving pprof", func() { 1505 var listener net.Listener 1506 var opts Options 1507 1508 BeforeEach(func() { 1509 listener = nil 1510 opts = Options{ 1511 newPprofListener: func(addr string) (net.Listener, error) { 1512 var err error 1513 listener, err = defaultPprofListener(addr) 1514 return listener, err 1515 }, 1516 } 1517 }) 1518 1519 AfterEach(func() { 1520 if listener != nil { 1521 listener.Close() 1522 } 1523 }) 1524 1525 It("should stop serving pprof when stop is called", func() { 1526 opts.PprofBindAddress = ":0" 1527 m, err := New(cfg, opts) 1528 Expect(err).NotTo(HaveOccurred()) 1529 1530 ctx, cancel := context.WithCancel(context.Background()) 1531 go func() { 1532 defer GinkgoRecover() 1533 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1534 }() 1535 <-m.Elected() 1536 1537 // Check the pprof started 1538 endpoint := fmt.Sprintf("http://%s", listener.Addr().String()) 1539 _, err = http.Get(endpoint) 1540 Expect(err).NotTo(HaveOccurred()) 1541 1542 // Shutdown the server 1543 cancel() 1544 1545 // Expect the pprof server to shutdown 1546 Eventually(func() error { 1547 _, err = http.Get(endpoint) 1548 return err 1549 }, 10*time.Second).ShouldNot(Succeed()) 1550 }) 1551 1552 It("should serve pprof endpoints", func() { 1553 opts.PprofBindAddress = ":0" 1554 m, err := New(cfg, opts) 1555 Expect(err).NotTo(HaveOccurred()) 1556 1557 ctx, cancel := context.WithCancel(context.Background()) 1558 defer cancel() 1559 go func() { 1560 defer GinkgoRecover() 1561 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1562 }() 1563 <-m.Elected() 1564 1565 pprofIndexEndpoint := fmt.Sprintf("http://%s/debug/pprof/", listener.Addr().String()) 1566 resp, err := http.Get(pprofIndexEndpoint) 1567 Expect(err).NotTo(HaveOccurred()) 1568 defer resp.Body.Close() 1569 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1570 1571 pprofCmdlineEndpoint := fmt.Sprintf("http://%s/debug/pprof/cmdline", listener.Addr().String()) 1572 resp, err = http.Get(pprofCmdlineEndpoint) 1573 Expect(err).NotTo(HaveOccurred()) 1574 defer resp.Body.Close() 1575 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1576 1577 pprofProfileEndpoint := fmt.Sprintf("http://%s/debug/pprof/profile", listener.Addr().String()) 1578 resp, err = http.Get(pprofProfileEndpoint) 1579 Expect(err).NotTo(HaveOccurred()) 1580 defer resp.Body.Close() 1581 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1582 1583 pprofSymbolEndpoint := fmt.Sprintf("http://%s/debug/pprof/symbol", listener.Addr().String()) 1584 resp, err = http.Get(pprofSymbolEndpoint) 1585 Expect(err).NotTo(HaveOccurred()) 1586 defer resp.Body.Close() 1587 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1588 1589 pprofTraceEndpoint := fmt.Sprintf("http://%s/debug/pprof/trace", listener.Addr().String()) 1590 resp, err = http.Get(pprofTraceEndpoint) 1591 Expect(err).NotTo(HaveOccurred()) 1592 defer resp.Body.Close() 1593 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 1594 }) 1595 }) 1596 1597 Describe("Add", func() { 1598 It("should immediately start the Component if the Manager has already Started another Component", 1599 func() { 1600 m, err := New(cfg, Options{}) 1601 Expect(err).NotTo(HaveOccurred()) 1602 mgr, ok := m.(*controllerManager) 1603 Expect(ok).To(BeTrue()) 1604 1605 // Add one component before starting 1606 c1 := make(chan struct{}) 1607 Expect(m.Add(RunnableFunc(func(context.Context) error { 1608 defer GinkgoRecover() 1609 close(c1) 1610 return nil 1611 }))).To(Succeed()) 1612 1613 ctx, cancel := context.WithCancel(context.Background()) 1614 defer cancel() 1615 go func() { 1616 defer GinkgoRecover() 1617 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1618 }() 1619 <-m.Elected() 1620 1621 // Wait for the Manager to start 1622 Eventually(func() bool { 1623 return mgr.runnables.Caches.Started() 1624 }).Should(BeTrue()) 1625 1626 // Add another component after starting 1627 c2 := make(chan struct{}) 1628 Expect(m.Add(RunnableFunc(func(context.Context) error { 1629 defer GinkgoRecover() 1630 close(c2) 1631 return nil 1632 }))).To(Succeed()) 1633 <-c1 1634 <-c2 1635 }) 1636 1637 It("should immediately start the Component if the Manager has already Started", func() { 1638 m, err := New(cfg, Options{}) 1639 Expect(err).NotTo(HaveOccurred()) 1640 mgr, ok := m.(*controllerManager) 1641 Expect(ok).To(BeTrue()) 1642 1643 ctx, cancel := context.WithCancel(context.Background()) 1644 defer cancel() 1645 go func() { 1646 defer GinkgoRecover() 1647 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1648 }() 1649 1650 // Wait for the Manager to start 1651 Eventually(func() bool { 1652 return mgr.runnables.Caches.Started() 1653 }).Should(BeTrue()) 1654 1655 c1 := make(chan struct{}) 1656 Expect(m.Add(RunnableFunc(func(context.Context) error { 1657 defer GinkgoRecover() 1658 close(c1) 1659 return nil 1660 }))).To(Succeed()) 1661 <-c1 1662 }) 1663 1664 It("should fail if attempted to start a second time", func() { 1665 m, err := New(cfg, Options{}) 1666 Expect(err).NotTo(HaveOccurred()) 1667 1668 ctx, cancel := context.WithCancel(context.Background()) 1669 defer cancel() 1670 go func() { 1671 defer GinkgoRecover() 1672 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1673 }() 1674 // Wait for the Manager to start 1675 Eventually(func() bool { 1676 mgr, ok := m.(*controllerManager) 1677 Expect(ok).To(BeTrue()) 1678 return mgr.runnables.Caches.Started() 1679 }).Should(BeTrue()) 1680 1681 err = m.Start(ctx) 1682 Expect(err).To(HaveOccurred()) 1683 Expect(err.Error()).To(Equal("manager already started")) 1684 1685 }) 1686 }) 1687 1688 It("should not leak goroutines when stopped", func() { 1689 currentGRs := goleak.IgnoreCurrent() 1690 1691 m, err := New(cfg, Options{}) 1692 Expect(err).NotTo(HaveOccurred()) 1693 1694 ctx, cancel := context.WithCancel(context.Background()) 1695 cancel() 1696 Expect(m.Start(ctx)).NotTo(HaveOccurred()) 1697 1698 // force-close keep-alive connections. These'll time anyway (after 1699 // like 30s or so) but force it to speed up the tests. 1700 clientTransport.CloseIdleConnections() 1701 Eventually(func() error { return goleak.Find(currentGRs) }).Should(Succeed()) 1702 }) 1703 1704 It("should not leak goroutines if the default event broadcaster is used & events are emitted", func() { 1705 currentGRs := goleak.IgnoreCurrent() 1706 1707 m, err := New(cfg, Options{ /* implicit: default setting for EventBroadcaster */ }) 1708 Expect(err).NotTo(HaveOccurred()) 1709 1710 By("adding a runnable that emits an event") 1711 ns := corev1.Namespace{} 1712 ns.Name = "default" 1713 1714 recorder := m.GetEventRecorderFor("rock-and-roll") 1715 Expect(m.Add(RunnableFunc(func(_ context.Context) error { 1716 recorder.Event(&ns, "Warning", "BallroomBlitz", "yeah, yeah, yeah-yeah-yeah") 1717 return nil 1718 }))).To(Succeed()) 1719 1720 By("starting the manager & waiting till we've sent our event") 1721 ctx, cancel := context.WithCancel(context.Background()) 1722 doneCh := make(chan struct{}) 1723 go func() { 1724 defer GinkgoRecover() 1725 defer close(doneCh) 1726 Expect(m.Start(ctx)).To(Succeed()) 1727 }() 1728 <-m.Elected() 1729 1730 Eventually(func() *corev1.Event { 1731 evts, err := clientset.CoreV1().Events("").Search(m.GetScheme(), &ns) 1732 Expect(err).NotTo(HaveOccurred()) 1733 1734 for i, evt := range evts.Items { 1735 if evt.Reason == "BallroomBlitz" { 1736 return &evts.Items[i] 1737 } 1738 } 1739 return nil 1740 }).ShouldNot(BeNil()) 1741 1742 By("making sure there's no extra go routines still running after we stop") 1743 cancel() 1744 <-doneCh 1745 1746 // force-close keep-alive connections. These'll time anyway (after 1747 // like 30s or so) but force it to speed up the tests. 1748 clientTransport.CloseIdleConnections() 1749 Eventually(func() error { return goleak.Find(currentGRs) }).Should(Succeed()) 1750 }) 1751 1752 It("should provide a function to get the Config", func() { 1753 m, err := New(cfg, Options{}) 1754 Expect(err).NotTo(HaveOccurred()) 1755 mgr, ok := m.(*controllerManager) 1756 Expect(ok).To(BeTrue()) 1757 Expect(m.GetConfig()).To(Equal(mgr.cluster.GetConfig())) 1758 }) 1759 1760 It("should provide a function to get the Client", func() { 1761 m, err := New(cfg, Options{}) 1762 Expect(err).NotTo(HaveOccurred()) 1763 mgr, ok := m.(*controllerManager) 1764 Expect(ok).To(BeTrue()) 1765 Expect(m.GetClient()).To(Equal(mgr.cluster.GetClient())) 1766 }) 1767 1768 It("should provide a function to get the Scheme", func() { 1769 m, err := New(cfg, Options{}) 1770 Expect(err).NotTo(HaveOccurred()) 1771 mgr, ok := m.(*controllerManager) 1772 Expect(ok).To(BeTrue()) 1773 Expect(m.GetScheme()).To(Equal(mgr.cluster.GetScheme())) 1774 }) 1775 1776 It("should provide a function to get the FieldIndexer", func() { 1777 m, err := New(cfg, Options{}) 1778 Expect(err).NotTo(HaveOccurred()) 1779 mgr, ok := m.(*controllerManager) 1780 Expect(ok).To(BeTrue()) 1781 Expect(m.GetFieldIndexer()).To(Equal(mgr.cluster.GetFieldIndexer())) 1782 }) 1783 1784 It("should provide a function to get the EventRecorder", func() { 1785 m, err := New(cfg, Options{}) 1786 Expect(err).NotTo(HaveOccurred()) 1787 Expect(m.GetEventRecorderFor("test")).NotTo(BeNil()) 1788 }) 1789 It("should provide a function to get the APIReader", func() { 1790 m, err := New(cfg, Options{}) 1791 Expect(err).NotTo(HaveOccurred()) 1792 Expect(m.GetAPIReader()).NotTo(BeNil()) 1793 }) 1794 }) 1795 1796 type runnableError struct { 1797 } 1798 1799 func (runnableError) Error() string { 1800 return "not feeling like that" 1801 } 1802 1803 var _ Runnable = &cacheProvider{} 1804 1805 type cacheProvider struct { 1806 cache cache.Cache 1807 } 1808 1809 func (c *cacheProvider) GetCache() cache.Cache { 1810 return c.cache 1811 } 1812 1813 func (c *cacheProvider) Start(ctx context.Context) error { 1814 return c.cache.Start(ctx) 1815 } 1816 1817 type startSignalingInformer struct { 1818 mu sync.Mutex 1819 1820 // The manager calls Start and WaitForCacheSync in 1821 // parallel, so we have to protect wasStarted with a Mutex 1822 // and block in WaitForCacheSync until it is true. 1823 wasStarted bool 1824 // was synced will be true once Start was called and 1825 // WaitForCacheSync returned, just like a real cache. 1826 wasSynced bool 1827 cache.Cache 1828 } 1829 1830 func (c *startSignalingInformer) Start(ctx context.Context) error { 1831 c.mu.Lock() 1832 c.wasStarted = true 1833 c.mu.Unlock() 1834 return c.Cache.Start(ctx) 1835 } 1836 1837 func (c *startSignalingInformer) WaitForCacheSync(ctx context.Context) bool { 1838 defer func() { 1839 c.mu.Lock() 1840 c.wasSynced = true 1841 c.mu.Unlock() 1842 }() 1843 return c.Cache.WaitForCacheSync(ctx) 1844 } 1845 1846 type startClusterAfterManager struct { 1847 informer *startSignalingInformer 1848 } 1849 1850 func (c *startClusterAfterManager) Start(ctx context.Context) error { 1851 return c.informer.Start(ctx) 1852 } 1853 1854 func (c *startClusterAfterManager) GetCache() cache.Cache { 1855 return c.informer 1856 } 1857 1858 // metricsDefaultServer is used to type check the default metrics server implementation 1859 // so we can retrieve the bind addr without having to make GetBindAddr a function on the 1860 // metricsserver.Server interface or resort to reflection. 1861 type metricsDefaultServer interface { 1862 GetBindAddr() string 1863 } 1864 1865 type needElection struct { 1866 ch chan struct{} 1867 } 1868 1869 func (n *needElection) Start(_ context.Context) error { 1870 n.ch <- struct{}{} 1871 return nil 1872 } 1873 1874 func (n *needElection) NeedLeaderElection() bool { 1875 return true 1876 }