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 }