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  }