mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	CLE controller and client changes
This commit is contained in:
		@@ -28,8 +28,9 @@ import (
 | 
				
			|||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/blang/semver/v4"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/api/meta"
 | 
						"k8s.io/apimachinery/pkg/api/meta"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
@@ -78,7 +79,9 @@ import (
 | 
				
			|||||||
	kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
 | 
						kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
 | 
				
			||||||
	garbagecollector "k8s.io/kubernetes/pkg/controller/garbagecollector"
 | 
						garbagecollector "k8s.io/kubernetes/pkg/controller/garbagecollector"
 | 
				
			||||||
	serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
 | 
						serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
 | 
				
			||||||
 | 
						kubefeatures "k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/serviceaccount"
 | 
						"k8s.io/kubernetes/pkg/serviceaccount"
 | 
				
			||||||
 | 
						"k8s.io/utils/clock"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@@ -289,6 +292,30 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
 | 
				
			|||||||
			return startSATokenControllerInit(ctx, controllerContext, controllerName)
 | 
								return startSATokenControllerInit(ctx, controllerContext, controllerName)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						ver, err := semver.ParseTolerant(version.Get().String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
 | 
				
			||||||
 | 
							// Start component identity lease management
 | 
				
			||||||
 | 
							leaseCandidate, err := leaderelection.NewCandidate(
 | 
				
			||||||
 | 
								c.Client,
 | 
				
			||||||
 | 
								id,
 | 
				
			||||||
 | 
								"kube-system",
 | 
				
			||||||
 | 
								"kube-controller-manager",
 | 
				
			||||||
 | 
								clock.RealClock{},
 | 
				
			||||||
 | 
								ver.FinalizeVersion(),
 | 
				
			||||||
 | 
								ver.FinalizeVersion(), // TODO: Use compatibility version when it's available
 | 
				
			||||||
 | 
								[]v1.CoordinatedLeaseStrategy{"OldestEmulationVersion"},
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							healthzHandler.AddHealthChecker(healthz.NewInformerSyncHealthz(leaseCandidate.InformerFactory))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							go leaseCandidate.Run(ctx)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start the main lock
 | 
						// Start the main lock
 | 
				
			||||||
	go leaderElectAndRun(ctx, c, id, electionChecker,
 | 
						go leaderElectAndRun(ctx, c, id, electionChecker,
 | 
				
			||||||
@@ -886,6 +913,7 @@ func leaderElectAndRun(ctx context.Context, c *config.CompletedConfig, lockIdent
 | 
				
			|||||||
		Callbacks:     callbacks,
 | 
							Callbacks:     callbacks,
 | 
				
			||||||
		WatchDog:      electionChecker,
 | 
							WatchDog:      electionChecker,
 | 
				
			||||||
		Name:          leaseName,
 | 
							Name:          leaseName,
 | 
				
			||||||
 | 
							Coordinated:   utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	panic("unreachable")
 | 
						panic("unreachable")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,9 @@ import (
 | 
				
			|||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	goruntime "runtime"
 | 
						goruntime "runtime"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/blang/semver/v4"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
						coordinationv1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
						utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
				
			||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/authentication/authenticator"
 | 
						"k8s.io/apiserver/pkg/authentication/authenticator"
 | 
				
			||||||
@@ -56,8 +57,11 @@ import (
 | 
				
			|||||||
	"k8s.io/component-base/version"
 | 
						"k8s.io/component-base/version"
 | 
				
			||||||
	"k8s.io/component-base/version/verflag"
 | 
						"k8s.io/component-base/version/verflag"
 | 
				
			||||||
	"k8s.io/klog/v2"
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
						"k8s.io/utils/clock"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	schedulerserverconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config"
 | 
						schedulerserverconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config"
 | 
				
			||||||
	"k8s.io/kubernetes/cmd/kube-scheduler/app/options"
 | 
						"k8s.io/kubernetes/cmd/kube-scheduler/app/options"
 | 
				
			||||||
 | 
						kubefeatures "k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/scheduler"
 | 
						"k8s.io/kubernetes/pkg/scheduler"
 | 
				
			||||||
	kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
 | 
						kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/scheduler/apis/config/latest"
 | 
						"k8s.io/kubernetes/pkg/scheduler/apis/config/latest"
 | 
				
			||||||
@@ -207,6 +211,34 @@ func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
	readyzChecks = append(readyzChecks, handlerSyncCheck)
 | 
						readyzChecks = append(readyzChecks, handlerSyncCheck)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cc.LeaderElection != nil && utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
 | 
				
			||||||
 | 
							binaryVersion, err := semver.ParseTolerant(utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent).BinaryVersion().String())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							emulationVersion, err := semver.ParseTolerant(utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent).EmulationVersion().String())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Start component identity lease management
 | 
				
			||||||
 | 
							leaseCandidate, err := leaderelection.NewCandidate(
 | 
				
			||||||
 | 
								cc.Client,
 | 
				
			||||||
 | 
								cc.LeaderElection.Lock.Identity(),
 | 
				
			||||||
 | 
								"kube-system",
 | 
				
			||||||
 | 
								"kube-scheduler",
 | 
				
			||||||
 | 
								clock.RealClock{},
 | 
				
			||||||
 | 
								binaryVersion.FinalizeVersion(),
 | 
				
			||||||
 | 
								emulationVersion.FinalizeVersion(),
 | 
				
			||||||
 | 
								[]coordinationv1.CoordinatedLeaseStrategy{"OldestEmulationVersion"},
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							readyzChecks = append(readyzChecks, healthz.NewInformerSyncHealthz(leaseCandidate.InformerFactory))
 | 
				
			||||||
 | 
							go leaseCandidate.Run(ctx)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start up the healthz server.
 | 
						// Start up the healthz server.
 | 
				
			||||||
	if cc.SecureServing != nil {
 | 
						if cc.SecureServing != nil {
 | 
				
			||||||
		handler := buildHandlerChain(newHealthEndpointsAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks, readyzChecks), cc.Authentication.Authenticator, cc.Authorization.Authorizer)
 | 
							handler := buildHandlerChain(newHealthEndpointsAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks, readyzChecks), cc.Authentication.Authenticator, cc.Authorization.Authorizer)
 | 
				
			||||||
@@ -245,6 +277,9 @@ func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	// If leader election is enabled, runCommand via LeaderElector until done and exit.
 | 
						// If leader election is enabled, runCommand via LeaderElector until done and exit.
 | 
				
			||||||
	if cc.LeaderElection != nil {
 | 
						if cc.LeaderElection != nil {
 | 
				
			||||||
 | 
							if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) {
 | 
				
			||||||
 | 
								cc.LeaderElection.Coordinated = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
 | 
							cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
 | 
				
			||||||
			OnStartedLeading: func(ctx context.Context) {
 | 
								OnStartedLeading: func(ctx context.Context) {
 | 
				
			||||||
				close(waitingForLeader)
 | 
									close(waitingForLeader)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1027,6 +1027,7 @@ EOF
 | 
				
			|||||||
      --feature-gates="${FEATURE_GATES}" \
 | 
					      --feature-gates="${FEATURE_GATES}" \
 | 
				
			||||||
      --authentication-kubeconfig "${CERT_DIR}"/scheduler.kubeconfig \
 | 
					      --authentication-kubeconfig "${CERT_DIR}"/scheduler.kubeconfig \
 | 
				
			||||||
      --authorization-kubeconfig "${CERT_DIR}"/scheduler.kubeconfig \
 | 
					      --authorization-kubeconfig "${CERT_DIR}"/scheduler.kubeconfig \
 | 
				
			||||||
 | 
					      --leader-elect=false \
 | 
				
			||||||
      --master="https://${API_HOST}:${API_SECURE_PORT}" >"${SCHEDULER_LOG}" 2>&1 &
 | 
					      --master="https://${API_HOST}:${API_SECURE_PORT}" >"${SCHEDULER_LOG}" 2>&1 &
 | 
				
			||||||
    SCHEDULER_PID=$!
 | 
					    SCHEDULER_PID=$!
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ limitations under the License.
 | 
				
			|||||||
package apiserver
 | 
					package apiserver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -41,6 +42,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controlplane/controller/apiserverleasegc"
 | 
						"k8s.io/kubernetes/pkg/controlplane/controller/apiserverleasegc"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust"
 | 
						"k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/controlplane/controller/leaderelection"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
 | 
						"k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controlplane/controller/systemnamespaces"
 | 
						"k8s.io/kubernetes/pkg/controlplane/controller/systemnamespaces"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/features"
 | 
						"k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
@@ -145,6 +147,27 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele
 | 
				
			|||||||
		return nil, fmt.Errorf("failed to get listener address: %w", err)
 | 
							return nil, fmt.Errorf("failed to get listener address: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.CoordinatedLeaderElection) {
 | 
				
			||||||
 | 
							leaseInformer := s.VersionedInformers.Coordination().V1().Leases()
 | 
				
			||||||
 | 
							lcInformer := s.VersionedInformers.Coordination().V1alpha1().LeaseCandidates()
 | 
				
			||||||
 | 
							// Ensure that informers are registered before starting. Coordinated Leader Election leader-elected
 | 
				
			||||||
 | 
							// and may register informer handlers after they are started.
 | 
				
			||||||
 | 
							_ = leaseInformer.Informer()
 | 
				
			||||||
 | 
							_ = lcInformer.Informer()
 | 
				
			||||||
 | 
							s.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-coordinated-leader-election-controller", func(hookContext genericapiserver.PostStartHookContext) error {
 | 
				
			||||||
 | 
								go leaderelection.RunWithLeaderElection(hookContext, s.GenericAPIServer.LoopbackClientConfig, func() (func(ctx context.Context, workers int), error) {
 | 
				
			||||||
 | 
									controller, err := leaderelection.NewController(
 | 
				
			||||||
 | 
										leaseInformer,
 | 
				
			||||||
 | 
										lcInformer,
 | 
				
			||||||
 | 
										client.CoordinationV1(),
 | 
				
			||||||
 | 
										client.CoordinationV1alpha1(),
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
									return controller.Run, err
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
 | 
						if utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
 | 
				
			||||||
		peeraddress := getPeerAddress(c.Extra.PeerAdvertiseAddress, c.Generic.PublicAddress, publicServicePort)
 | 
							peeraddress := getPeerAddress(c.Extra.PeerAdvertiseAddress, c.Generic.PublicAddress, publicServicePort)
 | 
				
			||||||
		peerEndpointCtrl := peerreconcilers.New(
 | 
							peerEndpointCtrl := peerreconcilers.New(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										135
									
								
								pkg/controlplane/controller/leaderelection/election.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								pkg/controlplane/controller/leaderelection/election.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"slices"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/blang/semver/v4"
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						v1alpha1 "k8s.io/api/coordination/v1alpha1"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func pickBestLeaderOldestEmulationVersion(candidates []*v1alpha1.LeaseCandidate) *v1alpha1.LeaseCandidate {
 | 
				
			||||||
 | 
						var electee *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
						for _, c := range candidates {
 | 
				
			||||||
 | 
							if !validLeaseCandidateForOldestEmulationVersion(c) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if electee == nil || compare(electee, c) > 0 {
 | 
				
			||||||
 | 
								electee = c
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if electee == nil {
 | 
				
			||||||
 | 
							klog.Infof("pickBestLeader: none found")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							klog.Infof("pickBestLeader: %s %s", electee.Namespace, electee.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return electee
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func shouldReelect(candidates []*v1alpha1.LeaseCandidate, currentLeader *v1alpha1.LeaseCandidate) bool {
 | 
				
			||||||
 | 
						klog.Infof("shouldReelect for candidates: %+v", candidates)
 | 
				
			||||||
 | 
						pickedLeader := pickBestLeaderOldestEmulationVersion(candidates)
 | 
				
			||||||
 | 
						if pickedLeader == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return compare(currentLeader, pickedLeader) > 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func pickBestStrategy(candidates []*v1alpha1.LeaseCandidate) v1.CoordinatedLeaseStrategy {
 | 
				
			||||||
 | 
						// TODO: This doesn't account for cycles within the preference graph
 | 
				
			||||||
 | 
						// We may have to do a topological sort to verify that the preference ordering is valid
 | 
				
			||||||
 | 
						var bestStrategy *v1.CoordinatedLeaseStrategy
 | 
				
			||||||
 | 
						for _, c := range candidates {
 | 
				
			||||||
 | 
							if len(c.Spec.PreferredStrategies) > 0 {
 | 
				
			||||||
 | 
								if bestStrategy == nil {
 | 
				
			||||||
 | 
									bestStrategy = &c.Spec.PreferredStrategies[0]
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if *bestStrategy != c.Spec.PreferredStrategies[0] {
 | 
				
			||||||
 | 
									if idx := slices.Index(c.Spec.PreferredStrategies, *bestStrategy); idx > 0 {
 | 
				
			||||||
 | 
										bestStrategy = &c.Spec.PreferredStrategies[0]
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										klog.Infof("Error: bad strategy ordering")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return (*bestStrategy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func validLeaseCandidateForOldestEmulationVersion(l *v1alpha1.LeaseCandidate) bool {
 | 
				
			||||||
 | 
						_, err := semver.ParseTolerant(l.Spec.EmulationVersion)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = semver.ParseTolerant(l.Spec.BinaryVersion)
 | 
				
			||||||
 | 
						return err == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getEmulationVersion(l *v1alpha1.LeaseCandidate) semver.Version {
 | 
				
			||||||
 | 
						value := l.Spec.EmulationVersion
 | 
				
			||||||
 | 
						v, err := semver.ParseTolerant(value)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return semver.Version{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getBinaryVersion(l *v1alpha1.LeaseCandidate) semver.Version {
 | 
				
			||||||
 | 
						value := l.Spec.BinaryVersion
 | 
				
			||||||
 | 
						v, err := semver.ParseTolerant(value)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return semver.Version{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// -1: lhs better, 1: rhs better
 | 
				
			||||||
 | 
					func compare(lhs, rhs *v1alpha1.LeaseCandidate) int {
 | 
				
			||||||
 | 
						lhsVersion := getEmulationVersion(lhs)
 | 
				
			||||||
 | 
						rhsVersion := getEmulationVersion(rhs)
 | 
				
			||||||
 | 
						result := lhsVersion.Compare(rhsVersion)
 | 
				
			||||||
 | 
						if result == 0 {
 | 
				
			||||||
 | 
							lhsVersion := getBinaryVersion(lhs)
 | 
				
			||||||
 | 
							rhsVersion := getBinaryVersion(rhs)
 | 
				
			||||||
 | 
							result = lhsVersion.Compare(rhsVersion)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if result == 0 {
 | 
				
			||||||
 | 
							if lhs.CreationTimestamp.After(rhs.CreationTimestamp.Time) {
 | 
				
			||||||
 | 
								return 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return -1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isLeaseExpired(lease *v1.Lease) bool {
 | 
				
			||||||
 | 
						currentTime := time.Now()
 | 
				
			||||||
 | 
						return lease.Spec.RenewTime == nil ||
 | 
				
			||||||
 | 
							lease.Spec.LeaseDurationSeconds == nil ||
 | 
				
			||||||
 | 
							lease.Spec.RenewTime.Add(time.Duration(*lease.Spec.LeaseDurationSeconds)*time.Second).Before(currentTime)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isLeaseCandidateExpired(lease *v1alpha1.LeaseCandidate) bool {
 | 
				
			||||||
 | 
						currentTime := time.Now()
 | 
				
			||||||
 | 
						return lease.Spec.RenewTime == nil ||
 | 
				
			||||||
 | 
							lease.Spec.RenewTime.Add(leaseCandidateValidDuration).Before(currentTime)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										522
									
								
								pkg/controlplane/controller/leaderelection/election_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										522
									
								
								pkg/controlplane/controller/leaderelection/election_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,522 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/blang/semver/v4"
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						v1alpha1 "k8s.io/api/coordination/v1alpha1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPickBestLeaderOldestEmulationVersion(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name       string
 | 
				
			||||||
 | 
							candidates []*v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							want       *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:       "empty",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{},
 | 
				
			||||||
 | 
								want:       nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "single candidate",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate1",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now()},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      "candidate1",
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple candidates, different emulation versions",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate1",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate2",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now()},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.2.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.2.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      "candidate1",
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "v1",
 | 
				
			||||||
 | 
										BinaryVersion:    "v1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple candidates, same emulation versions, different binary versions",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate1",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate2",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now()},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.2.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      "candidate1",
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "multiple candidates, same emulation versions, same binary versions, different creation timestamps",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate1",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:              "candidate2",
 | 
				
			||||||
 | 
											Namespace:         "default",
 | 
				
			||||||
 | 
											CreationTimestamp: metav1.Time{Time: time.Now()},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
											BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      "candidate1",
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got := pickBestLeaderOldestEmulationVersion(tt.candidates)
 | 
				
			||||||
 | 
								if got != nil && tt.want != nil {
 | 
				
			||||||
 | 
									if got.Name != tt.want.Name || got.Namespace != tt.want.Namespace {
 | 
				
			||||||
 | 
										t.Errorf("pickBestLeaderOldestEmulationVersion() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else if got != tt.want {
 | 
				
			||||||
 | 
									t.Errorf("pickBestLeaderOldestEmulationVersion() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidLeaseCandidateForOldestEmulationVersion(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							candidate *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							want      bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid emulation and binary versions",
 | 
				
			||||||
 | 
								candidate: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid emulation version",
 | 
				
			||||||
 | 
								candidate: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "invalid",
 | 
				
			||||||
 | 
										BinaryVersion:    "0.1.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid binary version",
 | 
				
			||||||
 | 
								candidate: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "invalid",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got := validLeaseCandidateForOldestEmulationVersion(tt.candidate)
 | 
				
			||||||
 | 
								if got != tt.want {
 | 
				
			||||||
 | 
									t.Errorf("validLeaseCandidateForOldestEmulationVersion() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetEmulationVersion(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							candidate *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							want      semver.Version
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid emulation version",
 | 
				
			||||||
 | 
								candidate: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "0.1.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: semver.MustParse("0.1.0"),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got := getEmulationVersion(tt.candidate)
 | 
				
			||||||
 | 
								if got.FinalizeVersion() != tt.want.FinalizeVersion() {
 | 
				
			||||||
 | 
									t.Errorf("getEmulationVersion() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetBinaryVersion(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							candidate *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							want      semver.Version
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid binary version",
 | 
				
			||||||
 | 
								candidate: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										BinaryVersion: "0.3.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: semver.MustParse("0.3.0"),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got := getBinaryVersion(tt.candidate)
 | 
				
			||||||
 | 
								if got.FinalizeVersion() != tt.want.FinalizeVersion() {
 | 
				
			||||||
 | 
									t.Errorf("getBinaryVersion() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCompare(t *testing.T) {
 | 
				
			||||||
 | 
						nowTime := time.Now()
 | 
				
			||||||
 | 
						cases := []struct {
 | 
				
			||||||
 | 
							name           string
 | 
				
			||||||
 | 
							lhs            *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							rhs            *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							expectedResult int
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "identical versions earlier timestamp",
 | 
				
			||||||
 | 
								lhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.21.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										CreationTimestamp: metav1.Time{Time: nowTime.Add(time.Duration(1))},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								rhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.21.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										CreationTimestamp: metav1.Time{Time: nowTime},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedResult: 1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "no lhs version",
 | 
				
			||||||
 | 
								lhs:  &v1alpha1.LeaseCandidate{},
 | 
				
			||||||
 | 
								rhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.21.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedResult: -1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "no rhs version",
 | 
				
			||||||
 | 
								lhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.21.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								rhs:            &v1alpha1.LeaseCandidate{},
 | 
				
			||||||
 | 
								expectedResult: 1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid lhs version",
 | 
				
			||||||
 | 
								lhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "xyz",
 | 
				
			||||||
 | 
										BinaryVersion:    "xyz",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								rhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.21.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedResult: -1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid rhs version",
 | 
				
			||||||
 | 
								lhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.21.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								rhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "xyz",
 | 
				
			||||||
 | 
										BinaryVersion:    "xyz",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedResult: 1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "lhs less than rhs",
 | 
				
			||||||
 | 
								lhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.19.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.20.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								rhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.20.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedResult: -1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "rhs less than lhs",
 | 
				
			||||||
 | 
								lhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.20.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.20.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								rhs: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion: "1.19.0",
 | 
				
			||||||
 | 
										BinaryVersion:    "1.20.0",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedResult: 1,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range cases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								result := compare(tc.lhs, tc.rhs)
 | 
				
			||||||
 | 
								if result != tc.expectedResult {
 | 
				
			||||||
 | 
									t.Errorf("Expected comparison result of %d but got %d", tc.expectedResult, result)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestShouldReelect(t *testing.T) {
 | 
				
			||||||
 | 
						cases := []struct {
 | 
				
			||||||
 | 
							name          string
 | 
				
			||||||
 | 
							candidates    []*v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							currentLeader *v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							expectResult  bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "candidate with newer binary version",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "component-identity-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.20.0",
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								currentLeader: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "component-identity-1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
										BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
										PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectResult: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "no newer candidates",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "component-identity-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								currentLeader: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "component-identity-1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
										BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
										PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectResult: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:       "no candidates",
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{},
 | 
				
			||||||
 | 
								currentLeader: &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "component-identity-1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
										EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
										BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
										PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectResult: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// TODO: Add test cases where candidates have invalid version numbers
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range cases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								result := shouldReelect(tc.candidates, tc.currentLeader)
 | 
				
			||||||
 | 
								if tc.expectResult != result {
 | 
				
			||||||
 | 
									t.Errorf("Expected %t but got %t", tc.expectResult, result)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,399 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						v1alpha1 "k8s.io/api/coordination/v1alpha1"
 | 
				
			||||||
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						coordinationv1informers "k8s.io/client-go/informers/coordination/v1"
 | 
				
			||||||
 | 
						coordinationv1alpha1 "k8s.io/client-go/informers/coordination/v1alpha1"
 | 
				
			||||||
 | 
						coordinationv1client "k8s.io/client-go/kubernetes/typed/coordination/v1"
 | 
				
			||||||
 | 
						coordinationv1alpha1client "k8s.io/client-go/kubernetes/typed/coordination/v1alpha1"
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/cache"
 | 
				
			||||||
 | 
						"k8s.io/client-go/util/workqueue"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
						"k8s.io/utils/ptr"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						controllerName          = "leader-election-controller"
 | 
				
			||||||
 | 
						ElectedByAnnotationName = "coordination.k8s.io/elected-by" // Value should be set to controllerName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Requeue interval is the interval at which a Lease is requeued to verify that it is being renewed properly.
 | 
				
			||||||
 | 
						requeueInterval                   = 5 * time.Second
 | 
				
			||||||
 | 
						defaultLeaseDurationSeconds int32 = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						electionDuration = 5 * time.Second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						leaseCandidateValidDuration = 5 * time.Minute
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Controller is the leader election controller, which observes component identity leases for
 | 
				
			||||||
 | 
					// components that have self-nominated as candidate leaders for leases and elects leaders
 | 
				
			||||||
 | 
					// for those leases, favoring candidates with higher versions.
 | 
				
			||||||
 | 
					type Controller struct {
 | 
				
			||||||
 | 
						leaseInformer     coordinationv1informers.LeaseInformer
 | 
				
			||||||
 | 
						leaseClient       coordinationv1client.CoordinationV1Interface
 | 
				
			||||||
 | 
						leaseRegistration cache.ResourceEventHandlerRegistration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						leaseCandidateInformer     coordinationv1alpha1.LeaseCandidateInformer
 | 
				
			||||||
 | 
						leaseCandidateClient       coordinationv1alpha1client.CoordinationV1alpha1Interface
 | 
				
			||||||
 | 
						leaseCandidateRegistration cache.ResourceEventHandlerRegistration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						queue workqueue.TypedRateLimitingInterface[types.NamespacedName]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) Run(ctx context.Context, workers int) {
 | 
				
			||||||
 | 
						defer utilruntime.HandleCrash()
 | 
				
			||||||
 | 
						defer c.queue.ShutDown()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							err := c.leaseInformer.Informer().RemoveEventHandler(c.leaseRegistration)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								klog.Warning("error removing leaseInformer eventhandler")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = c.leaseCandidateInformer.Informer().RemoveEventHandler(c.leaseCandidateRegistration)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								klog.Warning("error removing leaseCandidateInformer eventhandler")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !cache.WaitForNamedCacheSync(controllerName, ctx.Done(), c.leaseRegistration.HasSynced, c.leaseCandidateRegistration.HasSynced) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// This controller is leader elected and may start after informers have already started. List on startup.
 | 
				
			||||||
 | 
						lcs, err := c.leaseCandidateInformer.Lister().List(labels.Everything())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							utilruntime.HandleError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, lc := range lcs {
 | 
				
			||||||
 | 
							c.processCandidate(lc)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						klog.Infof("Workers: %d", workers)
 | 
				
			||||||
 | 
						for i := 0; i < workers; i++ {
 | 
				
			||||||
 | 
							klog.Infof("Starting worker")
 | 
				
			||||||
 | 
							go wait.UntilWithContext(ctx, c.runElectionWorker, time.Second)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						<-ctx.Done()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewController(leaseInformer coordinationv1informers.LeaseInformer, leaseCandidateInformer coordinationv1alpha1.LeaseCandidateInformer, leaseClient coordinationv1client.CoordinationV1Interface, leaseCandidateClient coordinationv1alpha1client.CoordinationV1alpha1Interface) (*Controller, error) {
 | 
				
			||||||
 | 
						c := &Controller{
 | 
				
			||||||
 | 
							leaseInformer:          leaseInformer,
 | 
				
			||||||
 | 
							leaseCandidateInformer: leaseCandidateInformer,
 | 
				
			||||||
 | 
							leaseClient:            leaseClient,
 | 
				
			||||||
 | 
							leaseCandidateClient:   leaseCandidateClient,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							queue: workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[types.NamespacedName](), workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{Name: controllerName}),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						leaseSynced, err := leaseInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
 | 
				
			||||||
 | 
							AddFunc: func(obj interface{}) {
 | 
				
			||||||
 | 
								c.processLease(obj)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							UpdateFunc: func(oldObj, newObj interface{}) {
 | 
				
			||||||
 | 
								c.processLease(newObj)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							DeleteFunc: func(oldObj interface{}) {
 | 
				
			||||||
 | 
								c.processLease(oldObj)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						leaseCandidateSynced, err := leaseCandidateInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
 | 
				
			||||||
 | 
							AddFunc: func(obj interface{}) {
 | 
				
			||||||
 | 
								c.processCandidate(obj)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							UpdateFunc: func(oldObj, newObj interface{}) {
 | 
				
			||||||
 | 
								c.processCandidate(newObj)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							DeleteFunc: func(oldObj interface{}) {
 | 
				
			||||||
 | 
								c.processCandidate(oldObj)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.leaseRegistration = leaseSynced
 | 
				
			||||||
 | 
						c.leaseCandidateRegistration = leaseCandidateSynced
 | 
				
			||||||
 | 
						return c, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) runElectionWorker(ctx context.Context) {
 | 
				
			||||||
 | 
						for c.processNextElectionItem(ctx) {
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) processNextElectionItem(ctx context.Context) bool {
 | 
				
			||||||
 | 
						key, shutdown := c.queue.Get()
 | 
				
			||||||
 | 
						if shutdown {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						completed, err := c.reconcileElectionStep(ctx, key)
 | 
				
			||||||
 | 
						utilruntime.HandleError(err)
 | 
				
			||||||
 | 
						if completed {
 | 
				
			||||||
 | 
							defer c.queue.AddAfter(key, requeueInterval)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.queue.Done(key)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) processCandidate(obj any) {
 | 
				
			||||||
 | 
						lc, ok := obj.(*v1alpha1.LeaseCandidate)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if lc == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Ignore candidates that transitioned to Pending because reelection is already in progress
 | 
				
			||||||
 | 
						if lc.Spec.PingTime != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.queue.Add(types.NamespacedName{Namespace: lc.Namespace, Name: lc.Spec.LeaseName})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) processLease(obj any) {
 | 
				
			||||||
 | 
						lease, ok := obj.(*v1.Lease)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.queue.Add(types.NamespacedName{Namespace: lease.Namespace, Name: lease.Name})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) electionNeeded(candidates []*v1alpha1.LeaseCandidate, leaseNN types.NamespacedName) (bool, error) {
 | 
				
			||||||
 | 
						lease, err := c.leaseInformer.Lister().Leases(leaseNN.Namespace).Get(leaseNN.Name)
 | 
				
			||||||
 | 
						if err != nil && !apierrors.IsNotFound(err) {
 | 
				
			||||||
 | 
							return false, fmt.Errorf("error reading lease")
 | 
				
			||||||
 | 
						} else if apierrors.IsNotFound(err) {
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if isLeaseExpired(lease) || lease.Spec.HolderIdentity == nil || *lease.Spec.HolderIdentity == "" {
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						prelimStrategy := pickBestStrategy(candidates)
 | 
				
			||||||
 | 
						if prelimStrategy != v1.OldestEmulationVersion {
 | 
				
			||||||
 | 
							klog.V(2).Infof("strategy %s is not recognized by CLE.", prelimStrategy)
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						prelimElectee := pickBestLeaderOldestEmulationVersion(candidates)
 | 
				
			||||||
 | 
						if prelimElectee == nil {
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						} else if lease != nil && lease.Spec.HolderIdentity != nil && prelimElectee.Name == *lease.Spec.HolderIdentity {
 | 
				
			||||||
 | 
							klog.V(2).Infof("Leader %s is already most optimal for lease %s %s", prelimElectee.Name, lease.Namespace, lease.Name)
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reconcileElectionStep steps through a step in an election.
 | 
				
			||||||
 | 
					// A step looks at the current state of Lease and LeaseCandidates and takes one of the following action
 | 
				
			||||||
 | 
					// - do nothing (because leader is already optimal or still waiting for an event)
 | 
				
			||||||
 | 
					// - request ack from candidates (update LeaseCandidate PingTime)
 | 
				
			||||||
 | 
					// - finds the most optimal candidate and elect (update the Lease object)
 | 
				
			||||||
 | 
					// Instead of keeping a map and lock on election, the state is
 | 
				
			||||||
 | 
					// calculated every time by looking at the lease, and set of available candidates.
 | 
				
			||||||
 | 
					// PingTime + electionDuration > time.Now: We just asked all candidates to ack and are still waiting for response
 | 
				
			||||||
 | 
					// PingTime + electionDuration < time.Now: Candidate has not responded within the appropriate PingTime. Continue the election.
 | 
				
			||||||
 | 
					// RenewTime + 5 seconds > time.Now: All candidates acked in the last 5 seconds, continue the election.
 | 
				
			||||||
 | 
					func (c *Controller) reconcileElectionStep(ctx context.Context, leaseNN types.NamespacedName) (requeue bool, err error) {
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						candidates, err := c.listAdmissableCandidates(leaseNN)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return true, err
 | 
				
			||||||
 | 
						} else if len(candidates) == 0 {
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						klog.V(4).Infof("reconcileElectionStep %q %q, candidates: %d", leaseNN.Namespace, leaseNN.Name, len(candidates))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if an election is really needed by looking at the current lease
 | 
				
			||||||
 | 
						// and set of candidates
 | 
				
			||||||
 | 
						needElection, err := c.electionNeeded(candidates, leaseNN)
 | 
				
			||||||
 | 
						if !needElection || err != nil {
 | 
				
			||||||
 | 
							return needElection, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fastTrackElection := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, candidate := range candidates {
 | 
				
			||||||
 | 
							// If a candidate has a PingTime within the election duration, they have not acked
 | 
				
			||||||
 | 
							// and we should wait until we receive their response
 | 
				
			||||||
 | 
							if candidate.Spec.PingTime != nil {
 | 
				
			||||||
 | 
								if candidate.Spec.PingTime.Add(electionDuration).After(now) {
 | 
				
			||||||
 | 
									// continue waiting for the election to timeout
 | 
				
			||||||
 | 
									return false, nil
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// election timed out without ack. Clear and start election.
 | 
				
			||||||
 | 
									fastTrackElection = true
 | 
				
			||||||
 | 
									clone := candidate.DeepCopy()
 | 
				
			||||||
 | 
									clone.Spec.PingTime = nil
 | 
				
			||||||
 | 
									_, err := c.leaseCandidateClient.LeaseCandidates(clone.Namespace).Update(ctx, clone, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return false, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !fastTrackElection {
 | 
				
			||||||
 | 
							continueElection := true
 | 
				
			||||||
 | 
							for _, candidate := range candidates {
 | 
				
			||||||
 | 
								// if renewTime of a candidate is longer than electionDuration old, we have to ping.
 | 
				
			||||||
 | 
								if candidate.Spec.RenewTime != nil && candidate.Spec.RenewTime.Add(electionDuration).Before(now) {
 | 
				
			||||||
 | 
									continueElection = false
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !continueElection {
 | 
				
			||||||
 | 
								// Send an "are you alive" signal to all candidates
 | 
				
			||||||
 | 
								for _, candidate := range candidates {
 | 
				
			||||||
 | 
									clone := candidate.DeepCopy()
 | 
				
			||||||
 | 
									clone.Spec.PingTime = &metav1.MicroTime{Time: time.Now()}
 | 
				
			||||||
 | 
									_, err := c.leaseCandidateClient.LeaseCandidates(clone.Namespace).Update(ctx, clone, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return false, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var ackedCandidates []*v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
						for _, candidate := range candidates {
 | 
				
			||||||
 | 
							if candidate.Spec.RenewTime.Add(electionDuration).After(now) {
 | 
				
			||||||
 | 
								ackedCandidates = append(ackedCandidates, candidate)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(ackedCandidates) == 0 {
 | 
				
			||||||
 | 
							return false, fmt.Errorf("no available candidates")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						strategy := pickBestStrategy(ackedCandidates)
 | 
				
			||||||
 | 
						if strategy != v1.OldestEmulationVersion {
 | 
				
			||||||
 | 
							klog.V(2).Infof("strategy %s is not recognized by CLE.", strategy)
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						electee := pickBestLeaderOldestEmulationVersion(ackedCandidates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if electee == nil {
 | 
				
			||||||
 | 
							return false, fmt.Errorf("should not happen, could not find suitable electee")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						electeeName := electee.Name
 | 
				
			||||||
 | 
						// create the leader election lease
 | 
				
			||||||
 | 
						leaderLease := &v1.Lease{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Namespace: leaseNN.Namespace,
 | 
				
			||||||
 | 
								Name:      leaseNN.Name,
 | 
				
			||||||
 | 
								Annotations: map[string]string{
 | 
				
			||||||
 | 
									ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
								HolderIdentity:       &electeeName,
 | 
				
			||||||
 | 
								Strategy:             &strategy,
 | 
				
			||||||
 | 
								LeaseDurationSeconds: ptr.To(defaultLeaseDurationSeconds),
 | 
				
			||||||
 | 
								RenewTime:            &metav1.MicroTime{Time: time.Now()},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = c.leaseClient.Leases(leaseNN.Namespace).Create(ctx, leaderLease, metav1.CreateOptions{})
 | 
				
			||||||
 | 
						// If the create was successful, then we can return here.
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							klog.Infof("Created lease %q %q for %q", leaseNN.Namespace, leaseNN.Name, electee.Name)
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If there was an error, return
 | 
				
			||||||
 | 
						if !apierrors.IsAlreadyExists(err) {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						existingLease, err := c.leaseClient.Leases(leaseNN.Namespace).Get(ctx, leaseNN.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						leaseClone := existingLease.DeepCopy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the Lease if it either does not have a holder or is expired
 | 
				
			||||||
 | 
						isExpired := isLeaseExpired(existingLease)
 | 
				
			||||||
 | 
						if leaseClone.Spec.HolderIdentity == nil || *leaseClone.Spec.HolderIdentity == "" || (isExpired && *leaseClone.Spec.HolderIdentity != electeeName) {
 | 
				
			||||||
 | 
							klog.Infof("lease %q %q is expired, resetting it and setting holder to %q", leaseNN.Namespace, leaseNN.Name, electee.Name)
 | 
				
			||||||
 | 
							leaseClone.Spec.Strategy = &strategy
 | 
				
			||||||
 | 
							leaseClone.Spec.PreferredHolder = nil
 | 
				
			||||||
 | 
							if leaseClone.ObjectMeta.Annotations == nil {
 | 
				
			||||||
 | 
								leaseClone.ObjectMeta.Annotations = make(map[string]string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							leaseClone.ObjectMeta.Annotations[ElectedByAnnotationName] = controllerName
 | 
				
			||||||
 | 
							leaseClone.Spec.HolderIdentity = &electeeName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							leaseClone.Spec.RenewTime = &metav1.MicroTime{Time: time.Now()}
 | 
				
			||||||
 | 
							leaseClone.Spec.LeaseDurationSeconds = ptr.To(defaultLeaseDurationSeconds)
 | 
				
			||||||
 | 
							leaseClone.Spec.AcquireTime = nil
 | 
				
			||||||
 | 
							_, err = c.leaseClient.Leases(leaseNN.Namespace).Update(ctx, leaseClone, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if leaseClone.Spec.HolderIdentity != nil && *leaseClone.Spec.HolderIdentity != electeeName {
 | 
				
			||||||
 | 
							klog.Infof("lease %q %q already exists for holder %q but should be held by %q, marking preferredHolder", leaseNN.Namespace, leaseNN.Name, *leaseClone.Spec.HolderIdentity, electee.Name)
 | 
				
			||||||
 | 
							leaseClone.Spec.PreferredHolder = &electeeName
 | 
				
			||||||
 | 
							leaseClone.Spec.Strategy = &strategy
 | 
				
			||||||
 | 
							_, err = c.leaseClient.Leases(leaseNN.Namespace).Update(ctx, leaseClone, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) listAdmissableCandidates(leaseNN types.NamespacedName) ([]*v1alpha1.LeaseCandidate, error) {
 | 
				
			||||||
 | 
						leases, err := c.leaseCandidateInformer.Lister().LeaseCandidates(leaseNN.Namespace).List(labels.Everything())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var results []*v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
						for _, l := range leases {
 | 
				
			||||||
 | 
							if l.Spec.LeaseName != leaseNN.Name {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !isLeaseCandidateExpired(l) {
 | 
				
			||||||
 | 
								results = append(results, l)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								klog.Infof("LeaseCandidate %s is expired", l.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return results, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,698 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						v1alpha1 "k8s.io/api/coordination/v1alpha1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						"k8s.io/client-go/informers"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes/fake"
 | 
				
			||||||
 | 
						"k8s.io/utils/ptr"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/cache"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReconcileElectionStep(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name                    string
 | 
				
			||||||
 | 
							leaseNN                 types.NamespacedName
 | 
				
			||||||
 | 
							candidates              []*v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							existingLease           *v1.Lease
 | 
				
			||||||
 | 
							expectedHolderIdentity  *string
 | 
				
			||||||
 | 
							expectedPreferredHolder string
 | 
				
			||||||
 | 
							expectedRequeue         bool
 | 
				
			||||||
 | 
							expectedError           bool
 | 
				
			||||||
 | 
							candidatesPinged        bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                   "no candidates, no lease, noop",
 | 
				
			||||||
 | 
								leaseNN:                types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates:             []*v1alpha1.LeaseCandidate{},
 | 
				
			||||||
 | 
								existingLease:          nil,
 | 
				
			||||||
 | 
								expectedHolderIdentity: nil,
 | 
				
			||||||
 | 
								expectedRequeue:        false,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                   "no candidates, lease exists. noop, not managed by CLE",
 | 
				
			||||||
 | 
								leaseNN:                types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates:             []*v1alpha1.LeaseCandidate{},
 | 
				
			||||||
 | 
								existingLease:          &v1.Lease{},
 | 
				
			||||||
 | 
								expectedHolderIdentity: nil,
 | 
				
			||||||
 | 
								expectedRequeue:        false,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, no existing lease should create lease",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease:          nil,
 | 
				
			||||||
 | 
								expectedHolderIdentity: ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
								expectedRequeue:        true,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, lease exists, unoptimal should set preferredHolder",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.18.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.18.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease: &v1.Lease{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
										Name:      "component-A",
 | 
				
			||||||
 | 
										Annotations: map[string]string{
 | 
				
			||||||
 | 
											ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
										HolderIdentity:       ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
										LeaseDurationSeconds: ptr.To(int32(10)),
 | 
				
			||||||
 | 
										RenewTime:            ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedHolderIdentity:  ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
								expectedPreferredHolder: "component-identity-2",
 | 
				
			||||||
 | 
								expectedRequeue:         true,
 | 
				
			||||||
 | 
								expectedError:           false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, should only elect leader from acked candidates",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											PingTime:            ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * electionDuration))),
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * electionDuration))),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.20.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.20.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease:          nil,
 | 
				
			||||||
 | 
								expectedHolderIdentity: ptr.To("component-identity-2"),
 | 
				
			||||||
 | 
								expectedRequeue:        true,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, lease exists, lease expired",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease: &v1.Lease{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Namespace: "default",
 | 
				
			||||||
 | 
										Name:      "component-A",
 | 
				
			||||||
 | 
										Annotations: map[string]string{
 | 
				
			||||||
 | 
											ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
										HolderIdentity:       ptr.To("component-identity-expired"),
 | 
				
			||||||
 | 
										LeaseDurationSeconds: ptr.To(int32(10)),
 | 
				
			||||||
 | 
										RenewTime:            ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedHolderIdentity: ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
								expectedRequeue:        true,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, no acked candidates should return error",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											PingTime:            ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now().Add(-1 * time.Minute))),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease:          nil,
 | 
				
			||||||
 | 
								expectedHolderIdentity: nil,
 | 
				
			||||||
 | 
								expectedRequeue:        false,
 | 
				
			||||||
 | 
								expectedError:          true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, should ping on election",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * electionDuration))),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease:          nil,
 | 
				
			||||||
 | 
								expectedHolderIdentity: nil,
 | 
				
			||||||
 | 
								expectedRequeue:        true,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
								candidatesPinged:       true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "candidates exist, ping within electionDuration should cause no state change",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "default", Name: "component-A"},
 | 
				
			||||||
 | 
								candidates: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "default",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											PingTime:            ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now().Add(-2 * electionDuration))),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								existingLease:          nil,
 | 
				
			||||||
 | 
								expectedHolderIdentity: nil,
 | 
				
			||||||
 | 
								expectedRequeue:        false,
 | 
				
			||||||
 | 
								expectedError:          false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range tests {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								ctx := context.Background()
 | 
				
			||||||
 | 
								client := fake.NewSimpleClientset()
 | 
				
			||||||
 | 
								informerFactory := informers.NewSharedInformerFactory(client, 0)
 | 
				
			||||||
 | 
								_ = informerFactory.Coordination().V1alpha1().LeaseCandidates().Lister()
 | 
				
			||||||
 | 
								controller, err := NewController(
 | 
				
			||||||
 | 
									informerFactory.Coordination().V1().Leases(),
 | 
				
			||||||
 | 
									informerFactory.Coordination().V1alpha1().LeaseCandidates(),
 | 
				
			||||||
 | 
									client.CoordinationV1(),
 | 
				
			||||||
 | 
									client.CoordinationV1alpha1(),
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								go informerFactory.Start(ctx.Done())
 | 
				
			||||||
 | 
								informerFactory.WaitForCacheSync(ctx.Done())
 | 
				
			||||||
 | 
								// Set up the fake client with the existing lease
 | 
				
			||||||
 | 
								if tc.existingLease != nil {
 | 
				
			||||||
 | 
									_, err = client.CoordinationV1().Leases(tc.existingLease.Namespace).Create(ctx, tc.existingLease, metav1.CreateOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set up the fake client with the candidates
 | 
				
			||||||
 | 
								for _, candidate := range tc.candidates {
 | 
				
			||||||
 | 
									_, err = client.CoordinationV1alpha1().LeaseCandidates(candidate.Namespace).Create(ctx, candidate, metav1.CreateOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								cache.WaitForCacheSync(ctx.Done(), controller.leaseCandidateInformer.Informer().HasSynced)
 | 
				
			||||||
 | 
								requeue, err := controller.reconcileElectionStep(ctx, tc.leaseNN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if requeue != tc.expectedRequeue {
 | 
				
			||||||
 | 
									t.Errorf("reconcileElectionStep() requeue = %v, want %v", requeue, tc.expectedRequeue)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if tc.expectedError && err == nil {
 | 
				
			||||||
 | 
									t.Errorf("reconcileElectionStep() error = %v, want error", err)
 | 
				
			||||||
 | 
								} else if !tc.expectedError && err != nil {
 | 
				
			||||||
 | 
									t.Errorf("reconcileElectionStep() error = %v, want nil", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check the lease holder identity
 | 
				
			||||||
 | 
								if tc.expectedHolderIdentity != nil {
 | 
				
			||||||
 | 
									lease, err := client.CoordinationV1().Leases(tc.leaseNN.Namespace).Get(ctx, tc.leaseNN.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if lease.Spec.HolderIdentity == nil || *lease.Spec.HolderIdentity != *tc.expectedHolderIdentity {
 | 
				
			||||||
 | 
										t.Errorf("reconcileElectionStep() holderIdentity = %v, want %v", *lease.Spec.HolderIdentity, *tc.expectedHolderIdentity)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if tc.expectedPreferredHolder != "" {
 | 
				
			||||||
 | 
										if lease.Spec.PreferredHolder == nil || *lease.Spec.PreferredHolder != tc.expectedPreferredHolder {
 | 
				
			||||||
 | 
											t.Errorf("reconcileElectionStep() preferredHolder = %v, want %v", lease.Spec.PreferredHolder, tc.expectedPreferredHolder)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Verify that ping to candidate was issued
 | 
				
			||||||
 | 
								if tc.candidatesPinged {
 | 
				
			||||||
 | 
									pinged := false
 | 
				
			||||||
 | 
									candidatesList, err := client.CoordinationV1alpha1().LeaseCandidates(tc.leaseNN.Namespace).List(ctx, metav1.ListOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									oldCandidateMap := make(map[string]*v1alpha1.LeaseCandidate)
 | 
				
			||||||
 | 
									for _, candidate := range tc.candidates {
 | 
				
			||||||
 | 
										oldCandidateMap[candidate.Name] = candidate
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									for _, candidate := range candidatesList.Items {
 | 
				
			||||||
 | 
										if candidate.Spec.PingTime != nil {
 | 
				
			||||||
 | 
											if oldCandidateMap[candidate.Name].Spec.PingTime == nil {
 | 
				
			||||||
 | 
												pinged = true
 | 
				
			||||||
 | 
												break
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !pinged {
 | 
				
			||||||
 | 
										t.Errorf("reconcileElectionStep() expected candidates to be pinged")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestController(t *testing.T) {
 | 
				
			||||||
 | 
						cases := []struct {
 | 
				
			||||||
 | 
							name                       string
 | 
				
			||||||
 | 
							leaseNN                    types.NamespacedName
 | 
				
			||||||
 | 
							createAfterControllerStart []*v1alpha1.LeaseCandidate
 | 
				
			||||||
 | 
							deleteAfterControllerStart []types.NamespacedName
 | 
				
			||||||
 | 
							expectedLeaderLeases       []*v1.Lease
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "single candidate leader election",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
 | 
				
			||||||
 | 
								createAfterControllerStart: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedLeaderLeases: []*v1.Lease{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-A",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
											HolderIdentity: ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "multiple candidate leader election",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
 | 
				
			||||||
 | 
								createAfterControllerStart: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.20.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-3",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.20.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.20.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedLeaderLeases: []*v1.Lease{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-A",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
											HolderIdentity: ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "deletion of lease triggers reelection",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
 | 
				
			||||||
 | 
								createAfterControllerStart: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										// Leader lease
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-A",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								deleteAfterControllerStart: []types.NamespacedName{
 | 
				
			||||||
 | 
									{Namespace: "kube-system", Name: "component-A"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedLeaderLeases: []*v1.Lease{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-A",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
											HolderIdentity: ptr.To("component-identity-1"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "better candidate triggers reelection",
 | 
				
			||||||
 | 
								leaseNN: types.NamespacedName{Namespace: "kube-system", Name: "component-A"},
 | 
				
			||||||
 | 
								createAfterControllerStart: []*v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										// Leader lease
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-A",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.20.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.20.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-identity-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
											LeaseName:           "component-A",
 | 
				
			||||||
 | 
											EmulationVersion:    "1.19.0",
 | 
				
			||||||
 | 
											BinaryVersion:       "1.19.0",
 | 
				
			||||||
 | 
											RenewTime:           ptr.To(metav1.NewMicroTime(time.Now())),
 | 
				
			||||||
 | 
											PreferredStrategies: []v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedLeaderLeases: []*v1.Lease{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Namespace: "kube-system",
 | 
				
			||||||
 | 
											Name:      "component-A",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												ElectedByAnnotationName: controllerName,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1.LeaseSpec{
 | 
				
			||||||
 | 
											HolderIdentity: ptr.To("component-identity-2"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range cases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 | 
				
			||||||
 | 
								defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								client := fake.NewSimpleClientset()
 | 
				
			||||||
 | 
								informerFactory := informers.NewSharedInformerFactory(client, 0)
 | 
				
			||||||
 | 
								controller, err := NewController(
 | 
				
			||||||
 | 
									informerFactory.Coordination().V1().Leases(),
 | 
				
			||||||
 | 
									informerFactory.Coordination().V1alpha1().LeaseCandidates(),
 | 
				
			||||||
 | 
									client.CoordinationV1(),
 | 
				
			||||||
 | 
									client.CoordinationV1alpha1(),
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								go informerFactory.Start(ctx.Done())
 | 
				
			||||||
 | 
								go controller.Run(ctx, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								go func() {
 | 
				
			||||||
 | 
									ticker := time.NewTicker(10 * time.Millisecond)
 | 
				
			||||||
 | 
									// Mock out the removal of preferredHolder leases.
 | 
				
			||||||
 | 
									// When controllers are running, they are expected to do this voluntarily
 | 
				
			||||||
 | 
									for {
 | 
				
			||||||
 | 
										select {
 | 
				
			||||||
 | 
										case <-ctx.Done():
 | 
				
			||||||
 | 
											return
 | 
				
			||||||
 | 
										case <-ticker.C:
 | 
				
			||||||
 | 
											for _, expectedLease := range tc.expectedLeaderLeases {
 | 
				
			||||||
 | 
												lease, err := client.CoordinationV1().Leases(expectedLease.Namespace).Get(ctx, expectedLease.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
												if err == nil {
 | 
				
			||||||
 | 
													if preferredHolder := lease.Spec.PreferredHolder; preferredHolder != nil {
 | 
				
			||||||
 | 
														err = client.CoordinationV1().Leases(expectedLease.Namespace).Delete(ctx, expectedLease.Name, metav1.DeleteOptions{})
 | 
				
			||||||
 | 
														if err != nil {
 | 
				
			||||||
 | 
															runtime.HandleError(err)
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								go func() {
 | 
				
			||||||
 | 
									ticker := time.NewTicker(10 * time.Millisecond)
 | 
				
			||||||
 | 
									// Mock out leasecandidate ack.
 | 
				
			||||||
 | 
									// When controllers are running, they are expected to watch and ack
 | 
				
			||||||
 | 
									for {
 | 
				
			||||||
 | 
										select {
 | 
				
			||||||
 | 
										case <-ctx.Done():
 | 
				
			||||||
 | 
											return
 | 
				
			||||||
 | 
										case <-ticker.C:
 | 
				
			||||||
 | 
											for _, lc := range tc.createAfterControllerStart {
 | 
				
			||||||
 | 
												lease, err := client.CoordinationV1alpha1().LeaseCandidates(lc.Namespace).Get(ctx, lc.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
												if err == nil {
 | 
				
			||||||
 | 
													if lease.Spec.PingTime != nil {
 | 
				
			||||||
 | 
														c := lease.DeepCopy()
 | 
				
			||||||
 | 
														c.Spec.PingTime = nil
 | 
				
			||||||
 | 
														c.Spec.RenewTime = &metav1.MicroTime{Time: time.Now()}
 | 
				
			||||||
 | 
														_, err = client.CoordinationV1alpha1().LeaseCandidates(lc.Namespace).Update(ctx, c, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
														if err != nil {
 | 
				
			||||||
 | 
															runtime.HandleError(err)
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, obj := range tc.createAfterControllerStart {
 | 
				
			||||||
 | 
									_, err := client.CoordinationV1alpha1().LeaseCandidates(obj.Namespace).Create(ctx, obj, metav1.CreateOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, obj := range tc.deleteAfterControllerStart {
 | 
				
			||||||
 | 
									err := client.CoordinationV1alpha1().LeaseCandidates(obj.Namespace).Delete(ctx, obj.Name, metav1.DeleteOptions{})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, expectedLease := range tc.expectedLeaderLeases {
 | 
				
			||||||
 | 
									var lease *v1.Lease
 | 
				
			||||||
 | 
									err = wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 600*time.Second, true, func(ctx context.Context) (done bool, err error) {
 | 
				
			||||||
 | 
										lease, err = client.CoordinationV1().Leases(expectedLease.Namespace).Get(ctx, expectedLease.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											if errors.IsNotFound(err) {
 | 
				
			||||||
 | 
												return false, nil
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											return true, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if expectedLease.Spec.HolderIdentity == nil || lease.Spec.HolderIdentity == nil {
 | 
				
			||||||
 | 
											return expectedLease.Spec.HolderIdentity == nil && lease.Spec.HolderIdentity == nil, nil
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if expectedLease.Spec.HolderIdentity != nil && lease.Spec.HolderIdentity != nil && *expectedLease.Spec.HolderIdentity != *lease.Spec.HolderIdentity {
 | 
				
			||||||
 | 
											return false, nil
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return true, nil
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if lease.Spec.HolderIdentity == nil {
 | 
				
			||||||
 | 
										t.Fatalf("Expected HolderIdentity of %s but got nil", expectedLease.Name)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if *lease.Spec.HolderIdentity != *expectedLease.Spec.HolderIdentity {
 | 
				
			||||||
 | 
										t.Errorf("Expected HolderIdentity of %s but got %s", *expectedLease.Spec.HolderIdentity, *lease.Spec.HolderIdentity)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/uuid"
 | 
				
			||||||
 | 
						"k8s.io/client-go/rest"
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/leaderelection"
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/leaderelection/resourcelock"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type NewRunner func() (func(ctx context.Context, workers int), error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RunWithLeaderElection runs the provided runner function with leader election.
 | 
				
			||||||
 | 
					// newRunnerFn might be called multiple times, and it should return another
 | 
				
			||||||
 | 
					// controller instance's Run method each time.
 | 
				
			||||||
 | 
					// RunWithLeaderElection only returns when the context is done, or initial
 | 
				
			||||||
 | 
					// leader election fails.
 | 
				
			||||||
 | 
					func RunWithLeaderElection(ctx context.Context, config *rest.Config, newRunnerFn NewRunner) {
 | 
				
			||||||
 | 
						var cancel context.CancelFunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						callbacks := leaderelection.LeaderCallbacks{
 | 
				
			||||||
 | 
							OnStartedLeading: func(ctx context.Context) {
 | 
				
			||||||
 | 
								ctx, cancel = context.WithCancel(ctx)
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
								run, err := newRunnerFn()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									klog.Infof("Error creating runner: %v", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								run(ctx, 1)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							OnStoppedLeading: func() {
 | 
				
			||||||
 | 
								cancel()
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hostname, err := os.Hostname()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.Infof("Error parsing hostname: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rl, err := resourcelock.NewFromKubeconfig(
 | 
				
			||||||
 | 
							"leases",
 | 
				
			||||||
 | 
							"kube-system",
 | 
				
			||||||
 | 
							controllerName,
 | 
				
			||||||
 | 
							resourcelock.ResourceLockConfig{
 | 
				
			||||||
 | 
								Identity: hostname + "_" + string(uuid.NewUUID()),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							config,
 | 
				
			||||||
 | 
							10,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.Infof("Error creating resourcelock: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
 | 
				
			||||||
 | 
							Lock:          rl,
 | 
				
			||||||
 | 
							LeaseDuration: 15 * time.Second,
 | 
				
			||||||
 | 
							RenewDeadline: 10 * time.Second,
 | 
				
			||||||
 | 
							RetryPeriod:   2 * time.Second,
 | 
				
			||||||
 | 
							Callbacks:     callbacks,
 | 
				
			||||||
 | 
							Name:          controllerName,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.Infof("Error creating leader elector: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						le.Run(ctx)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -91,6 +91,7 @@
 | 
				
			|||||||
  ignoredSubTrees:
 | 
					  ignoredSubTrees:
 | 
				
			||||||
  - "./staging/src/k8s.io/client-go/tools/cache/testing"
 | 
					  - "./staging/src/k8s.io/client-go/tools/cache/testing"
 | 
				
			||||||
  - "./staging/src/k8s.io/client-go/tools/leaderelection/resourcelock"
 | 
					  - "./staging/src/k8s.io/client-go/tools/leaderelection/resourcelock"
 | 
				
			||||||
 | 
					  - "./staging/src/k8s.io/client-go/tools/leaderelection"
 | 
				
			||||||
  - "./staging/src/k8s.io/client-go/tools/portforward"
 | 
					  - "./staging/src/k8s.io/client-go/tools/portforward"
 | 
				
			||||||
  - "./staging/src/k8s.io/client-go/tools/record"
 | 
					  - "./staging/src/k8s.io/client-go/tools/record"
 | 
				
			||||||
  - "./staging/src/k8s.io/client-go/tools/events"
 | 
					  - "./staging/src/k8s.io/client-go/tools/events"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -159,6 +159,9 @@ type LeaderElectionConfig struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Name is the name of the resource lock for debugging
 | 
						// Name is the name of the resource lock for debugging
 | 
				
			||||||
	Name string
 | 
						Name string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Coordinated will use the Coordinated Leader Election feature
 | 
				
			||||||
 | 
						Coordinated bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LeaderCallbacks are callbacks that are triggered during certain
 | 
					// LeaderCallbacks are callbacks that are triggered during certain
 | 
				
			||||||
@@ -249,7 +252,11 @@ func (le *LeaderElector) acquire(ctx context.Context) bool {
 | 
				
			|||||||
	desc := le.config.Lock.Describe()
 | 
						desc := le.config.Lock.Describe()
 | 
				
			||||||
	klog.Infof("attempting to acquire leader lease %v...", desc)
 | 
						klog.Infof("attempting to acquire leader lease %v...", desc)
 | 
				
			||||||
	wait.JitterUntil(func() {
 | 
						wait.JitterUntil(func() {
 | 
				
			||||||
		succeeded = le.tryAcquireOrRenew(ctx)
 | 
							if !le.config.Coordinated {
 | 
				
			||||||
 | 
								succeeded = le.tryAcquireOrRenew(ctx)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								succeeded = le.tryCoordinatedRenew(ctx)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		le.maybeReportTransition()
 | 
							le.maybeReportTransition()
 | 
				
			||||||
		if !succeeded {
 | 
							if !succeeded {
 | 
				
			||||||
			klog.V(4).Infof("failed to acquire lease %v", desc)
 | 
								klog.V(4).Infof("failed to acquire lease %v", desc)
 | 
				
			||||||
@@ -272,7 +279,11 @@ func (le *LeaderElector) renew(ctx context.Context) {
 | 
				
			|||||||
		timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
 | 
							timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
 | 
				
			||||||
		defer timeoutCancel()
 | 
							defer timeoutCancel()
 | 
				
			||||||
		err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
 | 
							err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
 | 
				
			||||||
			return le.tryAcquireOrRenew(timeoutCtx), nil
 | 
								if !le.config.Coordinated {
 | 
				
			||||||
 | 
									return le.tryAcquireOrRenew(timeoutCtx), nil
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return le.tryCoordinatedRenew(timeoutCtx), nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}, timeoutCtx.Done())
 | 
							}, timeoutCtx.Done())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		le.maybeReportTransition()
 | 
							le.maybeReportTransition()
 | 
				
			||||||
@@ -282,7 +293,6 @@ func (le *LeaderElector) renew(ctx context.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		le.metrics.leaderOff(le.config.Name)
 | 
							le.metrics.leaderOff(le.config.Name)
 | 
				
			||||||
		klog.Infof("failed to renew lease %v: %v", desc, err)
 | 
					 | 
				
			||||||
		cancel()
 | 
							cancel()
 | 
				
			||||||
	}, le.config.RetryPeriod, ctx.Done())
 | 
						}, le.config.RetryPeriod, ctx.Done())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -315,6 +325,81 @@ func (le *LeaderElector) release() bool {
 | 
				
			|||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// tryCoordinatedRenew checks if it acquired a lease and tries to renew the
 | 
				
			||||||
 | 
					// lease if it has already been acquired. Returns true on success else returns
 | 
				
			||||||
 | 
					// false.
 | 
				
			||||||
 | 
					func (le *LeaderElector) tryCoordinatedRenew(ctx context.Context) bool {
 | 
				
			||||||
 | 
						now := metav1.NewTime(le.clock.Now())
 | 
				
			||||||
 | 
						leaderElectionRecord := rl.LeaderElectionRecord{
 | 
				
			||||||
 | 
							HolderIdentity:       le.config.Lock.Identity(),
 | 
				
			||||||
 | 
							LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
 | 
				
			||||||
 | 
							RenewTime:            now,
 | 
				
			||||||
 | 
							AcquireTime:          now,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 1. obtain the electionRecord
 | 
				
			||||||
 | 
						oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if !errors.IsNotFound(err) {
 | 
				
			||||||
 | 
								klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							klog.Infof("lease lock not found: %v", le.config.Lock.Describe())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2. Record obtained, check the Identity & Time
 | 
				
			||||||
 | 
						if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
 | 
				
			||||||
 | 
							le.setObservedRecord(oldLeaderElectionRecord)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							le.observedRawRecord = oldLeaderElectionRawRecord
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						hasExpired := le.observedTime.Add(time.Second * time.Duration(oldLeaderElectionRecord.LeaseDurationSeconds)).Before(now.Time)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if hasExpired {
 | 
				
			||||||
 | 
							klog.Infof("lock has expired: %v", le.config.Lock.Describe())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !le.IsLeader() {
 | 
				
			||||||
 | 
							klog.V(4).Infof("lock is held by %v and has not yet expired: %v", oldLeaderElectionRecord.HolderIdentity, le.config.Lock.Describe())
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2b. If the lease has been marked as "end of term", don't renew it
 | 
				
			||||||
 | 
						if le.IsLeader() && oldLeaderElectionRecord.PreferredHolder != "" {
 | 
				
			||||||
 | 
							klog.V(4).Infof("lock is marked as 'end of term': %v", le.config.Lock.Describe())
 | 
				
			||||||
 | 
							// TODO: Instead of letting lease expire, the holder may deleted it directly
 | 
				
			||||||
 | 
							// This will not be compatible with all controllers, so it needs to be opt-in behavior..
 | 
				
			||||||
 | 
							// We must ensure all code guarded by this lease has successfully completed
 | 
				
			||||||
 | 
							// prior to releasing or there may be two processes
 | 
				
			||||||
 | 
							// simultaneously acting on the critical path.
 | 
				
			||||||
 | 
							// Usually once this returns false, the process is terminated..
 | 
				
			||||||
 | 
							// xref: OnStoppedLeading
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 3. We're going to try to update. The leaderElectionRecord is set to it's default
 | 
				
			||||||
 | 
						// here. Let's correct it before updating.
 | 
				
			||||||
 | 
						if le.IsLeader() {
 | 
				
			||||||
 | 
							leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
 | 
				
			||||||
 | 
							leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
 | 
				
			||||||
 | 
							leaderElectionRecord.Strategy = oldLeaderElectionRecord.Strategy
 | 
				
			||||||
 | 
							le.metrics.slowpathExercised(le.config.Name)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// update the lock itself
 | 
				
			||||||
 | 
						if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {
 | 
				
			||||||
 | 
							klog.Errorf("Failed to update lock: %v", err)
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						le.setObservedRecord(&leaderElectionRecord)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
 | 
					// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
 | 
				
			||||||
// else it tries to renew the lease if it has already been acquired. Returns true
 | 
					// else it tries to renew the lease if it has already been acquired. Returns true
 | 
				
			||||||
// on success else returns false.
 | 
					// on success else returns false.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/go-cmp/cmp"
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	coordinationv1 "k8s.io/api/coordination/v1"
 | 
						coordinationv1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
	corev1 "k8s.io/api/core/v1"
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/api/equality"
 | 
						"k8s.io/apimachinery/pkg/api/equality"
 | 
				
			||||||
@@ -37,8 +38,6 @@ import (
 | 
				
			|||||||
	rl "k8s.io/client-go/tools/leaderelection/resourcelock"
 | 
						rl "k8s.io/client-go/tools/leaderelection/resourcelock"
 | 
				
			||||||
	"k8s.io/client-go/tools/record"
 | 
						"k8s.io/client-go/tools/record"
 | 
				
			||||||
	"k8s.io/utils/clock"
 | 
						"k8s.io/utils/clock"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createLockObject(t *testing.T, objectType, namespace, name string, record *rl.LeaderElectionRecord) (obj runtime.Object) {
 | 
					func createLockObject(t *testing.T, objectType, namespace, name string, record *rl.LeaderElectionRecord) (obj runtime.Object) {
 | 
				
			||||||
@@ -353,6 +352,147 @@ func testTryAcquireOrRenew(t *testing.T, objectType string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestTryCoordinatedRenew(t *testing.T) {
 | 
				
			||||||
 | 
						objectType := "leases"
 | 
				
			||||||
 | 
						clock := clock.RealClock{}
 | 
				
			||||||
 | 
						future := clock.Now().Add(1000 * time.Hour)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name           string
 | 
				
			||||||
 | 
							observedRecord rl.LeaderElectionRecord
 | 
				
			||||||
 | 
							observedTime   time.Time
 | 
				
			||||||
 | 
							retryAfter     time.Duration
 | 
				
			||||||
 | 
							reactors       []Reactor
 | 
				
			||||||
 | 
							expectedEvents []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectSuccess    bool
 | 
				
			||||||
 | 
							transitionLeader bool
 | 
				
			||||||
 | 
							outHolder        string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "don't acquire from led, acked object",
 | 
				
			||||||
 | 
								reactors: []Reactor{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										verb: "get",
 | 
				
			||||||
 | 
										reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
 | 
				
			||||||
 | 
											return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								observedTime: future,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expectSuccess: false,
 | 
				
			||||||
 | 
								outHolder:     "bing",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "renew already acquired object",
 | 
				
			||||||
 | 
								reactors: []Reactor{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										verb: "get",
 | 
				
			||||||
 | 
										reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
 | 
				
			||||||
 | 
											return true, createLockObject(t, objectType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										verb: "update",
 | 
				
			||||||
 | 
										reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
 | 
				
			||||||
 | 
											return true, action.(fakeclient.CreateAction).GetObject(), nil
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								observedTime:   future,
 | 
				
			||||||
 | 
								observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expectSuccess: true,
 | 
				
			||||||
 | 
								outHolder:     "baz",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := range tests {
 | 
				
			||||||
 | 
							test := &tests[i]
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								// OnNewLeader is called async so we have to wait for it.
 | 
				
			||||||
 | 
								var wg sync.WaitGroup
 | 
				
			||||||
 | 
								wg.Add(1)
 | 
				
			||||||
 | 
								var reportedLeader string
 | 
				
			||||||
 | 
								var lock rl.Interface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"}
 | 
				
			||||||
 | 
								recorder := record.NewFakeRecorder(100)
 | 
				
			||||||
 | 
								resourceLockConfig := rl.ResourceLockConfig{
 | 
				
			||||||
 | 
									Identity:      "baz",
 | 
				
			||||||
 | 
									EventRecorder: recorder,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								c := &fake.Clientset{}
 | 
				
			||||||
 | 
								for _, reactor := range test.reactors {
 | 
				
			||||||
 | 
									c.AddReactor(reactor.verb, objectType, reactor.reaction)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
 | 
				
			||||||
 | 
									t.Errorf("unreachable action. testclient called too many times: %+v", action)
 | 
				
			||||||
 | 
									return true, nil, fmt.Errorf("unreachable action")
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								lock = &rl.LeaseLock{
 | 
				
			||||||
 | 
									LeaseMeta:  objectMeta,
 | 
				
			||||||
 | 
									LockConfig: resourceLockConfig,
 | 
				
			||||||
 | 
									Client:     c.CoordinationV1(),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								lec := LeaderElectionConfig{
 | 
				
			||||||
 | 
									Lock:          lock,
 | 
				
			||||||
 | 
									LeaseDuration: 10 * time.Second,
 | 
				
			||||||
 | 
									Callbacks: LeaderCallbacks{
 | 
				
			||||||
 | 
										OnNewLeader: func(l string) {
 | 
				
			||||||
 | 
											defer wg.Done()
 | 
				
			||||||
 | 
											reportedLeader = l
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Coordinated: true,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								observedRawRecord := GetRawRecordOrDie(t, objectType, test.observedRecord)
 | 
				
			||||||
 | 
								le := &LeaderElector{
 | 
				
			||||||
 | 
									config:            lec,
 | 
				
			||||||
 | 
									observedRecord:    test.observedRecord,
 | 
				
			||||||
 | 
									observedRawRecord: observedRawRecord,
 | 
				
			||||||
 | 
									observedTime:      test.observedTime,
 | 
				
			||||||
 | 
									clock:             clock,
 | 
				
			||||||
 | 
									metrics:           globalMetricsFactory.newLeaderMetrics(),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if test.expectSuccess != le.tryCoordinatedRenew(context.Background()) {
 | 
				
			||||||
 | 
									if test.retryAfter != 0 {
 | 
				
			||||||
 | 
										time.Sleep(test.retryAfter)
 | 
				
			||||||
 | 
										if test.expectSuccess != le.tryCoordinatedRenew(context.Background()) {
 | 
				
			||||||
 | 
											t.Errorf("unexpected result of tryCoordinatedRenew: [succeeded=%v]", !test.expectSuccess)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										t.Errorf("unexpected result of gryCoordinatedRenew: [succeeded=%v]", !test.expectSuccess)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								le.observedRecord.AcquireTime = metav1.Time{}
 | 
				
			||||||
 | 
								le.observedRecord.RenewTime = metav1.Time{}
 | 
				
			||||||
 | 
								if le.observedRecord.HolderIdentity != test.outHolder {
 | 
				
			||||||
 | 
									t.Errorf("expected holder:\n\t%+v\ngot:\n\t%+v", test.outHolder, le.observedRecord.HolderIdentity)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if len(test.reactors) != len(c.Actions()) {
 | 
				
			||||||
 | 
									t.Errorf("wrong number of api interactions")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 {
 | 
				
			||||||
 | 
									t.Errorf("leader should have transitioned but did not")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 {
 | 
				
			||||||
 | 
									t.Errorf("leader should not have transitioned but did")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								le.maybeReportTransition()
 | 
				
			||||||
 | 
								wg.Wait()
 | 
				
			||||||
 | 
								if reportedLeader != test.outHolder {
 | 
				
			||||||
 | 
									t.Errorf("reported leader was not the new leader. expected %q, got %q", test.outHolder, reportedLeader)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assertEqualEvents(t, test.expectedEvents, recorder.Events)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Will test leader election using lease as the resource
 | 
					// Will test leader election using lease as the resource
 | 
				
			||||||
func TestTryAcquireOrRenewLeases(t *testing.T) {
 | 
					func TestTryAcquireOrRenewLeases(t *testing.T) {
 | 
				
			||||||
	testTryAcquireOrRenew(t, "leases")
 | 
						testTryAcquireOrRenew(t, "leases")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,196 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						v1alpha1 "k8s.io/api/coordination/v1alpha1"
 | 
				
			||||||
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/fields"
 | 
				
			||||||
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
 | 
						"k8s.io/client-go/informers"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes"
 | 
				
			||||||
 | 
						coordinationv1alpha1client "k8s.io/client-go/kubernetes/typed/coordination/v1alpha1"
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/cache"
 | 
				
			||||||
 | 
						"k8s.io/client-go/util/workqueue"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
						"k8s.io/utils/clock"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const requeueInterval = 5 * time.Minute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LeaseCandidate struct {
 | 
				
			||||||
 | 
						LeaseClient            coordinationv1alpha1client.LeaseCandidateInterface
 | 
				
			||||||
 | 
						LeaseCandidateInformer cache.SharedIndexInformer
 | 
				
			||||||
 | 
						InformerFactory        informers.SharedInformerFactory
 | 
				
			||||||
 | 
						HasSynced              cache.InformerSynced
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// At most there will be one item in this Queue (since we only watch one item)
 | 
				
			||||||
 | 
						queue workqueue.TypedRateLimitingInterface[int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name      string
 | 
				
			||||||
 | 
						namespace string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// controller lease
 | 
				
			||||||
 | 
						leaseName string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Clock clock.Clock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						binaryVersion, emulationVersion string
 | 
				
			||||||
 | 
						preferredStrategies             []v1.CoordinatedLeaseStrategy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewCandidate(clientset kubernetes.Interface,
 | 
				
			||||||
 | 
						candidateName string,
 | 
				
			||||||
 | 
						candidateNamespace string,
 | 
				
			||||||
 | 
						targetLease string,
 | 
				
			||||||
 | 
						clock clock.Clock,
 | 
				
			||||||
 | 
						binaryVersion, emulationVersion string,
 | 
				
			||||||
 | 
						preferredStrategies []v1.CoordinatedLeaseStrategy,
 | 
				
			||||||
 | 
					) (*LeaseCandidate, error) {
 | 
				
			||||||
 | 
						fieldSelector := fields.OneTermEqualSelector("metadata.name", candidateName).String()
 | 
				
			||||||
 | 
						// A separate informer factory is required because this must start before informerFactories
 | 
				
			||||||
 | 
						// are started for leader elected components
 | 
				
			||||||
 | 
						informerFactory := informers.NewSharedInformerFactoryWithOptions(
 | 
				
			||||||
 | 
							clientset, 5*time.Minute,
 | 
				
			||||||
 | 
							informers.WithTweakListOptions(func(options *metav1.ListOptions) {
 | 
				
			||||||
 | 
								options.FieldSelector = fieldSelector
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						leaseCandidateInformer := informerFactory.Coordination().V1alpha1().LeaseCandidates().Informer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lc := &LeaseCandidate{
 | 
				
			||||||
 | 
							LeaseClient:            clientset.CoordinationV1alpha1().LeaseCandidates(candidateNamespace),
 | 
				
			||||||
 | 
							LeaseCandidateInformer: leaseCandidateInformer,
 | 
				
			||||||
 | 
							InformerFactory:        informerFactory,
 | 
				
			||||||
 | 
							name:                   candidateName,
 | 
				
			||||||
 | 
							namespace:              candidateNamespace,
 | 
				
			||||||
 | 
							leaseName:              targetLease,
 | 
				
			||||||
 | 
							Clock:                  clock,
 | 
				
			||||||
 | 
							binaryVersion:          binaryVersion,
 | 
				
			||||||
 | 
							emulationVersion:       emulationVersion,
 | 
				
			||||||
 | 
							preferredStrategies:    preferredStrategies,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						lc.queue = workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[int](), workqueue.TypedRateLimitingQueueConfig[int]{Name: "leasecandidate"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						synced, err := leaseCandidateInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
 | 
				
			||||||
 | 
							UpdateFunc: func(oldObj, newObj interface{}) {
 | 
				
			||||||
 | 
								if leasecandidate, ok := newObj.(*v1alpha1.LeaseCandidate); ok {
 | 
				
			||||||
 | 
									if leasecandidate.Spec.PingTime != nil {
 | 
				
			||||||
 | 
										lc.enqueueLease()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						lc.HasSynced = synced.HasSynced
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return lc, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LeaseCandidate) Run(ctx context.Context) {
 | 
				
			||||||
 | 
						defer c.queue.ShutDown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go c.InformerFactory.Start(ctx.Done())
 | 
				
			||||||
 | 
						if !cache.WaitForNamedCacheSync("leasecandidateclient", ctx.Done(), c.HasSynced) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.enqueueLease()
 | 
				
			||||||
 | 
						go c.runWorker(ctx)
 | 
				
			||||||
 | 
						<-ctx.Done()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LeaseCandidate) runWorker(ctx context.Context) {
 | 
				
			||||||
 | 
						for c.processNextWorkItem(ctx) {
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LeaseCandidate) processNextWorkItem(ctx context.Context) bool {
 | 
				
			||||||
 | 
						key, shutdown := c.queue.Get()
 | 
				
			||||||
 | 
						if shutdown {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer c.queue.Done(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := c.ensureLease(ctx)
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							c.queue.AddAfter(key, requeueInterval)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						utilruntime.HandleError(err)
 | 
				
			||||||
 | 
						klog.Infof("processNextWorkItem.AddRateLimited: %v", key)
 | 
				
			||||||
 | 
						c.queue.AddRateLimited(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LeaseCandidate) enqueueLease() {
 | 
				
			||||||
 | 
						c.queue.Add(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ensureLease creates the lease if it does not exist and renew it if it exists. Returns the lease and
 | 
				
			||||||
 | 
					// a bool (true if this call created the lease), or any error that occurs.
 | 
				
			||||||
 | 
					func (c *LeaseCandidate) ensureLease(ctx context.Context) error {
 | 
				
			||||||
 | 
						lease, err := c.LeaseClient.Get(ctx, c.name, metav1.GetOptions{})
 | 
				
			||||||
 | 
						if apierrors.IsNotFound(err) {
 | 
				
			||||||
 | 
							klog.V(2).Infof("Creating lease candidate")
 | 
				
			||||||
 | 
							// lease does not exist, create it.
 | 
				
			||||||
 | 
							leaseToCreate := c.newLease()
 | 
				
			||||||
 | 
							_, err := c.LeaseClient.Create(ctx, leaseToCreate, metav1.CreateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							klog.V(2).Infof("Created lease candidate")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						klog.V(2).Infof("lease candidate exists.. renewing")
 | 
				
			||||||
 | 
						clone := lease.DeepCopy()
 | 
				
			||||||
 | 
						clone.Spec.RenewTime = &metav1.MicroTime{Time: c.Clock.Now()}
 | 
				
			||||||
 | 
						clone.Spec.PingTime = nil
 | 
				
			||||||
 | 
						_, err = c.LeaseClient.Update(ctx, clone, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LeaseCandidate) newLease() *v1alpha1.LeaseCandidate {
 | 
				
			||||||
 | 
						lease := &v1alpha1.LeaseCandidate{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      c.name,
 | 
				
			||||||
 | 
								Namespace: c.namespace,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: v1alpha1.LeaseCandidateSpec{
 | 
				
			||||||
 | 
								LeaseName:           c.leaseName,
 | 
				
			||||||
 | 
								BinaryVersion:       c.binaryVersion,
 | 
				
			||||||
 | 
								EmulationVersion:    c.emulationVersion,
 | 
				
			||||||
 | 
								PreferredStrategies: c.preferredStrategies,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						lease.Spec.RenewTime = &metav1.MicroTime{Time: c.Clock.Now()}
 | 
				
			||||||
 | 
						return lease
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package leaderelection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes/fake"
 | 
				
			||||||
 | 
						"k8s.io/utils/clock"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type testcase struct {
 | 
				
			||||||
 | 
						candidateName, candidateNamespace, leaseName string
 | 
				
			||||||
 | 
						binaryVersion, emulationVersion              string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestLeaseCandidateCreation(t *testing.T) {
 | 
				
			||||||
 | 
						tc := testcase{
 | 
				
			||||||
 | 
							candidateName:      "foo",
 | 
				
			||||||
 | 
							candidateNamespace: "default",
 | 
				
			||||||
 | 
							leaseName:          "lease",
 | 
				
			||||||
 | 
							binaryVersion:      "1.30.0",
 | 
				
			||||||
 | 
							emulationVersion:   "1.30.0",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client := fake.NewSimpleClientset()
 | 
				
			||||||
 | 
						candidate, err := NewCandidate(
 | 
				
			||||||
 | 
							client,
 | 
				
			||||||
 | 
							tc.candidateName,
 | 
				
			||||||
 | 
							tc.candidateNamespace,
 | 
				
			||||||
 | 
							tc.leaseName,
 | 
				
			||||||
 | 
							clock.RealClock{},
 | 
				
			||||||
 | 
							tc.binaryVersion,
 | 
				
			||||||
 | 
							tc.emulationVersion,
 | 
				
			||||||
 | 
							[]v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go candidate.Run(ctx)
 | 
				
			||||||
 | 
						err = pollForLease(ctx, tc, client, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestLeaseCandidateAck(t *testing.T) {
 | 
				
			||||||
 | 
						tc := testcase{
 | 
				
			||||||
 | 
							candidateName:      "foo",
 | 
				
			||||||
 | 
							candidateNamespace: "default",
 | 
				
			||||||
 | 
							leaseName:          "lease",
 | 
				
			||||||
 | 
							binaryVersion:      "1.30.0",
 | 
				
			||||||
 | 
							emulationVersion:   "1.30.0",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client := fake.NewSimpleClientset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						candidate, err := NewCandidate(
 | 
				
			||||||
 | 
							client,
 | 
				
			||||||
 | 
							tc.candidateName,
 | 
				
			||||||
 | 
							tc.candidateNamespace,
 | 
				
			||||||
 | 
							tc.leaseName,
 | 
				
			||||||
 | 
							clock.RealClock{},
 | 
				
			||||||
 | 
							tc.binaryVersion,
 | 
				
			||||||
 | 
							tc.emulationVersion,
 | 
				
			||||||
 | 
							[]v1.CoordinatedLeaseStrategy{v1.OldestEmulationVersion},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go candidate.Run(ctx)
 | 
				
			||||||
 | 
						err = pollForLease(ctx, tc, client, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update PingTime and verify that the client renews
 | 
				
			||||||
 | 
						ensureAfter := &metav1.MicroTime{Time: time.Now()}
 | 
				
			||||||
 | 
						lc, err := client.CoordinationV1alpha1().LeaseCandidates(tc.candidateNamespace).Get(ctx, tc.candidateName, metav1.GetOptions{})
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							if lc.Spec.PingTime == nil {
 | 
				
			||||||
 | 
								c := lc.DeepCopy()
 | 
				
			||||||
 | 
								c.Spec.PingTime = &metav1.MicroTime{Time: time.Now()}
 | 
				
			||||||
 | 
								_, err = client.CoordinationV1alpha1().LeaseCandidates(tc.candidateNamespace).Update(ctx, c, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Error(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = pollForLease(ctx, tc, client, ensureAfter)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func pollForLease(ctx context.Context, tc testcase, client *fake.Clientset, t *metav1.MicroTime) error {
 | 
				
			||||||
 | 
						return wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 10*time.Second, true, func(ctx context.Context) (done bool, err error) {
 | 
				
			||||||
 | 
							lc, err := client.CoordinationV1alpha1().LeaseCandidates(tc.candidateNamespace).Get(ctx, tc.candidateName, metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if errors.IsNotFound(err) {
 | 
				
			||||||
 | 
									return false, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return true, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if lc.Spec.BinaryVersion == tc.binaryVersion &&
 | 
				
			||||||
 | 
								lc.Spec.EmulationVersion == tc.emulationVersion &&
 | 
				
			||||||
 | 
								lc.Spec.LeaseName == tc.leaseName &&
 | 
				
			||||||
 | 
								lc.Spec.PingTime == nil &&
 | 
				
			||||||
 | 
								lc.Spec.RenewTime != nil {
 | 
				
			||||||
 | 
								// Ensure that if a time is provided, the renewTime occurred after the provided time.
 | 
				
			||||||
 | 
								if t != nil && t.After(lc.Spec.RenewTime.Time) {
 | 
				
			||||||
 | 
									return false, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -19,14 +19,15 @@ package resourcelock
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	clientset "k8s.io/client-go/kubernetes"
 | 
					 | 
				
			||||||
	restclient "k8s.io/client-go/rest"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						clientset "k8s.io/client-go/kubernetes"
 | 
				
			||||||
	coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
 | 
						coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
 | 
				
			||||||
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
						corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
				
			||||||
 | 
						restclient "k8s.io/client-go/rest"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -114,11 +115,13 @@ type LeaderElectionRecord struct {
 | 
				
			|||||||
	// attempt to acquire leases with empty identities and will wait for the full lease
 | 
						// attempt to acquire leases with empty identities and will wait for the full lease
 | 
				
			||||||
	// interval to expire before attempting to reacquire. This value is set to empty when
 | 
						// interval to expire before attempting to reacquire. This value is set to empty when
 | 
				
			||||||
	// a client voluntarily steps down.
 | 
						// a client voluntarily steps down.
 | 
				
			||||||
	HolderIdentity       string      `json:"holderIdentity"`
 | 
						HolderIdentity       string                      `json:"holderIdentity"`
 | 
				
			||||||
	LeaseDurationSeconds int         `json:"leaseDurationSeconds"`
 | 
						LeaseDurationSeconds int                         `json:"leaseDurationSeconds"`
 | 
				
			||||||
	AcquireTime          metav1.Time `json:"acquireTime"`
 | 
						AcquireTime          metav1.Time                 `json:"acquireTime"`
 | 
				
			||||||
	RenewTime            metav1.Time `json:"renewTime"`
 | 
						RenewTime            metav1.Time                 `json:"renewTime"`
 | 
				
			||||||
	LeaderTransitions    int         `json:"leaderTransitions"`
 | 
						LeaderTransitions    int                         `json:"leaderTransitions"`
 | 
				
			||||||
 | 
						Strategy             v1.CoordinatedLeaseStrategy `json:"strategy"`
 | 
				
			||||||
 | 
						PreferredHolder      string                      `json:"preferredHolder"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EventRecorder records a change in the ResourceLock.
 | 
					// EventRecorder records a change in the ResourceLock.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -122,6 +122,12 @@ func LeaseSpecToLeaderElectionRecord(spec *coordinationv1.LeaseSpec) *LeaderElec
 | 
				
			|||||||
	if spec.RenewTime != nil {
 | 
						if spec.RenewTime != nil {
 | 
				
			||||||
		r.RenewTime = metav1.Time{Time: spec.RenewTime.Time}
 | 
							r.RenewTime = metav1.Time{Time: spec.RenewTime.Time}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if spec.PreferredHolder != nil {
 | 
				
			||||||
 | 
							r.PreferredHolder = *spec.PreferredHolder
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if spec.Strategy != nil {
 | 
				
			||||||
 | 
							r.Strategy = *spec.Strategy
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return &r
 | 
						return &r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -129,11 +135,18 @@ func LeaseSpecToLeaderElectionRecord(spec *coordinationv1.LeaseSpec) *LeaderElec
 | 
				
			|||||||
func LeaderElectionRecordToLeaseSpec(ler *LeaderElectionRecord) coordinationv1.LeaseSpec {
 | 
					func LeaderElectionRecordToLeaseSpec(ler *LeaderElectionRecord) coordinationv1.LeaseSpec {
 | 
				
			||||||
	leaseDurationSeconds := int32(ler.LeaseDurationSeconds)
 | 
						leaseDurationSeconds := int32(ler.LeaseDurationSeconds)
 | 
				
			||||||
	leaseTransitions := int32(ler.LeaderTransitions)
 | 
						leaseTransitions := int32(ler.LeaderTransitions)
 | 
				
			||||||
	return coordinationv1.LeaseSpec{
 | 
						spec := coordinationv1.LeaseSpec{
 | 
				
			||||||
		HolderIdentity:       &ler.HolderIdentity,
 | 
							HolderIdentity:       &ler.HolderIdentity,
 | 
				
			||||||
		LeaseDurationSeconds: &leaseDurationSeconds,
 | 
							LeaseDurationSeconds: &leaseDurationSeconds,
 | 
				
			||||||
		AcquireTime:          &metav1.MicroTime{Time: ler.AcquireTime.Time},
 | 
							AcquireTime:          &metav1.MicroTime{Time: ler.AcquireTime.Time},
 | 
				
			||||||
		RenewTime:            &metav1.MicroTime{Time: ler.RenewTime.Time},
 | 
							RenewTime:            &metav1.MicroTime{Time: ler.RenewTime.Time},
 | 
				
			||||||
		LeaseTransitions:     &leaseTransitions,
 | 
							LeaseTransitions:     &leaseTransitions,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if ler.PreferredHolder != "" {
 | 
				
			||||||
 | 
							spec.PreferredHolder = &ler.PreferredHolder
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if ler.Strategy != "" {
 | 
				
			||||||
 | 
							spec.Strategy = &ler.Strategy
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return spec
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										296
									
								
								test/integration/apiserver/coordinated_leader_election_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								test/integration/apiserver/coordinated_leader_election_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,296 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2024 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package apiserver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/coordination/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						genericfeatures "k8s.io/apiserver/pkg/features"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						kubernetes "k8s.io/client-go/kubernetes"
 | 
				
			||||||
 | 
						"k8s.io/client-go/rest"
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/leaderelection"
 | 
				
			||||||
 | 
						"k8s.io/client-go/tools/leaderelection/resourcelock"
 | 
				
			||||||
 | 
						featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
						"k8s.io/utils/clock"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/test/integration/framework"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSingleLeaseCandidate(t *testing.T) {
 | 
				
			||||||
 | 
						featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CoordinatedLeaderElection, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						cletest := setupCLE(config, ctx, cancel, t)
 | 
				
			||||||
 | 
						defer cletest.cleanup()
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo1", "default", "foo", "1.20.0", "1.20.0")
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo1")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMultipleLeaseCandidate(t *testing.T) {
 | 
				
			||||||
 | 
						featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CoordinatedLeaderElection, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						cletest := setupCLE(config, ctx, cancel, t)
 | 
				
			||||||
 | 
						defer cletest.cleanup()
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo1", "default", "foo", "1.20.0", "1.20.0")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo2", "default", "foo", "1.20.0", "1.19.0")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo3", "default", "foo", "1.19.0", "1.19.0")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo4", "default", "foo", "1.2.0", "1.19.0")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo5", "default", "foo", "1.20.0", "1.19.0")
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo3")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestLeaderDisappear(t *testing.T) {
 | 
				
			||||||
 | 
						featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CoordinatedLeaderElection, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						cletest := setupCLE(config, ctx, cancel, t)
 | 
				
			||||||
 | 
						defer cletest.cleanup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo1", "default", "foo", "1.20.0", "1.20.0")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo2", "default", "foo", "1.20.0", "1.19.0")
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo2")
 | 
				
			||||||
 | 
						cletest.cancelController("foo2", "default")
 | 
				
			||||||
 | 
						cletest.deleteLC("foo2", "default")
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo1")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestLeaseSwapIfBetterAvailable(t *testing.T) {
 | 
				
			||||||
 | 
						featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CoordinatedLeaderElection, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						cletest := setupCLE(config, ctx, cancel, t)
 | 
				
			||||||
 | 
						defer cletest.cleanup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("bar1", "default", "bar", "1.20.0", "1.20.0")
 | 
				
			||||||
 | 
						cletest.pollForLease("bar", "default", "bar1")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("bar2", "default", "bar", "1.19.0", "1.19.0")
 | 
				
			||||||
 | 
						cletest.pollForLease("bar", "default", "bar2")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestUpgradeSkew tests that a legacy client and a CLE aware client operating on the same lease do not cause errors
 | 
				
			||||||
 | 
					func TestUpgradeSkew(t *testing.T) {
 | 
				
			||||||
 | 
						featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CoordinatedLeaderElection, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						cletest := setupCLE(config, ctx, cancel, t)
 | 
				
			||||||
 | 
						defer cletest.cleanup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeLegacyController("foo1-130", "default", "foo")
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo1-130")
 | 
				
			||||||
 | 
						go cletest.createAndRunFakeController("foo1-131", "default", "foo", "1.31.0", "1.31.0")
 | 
				
			||||||
 | 
						// running a new controller should not kick off old leader
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo1-130")
 | 
				
			||||||
 | 
						cletest.cancelController("foo1-130", "default")
 | 
				
			||||||
 | 
						cletest.pollForLease("foo", "default", "foo1-131")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ctxCancelPair struct {
 | 
				
			||||||
 | 
						ctx    context.Context
 | 
				
			||||||
 | 
						cancel func()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					type cleTest struct {
 | 
				
			||||||
 | 
						config    *rest.Config
 | 
				
			||||||
 | 
						clientset *kubernetes.Clientset
 | 
				
			||||||
 | 
						t         *testing.T
 | 
				
			||||||
 | 
						ctxList   map[string]ctxCancelPair
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t cleTest) createAndRunFakeLegacyController(name string, namespace string, targetLease string) {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						t.ctxList[name+"/"+namespace] = ctxCancelPair{ctx, cancel}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						electionChecker := leaderelection.NewLeaderHealthzAdaptor(time.Second * 20)
 | 
				
			||||||
 | 
						go leaderElectAndRunUncoordinated(ctx, t.config, name, electionChecker,
 | 
				
			||||||
 | 
							namespace,
 | 
				
			||||||
 | 
							"leases",
 | 
				
			||||||
 | 
							targetLease,
 | 
				
			||||||
 | 
							leaderelection.LeaderCallbacks{
 | 
				
			||||||
 | 
								OnStartedLeading: func(ctx context.Context) {
 | 
				
			||||||
 | 
									klog.Info("Elected leader, starting..")
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								OnStoppedLeading: func() {
 | 
				
			||||||
 | 
									klog.Errorf("%s Lost leadership, stopping", name)
 | 
				
			||||||
 | 
									// klog.FlushAndExit(klog.ExitFlushTimeout, 1)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (t cleTest) createAndRunFakeController(name string, namespace string, targetLease string, binaryVersion string, compatibilityVersion string) {
 | 
				
			||||||
 | 
						identityLease, err := leaderelection.NewCandidate(
 | 
				
			||||||
 | 
							t.clientset,
 | 
				
			||||||
 | 
							name,
 | 
				
			||||||
 | 
							namespace,
 | 
				
			||||||
 | 
							targetLease,
 | 
				
			||||||
 | 
							clock.RealClock{},
 | 
				
			||||||
 | 
							binaryVersion,
 | 
				
			||||||
 | 
							compatibilityVersion,
 | 
				
			||||||
 | 
							[]v1.CoordinatedLeaseStrategy{"OldestEmulationVersion"},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
						t.ctxList[name+"/"+namespace] = ctxCancelPair{ctx, cancel}
 | 
				
			||||||
 | 
						go identityLease.Run(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						electionChecker := leaderelection.NewLeaderHealthzAdaptor(time.Second * 20)
 | 
				
			||||||
 | 
						go leaderElectAndRunCoordinated(ctx, t.config, name, electionChecker,
 | 
				
			||||||
 | 
							namespace,
 | 
				
			||||||
 | 
							"leases",
 | 
				
			||||||
 | 
							targetLease,
 | 
				
			||||||
 | 
							leaderelection.LeaderCallbacks{
 | 
				
			||||||
 | 
								OnStartedLeading: func(ctx context.Context) {
 | 
				
			||||||
 | 
									klog.Info("Elected leader, starting..")
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								OnStoppedLeading: func() {
 | 
				
			||||||
 | 
									klog.Errorf("%s Lost leadership, stopping", name)
 | 
				
			||||||
 | 
									// klog.FlushAndExit(klog.ExitFlushTimeout, 1)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func leaderElectAndRunUncoordinated(ctx context.Context, kubeconfig *rest.Config, lockIdentity string, electionChecker *leaderelection.HealthzAdaptor, resourceNamespace, resourceLock, leaseName string, callbacks leaderelection.LeaderCallbacks) {
 | 
				
			||||||
 | 
						leaderElectAndRun(ctx, kubeconfig, lockIdentity, electionChecker, resourceNamespace, resourceLock, leaseName, callbacks, false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func leaderElectAndRunCoordinated(ctx context.Context, kubeconfig *rest.Config, lockIdentity string, electionChecker *leaderelection.HealthzAdaptor, resourceNamespace, resourceLock, leaseName string, callbacks leaderelection.LeaderCallbacks) {
 | 
				
			||||||
 | 
						leaderElectAndRun(ctx, kubeconfig, lockIdentity, electionChecker, resourceNamespace, resourceLock, leaseName, callbacks, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func leaderElectAndRun(ctx context.Context, kubeconfig *rest.Config, lockIdentity string, electionChecker *leaderelection.HealthzAdaptor, resourceNamespace, resourceLock, leaseName string, callbacks leaderelection.LeaderCallbacks, coordinated bool) {
 | 
				
			||||||
 | 
						logger := klog.FromContext(ctx)
 | 
				
			||||||
 | 
						rl, err := resourcelock.NewFromKubeconfig(resourceLock,
 | 
				
			||||||
 | 
							resourceNamespace,
 | 
				
			||||||
 | 
							leaseName,
 | 
				
			||||||
 | 
							resourcelock.ResourceLockConfig{
 | 
				
			||||||
 | 
								Identity: lockIdentity,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							kubeconfig,
 | 
				
			||||||
 | 
							5*time.Second)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logger.Error(err, "Error creating lock")
 | 
				
			||||||
 | 
							klog.FlushAndExit(klog.ExitFlushTimeout, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
 | 
				
			||||||
 | 
							Lock:          rl,
 | 
				
			||||||
 | 
							LeaseDuration: 5 * time.Second,
 | 
				
			||||||
 | 
							RenewDeadline: 3 * time.Second,
 | 
				
			||||||
 | 
							RetryPeriod:   2 * time.Second,
 | 
				
			||||||
 | 
							Callbacks:     callbacks,
 | 
				
			||||||
 | 
							WatchDog:      electionChecker,
 | 
				
			||||||
 | 
							Name:          leaseName,
 | 
				
			||||||
 | 
							Coordinated:   coordinated,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t cleTest) pollForLease(name, namespace, holder string) {
 | 
				
			||||||
 | 
						err := wait.PollUntilContextTimeout(t.ctxList["main"].ctx, 1000*time.Millisecond, 15*time.Second, true, func(ctx context.Context) (done bool, err error) {
 | 
				
			||||||
 | 
							lease, err := t.clientset.CoordinationV1().Leases(namespace).Get(ctx, name, metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println(err)
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return lease.Spec.HolderIdentity != nil && *lease.Spec.HolderIdentity == holder, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.t.Fatalf("timeout awiting for Lease %s %s err: %v", name, namespace, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t cleTest) cancelController(name, namespace string) {
 | 
				
			||||||
 | 
						t.ctxList[name+"/"+namespace].cancel()
 | 
				
			||||||
 | 
						delete(t.ctxList, name+"/"+namespace)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t cleTest) cleanup() {
 | 
				
			||||||
 | 
						err := t.clientset.CoordinationV1().Leases("kube-system").Delete(context.TODO(), "leader-election-controller", metav1.DeleteOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, c := range t.ctxList {
 | 
				
			||||||
 | 
							c.cancel()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t cleTest) deleteLC(name, namespace string) {
 | 
				
			||||||
 | 
						err := t.clientset.CoordinationV1alpha1().LeaseCandidates(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func setupCLE(config *rest.Config, ctx context.Context, cancel func(), t *testing.T) cleTest {
 | 
				
			||||||
 | 
						clientset, err := kubernetes.NewForConfig(config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a := ctxCancelPair{ctx, cancel}
 | 
				
			||||||
 | 
						return cleTest{
 | 
				
			||||||
 | 
							config:    config,
 | 
				
			||||||
 | 
							clientset: clientset,
 | 
				
			||||||
 | 
							ctxList:   map[string]ctxCancelPair{"main": a},
 | 
				
			||||||
 | 
							t:         t,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user