github.skymusic.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  }