sigs.k8s.io/cluster-api@v1.6.3/controlplane/kubeadm/internal/controllers/scale_test.go (about) 1 /* 2 Copyright 2020 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 controllers 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 . "github.com/onsi/gomega" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/tools/record" 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 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 35 controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" 36 "sigs.k8s.io/cluster-api/controlplane/kubeadm/internal" 37 "sigs.k8s.io/cluster-api/util" 38 "sigs.k8s.io/cluster-api/util/collections" 39 "sigs.k8s.io/cluster-api/util/conditions" 40 ) 41 42 func TestKubeadmControlPlaneReconciler_initializeControlPlane(t *testing.T) { 43 setup := func(t *testing.T, g *WithT) *corev1.Namespace { 44 t.Helper() 45 46 t.Log("Creating the namespace") 47 ns, err := env.CreateNamespace(ctx, "test-kcp-reconciler-initializecontrolplane") 48 g.Expect(err).ToNot(HaveOccurred()) 49 50 return ns 51 } 52 53 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace) { 54 t.Helper() 55 56 t.Log("Deleting the namespace") 57 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 58 } 59 60 g := NewWithT(t) 61 namespace := setup(t, g) 62 defer teardown(t, g, namespace) 63 64 cluster, kcp, genericInfrastructureMachineTemplate := createClusterWithControlPlane(namespace.Name) 65 g.Expect(env.Create(ctx, genericInfrastructureMachineTemplate, client.FieldOwner("manager"))).To(Succeed()) 66 kcp.UID = types.UID(util.RandomString(10)) 67 68 r := &KubeadmControlPlaneReconciler{ 69 Client: env, 70 recorder: record.NewFakeRecorder(32), 71 managementClusterUncached: &fakeManagementCluster{ 72 Management: &internal.Management{Client: env}, 73 Workload: fakeWorkloadCluster{}, 74 }, 75 } 76 controlPlane := &internal.ControlPlane{ 77 Cluster: cluster, 78 KCP: kcp, 79 } 80 81 result, err := r.initializeControlPlane(ctx, controlPlane) 82 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 83 g.Expect(err).ToNot(HaveOccurred()) 84 85 machineList := &clusterv1.MachineList{} 86 g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace))).To(Succeed()) 87 g.Expect(machineList.Items).To(HaveLen(1)) 88 89 res, err := collections.GetFilteredMachinesForCluster(ctx, env.GetAPIReader(), cluster, collections.OwnedMachines(kcp)) 90 g.Expect(res).To(HaveLen(1)) 91 g.Expect(err).ToNot(HaveOccurred()) 92 93 g.Expect(machineList.Items[0].Namespace).To(Equal(cluster.Namespace)) 94 g.Expect(machineList.Items[0].Name).To(HavePrefix(kcp.Name)) 95 96 g.Expect(machineList.Items[0].Spec.InfrastructureRef.Namespace).To(Equal(cluster.Namespace)) 97 g.Expect(machineList.Items[0].Spec.InfrastructureRef.Name).To(HavePrefix(genericInfrastructureMachineTemplate.GetName())) 98 g.Expect(machineList.Items[0].Spec.InfrastructureRef.APIVersion).To(Equal(genericInfrastructureMachineTemplate.GetAPIVersion())) 99 g.Expect(machineList.Items[0].Spec.InfrastructureRef.Kind).To(Equal("GenericInfrastructureMachine")) 100 101 g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Namespace).To(Equal(cluster.Namespace)) 102 g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Name).To(HavePrefix(kcp.Name)) 103 g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.APIVersion).To(Equal(bootstrapv1.GroupVersion.String())) 104 g.Expect(machineList.Items[0].Spec.Bootstrap.ConfigRef.Kind).To(Equal("KubeadmConfig")) 105 } 106 107 func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) { 108 t.Run("creates a control plane Machine if preflight checks pass", func(t *testing.T) { 109 setup := func(t *testing.T, g *WithT) *corev1.Namespace { 110 t.Helper() 111 112 t.Log("Creating the namespace") 113 ns, err := env.CreateNamespace(ctx, "test-kcp-reconciler-scaleupcontrolplane") 114 g.Expect(err).ToNot(HaveOccurred()) 115 116 return ns 117 } 118 119 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace) { 120 t.Helper() 121 122 t.Log("Deleting the namespace") 123 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 124 } 125 126 g := NewWithT(t) 127 namespace := setup(t, g) 128 defer teardown(t, g, namespace) 129 130 cluster, kcp, genericInfrastructureMachineTemplate := createClusterWithControlPlane(namespace.Name) 131 g.Expect(env.Create(ctx, genericInfrastructureMachineTemplate, client.FieldOwner("manager"))).To(Succeed()) 132 kcp.UID = types.UID(util.RandomString(10)) 133 setKCPHealthy(kcp) 134 135 fmc := &fakeManagementCluster{ 136 Machines: collections.New(), 137 Workload: fakeWorkloadCluster{}, 138 } 139 140 for i := 0; i < 2; i++ { 141 m, _ := createMachineNodePair(fmt.Sprintf("test-%d", i), cluster, kcp, true) 142 setMachineHealthy(m) 143 fmc.Machines.Insert(m) 144 } 145 146 r := &KubeadmControlPlaneReconciler{ 147 Client: env, 148 managementCluster: fmc, 149 managementClusterUncached: fmc, 150 recorder: record.NewFakeRecorder(32), 151 } 152 controlPlane := &internal.ControlPlane{ 153 KCP: kcp, 154 Cluster: cluster, 155 Machines: fmc.Machines, 156 } 157 158 result, err := r.scaleUpControlPlane(ctx, controlPlane) 159 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 160 g.Expect(err).ToNot(HaveOccurred()) 161 162 controlPlaneMachines := clusterv1.MachineList{} 163 g.Expect(env.GetAPIReader().List(ctx, &controlPlaneMachines, client.InNamespace(namespace.Name))).To(Succeed()) 164 // A new machine should have been created. 165 // Note: expected length is 1 because only the newly created machine is on API server. Other machines are 166 // in-memory only during the test. 167 g.Expect(controlPlaneMachines.Items).To(HaveLen(1)) 168 }) 169 t.Run("does not create a control plane Machine if preflight checks fail", func(t *testing.T) { 170 setup := func(t *testing.T, g *WithT) *corev1.Namespace { 171 t.Helper() 172 173 t.Log("Creating the namespace") 174 ns, err := env.CreateNamespace(ctx, "test-kcp-reconciler-scaleupcontrolplane") 175 g.Expect(err).ToNot(HaveOccurred()) 176 177 return ns 178 } 179 180 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace) { 181 t.Helper() 182 183 t.Log("Deleting the namespace") 184 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 185 } 186 187 g := NewWithT(t) 188 namespace := setup(t, g) 189 defer teardown(t, g, namespace) 190 191 cluster, kcp, genericInfrastructureMachineTemplate := createClusterWithControlPlane(namespace.Name) 192 g.Expect(env.Create(ctx, genericInfrastructureMachineTemplate, client.FieldOwner("manager"))).To(Succeed()) 193 kcp.UID = types.UID(util.RandomString(10)) 194 cluster.UID = types.UID(util.RandomString(10)) 195 cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com" 196 cluster.Spec.ControlPlaneEndpoint.Port = 6443 197 cluster.Status.InfrastructureReady = true 198 199 beforeMachines := collections.New() 200 for i := 0; i < 2; i++ { 201 m, _ := createMachineNodePair(fmt.Sprintf("test-%d", i), cluster.DeepCopy(), kcp.DeepCopy(), true) 202 beforeMachines.Insert(m) 203 } 204 205 fmc := &fakeManagementCluster{ 206 Machines: beforeMachines.DeepCopy(), 207 Workload: fakeWorkloadCluster{}, 208 } 209 210 r := &KubeadmControlPlaneReconciler{ 211 Client: env, 212 SecretCachingClient: secretCachingClient, 213 managementCluster: fmc, 214 managementClusterUncached: fmc, 215 recorder: record.NewFakeRecorder(32), 216 } 217 218 controlPlane, adoptableMachineFound, err := r.initControlPlaneScope(ctx, cluster, kcp) 219 g.Expect(err).ToNot(HaveOccurred()) 220 g.Expect(adoptableMachineFound).To(BeFalse()) 221 222 result, err := r.scaleUpControlPlane(context.Background(), controlPlane) 223 g.Expect(err).ToNot(HaveOccurred()) 224 g.Expect(result).To(BeComparableTo(ctrl.Result{RequeueAfter: preflightFailedRequeueAfter})) 225 226 // scaleUpControlPlane is never called due to health check failure and new machine is not created to scale up. 227 controlPlaneMachines := &clusterv1.MachineList{} 228 g.Expect(env.GetAPIReader().List(context.Background(), controlPlaneMachines, client.InNamespace(namespace.Name))).To(Succeed()) 229 // No new machine should be created. 230 // Note: expected length is 0 because no machine is created and hence no machine is on the API server. 231 // Other machines are in-memory only during the test. 232 g.Expect(controlPlaneMachines.Items).To(BeEmpty()) 233 234 endMachines := collections.FromMachineList(controlPlaneMachines) 235 for _, m := range endMachines { 236 bm, ok := beforeMachines[m.Name] 237 g.Expect(ok).To(BeTrue()) 238 g.Expect(m).To(BeComparableTo(bm)) 239 } 240 }) 241 } 242 243 func TestKubeadmControlPlaneReconciler_scaleDownControlPlane_NoError(t *testing.T) { 244 t.Run("deletes control plane Machine if preflight checks pass", func(t *testing.T) { 245 g := NewWithT(t) 246 247 machines := map[string]*clusterv1.Machine{ 248 "one": machine("one"), 249 } 250 setMachineHealthy(machines["one"]) 251 fakeClient := newFakeClient(machines["one"]) 252 253 r := &KubeadmControlPlaneReconciler{ 254 recorder: record.NewFakeRecorder(32), 255 Client: fakeClient, 256 SecretCachingClient: fakeClient, 257 managementCluster: &fakeManagementCluster{ 258 Workload: fakeWorkloadCluster{}, 259 }, 260 } 261 262 cluster := &clusterv1.Cluster{} 263 kcp := &controlplanev1.KubeadmControlPlane{ 264 Spec: controlplanev1.KubeadmControlPlaneSpec{ 265 Version: "v1.19.1", 266 }, 267 } 268 setKCPHealthy(kcp) 269 controlPlane := &internal.ControlPlane{ 270 KCP: kcp, 271 Cluster: cluster, 272 Machines: machines, 273 } 274 controlPlane.InjectTestManagementCluster(r.managementCluster) 275 276 result, err := r.scaleDownControlPlane(context.Background(), controlPlane, controlPlane.Machines) 277 g.Expect(err).ToNot(HaveOccurred()) 278 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 279 280 controlPlaneMachines := clusterv1.MachineList{} 281 g.Expect(fakeClient.List(context.Background(), &controlPlaneMachines)).To(Succeed()) 282 g.Expect(controlPlaneMachines.Items).To(BeEmpty()) 283 }) 284 t.Run("deletes the oldest control plane Machine even if preflight checks fails", func(t *testing.T) { 285 g := NewWithT(t) 286 287 machines := map[string]*clusterv1.Machine{ 288 "one": machine("one", withTimestamp(time.Now().Add(-1*time.Minute))), 289 "two": machine("two", withTimestamp(time.Now())), 290 "three": machine("three", withTimestamp(time.Now())), 291 } 292 setMachineHealthy(machines["two"]) 293 setMachineHealthy(machines["three"]) 294 fakeClient := newFakeClient(machines["one"], machines["two"], machines["three"]) 295 296 r := &KubeadmControlPlaneReconciler{ 297 recorder: record.NewFakeRecorder(32), 298 Client: fakeClient, 299 SecretCachingClient: fakeClient, 300 managementCluster: &fakeManagementCluster{ 301 Workload: fakeWorkloadCluster{}, 302 }, 303 } 304 305 cluster := &clusterv1.Cluster{} 306 kcp := &controlplanev1.KubeadmControlPlane{ 307 Spec: controlplanev1.KubeadmControlPlaneSpec{ 308 Version: "v1.19.1", 309 }, 310 } 311 controlPlane := &internal.ControlPlane{ 312 KCP: kcp, 313 Cluster: cluster, 314 Machines: machines, 315 } 316 controlPlane.InjectTestManagementCluster(r.managementCluster) 317 318 result, err := r.scaleDownControlPlane(context.Background(), controlPlane, controlPlane.Machines) 319 g.Expect(err).ToNot(HaveOccurred()) 320 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 321 322 controlPlaneMachines := clusterv1.MachineList{} 323 g.Expect(fakeClient.List(context.Background(), &controlPlaneMachines)).To(Succeed()) 324 g.Expect(controlPlaneMachines.Items).To(HaveLen(2)) 325 }) 326 327 t.Run("does not scale down if preflight checks fail on any machine other than the one being deleted", func(t *testing.T) { 328 g := NewWithT(t) 329 330 machines := map[string]*clusterv1.Machine{ 331 "one": machine("one", withTimestamp(time.Now().Add(-1*time.Minute))), 332 "two": machine("two", withTimestamp(time.Now())), 333 "three": machine("three", withTimestamp(time.Now())), 334 } 335 setMachineHealthy(machines["three"]) 336 fakeClient := newFakeClient(machines["one"], machines["two"], machines["three"]) 337 338 r := &KubeadmControlPlaneReconciler{ 339 recorder: record.NewFakeRecorder(32), 340 Client: fakeClient, 341 SecretCachingClient: fakeClient, 342 managementCluster: &fakeManagementCluster{ 343 Workload: fakeWorkloadCluster{}, 344 }, 345 } 346 347 cluster := &clusterv1.Cluster{} 348 kcp := &controlplanev1.KubeadmControlPlane{} 349 controlPlane := &internal.ControlPlane{ 350 KCP: kcp, 351 Cluster: cluster, 352 Machines: machines, 353 } 354 controlPlane.InjectTestManagementCluster(r.managementCluster) 355 356 result, err := r.scaleDownControlPlane(context.Background(), controlPlane, controlPlane.Machines) 357 g.Expect(err).ToNot(HaveOccurred()) 358 g.Expect(result).To(BeComparableTo(ctrl.Result{RequeueAfter: preflightFailedRequeueAfter})) 359 360 controlPlaneMachines := clusterv1.MachineList{} 361 g.Expect(fakeClient.List(context.Background(), &controlPlaneMachines)).To(Succeed()) 362 g.Expect(controlPlaneMachines.Items).To(HaveLen(3)) 363 }) 364 } 365 366 func TestSelectMachineForScaleDown(t *testing.T) { 367 kcp := controlplanev1.KubeadmControlPlane{ 368 Spec: controlplanev1.KubeadmControlPlaneSpec{}, 369 } 370 startDate := time.Date(2000, 1, 1, 1, 0, 0, 0, time.UTC) 371 m1 := machine("machine-1", withFailureDomain("one"), withTimestamp(startDate.Add(time.Hour))) 372 m2 := machine("machine-2", withFailureDomain("one"), withTimestamp(startDate.Add(-3*time.Hour))) 373 m3 := machine("machine-3", withFailureDomain("one"), withTimestamp(startDate.Add(-4*time.Hour))) 374 m4 := machine("machine-4", withFailureDomain("two"), withTimestamp(startDate.Add(-time.Hour))) 375 m5 := machine("machine-5", withFailureDomain("two"), withTimestamp(startDate.Add(-2*time.Hour))) 376 m6 := machine("machine-6", withFailureDomain("two"), withTimestamp(startDate.Add(-7*time.Hour))) 377 m7 := machine("machine-7", withFailureDomain("two"), withTimestamp(startDate.Add(-5*time.Hour)), withAnnotation("cluster.x-k8s.io/delete-machine")) 378 m8 := machine("machine-8", withFailureDomain("two"), withTimestamp(startDate.Add(-6*time.Hour)), withAnnotation("cluster.x-k8s.io/delete-machine")) 379 380 mc3 := collections.FromMachines(m1, m2, m3, m4, m5) 381 mc6 := collections.FromMachines(m6, m7, m8) 382 fd := clusterv1.FailureDomains{ 383 "one": failureDomain(true), 384 "two": failureDomain(true), 385 } 386 387 needsUpgradeControlPlane := &internal.ControlPlane{ 388 KCP: &kcp, 389 Cluster: &clusterv1.Cluster{Status: clusterv1.ClusterStatus{FailureDomains: fd}}, 390 Machines: mc3, 391 } 392 upToDateControlPlane := &internal.ControlPlane{ 393 KCP: &kcp, 394 Cluster: &clusterv1.Cluster{Status: clusterv1.ClusterStatus{FailureDomains: fd}}, 395 Machines: mc3.Filter(func(m *clusterv1.Machine) bool { 396 return m.Name != "machine-5" 397 }), 398 } 399 annotatedControlPlane := &internal.ControlPlane{ 400 KCP: &kcp, 401 Cluster: &clusterv1.Cluster{Status: clusterv1.ClusterStatus{FailureDomains: fd}}, 402 Machines: mc6, 403 } 404 405 testCases := []struct { 406 name string 407 cp *internal.ControlPlane 408 outDatedMachines collections.Machines 409 expectErr bool 410 expectedMachine clusterv1.Machine 411 }{ 412 { 413 name: "when there are machines needing upgrade, it returns the oldest machine in the failure domain with the most machines needing upgrade", 414 cp: needsUpgradeControlPlane, 415 outDatedMachines: collections.FromMachines(m5), 416 expectErr: false, 417 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-5"}}, 418 }, 419 { 420 name: "when there are no outdated machines, it returns the oldest machine in the largest failure domain", 421 cp: upToDateControlPlane, 422 outDatedMachines: collections.New(), 423 expectErr: false, 424 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-3"}}, 425 }, 426 { 427 name: "when there is a single machine marked with delete annotation key in machine collection, it returns only that marked machine", 428 cp: annotatedControlPlane, 429 outDatedMachines: collections.FromMachines(m6, m7), 430 expectErr: false, 431 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-7"}}, 432 }, 433 { 434 name: "when there are machines marked with delete annotation key in machine collection, it returns the oldest marked machine first", 435 cp: annotatedControlPlane, 436 outDatedMachines: collections.FromMachines(m7, m8), 437 expectErr: false, 438 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-8"}}, 439 }, 440 { 441 name: "when there are annotated machines which are part of the annotatedControlPlane but not in outdatedMachines, it returns the oldest marked machine first", 442 cp: annotatedControlPlane, 443 outDatedMachines: collections.New(), 444 expectErr: false, 445 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-8"}}, 446 }, 447 { 448 name: "when there are machines needing upgrade, it returns the oldest machine in the failure domain with the most machines needing upgrade", 449 cp: needsUpgradeControlPlane, 450 outDatedMachines: collections.FromMachines(m7, m3), 451 expectErr: false, 452 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-7"}}, 453 }, 454 { 455 name: "when there is an up to date machine with delete annotation, while there are any outdated machines without annotatio that still exist, it returns oldest marked machine first", 456 cp: upToDateControlPlane, 457 outDatedMachines: collections.FromMachines(m5, m3, m8, m7, m6, m1, m2), 458 expectErr: false, 459 expectedMachine: clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "machine-8"}}, 460 }, 461 } 462 463 for _, tc := range testCases { 464 t.Run(tc.name, func(t *testing.T) { 465 g := NewWithT(t) 466 467 selectedMachine, err := selectMachineForScaleDown(tc.cp, tc.outDatedMachines) 468 469 if tc.expectErr { 470 g.Expect(err).To(HaveOccurred()) 471 return 472 } 473 474 g.Expect(err).ToNot(HaveOccurred()) 475 g.Expect(tc.expectedMachine.Name).To(Equal(selectedMachine.Name)) 476 }) 477 } 478 } 479 480 func TestPreflightChecks(t *testing.T) { 481 testCases := []struct { 482 name string 483 kcp *controlplanev1.KubeadmControlPlane 484 machines []*clusterv1.Machine 485 expectResult ctrl.Result 486 }{ 487 { 488 name: "control plane without machines (not initialized) should pass", 489 kcp: &controlplanev1.KubeadmControlPlane{}, 490 expectResult: ctrl.Result{}, 491 }, 492 { 493 name: "control plane with a deleting machine should requeue", 494 kcp: &controlplanev1.KubeadmControlPlane{}, 495 machines: []*clusterv1.Machine{ 496 { 497 ObjectMeta: metav1.ObjectMeta{ 498 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 499 }, 500 }, 501 }, 502 expectResult: ctrl.Result{RequeueAfter: deleteRequeueAfter}, 503 }, 504 { 505 name: "control plane without a nodeRef should requeue", 506 kcp: &controlplanev1.KubeadmControlPlane{}, 507 machines: []*clusterv1.Machine{ 508 { 509 Status: clusterv1.MachineStatus{ 510 NodeRef: nil, 511 }, 512 }, 513 }, 514 expectResult: ctrl.Result{RequeueAfter: preflightFailedRequeueAfter}, 515 }, 516 { 517 name: "control plane with an unhealthy machine condition should requeue", 518 kcp: &controlplanev1.KubeadmControlPlane{}, 519 machines: []*clusterv1.Machine{ 520 { 521 Status: clusterv1.MachineStatus{ 522 NodeRef: &corev1.ObjectReference{ 523 Kind: "Node", 524 Name: "node-1", 525 }, 526 Conditions: clusterv1.Conditions{ 527 *conditions.FalseCondition(controlplanev1.MachineAPIServerPodHealthyCondition, "fooReason", clusterv1.ConditionSeverityError, ""), 528 *conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition), 529 *conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition), 530 *conditions.TrueCondition(controlplanev1.MachineEtcdPodHealthyCondition), 531 *conditions.TrueCondition(controlplanev1.MachineEtcdMemberHealthyCondition), 532 }, 533 }, 534 }, 535 }, 536 expectResult: ctrl.Result{RequeueAfter: preflightFailedRequeueAfter}, 537 }, 538 { 539 name: "control plane with an healthy machine and an healthy kcp condition should pass", 540 kcp: &controlplanev1.KubeadmControlPlane{ 541 Status: controlplanev1.KubeadmControlPlaneStatus{ 542 Conditions: clusterv1.Conditions{ 543 *conditions.TrueCondition(controlplanev1.ControlPlaneComponentsHealthyCondition), 544 *conditions.TrueCondition(controlplanev1.EtcdClusterHealthyCondition), 545 }, 546 }, 547 }, 548 machines: []*clusterv1.Machine{ 549 { 550 Status: clusterv1.MachineStatus{ 551 NodeRef: &corev1.ObjectReference{ 552 Kind: "Node", 553 Name: "node-1", 554 }, 555 Conditions: clusterv1.Conditions{ 556 *conditions.TrueCondition(controlplanev1.MachineAPIServerPodHealthyCondition), 557 *conditions.TrueCondition(controlplanev1.MachineControllerManagerPodHealthyCondition), 558 *conditions.TrueCondition(controlplanev1.MachineSchedulerPodHealthyCondition), 559 *conditions.TrueCondition(controlplanev1.MachineEtcdPodHealthyCondition), 560 *conditions.TrueCondition(controlplanev1.MachineEtcdMemberHealthyCondition), 561 }, 562 }, 563 }, 564 }, 565 expectResult: ctrl.Result{}, 566 }, 567 } 568 569 for _, tt := range testCases { 570 t.Run(tt.name, func(t *testing.T) { 571 g := NewWithT(t) 572 573 r := &KubeadmControlPlaneReconciler{ 574 recorder: record.NewFakeRecorder(32), 575 } 576 controlPlane := &internal.ControlPlane{ 577 Cluster: &clusterv1.Cluster{}, 578 KCP: tt.kcp, 579 Machines: collections.FromMachines(tt.machines...), 580 } 581 result, err := r.preflightChecks(context.TODO(), controlPlane) 582 g.Expect(err).ToNot(HaveOccurred()) 583 g.Expect(result).To(BeComparableTo(tt.expectResult)) 584 }) 585 } 586 } 587 588 func TestPreflightCheckCondition(t *testing.T) { 589 condition := clusterv1.ConditionType("fooCondition") 590 testCases := []struct { 591 name string 592 machine *clusterv1.Machine 593 expectErr bool 594 }{ 595 { 596 name: "missing condition should return error", 597 machine: &clusterv1.Machine{}, 598 expectErr: true, 599 }, 600 { 601 name: "false condition should return error", 602 machine: &clusterv1.Machine{ 603 Status: clusterv1.MachineStatus{ 604 Conditions: clusterv1.Conditions{ 605 *conditions.FalseCondition(condition, "fooReason", clusterv1.ConditionSeverityError, ""), 606 }, 607 }, 608 }, 609 expectErr: true, 610 }, 611 { 612 name: "unknown condition should return error", 613 machine: &clusterv1.Machine{ 614 Status: clusterv1.MachineStatus{ 615 Conditions: clusterv1.Conditions{ 616 *conditions.UnknownCondition(condition, "fooReason", ""), 617 }, 618 }, 619 }, 620 expectErr: true, 621 }, 622 { 623 name: "true condition should not return error", 624 machine: &clusterv1.Machine{ 625 Status: clusterv1.MachineStatus{ 626 Conditions: clusterv1.Conditions{ 627 *conditions.TrueCondition(condition), 628 }, 629 }, 630 }, 631 expectErr: false, 632 }, 633 } 634 635 for _, tt := range testCases { 636 t.Run(tt.name, func(t *testing.T) { 637 g := NewWithT(t) 638 639 err := preflightCheckCondition("machine", tt.machine, condition) 640 641 if tt.expectErr { 642 g.Expect(err).To(HaveOccurred()) 643 return 644 } 645 g.Expect(err).ToNot(HaveOccurred()) 646 }) 647 } 648 } 649 650 func failureDomain(controlPlane bool) clusterv1.FailureDomainSpec { 651 return clusterv1.FailureDomainSpec{ 652 ControlPlane: controlPlane, 653 } 654 } 655 656 func withFailureDomain(fd string) machineOpt { 657 return func(m *clusterv1.Machine) { 658 m.Spec.FailureDomain = &fd 659 } 660 } 661 662 func withAnnotation(annotation string) machineOpt { 663 return func(m *clusterv1.Machine) { 664 m.ObjectMeta.Annotations = map[string]string{annotation: ""} 665 } 666 } 667 668 func withTimestamp(t time.Time) machineOpt { 669 return func(m *clusterv1.Machine) { 670 m.CreationTimestamp = metav1.NewTime(t) 671 } 672 }