k8s.io/client-go@v0.31.1/tools/leaderelection/leasecandidate.go (about) 1 /* 2 Copyright 2024 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 leaderelection 18 19 import ( 20 "context" 21 "reflect" 22 "time" 23 24 v1 "k8s.io/api/coordination/v1" 25 v1alpha1 "k8s.io/api/coordination/v1alpha1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/fields" 29 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 30 "k8s.io/client-go/informers" 31 "k8s.io/client-go/kubernetes" 32 coordinationv1alpha1client "k8s.io/client-go/kubernetes/typed/coordination/v1alpha1" 33 "k8s.io/client-go/tools/cache" 34 "k8s.io/client-go/util/workqueue" 35 "k8s.io/klog/v2" 36 "k8s.io/utils/clock" 37 ) 38 39 const requeueInterval = 5 * time.Minute 40 41 type CacheSyncWaiter interface { 42 WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool 43 } 44 45 type LeaseCandidate struct { 46 leaseClient coordinationv1alpha1client.LeaseCandidateInterface 47 leaseCandidateInformer cache.SharedIndexInformer 48 informerFactory informers.SharedInformerFactory 49 hasSynced cache.InformerSynced 50 51 // At most there will be one item in this Queue (since we only watch one item) 52 queue workqueue.TypedRateLimitingInterface[int] 53 54 name string 55 namespace string 56 57 // controller lease 58 leaseName string 59 60 clock clock.Clock 61 62 binaryVersion, emulationVersion string 63 preferredStrategies []v1.CoordinatedLeaseStrategy 64 } 65 66 // NewCandidate creates new LeaseCandidate controller that creates a 67 // LeaseCandidate object if it does not exist and watches changes 68 // to the corresponding object and renews if PingTime is set. 69 // WARNING: This is an ALPHA feature. Ensure that the CoordinatedLeaderElection 70 // feature gate is on. 71 func NewCandidate(clientset kubernetes.Interface, 72 candidateNamespace string, 73 candidateName string, 74 targetLease string, 75 binaryVersion, emulationVersion string, 76 preferredStrategies []v1.CoordinatedLeaseStrategy, 77 ) (*LeaseCandidate, CacheSyncWaiter, error) { 78 fieldSelector := fields.OneTermEqualSelector("metadata.name", candidateName).String() 79 // A separate informer factory is required because this must start before informerFactories 80 // are started for leader elected components 81 informerFactory := informers.NewSharedInformerFactoryWithOptions( 82 clientset, 5*time.Minute, 83 informers.WithTweakListOptions(func(options *metav1.ListOptions) { 84 options.FieldSelector = fieldSelector 85 }), 86 ) 87 leaseCandidateInformer := informerFactory.Coordination().V1alpha1().LeaseCandidates().Informer() 88 89 lc := &LeaseCandidate{ 90 leaseClient: clientset.CoordinationV1alpha1().LeaseCandidates(candidateNamespace), 91 leaseCandidateInformer: leaseCandidateInformer, 92 informerFactory: informerFactory, 93 name: candidateName, 94 namespace: candidateNamespace, 95 leaseName: targetLease, 96 clock: clock.RealClock{}, 97 binaryVersion: binaryVersion, 98 emulationVersion: emulationVersion, 99 preferredStrategies: preferredStrategies, 100 } 101 lc.queue = workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[int](), workqueue.TypedRateLimitingQueueConfig[int]{Name: "leasecandidate"}) 102 103 h, err := leaseCandidateInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 104 UpdateFunc: func(oldObj, newObj interface{}) { 105 if leasecandidate, ok := newObj.(*v1alpha1.LeaseCandidate); ok { 106 if leasecandidate.Spec.PingTime != nil && leasecandidate.Spec.PingTime.After(leasecandidate.Spec.RenewTime.Time) { 107 lc.enqueueLease() 108 } 109 } 110 }, 111 }) 112 if err != nil { 113 return nil, nil, err 114 } 115 lc.hasSynced = h.HasSynced 116 117 return lc, informerFactory, nil 118 } 119 120 func (c *LeaseCandidate) Run(ctx context.Context) { 121 defer c.queue.ShutDown() 122 123 c.informerFactory.Start(ctx.Done()) 124 if !cache.WaitForNamedCacheSync("leasecandidateclient", ctx.Done(), c.hasSynced) { 125 return 126 } 127 128 c.enqueueLease() 129 go c.runWorker(ctx) 130 <-ctx.Done() 131 } 132 133 func (c *LeaseCandidate) runWorker(ctx context.Context) { 134 for c.processNextWorkItem(ctx) { 135 } 136 } 137 138 func (c *LeaseCandidate) processNextWorkItem(ctx context.Context) bool { 139 key, shutdown := c.queue.Get() 140 if shutdown { 141 return false 142 } 143 defer c.queue.Done(key) 144 145 err := c.ensureLease(ctx) 146 if err == nil { 147 c.queue.AddAfter(key, requeueInterval) 148 return true 149 } 150 151 utilruntime.HandleError(err) 152 c.queue.AddRateLimited(key) 153 154 return true 155 } 156 157 func (c *LeaseCandidate) enqueueLease() { 158 c.queue.Add(0) 159 } 160 161 // ensureLease creates the lease if it does not exist and renew it if it exists. Returns the lease and 162 // a bool (true if this call created the lease), or any error that occurs. 163 func (c *LeaseCandidate) ensureLease(ctx context.Context) error { 164 lease, err := c.leaseClient.Get(ctx, c.name, metav1.GetOptions{}) 165 if apierrors.IsNotFound(err) { 166 klog.V(2).Infof("Creating lease candidate") 167 // lease does not exist, create it. 168 leaseToCreate := c.newLeaseCandidate() 169 if _, err := c.leaseClient.Create(ctx, leaseToCreate, metav1.CreateOptions{}); err != nil { 170 return err 171 } 172 klog.V(2).Infof("Created lease candidate") 173 return nil 174 } else if err != nil { 175 return err 176 } 177 klog.V(2).Infof("lease candidate exists. Renewing.") 178 clone := lease.DeepCopy() 179 clone.Spec.RenewTime = &metav1.MicroTime{Time: c.clock.Now()} 180 _, err = c.leaseClient.Update(ctx, clone, metav1.UpdateOptions{}) 181 if err != nil { 182 return err 183 } 184 return nil 185 } 186 187 func (c *LeaseCandidate) newLeaseCandidate() *v1alpha1.LeaseCandidate { 188 lc := &v1alpha1.LeaseCandidate{ 189 ObjectMeta: metav1.ObjectMeta{ 190 Name: c.name, 191 Namespace: c.namespace, 192 }, 193 Spec: v1alpha1.LeaseCandidateSpec{ 194 LeaseName: c.leaseName, 195 BinaryVersion: c.binaryVersion, 196 EmulationVersion: c.emulationVersion, 197 PreferredStrategies: c.preferredStrategies, 198 }, 199 } 200 lc.Spec.RenewTime = &metav1.MicroTime{Time: c.clock.Now()} 201 return lc 202 }