sigs.k8s.io/cluster-api/bootstrap/kubeadm@v0.0.0-20191016155141-23a891785b60/internal/locking/control_plane_init_mutex.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes 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 locking
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  
    24  	"github.com/go-logr/logr"
    25  	"github.com/pkg/errors"
    26  	apicorev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  )
    32  
    33  const semaphoreInformationKey = "lock-information"
    34  
    35  // ControlPlaneInitMutex uses a ConfigMap to synchronize cluster initialization.
    36  type ControlPlaneInitMutex struct {
    37  	log    logr.Logger
    38  	client client.Client
    39  }
    40  
    41  // NewControlPlaneInitMutex returns a lock that can be held by a control plane node before init.
    42  func NewControlPlaneInitMutex(log logr.Logger, client client.Client) *ControlPlaneInitMutex {
    43  	return &ControlPlaneInitMutex{
    44  		log:    log,
    45  		client: client,
    46  	}
    47  }
    48  
    49  // Lock allows a control plane node to be the first and only node to run kubeadm init
    50  func (c *ControlPlaneInitMutex) Lock(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) bool {
    51  	sema := newSemaphore()
    52  	cmName := configMapName(cluster.Name)
    53  	log := c.log.WithValues("namespace", cluster.Namespace, "cluster-name", cluster.Name, "configmap-name", cmName)
    54  	err := c.client.Get(ctx, client.ObjectKey{
    55  		Namespace: cluster.Namespace,
    56  		Name:      cmName,
    57  	}, &sema.ConfigMap)
    58  	switch {
    59  	case apierrors.IsNotFound(err):
    60  		break
    61  	case err != nil:
    62  		log.Error(err, "Failed to acquire lock")
    63  		return false
    64  	default: // successfully found an existing config map
    65  		info, err := sema.information()
    66  		if err != nil {
    67  			log.Error(err, "Failed to get information about the existing lock")
    68  			return false
    69  		}
    70  		// the machine requesting the lock is the machine that created the lock, therefore the lock is acquired
    71  		if info.MachineName == machine.Name {
    72  			return true
    73  		}
    74  		log.Info("Waiting on on another machine to initialize", "init-machine", info.MachineName)
    75  		return false
    76  	}
    77  
    78  	// Adds owner reference, namespace and name
    79  	sema.setMetadata(cluster)
    80  	// Adds the additional information
    81  	if err := sema.setInformation(&information{MachineName: machine.Name}); err != nil {
    82  		log.Error(err, "Failed to acquire lock while setting semaphore information")
    83  		return false
    84  	}
    85  
    86  	log.Info("Attempting to acquire the lock")
    87  	err = c.client.Create(ctx, &sema.ConfigMap)
    88  	switch {
    89  	case apierrors.IsAlreadyExists(err):
    90  		log.Info("Cannot acquire the lock. The lock has been acquired by someone else")
    91  		return false
    92  	case err != nil:
    93  		log.Error(err, "Error acquiring the lock")
    94  		return false
    95  	default:
    96  		return true
    97  	}
    98  }
    99  
   100  // Unlock releases the lock
   101  func (c *ControlPlaneInitMutex) Unlock(ctx context.Context, cluster *clusterv1.Cluster) bool {
   102  	sema := newSemaphore()
   103  	cmName := configMapName(cluster.Name)
   104  	log := c.log.WithValues("namespace", cluster.Namespace, "cluster-name", cluster.Name, "configmap-name", cmName)
   105  	log.Info("Checking for lock")
   106  	err := c.client.Get(ctx, client.ObjectKey{
   107  		Namespace: cluster.Namespace,
   108  		Name:      cmName,
   109  	}, &sema.ConfigMap)
   110  	switch {
   111  	case apierrors.IsNotFound(err):
   112  		log.Info("Control plane init lock not found, it may have been released already")
   113  		return true
   114  	case err != nil:
   115  		log.Error(err, "Error unlocking the control plane init lock")
   116  		return false
   117  	default:
   118  		// Delete the config map semaphore if there is no error fetching it
   119  		if err := c.client.Delete(ctx, &sema.ConfigMap); err != nil {
   120  			// TODO: return true on apierrors.IsNotFound
   121  			log.Error(err, "Error deleting the config map underlying the control plane init lock")
   122  			return false
   123  		}
   124  		return true
   125  	}
   126  }
   127  
   128  type information struct {
   129  	MachineName string `json:"machineName"`
   130  }
   131  
   132  type semaphore struct {
   133  	apicorev1.ConfigMap
   134  }
   135  
   136  func newSemaphore() *semaphore {
   137  	return &semaphore{apicorev1.ConfigMap{}}
   138  }
   139  
   140  func configMapName(clusterName string) string {
   141  	return fmt.Sprintf("%s-lock", clusterName)
   142  }
   143  
   144  func (s semaphore) information() (*information, error) {
   145  	li := &information{}
   146  	if err := json.Unmarshal([]byte(s.Data[semaphoreInformationKey]), li); err != nil {
   147  		return nil, errors.Wrap(err, "failed to unmarshal semaphore information")
   148  	}
   149  	return li, nil
   150  }
   151  
   152  func (s semaphore) setInformation(information *information) error {
   153  	b, err := json.Marshal(information)
   154  	if err != nil {
   155  		return errors.Wrap(err, "failed to marshal semaphore information")
   156  	}
   157  	s.Data = map[string]string{}
   158  	s.Data[semaphoreInformationKey] = string(b)
   159  	return nil
   160  }
   161  
   162  func (s *semaphore) setMetadata(cluster *clusterv1.Cluster) {
   163  	s.ObjectMeta = metav1.ObjectMeta{
   164  		Namespace: cluster.Namespace,
   165  		Name:      configMapName(cluster.Name),
   166  		OwnerReferences: []metav1.OwnerReference{
   167  			{
   168  				APIVersion: cluster.APIVersion,
   169  				Kind:       cluster.Kind,
   170  				Name:       cluster.Name,
   171  				UID:        cluster.UID,
   172  			},
   173  		},
   174  	}
   175  }