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