github.com/openebs/api@v1.12.0/pkg/kubernetes/leaderelection/leader_election.go (about) 1 /* 2 Copyright 2020 The OpenEBS Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package leaderelection 18 19 import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "regexp" 25 "strings" 26 "time" 27 28 v1 "k8s.io/api/core/v1" 29 "k8s.io/client-go/kubernetes" 30 "k8s.io/client-go/kubernetes/scheme" 31 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 32 "k8s.io/client-go/tools/leaderelection" 33 "k8s.io/client-go/tools/leaderelection/resourcelock" 34 "k8s.io/client-go/tools/record" 35 "k8s.io/klog" 36 ) 37 38 const ( 39 openEBS = "openebs" 40 defaultLeaseDuration = 15 * time.Second 41 defaultRenewDeadline = 10 * time.Second 42 defaultRetryPeriod = 5 * time.Second 43 ) 44 45 // LeaderElection is a convenience wrapper around client-go's leader election library. 46 type LeaderElection struct { 47 runFunc func(ctx context.Context) 48 49 // the lockName identifies the leader election config and should be shared across all members 50 lockName string 51 // the identity is the unique identity of the currently running member 52 identity string 53 // the namespace to store the lock resource 54 namespace string 55 // resourceLock defines the type of leaderelection that should be used 56 // valid options are resourcelock.LeasesResourceLock, resourcelock.EndpointsResourceLock, 57 // and resourcelock.ConfigMapsResourceLock 58 resourceLock string 59 60 leaseDuration time.Duration 61 renewDeadline time.Duration 62 retryPeriod time.Duration 63 64 clientset kubernetes.Interface 65 } 66 67 // NewLeaderElection returns the default & preferred leader election type 68 func NewLeaderElection(clientset kubernetes.Interface, lockName string, runFunc func(ctx context.Context)) *LeaderElection { 69 return NewLeaderElectionWithLeases(clientset, lockName, runFunc) 70 } 71 72 // NewLeaderElectionWithLeases returns an implementation of leader election using Leases 73 func NewLeaderElectionWithLeases(clientset kubernetes.Interface, lockName string, runFunc func(ctx context.Context)) *LeaderElection { 74 return &LeaderElection{ 75 runFunc: runFunc, 76 lockName: lockName, 77 resourceLock: resourcelock.LeasesResourceLock, 78 leaseDuration: defaultLeaseDuration, 79 renewDeadline: defaultRenewDeadline, 80 retryPeriod: defaultRetryPeriod, 81 clientset: clientset, 82 } 83 } 84 85 // NewLeaderElectionWithEndpoints returns an implementation of leader election using Endpoints 86 func NewLeaderElectionWithEndpoints(clientset kubernetes.Interface, lockName string, runFunc func(ctx context.Context)) *LeaderElection { 87 return &LeaderElection{ 88 runFunc: runFunc, 89 lockName: lockName, 90 resourceLock: resourcelock.EndpointsResourceLock, 91 leaseDuration: defaultLeaseDuration, 92 renewDeadline: defaultRenewDeadline, 93 retryPeriod: defaultRetryPeriod, 94 clientset: clientset, 95 } 96 } 97 98 // NewLeaderElectionWithConfigMaps returns an implementation of leader election using ConfigMaps 99 func NewLeaderElectionWithConfigMaps(clientset kubernetes.Interface, lockName string, runFunc func(ctx context.Context)) *LeaderElection { 100 return &LeaderElection{ 101 runFunc: runFunc, 102 lockName: lockName, 103 resourceLock: resourcelock.ConfigMapsResourceLock, 104 leaseDuration: defaultLeaseDuration, 105 renewDeadline: defaultRenewDeadline, 106 retryPeriod: defaultRetryPeriod, 107 clientset: clientset, 108 } 109 } 110 111 // WithIdentity ... 112 func (l *LeaderElection) WithIdentity(identity string) { 113 l.identity = identity 114 } 115 116 // WithNamespace ... 117 func (l *LeaderElection) WithNamespace(namespace string) { 118 l.namespace = namespace 119 } 120 121 // WithLeaseDuration ... 122 func (l *LeaderElection) WithLeaseDuration(leaseDuration time.Duration) { 123 l.leaseDuration = leaseDuration 124 } 125 126 // WithRenewDeadline ... 127 func (l *LeaderElection) WithRenewDeadline(renewDeadline time.Duration) { 128 l.renewDeadline = renewDeadline 129 } 130 131 // WithRetryPeriod ... 132 func (l *LeaderElection) WithRetryPeriod(retryPeriod time.Duration) { 133 l.retryPeriod = retryPeriod 134 } 135 136 // Run starts the leader loop 137 func (l *LeaderElection) Run() error { 138 if l.identity == "" { 139 id, err := defaultLeaderElectionIdentity() 140 if err != nil { 141 return fmt.Errorf("error getting the default leader identity: %v", err) 142 } 143 144 l.identity = id 145 } 146 147 if l.namespace == "" { 148 l.namespace = inClusterNamespace() 149 } 150 151 broadcaster := record.NewBroadcaster() 152 broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: l.clientset.CoreV1().Events(l.namespace)}) 153 eventRecorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: fmt.Sprintf("%s/%s", l.lockName, string(l.identity))}) 154 155 rlConfig := resourcelock.ResourceLockConfig{ 156 Identity: sanitizeName(l.identity), 157 EventRecorder: eventRecorder, 158 } 159 160 lock, err := resourcelock.New(l.resourceLock, l.namespace, sanitizeName(l.lockName), l.clientset.CoreV1(), l.clientset.CoordinationV1(), rlConfig) 161 if err != nil { 162 return err 163 } 164 165 leaderConfig := leaderelection.LeaderElectionConfig{ 166 Lock: lock, 167 LeaseDuration: l.leaseDuration, 168 RenewDeadline: l.renewDeadline, 169 RetryPeriod: l.retryPeriod, 170 Callbacks: leaderelection.LeaderCallbacks{ 171 OnStartedLeading: func(ctx context.Context) { 172 klog.V(2).Info("became leader, starting") 173 l.runFunc(ctx) 174 }, 175 OnStoppedLeading: func() { 176 klog.Fatal("stopped leading") 177 }, 178 OnNewLeader: func(identity string) { 179 klog.V(3).Infof("new leader detected, current leader: %s", identity) 180 }, 181 }, 182 } 183 // RunOrDie starts a client with the provided config or panics if the 184 // leaderconfig fails to validate. 185 leaderelection.RunOrDie(context.TODO(), leaderConfig) 186 return nil // should never reach here 187 } 188 189 func defaultLeaderElectionIdentity() (string, error) { 190 return os.Hostname() 191 } 192 193 // sanitizeName sanitizes the provided string so it can be consumed by leader election library 194 func sanitizeName(name string) string { 195 re := regexp.MustCompile("[^a-zA-Z0-9-]") 196 name = re.ReplaceAllString(name, "-") 197 if name[len(name)-1] == '-' { 198 // name must not end with '-' 199 name = name + "X" 200 } 201 return name 202 } 203 204 // inClusterNamespace returns the namespace in which the pod is running in by checking 205 // the env var POD_NAMESPACE, then the file /var/run/secrets/kubernetes.io/serviceaccount/namespace. 206 // if neither returns a valid namespace, the "openebs" namespace is returned 207 func inClusterNamespace() string { 208 if ns := os.Getenv("POD_NAMESPACE"); ns != "" { 209 return ns 210 } 211 212 if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { 213 if ns := strings.TrimSpace(string(data)); len(ns) > 0 { 214 return ns 215 } 216 } 217 return openEBS 218 }