Skip to content

Commit d39bab8

Browse files
alexandremahdhaouik8s-infra-cherrypick-robot
authored and
k8s-infra-cherrypick-robot
committed
🐛 prevent leader election when shutting down a non-elected manager
When leader election is enabled, a non-leader manager would never start the LeaderElection runnable group. Thus, as the shutdown process calls the sync.Once Start func of the runnableGroup; it will start a new election. This change ensures `Start` is ineffective during shutdown. The test ensures the LeaderElection runnableGroup is not started during shutdown. Signed-off-by: Alexandre Mahdhaoui <[email protected]>
1 parent 8968da8 commit d39bab8

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

pkg/manager/internal.go

+2
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,8 @@ func (cm *controllerManager) engageStopProcedure(stopComplete <-chan struct{}) e
518518

519519
// Stop all the leader election runnables, which includes reconcilers.
520520
cm.logger.Info("Stopping and waiting for leader election runnables")
521+
// Prevent leader election when shutting down a non-elected manager
522+
cm.runnables.LeaderElection.startOnce.Do(func() {})
521523
cm.runnables.LeaderElection.StopAndWait(cm.shutdownCtx)
522524

523525
// Stop the caches before the leader election runnables, this is an important

pkg/manager/manager_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,85 @@ var _ = Describe("manger.Manager", func() {
378378

379379
Expect(cm.gracefulShutdownTimeout.Nanoseconds()).To(Equal(int64(0)))
380380
})
381+
382+
It("should prevent leader election when shutting down a non-elected manager", func() {
383+
var rl resourcelock.Interface
384+
m1, err := New(cfg, Options{
385+
LeaderElection: true,
386+
LeaderElectionNamespace: "default",
387+
LeaderElectionID: "test-leader-election-id",
388+
newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) {
389+
var err error
390+
rl, err = leaderelection.NewResourceLock(config, recorderProvider, options)
391+
return rl, err
392+
},
393+
HealthProbeBindAddress: "0",
394+
Metrics: metricsserver.Options{BindAddress: "0"},
395+
PprofBindAddress: "0",
396+
})
397+
Expect(err).ToNot(HaveOccurred())
398+
Expect(m1).ToNot(BeNil())
399+
Expect(rl.Describe()).To(Equal("default/test-leader-election-id"))
400+
401+
m1cm, ok := m1.(*controllerManager)
402+
Expect(ok).To(BeTrue())
403+
m1cm.onStoppedLeading = func() {}
404+
405+
m2, err := New(cfg, Options{
406+
LeaderElection: true,
407+
LeaderElectionNamespace: "default",
408+
LeaderElectionID: "test-leader-election-id",
409+
newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) {
410+
var err error
411+
rl, err = leaderelection.NewResourceLock(config, recorderProvider, options)
412+
return rl, err
413+
},
414+
HealthProbeBindAddress: "0",
415+
Metrics: metricsserver.Options{BindAddress: "0"},
416+
PprofBindAddress: "0",
417+
})
418+
Expect(err).ToNot(HaveOccurred())
419+
Expect(m2).ToNot(BeNil())
420+
Expect(rl.Describe()).To(Equal("default/test-leader-election-id"))
421+
422+
m1done := make(chan struct{})
423+
Expect(m1.Add(RunnableFunc(func(ctx context.Context) error {
424+
defer GinkgoRecover()
425+
close(m1done)
426+
return nil
427+
}))).To(Succeed())
428+
429+
ctx1, cancel1 := context.WithCancel(context.Background())
430+
defer cancel1()
431+
go func() {
432+
defer GinkgoRecover()
433+
Expect(m1.Elected()).ShouldNot(BeClosed())
434+
Expect(m1.Start(ctx1)).NotTo(HaveOccurred())
435+
}()
436+
<-m1.Elected()
437+
<-m1done
438+
439+
electionRunnable := &needElection{make(chan struct{})}
440+
441+
Expect(m2.Add(electionRunnable)).To(Succeed())
442+
443+
ctx2, cancel2 := context.WithCancel(context.Background())
444+
m2done := make(chan struct{})
445+
go func() {
446+
defer GinkgoRecover()
447+
Expect(m2.Start(ctx2)).NotTo(HaveOccurred())
448+
close(m2done)
449+
}()
450+
Consistently(m2.Elected()).ShouldNot(Receive())
451+
452+
go func() {
453+
defer GinkgoRecover()
454+
Consistently(electionRunnable.ch).ShouldNot(Receive())
455+
}()
456+
cancel2()
457+
<-m2done
458+
})
459+
381460
It("should default ID to controller-runtime if ID is not set", func() {
382461
var rl resourcelock.Interface
383462
m1, err := New(cfg, Options{
@@ -1929,3 +2008,16 @@ func (f *fakeDeferredLoader) InjectScheme(scheme *runtime.Scheme) error {
19292008
type metricsDefaultServer interface {
19302009
GetBindAddr() string
19312010
}
2011+
2012+
type needElection struct {
2013+
ch chan struct{}
2014+
}
2015+
2016+
func (n *needElection) Start(_ context.Context) error {
2017+
n.ch <- struct{}{}
2018+
return nil
2019+
}
2020+
2021+
func (n *needElection) NeedLeaderElection() bool {
2022+
return true
2023+
}

0 commit comments

Comments
 (0)