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 }