diff --git a/go.mod b/go.mod index 12a4ee2f19..56a2a6710a 100644 --- a/go.mod +++ b/go.mod @@ -15,9 +15,9 @@ require ( github.com/prometheus/client_model v0.6.2 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.1 - golang.org/x/mod v0.35.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.45.0 + golang.org/x/mod v0.37.0 + golang.org/x/sync v0.22.0 + golang.org/x/sys v0.47.0 gomodules.xyz/jsonpatch/v2 v2.4.0 gopkg.in/evanphx/json-patch.v4 v4.13.0 // Using v4 to match upstream k8s.io/api v0.37.0-alpha.3 @@ -89,15 +89,15 @@ require ( go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/net v0.55.1-0.20260602153038-42abb857022c // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.81.1 // indirect + google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/component-base v0.37.0-alpha.3 // indirect @@ -107,3 +107,13 @@ require ( sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect ) + +replace ( + k8s.io/api => github.com/tchap/kubernetes/staging/src/k8s.io/api v0.0.0-20260721134614-96ce75716ad1 + k8s.io/apiextensions-apiserver => github.com/tchap/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721134614-96ce75716ad1 + k8s.io/apimachinery => github.com/tchap/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721134614-96ce75716ad1 + k8s.io/apiserver => github.com/tchap/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721134614-96ce75716ad1 + k8s.io/client-go => github.com/tchap/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721134614-96ce75716ad1 + k8s.io/component-base => github.com/tchap/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721134614-96ce75716ad1 + k8s.io/streaming => github.com/tchap/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721134614-96ce75716ad1 +) diff --git a/go.sum b/go.sum index 20b37e9ce1..c25d85d647 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,20 @@ github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+Q github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tchap/kubernetes/staging/src/k8s.io/api v0.0.0-20260721134614-96ce75716ad1 h1:DOrOyEBt4AcTMopfJ3jro4IBSFftVqbPXiz7Ki/ntg0= +github.com/tchap/kubernetes/staging/src/k8s.io/api v0.0.0-20260721134614-96ce75716ad1/go.mod h1:cmly/3k2PqLXcOTQwqmVnZD6h6JDCXiLbcJpKBMBugs= +github.com/tchap/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721134614-96ce75716ad1 h1:iW2M+Azp/V/OP1c6Lz6qA91uSZCs28nhTEDENCy/XcE= +github.com/tchap/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721134614-96ce75716ad1/go.mod h1:pADykcqbLWJnajJNh5FEXGTglseeXwXDy2Zj/i1rezE= +github.com/tchap/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721134614-96ce75716ad1 h1:drKhOfRhBSCeAPpU+K0LrKGt7lFqUlvVNT3wGE8RNjQ= +github.com/tchap/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721134614-96ce75716ad1/go.mod h1:g1CPtKFkdq02v7P6/JujPV0xFgx2NagrpfM5uBCtIMw= +github.com/tchap/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721134614-96ce75716ad1 h1:dtJfMs9vYMqkfHV6gjsSgEiIjJjEhqisHu4BlIgcguQ= +github.com/tchap/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721134614-96ce75716ad1/go.mod h1:kPfkXPCPD6ac8OqzZpa/m1pKf5O3a/cai7nHJPDhcXA= +github.com/tchap/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721134614-96ce75716ad1 h1:2kQ9zYoOLboZyNK0ePwZ2HuM3nNyIW5xc6Hoi0Erugg= +github.com/tchap/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721134614-96ce75716ad1/go.mod h1:k338h8/YifXSfjKlaKXVTyO3e5dfuXb1VP9z+uJPogs= +github.com/tchap/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721134614-96ce75716ad1 h1:8iR7xFFkzAfDCBOfSkSpg/qfUsye2opcwiGEXP1kT1c= +github.com/tchap/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721134614-96ce75716ad1/go.mod h1:XPaCvVj1ZpMk1pxxwQ6YPSz7huzXm0Htf2Uwym6/SBk= +github.com/tchap/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721134614-96ce75716ad1 h1:3R74GpVdmINtNKjFkAedavmJ0NHFRH7e8Ah1C+N3Mbs= +github.com/tchap/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721134614-96ce75716ad1/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -196,24 +210,24 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.55.1-0.20260602153038-42abb857022c h1:hzdRVvjwthcq46T3ybsDtJpg1iZZaW3zik9P3tsYJeo= -golang.org/x/net v0.55.1-0.20260602153038-42abb857022c/go.mod h1:Mo4aFsyVPXieTwCJ1g83a3xU8RY7PkHFQqQ7pkWnuM0= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= @@ -222,8 +236,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1: google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -235,24 +249,10 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.37.0-alpha.3 h1:8RLadjrxX6Nqz8yF13ADHYolBZ4ynBeE3NfzWyiGnyQ= -k8s.io/api v0.37.0-alpha.3/go.mod h1:Pg8TFuiCNviZSgHzFT2II6ux8lbFQo02ObX9xOIuYrM= -k8s.io/apiextensions-apiserver v0.37.0-alpha.3 h1:BZM5iUxbTszd5oj+xt4LH3/sZVKORWJE1Ws3jm03dLk= -k8s.io/apiextensions-apiserver v0.37.0-alpha.3/go.mod h1:/vL2D5lHe/yN43jd8bQ7nypbN8QsVGI6A28GLB0ZnOA= -k8s.io/apimachinery v0.37.0-alpha.3 h1:zxta8QqejeD3A3rEYUC63wSp/kh7KNr35TsX1p3SYTE= -k8s.io/apimachinery v0.37.0-alpha.3/go.mod h1:Uh/MjLZyS9W1TeoRsrsZMf91DZn8Kwox2FEyjwJoy5Q= -k8s.io/apiserver v0.37.0-alpha.3 h1:qnxt+4JHVIVo390KVXsGyggYKbya05FHDZ3U3MLD8RA= -k8s.io/apiserver v0.37.0-alpha.3/go.mod h1:cAdtKXbCtWktatU8ugGNet8WPhlsEgc7xbDxULOUQY8= -k8s.io/client-go v0.37.0-alpha.3 h1:4p6rVMrCMjs8QJSS/z4UhWOps46iB24F+6klnPHfRHI= -k8s.io/client-go v0.37.0-alpha.3/go.mod h1:fr3/4H2dyns23ZKNGP0SgtE2p9gvI6N5aOqMAXBOlPQ= -k8s.io/component-base v0.37.0-alpha.3 h1:5g6BZuGPfxnu0Zn+13jsW+akc0wH/1pneChppl4nyu8= -k8s.io/component-base v0.37.0-alpha.3/go.mod h1:Ocb0GmFTg75YY2YQI1lX9VHixDUGL6iRy+5IWwCJR6s= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974 h1:JVogoTvOj6gutlx8bUwGh0e8o8L4X8nDbTLyONmoVvk= k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= -k8s.io/streaming v0.37.0-alpha.3 h1:6XwRAwOC3DEqgHQTsqHfqLtdJpYKHJ+HShPfEzs5cVM= -k8s.io/streaming v0.37.0-alpha.3/go.mod h1:jksMJuFO/NrJlKuDkIomVgAJnK+Yu9h8KTrkZ8ESiac= k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 h1:/YpDJ4vReG7ZmzSpBGxduXgywWkJU9zHubgJG03MT+Y= diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index 55fa2fca61..549a519362 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -617,12 +617,27 @@ func (cm *controllerManager) initLeaderElector() (*leaderelection.LeaderElector, RenewDeadline: cm.renewDeadline, RetryPeriod: cm.retryPeriod, Callbacks: leaderelection.LeaderCallbacks{ - OnStartedLeading: func(_ context.Context) { + OnStartedLeading: func(ctx context.Context) { if err := cm.startLeaderElectionRunnables(); err != nil { cm.errChan <- err return } close(cm.elected) + + if !cm.leaderElectionReleaseOnCancel { + return + } + // When ReleaseOnCancel is set, Run() calls OnStartedLeading synchronously + // and keeps renewing the lease until it returns, then releases the lease. + // We must block until ctx is cancelled and all runnables have finished. + <-ctx.Done() + stopCtx := context.Background() + if cm.gracefulShutdownTimeout >= 0 { + var cancel context.CancelFunc + stopCtx, cancel = context.WithTimeout(stopCtx, cm.gracefulShutdownTimeout) + defer cancel() + } + cm.runnables.LeaderElection.StopAndWait(stopCtx) }, OnStoppedLeading: func() { if cm.onStoppedLeading != nil { diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index 10a73072bb..5543943196 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -459,7 +459,7 @@ var _ = Describe("manger.Manager", func() { _, isLeaseLock := cm.resourceLock.(*resourcelock.LeaseLock) Expect(isLeaseLock).To(BeTrue()) }) - It("should release lease if ElectionReleaseOnCancel is true", func(specCtx SpecContext) { + It("should release lease if ReleaseOnCancel is true", func(specCtx SpecContext) { var rl resourcelock.Interface m, err := New(cfg, Options{ LeaderElection: true, @@ -490,6 +490,106 @@ var _ = Describe("manger.Manager", func() { Expect(err).ToNot(HaveOccurred()) Expect(record.HolderIdentity).To(BeEmpty()) }) + It("should stop leader election runnables before releasing lease when ReleaseOnCancel is true", func(specCtx SpecContext) { + var rl resourcelock.Interface + m, err := New(cfg, Options{ + LeaderElection: true, + LeaderElectionResourceLock: resourcelock.LeasesResourceLock, + LeaderElectionID: "controller-runtime", + LeaderElectionNamespace: "my-ns", + LeaderElectionReleaseOnCancel: true, + HealthProbeBindAddress: "0", + Metrics: metricsserver.Options{BindAddress: "0"}, + newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { + var err error + rl, err = fakeleaderelection.NewResourceLock(config, recorderProvider, options) + return rl, err + }, + }) + Expect(err).ToNot(HaveOccurred()) + + cm := m.(*controllerManager) + cm.gracefulShutdownTimeout = 5 * time.Second + + slowRunnable := RunnableFunc(func(ctx context.Context) error { + defer GinkgoRecover() + <-ctx.Done() + time.Sleep(200 * time.Millisecond) + // Verify the lease is still held while the runnable is shutting down. + record, _, err := rl.Get(context.Background()) + Expect(err).ToNot(HaveOccurred()) + Expect(record.HolderIdentity).ToNot(BeEmpty(), "lease was released before runnable finished") + return nil + }) + Expect(m.Add(slowRunnable)).To(Succeed()) + + ctx, cancel := context.WithCancel(specCtx) + mgrDone := make(chan struct{}) + go func() { + defer GinkgoRecover() + defer close(mgrDone) + Expect(m.Start(ctx)).NotTo(HaveOccurred()) + }() + <-cm.elected + cancel() + <-mgrDone + + record, _, err := rl.Get(specCtx) + Expect(err).ToNot(HaveOccurred()) + Expect(record.HolderIdentity).To(BeEmpty()) + }) + It("should stop leader election runnables before releasing lease on renewal failure", func(specCtx SpecContext) { + var rl resourcelock.Interface + m, err := New(cfg, Options{ + LeaderElection: true, + LeaderElectionResourceLock: resourcelock.LeasesResourceLock, + LeaderElectionID: "controller-runtime", + LeaderElectionNamespace: "my-ns", + LeaderElectionReleaseOnCancel: true, + HealthProbeBindAddress: "0", + Metrics: metricsserver.Options{BindAddress: "0"}, + newResourceLock: func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { + var err error + rl, err = fakeleaderelection.NewResourceLock(config, recorderProvider, options) + return rl, err + }, + }) + Expect(err).ToNot(HaveOccurred()) + + cm := m.(*controllerManager) + cm.gracefulShutdownTimeout = 5 * time.Second + + runnableStopped := make(chan struct{}) + slowRunnable := RunnableFunc(func(ctx context.Context) error { + <-ctx.Done() + time.Sleep(200 * time.Millisecond) + close(runnableStopped) + return nil + }) + Expect(m.Add(slowRunnable)).To(Succeed()) + + mgrDone := make(chan struct{}) + go func() { + defer GinkgoRecover() + defer close(mgrDone) + err := m.Start(specCtx) + // Renewal failure causes "leader election lost" + Expect(err).To(HaveOccurred()) + }() + <-cm.elected + + // Block renewal to simulate failure + rl.(fakeleaderelection.ControllableResourceLockInterface).BlockLeaderElection() + <-mgrDone + + // The runnable must have finished before Start returned. + select { + case <-runnableStopped: + // Success + default: + Fail("leader election runnable was not stopped before manager returned on renewal failure") + } + }) It("should set the leaselocks's label field when LeaderElectionLabels is set", func() { labels := map[string]string{"my-key": "my-val"} m, err := New(cfg, Options{