sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/locking/control_plane_init_mutex_test.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 "errors" 22 "fmt" 23 "testing" 24 25 . "github.com/onsi/gomega" 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/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/apimachinery/pkg/types" 32 ctrl "sigs.k8s.io/controller-runtime" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 "sigs.k8s.io/controller-runtime/pkg/client/fake" 35 36 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 37 ) 38 39 const ( 40 clusterName = "test-cluster" 41 clusterNamespace = "test-namespace" 42 ) 43 44 var ( 45 ctx = ctrl.SetupSignalHandler() 46 ) 47 48 func TestControlPlaneInitMutex_Lock(t *testing.T) { 49 g := NewWithT(t) 50 51 scheme := runtime.NewScheme() 52 g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed()) 53 g.Expect(corev1.AddToScheme(scheme)).To(Succeed()) 54 55 uid := types.UID("test-uid") 56 tests := []struct { 57 name string 58 client client.Client 59 shouldAcquire bool 60 }{ 61 { 62 name: "should successfully acquire lock if the config cannot be found", 63 client: &fakeClient{ 64 Client: fake.NewClientBuilder().WithScheme(scheme).Build(), 65 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 66 }, 67 shouldAcquire: true, 68 }, 69 { 70 name: "should not acquire lock if already exits", 71 client: &fakeClient{ 72 Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.ConfigMap{ 73 ObjectMeta: metav1.ObjectMeta{ 74 Name: configMapName(clusterName), 75 Namespace: clusterNamespace, 76 }, 77 }).Build(), 78 }, 79 shouldAcquire: false, 80 }, 81 { 82 name: "should not acquire lock if cannot create config map", 83 client: &fakeClient{ 84 Client: fake.NewClientBuilder().WithScheme(scheme).Build(), 85 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, configMapName(clusterName)), 86 createError: errors.New("create error"), 87 }, 88 shouldAcquire: false, 89 }, 90 { 91 name: "should not acquire lock if config map already exists while creating", 92 client: &fakeClient{ 93 Client: fake.NewClientBuilder().WithScheme(scheme).Build(), 94 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 95 createError: apierrors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 96 }, 97 shouldAcquire: false, 98 }, 99 } 100 101 for _, tc := range tests { 102 tc := tc 103 t.Run(tc.name, func(t *testing.T) { 104 gs := NewWithT(t) 105 106 l := &ControlPlaneInitMutex{ 107 client: tc.client, 108 } 109 110 cluster := &clusterv1.Cluster{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Namespace: clusterNamespace, 113 Name: clusterName, 114 UID: uid, 115 }, 116 } 117 machine := &clusterv1.Machine{ 118 ObjectMeta: metav1.ObjectMeta{ 119 Name: fmt.Sprintf("machine-%s", cluster.Name), 120 }, 121 } 122 123 gs.Expect(l.Lock(ctx, cluster, machine)).To(Equal(tc.shouldAcquire)) 124 }) 125 } 126 } 127 128 func TestControlPlaneInitMutex_LockWithMachineDeletion(t *testing.T) { 129 g := NewWithT(t) 130 131 scheme := runtime.NewScheme() 132 g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed()) 133 g.Expect(corev1.AddToScheme(scheme)).To(Succeed()) 134 135 newMachineName := "new-machine" 136 tests := []struct { 137 name string 138 client client.Client 139 expectedMachineName string 140 }{ 141 { 142 name: "should not give the lock to new machine if the machine that created it does exist", 143 client: &fakeClient{ 144 Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( 145 &corev1.ConfigMap{ 146 ObjectMeta: metav1.ObjectMeta{ 147 Name: configMapName(clusterName), 148 Namespace: clusterNamespace}, 149 Data: map[string]string{ 150 "lock-information": "{\"machineName\":\"existent-machine\"}", 151 }}, 152 &clusterv1.Machine{ 153 ObjectMeta: metav1.ObjectMeta{ 154 Name: "existent-machine", 155 Namespace: clusterNamespace, 156 }, 157 }, 158 ).Build(), 159 }, 160 expectedMachineName: "existent-machine", 161 }, 162 { 163 name: "should give the lock to new machine if the machine that created it does not exist", 164 client: &fakeClient{ 165 Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( 166 &corev1.ConfigMap{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: configMapName(clusterName), 169 Namespace: clusterNamespace}, 170 Data: map[string]string{ 171 "lock-information": "{\"machineName\":\"non-existent-machine\"}", 172 }}, 173 ).Build(), 174 }, 175 expectedMachineName: newMachineName, 176 }, 177 } 178 for _, tc := range tests { 179 t.Run(tc.name, func(*testing.T) { 180 l := &ControlPlaneInitMutex{ 181 client: tc.client, 182 } 183 184 cluster := &clusterv1.Cluster{ 185 ObjectMeta: metav1.ObjectMeta{ 186 Namespace: clusterNamespace, 187 Name: clusterName, 188 }, 189 } 190 machine := &clusterv1.Machine{ 191 ObjectMeta: metav1.ObjectMeta{ 192 Name: newMachineName, 193 }, 194 } 195 196 g.Eventually(func(g Gomega) error { 197 l.Lock(ctx, cluster, machine) 198 199 cm := &corev1.ConfigMap{} 200 g.Expect(tc.client.Get(ctx, client.ObjectKey{ 201 Name: configMapName(clusterName), 202 Namespace: cluster.Namespace, 203 }, cm)).To(Succeed()) 204 205 info, err := semaphore{cm}.information() 206 g.Expect(err).ToNot(HaveOccurred()) 207 208 g.Expect(info.MachineName).To(Equal(tc.expectedMachineName)) 209 return nil 210 }, "20s").Should(Succeed()) 211 }) 212 } 213 } 214 215 func TestControlPlaneInitMutex_UnLock(t *testing.T) { 216 uid := types.UID("test-uid") 217 configMap := &corev1.ConfigMap{ 218 ObjectMeta: metav1.ObjectMeta{ 219 Name: configMapName(clusterName), 220 Namespace: clusterNamespace, 221 }, 222 } 223 tests := []struct { 224 name string 225 client client.Client 226 shouldRelease bool 227 }{ 228 { 229 name: "should release lock by deleting config map", 230 client: &fakeClient{ 231 Client: fake.NewClientBuilder().Build(), 232 }, 233 shouldRelease: true, 234 }, 235 { 236 name: "should not release lock if cannot delete config map", 237 client: &fakeClient{ 238 Client: fake.NewClientBuilder().WithObjects(configMap).Build(), 239 deleteError: errors.New("delete error"), 240 }, 241 shouldRelease: false, 242 }, 243 { 244 name: "should release lock if config map does not exist", 245 client: &fakeClient{ 246 Client: fake.NewClientBuilder().Build(), 247 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 248 }, 249 shouldRelease: true, 250 }, 251 { 252 name: "should not release lock if error while getting config map", 253 client: &fakeClient{ 254 Client: fake.NewClientBuilder().Build(), 255 getError: errors.New("get error"), 256 }, 257 shouldRelease: false, 258 }, 259 } 260 261 for _, tc := range tests { 262 tc := tc 263 t.Run(tc.name, func(t *testing.T) { 264 gs := NewWithT(t) 265 266 l := &ControlPlaneInitMutex{ 267 client: tc.client, 268 } 269 270 cluster := &clusterv1.Cluster{ 271 ObjectMeta: metav1.ObjectMeta{ 272 Namespace: clusterNamespace, 273 Name: clusterName, 274 UID: uid, 275 }, 276 } 277 278 gs.Expect(l.Unlock(ctx, cluster)).To(Equal(tc.shouldRelease)) 279 }) 280 } 281 } 282 283 type fakeClient struct { 284 client.Client 285 getError error 286 createError error 287 deleteError error 288 } 289 290 func (fc *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { 291 if fc.getError != nil { 292 return fc.getError 293 } 294 return fc.Client.Get(ctx, key, obj, opts...) 295 } 296 297 func (fc *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { 298 if fc.createError != nil { 299 return fc.createError 300 } 301 return fc.Client.Create(ctx, obj, opts...) 302 } 303 304 func (fc *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { 305 if fc.deleteError != nil { 306 return fc.deleteError 307 } 308 return fc.Client.Delete(ctx, obj, opts...) 309 }