1 // Copyright 2018 The Operator-SDK Authors
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
21 "github.com/operator-framework/operator-sdk/pkg/k8sutil"
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"
32 var log = logf.Log.WithName("leader")
34 // maxBackoffInterval defines the maximum amount of time to wait between
35 // attempts to become the leader.
36 const maxBackoffInterval = time.Second * 16
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.")
48 ns, err := k8sutil.GetOperatorNamespace()
50 if err == k8sutil.ErrNoNamespace {
51 log.Info("Skipping leader election; not running in a cluster.")
57 config, err := config.GetConfig()
62 client, err := crclient.New(config, crclient.Options{})
67 owner, err := myOwnerRef(ctx, client, ns)
72 // check for existing lock from this pod, in case we got restarted
73 existing := &corev1.ConfigMap{}
74 key := crclient.ObjectKey{Namespace: ns, Name: lockName}
75 err = client.Get(ctx, key, existing)
79 for _, existingOwner := range existing.GetOwnerReferences() {
80 if existingOwner.Name == owner.Name {
81 log.Info("Found existing lock with my name. I was likely restarted.")
82 log.Info("Continuing as the leader.")
85 log.Info("Found existing lock", "LockOwner", existingOwner.Name)
88 case apierrors.IsNotFound(err):
89 log.Info("No pre-existing lock was found.")
91 log.Error(err, "Unknown error trying to get ConfigMap")
95 cm := &corev1.ConfigMap{
96 ObjectMeta: metav1.ObjectMeta{
99 OwnerReferences: []metav1.OwnerReference{*owner},
103 // try to create a lock
104 backoff := time.Second
106 err := client.Create(ctx, cm)
109 log.Info("Became the leader.")
111 case apierrors.IsAlreadyExists(err):
112 log.Info("Not the leader. Waiting.")
114 case <-time.After(wait.Jitter(backoff, .2)):
115 if backoff < maxBackoffInterval {
123 log.Error(err, "Unknown error creating ConfigMap")
129 // myOwnerRef returns an OwnerReference that corresponds to the pod in which
130 // this code is currently running.
131 // It expects the environment variable POD_NAME to be set by the downwards API
132 func myOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) {
133 myPod, err := k8sutil.GetPod(ctx, client, ns)
138 owner := &metav1.OwnerReference{
141 Name: myPod.ObjectMeta.Name,
142 UID: myPod.ObjectMeta.UID,