sigs.k8s.io/cluster-api/bootstrap/kubeadm@v0.0.0-20191016155141-23a891785b60/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 "encoding/json" 22 "errors" 23 "fmt" 24 "testing" 25 26 "github.com/go-logr/logr" 27 corev1 "k8s.io/api/core/v1" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/klog" 34 clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 "sigs.k8s.io/controller-runtime/pkg/client/fake" 37 "sigs.k8s.io/controller-runtime/pkg/runtime/log" 38 ) 39 40 const ( 41 clusterName = "test-cluster" 42 clusterNamespace = "test-namespace" 43 ) 44 45 func init() { 46 klog.InitFlags(nil) 47 } 48 49 func TestControlPlaneInitMutex_Lock(t *testing.T) { 50 scheme := runtime.NewScheme() 51 if err := clusterv1.AddToScheme(scheme); err != nil { 52 t.Fatal(err) 53 } 54 if err := corev1.AddToScheme(scheme); err != nil { 55 t.Fatal(err) 56 } 57 uid := types.UID("test-uid") 58 59 tests := []struct { 60 name string 61 context context.Context 62 client client.Client 63 shouldAcquire bool 64 }{ 65 { 66 name: "should successfully acquire lock if the config cannot be found", 67 context: context.Background(), 68 client: &fakeClient{ 69 Client: fake.NewFakeClientWithScheme(scheme), 70 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 71 }, 72 shouldAcquire: true, 73 }, 74 { 75 name: "should not acquire lock if already exits", 76 context: context.Background(), 77 client: &fakeClient{ 78 Client: fake.NewFakeClientWithScheme(scheme, &corev1.ConfigMap{ 79 ObjectMeta: metav1.ObjectMeta{ 80 Name: configMapName(clusterName), 81 Namespace: clusterNamespace, 82 }, 83 }), 84 }, 85 shouldAcquire: false, 86 }, 87 { 88 name: "should not acquire lock if cannot create config map", 89 context: context.Background(), 90 client: &fakeClient{ 91 Client: fake.NewFakeClientWithScheme(scheme), 92 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, configMapName(clusterName)), 93 createError: errors.New("create error"), 94 }, 95 shouldAcquire: false, 96 }, 97 { 98 name: "should not acquire lock if config map already exists while creating", 99 context: context.Background(), 100 client: &fakeClient{ 101 Client: fake.NewFakeClientWithScheme(scheme), 102 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 103 createError: apierrors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 104 }, 105 shouldAcquire: false, 106 }, 107 } 108 109 for _, tc := range tests { 110 tc := tc 111 t.Run(tc.name, func(t *testing.T) { 112 l := &ControlPlaneInitMutex{ 113 log: log.Log, 114 client: tc.client, 115 } 116 117 cluster := &clusterv1.Cluster{ 118 ObjectMeta: metav1.ObjectMeta{ 119 Namespace: clusterNamespace, 120 Name: clusterName, 121 UID: uid, 122 }, 123 } 124 machine := &clusterv1.Machine{ 125 ObjectMeta: metav1.ObjectMeta{ 126 Name: fmt.Sprintf("machine-%s", cluster.Name), 127 }, 128 } 129 130 actual := l.Lock(context.Background(), cluster, machine) 131 if actual != tc.shouldAcquire { 132 t.Fatalf("acquired was %v, but it should be %v", actual, tc.shouldAcquire) 133 } 134 }) 135 } 136 } 137 func TestControlPlaneInitMutex_UnLock(t *testing.T) { 138 scheme := runtime.NewScheme() 139 if err := clusterv1.AddToScheme(scheme); err != nil { 140 t.Fatal(err) 141 } 142 if err := corev1.AddToScheme(scheme); err != nil { 143 t.Fatal(err) 144 } 145 uid := types.UID("test-uid") 146 configMap := &corev1.ConfigMap{ 147 ObjectMeta: metav1.ObjectMeta{ 148 Name: configMapName(clusterName), 149 Namespace: clusterNamespace, 150 }, 151 } 152 tests := []struct { 153 name string 154 context context.Context 155 client client.Client 156 shouldRelease bool 157 }{ 158 { 159 name: "should release lock by deleting config map", 160 context: context.Background(), 161 client: &fakeClient{ 162 Client: fake.NewFakeClientWithScheme(scheme), 163 }, 164 shouldRelease: true, 165 }, 166 { 167 name: "should not release lock if cannot delete config map", 168 context: context.Background(), 169 client: &fakeClient{ 170 Client: fake.NewFakeClientWithScheme(scheme, configMap), 171 deleteError: errors.New("delete error"), 172 }, 173 shouldRelease: false, 174 }, 175 { 176 name: "should release lock if config map does not exist", 177 context: context.Background(), 178 client: &fakeClient{ 179 Client: fake.NewFakeClientWithScheme(scheme), 180 getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 181 }, 182 shouldRelease: true, 183 }, 184 { 185 name: "should not release lock if error while getting config map", 186 context: context.Background(), 187 client: &fakeClient{ 188 Client: fake.NewFakeClientWithScheme(scheme), 189 getError: errors.New("get error"), 190 }, 191 shouldRelease: false, 192 }, 193 } 194 195 for _, tc := range tests { 196 tc := tc 197 t.Run(tc.name, func(t *testing.T) { 198 l := &ControlPlaneInitMutex{ 199 log: log.Log, 200 client: tc.client, 201 } 202 203 cluster := &clusterv1.Cluster{ 204 ObjectMeta: metav1.ObjectMeta{ 205 Namespace: clusterNamespace, 206 Name: clusterName, 207 UID: uid, 208 }, 209 } 210 211 released := l.Unlock(context.Background(), cluster) 212 if released != tc.shouldRelease { 213 t.Fatalf("released was %v, but it should be %v\n", released, tc.shouldRelease) 214 } 215 216 }) 217 } 218 } 219 220 func TestInfoLines_Lock(t *testing.T) { 221 scheme := runtime.NewScheme() 222 if err := clusterv1.AddToScheme(scheme); err != nil { 223 t.Fatal(err) 224 } 225 if err := corev1.AddToScheme(scheme); err != nil { 226 t.Fatal(err) 227 } 228 uid := types.UID("test-uid") 229 info := information{MachineName: "my-control-plane"} 230 b, err := json.Marshal(info) 231 if err != nil { 232 t.Fatal("failed to marshal info") 233 } 234 c := &fakeClient{ 235 Client: fake.NewFakeClientWithScheme(scheme, &corev1.ConfigMap{ 236 ObjectMeta: metav1.ObjectMeta{ 237 Name: configMapName(clusterName), 238 Namespace: clusterNamespace, 239 }, 240 Data: map[string]string{semaphoreInformationKey: string(b)}, 241 }), 242 } 243 244 logtester := &logtests{ 245 InfoLog: make([]line, 0), 246 } 247 l := &ControlPlaneInitMutex{ 248 log: logtester, 249 client: c, 250 } 251 252 cluster := &clusterv1.Cluster{ 253 ObjectMeta: metav1.ObjectMeta{ 254 Namespace: clusterNamespace, 255 Name: clusterName, 256 UID: uid, 257 }, 258 } 259 machine := &clusterv1.Machine{ 260 ObjectMeta: metav1.ObjectMeta{ 261 Name: fmt.Sprintf("machine-%s", cluster.Name), 262 }, 263 } 264 if l.Lock(context.Background(), cluster, machine) != false { 265 t.Fatal("acquired lock but did not expect to") 266 } 267 foundLogLine := false 268 for _, line := range logtester.InfoLog { 269 fmt.Println(line) 270 for k, v := range line.data { 271 if k == "init-machine" && v.(string) == "my-control-plane" { 272 foundLogLine = true 273 } 274 } 275 } 276 if !foundLogLine { 277 t.Fatalf("Did not find the log line containing the name of the machine currently intializing") 278 } 279 } 280 281 type fakeClient struct { 282 client.Client 283 getError error 284 createError error 285 deleteError error 286 } 287 288 func (fc *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error { 289 if fc.getError != nil { 290 return fc.getError 291 } 292 return fc.Client.Get(ctx, key, obj) 293 } 294 295 func (fc *fakeClient) Create(ctx context.Context, obj runtime.Object, opts ...client.CreateOption) error { 296 if fc.createError != nil { 297 return fc.createError 298 } 299 return fc.Client.Create(ctx, obj, opts...) 300 } 301 302 func (fc *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error { 303 if fc.deleteError != nil { 304 return fc.deleteError 305 } 306 return fc.Client.Delete(ctx, obj, opts...) 307 } 308 309 type logtests struct { 310 logr.Logger 311 InfoLog []line 312 } 313 314 type line struct { 315 line string 316 data map[string]interface{} 317 } 318 319 func (l *logtests) Info(msg string, keysAndValues ...interface{}) { 320 data := make(map[string]interface{}) 321 for i := 0; i < len(keysAndValues); i += 2 { 322 data[keysAndValues[i].(string)] = keysAndValues[i+1] 323 } 324 l.InfoLog = append(l.InfoLog, line{ 325 line: msg, 326 data: data, 327 }) 328 } 329 func (l *logtests) WithValues(keysAndValues ...interface{}) logr.Logger { 330 return l 331 }