Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / operator-framework / operator-sdk / pkg / leader / leader.go
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         key := crclient.ObjectKey{Namespace: ns, Name: lockName}
75         err = client.Get(ctx, key, existing)
76
77         switch {
78         case err == nil:
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.")
83                                 return nil
84                         } else {
85                                 log.Info("Found existing lock", "LockOwner", existingOwner.Name)
86                         }
87                 }
88         case apierrors.IsNotFound(err):
89                 log.Info("No pre-existing lock was found.")
90         default:
91                 log.Error(err, "Unknown error trying to get ConfigMap")
92                 return err
93         }
94
95         cm := &corev1.ConfigMap{
96                 ObjectMeta: metav1.ObjectMeta{
97                         Name:            lockName,
98                         Namespace:       ns,
99                         OwnerReferences: []metav1.OwnerReference{*owner},
100                 },
101         }
102
103         // try to create a lock
104         backoff := time.Second
105         for {
106                 err := client.Create(ctx, cm)
107                 switch {
108                 case err == nil:
109                         log.Info("Became the leader.")
110                         return nil
111                 case apierrors.IsAlreadyExists(err):
112                         log.Info("Not the leader. Waiting.")
113                         select {
114                         case <-time.After(wait.Jitter(backoff, .2)):
115                                 if backoff < maxBackoffInterval {
116                                         backoff *= 2
117                                 }
118                                 continue
119                         case <-ctx.Done():
120                                 return ctx.Err()
121                         }
122                 default:
123                         log.Error(err, "Unknown error creating ConfigMap")
124                         return err
125                 }
126         }
127 }
128
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)
134         if err != nil {
135                 return nil, err
136         }
137
138         owner := &metav1.OwnerReference{
139                 APIVersion: "v1",
140                 Kind:       "Pod",
141                 Name:       myPod.ObjectMeta.Name,
142                 UID:        myPod.ObjectMeta.UID,
143         }
144         return owner, nil
145 }