github.imxd.top/operator-framework/operator-sdk@v0.8.2/pkg/leader/leader.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package leader 16 17 import ( 18 "context" 19 "time" 20 21 "github.com/operator-framework/operator-sdk/pkg/k8sutil" 22 23 corev1 "k8s.io/api/core/v1" 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/util/wait" 27 crclient "sigs.k8s.io/controller-runtime/pkg/client" 28 "sigs.k8s.io/controller-runtime/pkg/client/config" 29 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 30 ) 31 32 var log = logf.Log.WithName("leader") 33 34 // maxBackoffInterval defines the maximum amount of time to wait between 35 // attempts to become the leader. 36 const maxBackoffInterval = time.Second * 16 37 38 // Become ensures that the current pod is the leader within its namespace. If 39 // run outside a cluster, it will skip leader election and return nil. It 40 // continuously tries to create a ConfigMap with the provided name and the 41 // current pod set as the owner reference. Only one can exist at a time with 42 // the same name, so the pod that successfully creates the ConfigMap is the 43 // leader. Upon termination of that pod, the garbage collector will delete the 44 // ConfigMap, enabling a different pod to become the leader. 45 func Become(ctx context.Context, lockName string) error { 46 log.Info("Trying to become the leader.") 47 48 ns, err := k8sutil.GetOperatorNamespace() 49 if err != nil { 50 if err == k8sutil.ErrNoNamespace { 51 log.Info("Skipping leader election; not running in a cluster.") 52 return nil 53 } 54 return err 55 } 56 57 config, err := config.GetConfig() 58 if err != nil { 59 return err 60 } 61 62 client, err := crclient.New(config, crclient.Options{}) 63 if err != nil { 64 return err 65 } 66 67 owner, err := myOwnerRef(ctx, client, ns) 68 if err != nil { 69 return err 70 } 71 72 // check for existing lock from this pod, in case we got restarted 73 existing := &corev1.ConfigMap{ 74 TypeMeta: metav1.TypeMeta{ 75 APIVersion: "v1", 76 Kind: "ConfigMap", 77 }, 78 } 79 key := crclient.ObjectKey{Namespace: ns, Name: lockName} 80 err = client.Get(ctx, key, existing) 81 82 switch { 83 case err == nil: 84 for _, existingOwner := range existing.GetOwnerReferences() { 85 if existingOwner.Name == owner.Name { 86 log.Info("Found existing lock with my name. I was likely restarted.") 87 log.Info("Continuing as the leader.") 88 return nil 89 } else { 90 log.Info("Found existing lock", "LockOwner", existingOwner.Name) 91 } 92 } 93 case apierrors.IsNotFound(err): 94 log.Info("No pre-existing lock was found.") 95 default: 96 log.Error(err, "Unknown error trying to get ConfigMap") 97 return err 98 } 99 100 cm := &corev1.ConfigMap{ 101 TypeMeta: metav1.TypeMeta{ 102 APIVersion: "v1", 103 Kind: "ConfigMap", 104 }, 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: lockName, 107 Namespace: ns, 108 OwnerReferences: []metav1.OwnerReference{*owner}, 109 }, 110 } 111 112 // try to create a lock 113 backoff := time.Second 114 for { 115 err := client.Create(ctx, cm) 116 switch { 117 case err == nil: 118 log.Info("Became the leader.") 119 return nil 120 case apierrors.IsAlreadyExists(err): 121 log.Info("Not the leader. Waiting.") 122 select { 123 case <-time.After(wait.Jitter(backoff, .2)): 124 if backoff < maxBackoffInterval { 125 backoff *= 2 126 } 127 continue 128 case <-ctx.Done(): 129 return ctx.Err() 130 } 131 default: 132 log.Error(err, "Unknown error creating ConfigMap") 133 return err 134 } 135 } 136 } 137 138 // myOwnerRef returns an OwnerReference that corresponds to the pod in which 139 // this code is currently running. 140 // It expects the environment variable POD_NAME to be set by the downwards API 141 func myOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) { 142 myPod, err := k8sutil.GetPod(ctx, client, ns) 143 if err != nil { 144 return nil, err 145 } 146 147 owner := &metav1.OwnerReference{ 148 APIVersion: "v1", 149 Kind: "Pod", 150 Name: myPod.ObjectMeta.Name, 151 UID: myPod.ObjectMeta.UID, 152 } 153 return owner, nil 154 }