sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/current_state_test.go (about) 1 /* 2 Copyright 2021 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 cluster 18 19 import ( 20 "testing" 21 "time" 22 23 "github.com/google/go-cmp/cmp" 24 . "github.com/onsi/gomega" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" 31 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 "sigs.k8s.io/cluster-api/exp/topology/scope" 34 "sigs.k8s.io/cluster-api/internal/test/builder" 35 "sigs.k8s.io/cluster-api/internal/topology/selectors" 36 ) 37 38 func TestGetCurrentState(t *testing.T) { 39 crds := []client.Object{ 40 builder.GenericControlPlaneCRD, 41 builder.GenericInfrastructureClusterCRD, 42 builder.GenericControlPlaneTemplateCRD, 43 builder.GenericInfrastructureClusterTemplateCRD, 44 builder.GenericBootstrapConfigTemplateCRD, 45 builder.GenericInfrastructureMachineTemplateCRD, 46 builder.GenericInfrastructureMachineCRD, 47 builder.GenericInfrastructureMachinePoolTemplateCRD, 48 builder.GenericInfrastructureMachinePoolCRD, 49 } 50 51 // The following is a block creating a number of objects for use in the test cases. 52 53 // InfrastructureCluster objects. 54 infraCluster := builder.InfrastructureCluster(metav1.NamespaceDefault, "infraOne"). 55 Build() 56 infraCluster.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 57 infraClusterNotTopologyOwned := builder.InfrastructureCluster(metav1.NamespaceDefault, "infraOne"). 58 Build() 59 infraClusterTemplate := builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infraTemplateOne"). 60 Build() 61 62 // ControlPlane and ControlPlaneInfrastructureMachineTemplate objects. 63 controlPlaneInfrastructureMachineTemplate := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfraTemplate"). 64 Build() 65 controlPlaneInfrastructureMachineTemplate.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 66 controlPlaneInfrastructureMachineTemplateNotTopologyOwned := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfraTemplate"). 67 Build() 68 controlPlaneTemplateWithInfrastructureMachine := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cpTemplateWithInfra1"). 69 WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 70 Build() 71 controlPlaneTemplateWithInfrastructureMachineNotTopologyOwned := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cpTemplateWithInfra1"). 72 WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplateNotTopologyOwned). 73 Build() 74 controlPlane := builder.ControlPlane(metav1.NamespaceDefault, "cp1"). 75 Build() 76 controlPlane.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 77 controlPlaneWithInfra := builder.ControlPlane(metav1.NamespaceDefault, "cp1"). 78 WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 79 Build() 80 controlPlaneWithInfra.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 81 controlPlaneWithInfraNotTopologyOwned := builder.ControlPlane(metav1.NamespaceDefault, "cp1"). 82 WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplateNotTopologyOwned). 83 Build() 84 controlPlaneNotTopologyOwned := builder.ControlPlane(metav1.NamespaceDefault, "cp1"). 85 Build() 86 87 // ClusterClass objects. 88 clusterClassWithControlPlaneInfra := builder.ClusterClass(metav1.NamespaceDefault, "class1"). 89 WithControlPlaneTemplate(controlPlaneTemplateWithInfrastructureMachine). 90 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 91 Build() 92 clusterClassWithControlPlaneInfraNotTopologyOwned := builder.ClusterClass(metav1.NamespaceDefault, "class1"). 93 WithControlPlaneTemplate(controlPlaneTemplateWithInfrastructureMachineNotTopologyOwned). 94 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplateNotTopologyOwned). 95 Build() 96 clusterClassWithNoControlPlaneInfra := builder.ClusterClass(metav1.NamespaceDefault, "class2"). 97 Build() 98 99 // MachineDeployment and related objects. 100 emptyMachineDeployments := make(map[string]*scope.MachineDeploymentState) 101 102 machineDeploymentInfrastructure := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1"). 103 Build() 104 machineDeploymentInfrastructure.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 105 machineDeploymentBootstrap := builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1"). 106 Build() 107 machineDeploymentBootstrap.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 108 109 machineDeployment := builder.MachineDeployment(metav1.NamespaceDefault, "md1"). 110 WithLabels(map[string]string{ 111 clusterv1.ClusterNameLabel: "cluster1", 112 clusterv1.ClusterTopologyOwnedLabel: "", 113 clusterv1.ClusterTopologyMachineDeploymentNameLabel: "md1", 114 }). 115 WithBootstrapTemplate(machineDeploymentBootstrap). 116 WithInfrastructureTemplate(machineDeploymentInfrastructure). 117 Build() 118 machineDeployment2 := builder.MachineDeployment(metav1.NamespaceDefault, "md2"). 119 WithLabels(map[string]string{ 120 clusterv1.ClusterNameLabel: "cluster1", 121 clusterv1.ClusterTopologyOwnedLabel: "", 122 clusterv1.ClusterTopologyMachineDeploymentNameLabel: "md2", 123 }). 124 WithBootstrapTemplate(machineDeploymentBootstrap). 125 WithInfrastructureTemplate(machineDeploymentInfrastructure). 126 Build() 127 128 // MachineHealthChecks for the MachineDeployment and the ControlPlane. 129 machineHealthCheckForMachineDeployment := builder.MachineHealthCheck(machineDeployment.Namespace, machineDeployment.Name). 130 WithSelector(*selectors.ForMachineDeploymentMHC(machineDeployment)). 131 WithUnhealthyConditions([]clusterv1.UnhealthyCondition{ 132 { 133 Type: corev1.NodeReady, 134 Status: corev1.ConditionUnknown, 135 Timeout: metav1.Duration{Duration: 5 * time.Minute}, 136 }, 137 { 138 Type: corev1.NodeReady, 139 Status: corev1.ConditionFalse, 140 Timeout: metav1.Duration{Duration: 5 * time.Minute}, 141 }, 142 }). 143 WithClusterName("cluster1"). 144 Build() 145 146 machineHealthCheckForControlPlane := builder.MachineHealthCheck(controlPlane.GetNamespace(), controlPlane.GetName()). 147 WithSelector(*selectors.ForControlPlaneMHC()). 148 WithUnhealthyConditions([]clusterv1.UnhealthyCondition{ 149 { 150 Type: corev1.NodeReady, 151 Status: corev1.ConditionUnknown, 152 Timeout: metav1.Duration{Duration: 5 * time.Minute}, 153 }, 154 { 155 Type: corev1.NodeReady, 156 Status: corev1.ConditionFalse, 157 Timeout: metav1.Duration{Duration: 5 * time.Minute}, 158 }, 159 }). 160 WithClusterName("cluster1"). 161 Build() 162 163 // MachinePool and related objects. 164 emptyMachinePools := make(map[string]*scope.MachinePoolState) 165 166 machinePoolInfrastructureTemplate := builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra2"). 167 Build() 168 machinePoolInfrastructure := builder.InfrastructureMachinePool(metav1.NamespaceDefault, "infra2"). 169 Build() 170 machinePoolInfrastructure.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 171 machinePoolBootstrapTemplate := builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap2"). 172 Build() 173 machinePoolBootstrap := builder.BootstrapConfig(metav1.NamespaceDefault, "bootstrap2"). 174 Build() 175 machinePoolBootstrap.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}) 176 177 machinePool := builder.MachinePool(metav1.NamespaceDefault, "mp1"). 178 WithLabels(map[string]string{ 179 clusterv1.ClusterNameLabel: "cluster1", 180 clusterv1.ClusterTopologyOwnedLabel: "", 181 clusterv1.ClusterTopologyMachinePoolNameLabel: "mp1", 182 }). 183 WithBootstrap(machinePoolBootstrap). 184 WithInfrastructure(machinePoolInfrastructure). 185 Build() 186 machinePool2 := builder.MachinePool(metav1.NamespaceDefault, "mp2"). 187 WithLabels(map[string]string{ 188 clusterv1.ClusterNameLabel: "cluster1", 189 clusterv1.ClusterTopologyOwnedLabel: "", 190 clusterv1.ClusterTopologyMachinePoolNameLabel: "mp2", 191 }). 192 WithBootstrap(machinePoolBootstrap). 193 WithInfrastructure(machinePoolInfrastructure). 194 Build() 195 196 tests := []struct { 197 name string 198 cluster *clusterv1.Cluster 199 blueprint *scope.ClusterBlueprint 200 objects []client.Object 201 want *scope.ClusterState 202 wantErr bool 203 }{ 204 { 205 name: "Should read a Cluster when being processed by the topology controller for the first time (without references)", 206 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").Build(), 207 blueprint: &scope.ClusterBlueprint{}, 208 // Expecting valid return with no ControlPlane or Infrastructure state defined and empty MachineDeployment state list 209 want: &scope.ClusterState{ 210 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 211 // No InfrastructureCluster or ControlPlane references! 212 Build(), 213 ControlPlane: &scope.ControlPlaneState{}, 214 InfrastructureCluster: nil, 215 MachineDeployments: emptyMachineDeployments, 216 MachinePools: emptyMachinePools, 217 }, 218 }, 219 { 220 name: "Fails if the Cluster references an InfrastructureCluster that does not exist", 221 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 222 WithInfrastructureCluster(infraCluster). 223 Build(), 224 blueprint: &scope.ClusterBlueprint{ 225 InfrastructureClusterTemplate: infraClusterTemplate, 226 }, 227 objects: []client.Object{ 228 // InfrastructureCluster is missing! 229 }, 230 wantErr: true, // this test fails as partial reconcile is undefined. 231 }, 232 { 233 name: "Fails if the Cluster references an InfrastructureCluster that is not topology owned", 234 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 235 WithInfrastructureCluster(infraClusterNotTopologyOwned). 236 Build(), 237 blueprint: &scope.ClusterBlueprint{ 238 InfrastructureClusterTemplate: infraClusterTemplate, 239 }, 240 objects: []client.Object{ 241 infraClusterNotTopologyOwned, 242 }, 243 wantErr: true, // this test fails as partial reconcile is undefined. 244 }, 245 { 246 name: "Fails if the Cluster references an Control Plane that is not topology owned", 247 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 248 WithControlPlane(controlPlaneNotTopologyOwned). 249 Build(), 250 blueprint: &scope.ClusterBlueprint{ 251 ClusterClass: clusterClassWithControlPlaneInfra, 252 ControlPlane: &scope.ControlPlaneBlueprint{ 253 Template: controlPlaneTemplateWithInfrastructureMachine, 254 }, 255 }, 256 objects: []client.Object{ 257 controlPlaneNotTopologyOwned, 258 }, 259 wantErr: true, // this test fails as partial reconcile is undefined. 260 }, 261 { 262 name: "Should read a partial Cluster (with InfrastructureCluster only)", 263 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 264 WithInfrastructureCluster(infraCluster). 265 // No ControlPlane reference! 266 Build(), 267 blueprint: &scope.ClusterBlueprint{ 268 InfrastructureClusterTemplate: infraClusterTemplate, 269 }, 270 objects: []client.Object{ 271 infraCluster, 272 }, 273 // Expecting valid return with no ControlPlane, MachineDeployment, or MachinePool state defined but with a valid Infrastructure state. 274 want: &scope.ClusterState{ 275 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 276 WithInfrastructureCluster(infraCluster). 277 Build(), 278 ControlPlane: &scope.ControlPlaneState{}, 279 InfrastructureCluster: infraCluster, 280 MachineDeployments: emptyMachineDeployments, 281 MachinePools: emptyMachinePools, 282 }, 283 }, 284 { 285 name: "Should read a partial Cluster (with InfrastructureCluster, ControlPlane, but without workers)", 286 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 287 WithControlPlane(controlPlane). 288 WithInfrastructureCluster(infraCluster). 289 Build(), 290 blueprint: &scope.ClusterBlueprint{ 291 ClusterClass: clusterClassWithNoControlPlaneInfra, 292 InfrastructureClusterTemplate: infraClusterTemplate, 293 ControlPlane: &scope.ControlPlaneBlueprint{ 294 Template: controlPlaneTemplateWithInfrastructureMachine, 295 }, 296 }, 297 objects: []client.Object{ 298 controlPlane, 299 infraCluster, 300 clusterClassWithNoControlPlaneInfra, 301 // Workers are missing! 302 }, 303 // Expecting valid return with ControlPlane, no ControlPlane Infrastructure state, InfrastructureCluster state, and no defined MachineDeployment or MachinePool state. 304 want: &scope.ClusterState{ 305 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 306 WithControlPlane(controlPlane). 307 WithInfrastructureCluster(infraCluster). 308 Build(), 309 ControlPlane: &scope.ControlPlaneState{Object: controlPlane, InfrastructureMachineTemplate: nil}, 310 InfrastructureCluster: infraCluster, 311 MachineDeployments: emptyMachineDeployments, 312 MachinePools: emptyMachinePools, 313 }, 314 }, 315 { 316 name: "Fails if the ClusterClass requires InfrastructureMachine for the ControlPlane, but the ControlPlane object does not have a reference for it", 317 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 318 WithControlPlane(controlPlane). 319 WithInfrastructureCluster(infraCluster). 320 Build(), 321 blueprint: &scope.ClusterBlueprint{ 322 ClusterClass: clusterClassWithControlPlaneInfra, 323 InfrastructureClusterTemplate: infraClusterTemplate, 324 ControlPlane: &scope.ControlPlaneBlueprint{ 325 Template: controlPlaneTemplateWithInfrastructureMachine, 326 }, 327 }, 328 objects: []client.Object{ 329 controlPlane, 330 infraCluster, 331 clusterClassWithControlPlaneInfra, 332 }, 333 // Expecting error from ControlPlane having no valid ControlPlane Infrastructure with ClusterClass requiring ControlPlane Infrastructure. 334 wantErr: true, 335 }, 336 { 337 name: "Should read a partial Cluster (with ControlPlane and ControlPlane InfrastructureMachineTemplate, but without InfrastructureCluster and workers)", 338 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 339 // No InfrastructureCluster! 340 WithControlPlane(controlPlaneWithInfra). 341 Build(), 342 blueprint: &scope.ClusterBlueprint{ 343 ClusterClass: clusterClassWithControlPlaneInfra, 344 ControlPlane: &scope.ControlPlaneBlueprint{ 345 Template: controlPlaneTemplateWithInfrastructureMachine, 346 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 347 }, 348 }, 349 objects: []client.Object{ 350 controlPlaneWithInfra, 351 controlPlaneInfrastructureMachineTemplate, 352 // Workers are missing! 353 }, 354 // Expecting valid return with valid ControlPlane state, but no ControlPlane Infrastructure, InfrastructureCluster, MachineDeployment, or MachinePool state defined. 355 want: &scope.ClusterState{ 356 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 357 WithControlPlane(controlPlaneWithInfra). 358 Build(), 359 ControlPlane: &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate}, 360 InfrastructureCluster: nil, 361 MachineDeployments: emptyMachineDeployments, 362 MachinePools: emptyMachinePools, 363 }, 364 }, 365 { 366 name: "Should read a partial Cluster (with InfrastructureCluster ControlPlane and ControlPlane InfrastructureMachineTemplate, but without workers)", 367 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 368 WithInfrastructureCluster(infraCluster). 369 WithControlPlane(controlPlaneWithInfra). 370 Build(), 371 blueprint: &scope.ClusterBlueprint{ 372 ClusterClass: clusterClassWithControlPlaneInfra, 373 InfrastructureClusterTemplate: infraClusterTemplate, 374 ControlPlane: &scope.ControlPlaneBlueprint{ 375 Template: controlPlaneTemplateWithInfrastructureMachine, 376 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 377 }, 378 }, 379 objects: []client.Object{ 380 infraCluster, 381 clusterClassWithControlPlaneInfra, 382 controlPlaneInfrastructureMachineTemplate, 383 controlPlaneWithInfra, 384 // Workers are missing! 385 }, 386 // Expecting valid return with valid ControlPlane state, ControlPlane Infrastructure state and InfrastructureCluster state, but no defined MachineDeployment or MachinePool state. 387 want: &scope.ClusterState{ 388 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 389 WithInfrastructureCluster(infraCluster). 390 WithControlPlane(controlPlaneWithInfra). 391 Build(), 392 ControlPlane: &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate}, 393 InfrastructureCluster: infraCluster, 394 MachineDeployments: emptyMachineDeployments, 395 MachinePools: emptyMachinePools, 396 }, 397 }, 398 { 399 name: "Should read a Cluster (with InfrastructureCluster, ControlPlane and ControlPlane InfrastructureMachineTemplate, workers)", 400 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 401 WithTopology(builder.ClusterTopology(). 402 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 403 Class: "mdClass", 404 Name: "md1", 405 }). 406 WithMachinePool(clusterv1.MachinePoolTopology{ 407 Class: "mpClass", 408 Name: "mp1", 409 }). 410 Build()). 411 Build(), 412 blueprint: &scope.ClusterBlueprint{ 413 ClusterClass: clusterClassWithControlPlaneInfra, 414 InfrastructureClusterTemplate: infraClusterTemplate, 415 ControlPlane: &scope.ControlPlaneBlueprint{ 416 Template: controlPlaneTemplateWithInfrastructureMachine, 417 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 418 }, 419 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 420 "mdClass": { 421 BootstrapTemplate: machineDeploymentBootstrap, 422 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 423 }, 424 }, 425 MachinePools: map[string]*scope.MachinePoolBlueprint{ 426 "mpClass": { 427 BootstrapTemplate: machinePoolBootstrap, 428 InfrastructureMachinePoolTemplate: machinePoolInfrastructure, 429 }, 430 }, 431 }, 432 objects: []client.Object{ 433 infraCluster, 434 clusterClassWithControlPlaneInfra, 435 controlPlaneInfrastructureMachineTemplate, 436 controlPlaneWithInfra, 437 machineDeploymentInfrastructure, 438 machineDeploymentBootstrap, 439 machineDeployment, 440 machinePoolInfrastructure, 441 machinePoolBootstrap, 442 machinePool, 443 }, 444 // Expecting valid return with valid ControlPlane, ControlPlane Infrastructure, InfrastructureCluster, MachineDeployment and MachinePool state. 445 want: &scope.ClusterState{ 446 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 447 WithTopology(builder.ClusterTopology(). 448 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 449 Class: "mdClass", 450 Name: "md1", 451 }). 452 WithMachinePool(clusterv1.MachinePoolTopology{ 453 Class: "mpClass", 454 Name: "mp1", 455 }). 456 Build()). 457 Build(), 458 ControlPlane: &scope.ControlPlaneState{}, 459 InfrastructureCluster: nil, 460 MachineDeployments: map[string]*scope.MachineDeploymentState{ 461 "md1": {Object: machineDeployment, BootstrapTemplate: machineDeploymentBootstrap, InfrastructureMachineTemplate: machineDeploymentInfrastructure}}, 462 MachinePools: map[string]*scope.MachinePoolState{ 463 "mp1": {Object: machinePool, BootstrapObject: machinePoolBootstrap, InfrastructureMachinePoolObject: machinePoolInfrastructure}}, 464 }, 465 }, 466 { 467 name: "Fails if the ControlPlane references an InfrastructureMachineTemplate that is not topology owned", 468 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 469 // No InfrastructureCluster! 470 WithControlPlane(controlPlaneWithInfraNotTopologyOwned). 471 Build(), 472 blueprint: &scope.ClusterBlueprint{ 473 ClusterClass: clusterClassWithControlPlaneInfraNotTopologyOwned, 474 ControlPlane: &scope.ControlPlaneBlueprint{ 475 Template: controlPlaneTemplateWithInfrastructureMachine, 476 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 477 }, 478 }, 479 objects: []client.Object{ 480 controlPlaneWithInfraNotTopologyOwned, 481 controlPlaneInfrastructureMachineTemplateNotTopologyOwned, 482 }, 483 wantErr: true, 484 }, 485 { 486 name: "Fails if the ControlPlane references an InfrastructureMachineTemplate that does not exist", 487 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 488 WithControlPlane(controlPlane). 489 Build(), 490 blueprint: &scope.ClusterBlueprint{ 491 ClusterClass: clusterClassWithControlPlaneInfra, 492 ControlPlane: &scope.ControlPlaneBlueprint{ 493 Template: controlPlaneTemplateWithInfrastructureMachine, 494 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 495 }, 496 }, 497 objects: []client.Object{ 498 clusterClassWithControlPlaneInfra, 499 controlPlane, 500 // InfrastructureMachineTemplate is missing! 501 }, 502 // Expecting error as ClusterClass references ControlPlane Infrastructure, but ControlPlane Infrastructure is missing in the cluster. 503 wantErr: true, 504 }, 505 { 506 name: "Should ignore unmanaged MachineDeployments/MachinePools and MachineDeployments/MachinePools belonging to other clusters", 507 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 508 Build(), 509 blueprint: &scope.ClusterBlueprint{ClusterClass: clusterClassWithControlPlaneInfra}, 510 objects: []client.Object{ 511 clusterClassWithControlPlaneInfra, 512 builder.MachineDeployment(metav1.NamespaceDefault, "no-managed-label"). 513 WithLabels(map[string]string{ 514 clusterv1.ClusterNameLabel: "cluster1", 515 // topology.cluster.x-k8s.io/owned label is missing (unmanaged)! 516 }). 517 WithBootstrapTemplate(machineDeploymentBootstrap). 518 WithInfrastructureTemplate(machineDeploymentInfrastructure). 519 Build(), 520 builder.MachineDeployment(metav1.NamespaceDefault, "wrong-cluster-label"). 521 WithLabels(map[string]string{ 522 clusterv1.ClusterNameLabel: "another-cluster", 523 clusterv1.ClusterTopologyOwnedLabel: "", 524 clusterv1.ClusterTopologyMachineDeploymentNameLabel: "md1", 525 }). 526 WithBootstrapTemplate(machineDeploymentBootstrap). 527 WithInfrastructureTemplate(machineDeploymentInfrastructure). 528 Build(), 529 builder.MachinePool(metav1.NamespaceDefault, "no-managed-label"). 530 WithLabels(map[string]string{ 531 clusterv1.ClusterNameLabel: "cluster1", 532 // topology.cluster.x-k8s.io/owned label is missing (unmanaged)! 533 }). 534 WithBootstrap(machinePoolBootstrap). 535 WithInfrastructure(machinePoolInfrastructure). 536 Build(), 537 builder.MachinePool(metav1.NamespaceDefault, "wrong-cluster-label"). 538 WithLabels(map[string]string{ 539 clusterv1.ClusterNameLabel: "another-cluster", 540 clusterv1.ClusterTopologyOwnedLabel: "", 541 clusterv1.ClusterTopologyMachinePoolNameLabel: "mp1", 542 }). 543 WithBootstrap(machinePoolBootstrap). 544 WithInfrastructure(machinePoolInfrastructure). 545 Build(), 546 }, 547 // Expect valid return with empty MachineDeployments and MachinePools properly filtered by label. 548 want: &scope.ClusterState{ 549 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 550 Build(), 551 ControlPlane: &scope.ControlPlaneState{}, 552 InfrastructureCluster: nil, 553 MachineDeployments: emptyMachineDeployments, 554 MachinePools: emptyMachinePools, 555 }, 556 }, 557 { 558 name: "Fails if there are MachineDeployments without the topology.cluster.x-k8s.io/deployment-name", 559 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 560 Build(), 561 blueprint: &scope.ClusterBlueprint{ClusterClass: clusterClassWithControlPlaneInfra}, 562 objects: []client.Object{ 563 clusterClassWithControlPlaneInfra, 564 builder.MachineDeployment(metav1.NamespaceDefault, "missing-topology-md-labelName"). 565 WithLabels(map[string]string{ 566 clusterv1.ClusterNameLabel: "cluster1", 567 clusterv1.ClusterTopologyOwnedLabel: "", 568 // topology.cluster.x-k8s.io/deployment-name label is missing! 569 }). 570 WithBootstrapTemplate(machineDeploymentBootstrap). 571 WithInfrastructureTemplate(machineDeploymentInfrastructure). 572 Build(), 573 }, 574 // Expect error to be thrown as no managed MachineDeployment is reconcilable unless it has a ClusterTopologyMachineDeploymentNameLabel. 575 wantErr: true, 576 }, 577 { 578 name: "Fails if there are MachineDeployments with the same topology.cluster.x-k8s.io/deployment-name", 579 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 580 WithTopology(builder.ClusterTopology(). 581 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 582 Class: "mdClass", 583 Name: "md1", 584 }). 585 Build()). 586 Build(), 587 blueprint: &scope.ClusterBlueprint{ 588 ClusterClass: clusterClassWithControlPlaneInfra, 589 ControlPlane: &scope.ControlPlaneBlueprint{ 590 Template: controlPlaneTemplateWithInfrastructureMachine, 591 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 592 }, 593 }, 594 objects: []client.Object{ 595 clusterClassWithControlPlaneInfra, 596 machineDeploymentInfrastructure, 597 machineDeploymentBootstrap, 598 machineDeployment, 599 builder.MachineDeployment(metav1.NamespaceDefault, "duplicate-labels"). 600 WithLabels(machineDeployment.Labels). // Another machine deployment with the same labels. 601 WithBootstrapTemplate(machineDeploymentBootstrap). 602 WithInfrastructureTemplate(machineDeploymentInfrastructure). 603 Build(), 604 }, 605 // Expect error as two MachineDeployments with the same ClusterTopologyOwnedLabel should not exist for one cluster 606 wantErr: true, 607 }, 608 { 609 name: "Fails if there are MachinePools without the topology.cluster.x-k8s.io/pool-name", 610 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 611 Build(), 612 blueprint: &scope.ClusterBlueprint{ClusterClass: clusterClassWithControlPlaneInfra}, 613 objects: []client.Object{ 614 clusterClassWithControlPlaneInfra, 615 builder.MachinePool(metav1.NamespaceDefault, "missing-topology-mp-labelName"). 616 WithLabels(map[string]string{ 617 clusterv1.ClusterNameLabel: "cluster1", 618 clusterv1.ClusterTopologyOwnedLabel: "", 619 // topology.cluster.x-k8s.io/pool-name label is missing! 620 }). 621 WithBootstrap(machinePoolBootstrap). 622 WithInfrastructure(machinePoolInfrastructure). 623 Build(), 624 }, 625 // Expect error to be thrown as no managed MachinePool is reconcilable unless it has a ClusterTopologyMachinePoolNameLabel. 626 wantErr: true, 627 }, 628 { 629 name: "Fails if there are MachinePools with the same topology.cluster.x-k8s.io/pool-name", 630 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 631 WithTopology(builder.ClusterTopology(). 632 WithMachinePool(clusterv1.MachinePoolTopology{ 633 Class: "mpClass", 634 Name: "mp1", 635 }). 636 Build()). 637 Build(), 638 blueprint: &scope.ClusterBlueprint{ 639 ClusterClass: clusterClassWithControlPlaneInfra, 640 ControlPlane: &scope.ControlPlaneBlueprint{ 641 Template: controlPlaneTemplateWithInfrastructureMachine, 642 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 643 }, 644 }, 645 objects: []client.Object{ 646 clusterClassWithControlPlaneInfra, 647 machinePoolInfrastructure, 648 machinePoolBootstrap, 649 machinePool, 650 builder.MachinePool(metav1.NamespaceDefault, "duplicate-labels"). 651 WithLabels(machinePool.Labels). // Another machine pool with the same labels. 652 WithBootstrap(machinePoolBootstrap). 653 WithInfrastructure(machinePoolInfrastructure). 654 Build(), 655 }, 656 // Expect error as two MachinePools with the same ClusterTopologyOwnedLabel should not exist for one cluster 657 wantErr: true, 658 }, 659 { 660 name: "Should read a full Cluster (With InfrastructureCluster, ControlPlane and ControlPlane Infrastructure, MachineDeployments, MachinePools)", 661 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 662 WithInfrastructureCluster(infraCluster). 663 WithControlPlane(controlPlaneWithInfra). 664 WithTopology(builder.ClusterTopology(). 665 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 666 Class: "mdClass", 667 Name: "md1", 668 }). 669 WithMachinePool(clusterv1.MachinePoolTopology{ 670 Class: "mpClass", 671 Name: "mp1", 672 }). 673 Build()). 674 Build(), 675 blueprint: &scope.ClusterBlueprint{ 676 ClusterClass: clusterClassWithControlPlaneInfra, 677 InfrastructureClusterTemplate: infraClusterTemplate, 678 ControlPlane: &scope.ControlPlaneBlueprint{ 679 Template: controlPlaneTemplateWithInfrastructureMachine, 680 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 681 }, 682 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 683 "mdClass": { 684 BootstrapTemplate: machineDeploymentBootstrap, 685 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 686 }, 687 }, 688 MachinePools: map[string]*scope.MachinePoolBlueprint{ 689 "mpClass": { 690 BootstrapTemplate: machinePoolBootstrapTemplate, 691 InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate, 692 }, 693 }, 694 }, 695 objects: []client.Object{ 696 infraCluster, 697 clusterClassWithControlPlaneInfra, 698 controlPlaneInfrastructureMachineTemplate, 699 controlPlaneWithInfra, 700 machineDeploymentInfrastructure, 701 machineDeploymentBootstrap, 702 machineDeployment, 703 machinePoolInfrastructure, 704 machinePoolBootstrap, 705 machinePool, 706 }, 707 // Expect valid return of full ClusterState with MachineDeployments and MachinePools properly filtered by label. 708 want: &scope.ClusterState{ 709 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 710 WithInfrastructureCluster(infraCluster). 711 WithControlPlane(controlPlaneWithInfra). 712 WithTopology(builder.ClusterTopology(). 713 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 714 Class: "mdClass", 715 Name: "md1", 716 }). 717 WithMachinePool(clusterv1.MachinePoolTopology{ 718 Class: "mpClass", 719 Name: "mp1", 720 }). 721 Build()). 722 Build(), 723 ControlPlane: &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate}, 724 InfrastructureCluster: infraCluster, 725 MachineDeployments: map[string]*scope.MachineDeploymentState{ 726 "md1": { 727 Object: machineDeployment, 728 BootstrapTemplate: machineDeploymentBootstrap, 729 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 730 }, 731 }, 732 MachinePools: map[string]*scope.MachinePoolState{ 733 "mp1": { 734 Object: machinePool, 735 BootstrapObject: machinePoolBootstrap, 736 InfrastructureMachinePoolObject: machinePoolInfrastructure, 737 }, 738 }, 739 }, 740 }, 741 { 742 name: "Should read a full Cluster, even if a MachineDeployment & MachinePool topology has been deleted and the MachineDeployment & MachinePool still exists", 743 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 744 WithInfrastructureCluster(infraCluster). 745 WithControlPlane(controlPlaneWithInfra). 746 WithTopology(builder.ClusterTopology(). 747 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 748 Class: "mdClass", 749 Name: "md1", 750 }). 751 WithMachinePool(clusterv1.MachinePoolTopology{ 752 Class: "mpClass", 753 Name: "mp1", 754 }). 755 Build()). 756 Build(), 757 blueprint: &scope.ClusterBlueprint{ 758 ClusterClass: clusterClassWithControlPlaneInfra, 759 InfrastructureClusterTemplate: infraClusterTemplate, 760 ControlPlane: &scope.ControlPlaneBlueprint{ 761 Template: controlPlaneTemplateWithInfrastructureMachine, 762 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 763 }, 764 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 765 "mdClass": { 766 BootstrapTemplate: machineDeploymentBootstrap, 767 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 768 }, 769 }, 770 MachinePools: map[string]*scope.MachinePoolBlueprint{ 771 "mpClass": { 772 BootstrapTemplate: machinePoolBootstrapTemplate, 773 InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate, 774 }, 775 }, 776 }, 777 objects: []client.Object{ 778 infraCluster, 779 clusterClassWithControlPlaneInfra, 780 controlPlaneInfrastructureMachineTemplate, 781 controlPlaneWithInfra, 782 machineDeploymentInfrastructure, 783 machineDeploymentBootstrap, 784 machineDeployment, 785 machineDeployment2, 786 machinePoolInfrastructure, 787 machinePoolBootstrap, 788 machinePool, 789 machinePool2, 790 }, 791 // Expect valid return of full ClusterState with MachineDeployments and MachinePools properly filtered by label. 792 want: &scope.ClusterState{ 793 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 794 WithInfrastructureCluster(infraCluster). 795 WithControlPlane(controlPlaneWithInfra). 796 WithTopology(builder.ClusterTopology(). 797 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 798 Class: "mdClass", 799 Name: "md1", 800 }). 801 WithMachinePool(clusterv1.MachinePoolTopology{ 802 Class: "mpClass", 803 Name: "mp1", 804 }). 805 Build()). 806 Build(), 807 ControlPlane: &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate}, 808 InfrastructureCluster: infraCluster, 809 MachineDeployments: map[string]*scope.MachineDeploymentState{ 810 "md1": { 811 Object: machineDeployment, 812 BootstrapTemplate: machineDeploymentBootstrap, 813 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 814 }, 815 "md2": { 816 Object: machineDeployment2, 817 BootstrapTemplate: machineDeploymentBootstrap, 818 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 819 }, 820 }, 821 MachinePools: map[string]*scope.MachinePoolState{ 822 "mp1": { 823 Object: machinePool, 824 BootstrapObject: machinePoolBootstrap, 825 InfrastructureMachinePoolObject: machinePoolInfrastructure, 826 }, 827 "mp2": { 828 Object: machinePool2, 829 BootstrapObject: machinePoolBootstrap, 830 InfrastructureMachinePoolObject: machinePoolInfrastructure, 831 }, 832 }, 833 }, 834 }, 835 { 836 name: "Fails if a Cluster has a MachineDeployment without the Bootstrap Template ref", 837 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 838 WithTopology(builder.ClusterTopology(). 839 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 840 Class: "mdClass", 841 Name: "md1", 842 }). 843 Build()). 844 Build(), 845 blueprint: &scope.ClusterBlueprint{ 846 ClusterClass: clusterClassWithControlPlaneInfra, 847 InfrastructureClusterTemplate: infraClusterTemplate, 848 ControlPlane: &scope.ControlPlaneBlueprint{ 849 Template: controlPlaneTemplateWithInfrastructureMachine, 850 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 851 }, 852 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 853 "mdClass": { 854 BootstrapTemplate: machineDeploymentBootstrap, 855 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 856 }, 857 }, 858 }, 859 objects: []client.Object{ 860 infraCluster, 861 clusterClassWithControlPlaneInfra, 862 controlPlaneInfrastructureMachineTemplate, 863 controlPlaneWithInfra, 864 machineDeploymentInfrastructure, 865 builder.MachineDeployment(metav1.NamespaceDefault, "no-bootstrap"). 866 WithLabels(machineDeployment.Labels). 867 // No BootstrapConfigTemplate reference! 868 WithInfrastructureTemplate(machineDeploymentInfrastructure). 869 Build(), 870 }, 871 // Expect error as Bootstrap Template not defined for MachineDeployments relevant to the Cluster. 872 wantErr: true, 873 }, 874 { 875 name: "Fails if a Cluster has a MachinePool without the Bootstrap Template ref", 876 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 877 WithTopology(builder.ClusterTopology(). 878 WithMachinePool(clusterv1.MachinePoolTopology{ 879 Class: "mpClass", 880 Name: "mp1", 881 }). 882 Build()). 883 Build(), 884 blueprint: &scope.ClusterBlueprint{ 885 ClusterClass: clusterClassWithControlPlaneInfra, 886 InfrastructureClusterTemplate: infraClusterTemplate, 887 ControlPlane: &scope.ControlPlaneBlueprint{ 888 Template: controlPlaneTemplateWithInfrastructureMachine, 889 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 890 }, 891 MachinePools: map[string]*scope.MachinePoolBlueprint{ 892 "mpClass": { 893 BootstrapTemplate: machinePoolBootstrapTemplate, 894 InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate, 895 }, 896 }, 897 }, 898 objects: []client.Object{ 899 infraCluster, 900 clusterClassWithControlPlaneInfra, 901 controlPlaneInfrastructureMachineTemplate, 902 controlPlaneWithInfra, 903 machinePoolInfrastructure, 904 builder.MachinePool(metav1.NamespaceDefault, "no-bootstrap"). 905 WithLabels(machinePool.Labels). 906 // No BootstrapConfig reference! 907 WithInfrastructure(machinePoolInfrastructure). 908 Build(), 909 }, 910 // Expect error as BootstrapConfig not defined for MachinePools relevant to the Cluster. 911 wantErr: true, 912 }, 913 { 914 name: "Fails if a Cluster has a MachineDeployments without the InfrastructureMachineTemplate ref", 915 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 916 WithTopology(builder.ClusterTopology(). 917 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 918 Class: "mdClass", 919 Name: "md1", 920 }). 921 Build()). 922 Build(), 923 blueprint: &scope.ClusterBlueprint{ 924 ClusterClass: clusterClassWithControlPlaneInfra, 925 InfrastructureClusterTemplate: infraClusterTemplate, 926 ControlPlane: &scope.ControlPlaneBlueprint{ 927 Template: controlPlaneTemplateWithInfrastructureMachine, 928 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 929 }, 930 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 931 "mdClass": { 932 BootstrapTemplate: machineDeploymentBootstrap, 933 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 934 }, 935 }, 936 }, 937 objects: []client.Object{ 938 infraCluster, 939 clusterClassWithControlPlaneInfra, 940 controlPlaneInfrastructureMachineTemplate, 941 controlPlaneWithInfra, 942 machineDeploymentBootstrap, 943 builder.MachineDeployment(metav1.NamespaceDefault, "no-infra"). 944 WithLabels(machineDeployment.Labels). 945 WithBootstrapTemplate(machineDeploymentBootstrap). 946 // No InfrastructureMachineTemplate reference! 947 Build(), 948 }, 949 // Expect error as Infrastructure Template not defined for MachineDeployment relevant to the Cluster. 950 wantErr: true, 951 }, 952 { 953 name: "Fails if a Cluster has a MachinePools without the InfrastructureMachinePool ref", 954 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 955 WithTopology(builder.ClusterTopology(). 956 WithMachinePool(clusterv1.MachinePoolTopology{ 957 Class: "mpClass", 958 Name: "mp1", 959 }). 960 Build()). 961 Build(), 962 blueprint: &scope.ClusterBlueprint{ 963 ClusterClass: clusterClassWithControlPlaneInfra, 964 InfrastructureClusterTemplate: infraClusterTemplate, 965 ControlPlane: &scope.ControlPlaneBlueprint{ 966 Template: controlPlaneTemplateWithInfrastructureMachine, 967 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 968 }, 969 MachinePools: map[string]*scope.MachinePoolBlueprint{ 970 "mpClass": { 971 BootstrapTemplate: machinePoolBootstrapTemplate, 972 InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate, 973 }, 974 }, 975 }, 976 objects: []client.Object{ 977 infraCluster, 978 clusterClassWithControlPlaneInfra, 979 controlPlaneInfrastructureMachineTemplate, 980 controlPlaneWithInfra, 981 machinePoolBootstrap, 982 builder.MachinePool(metav1.NamespaceDefault, "no-infra"). 983 WithLabels(machinePool.Labels). 984 WithBootstrap(machinePoolBootstrap). 985 // No InfrastructureMachinePool reference! 986 Build(), 987 }, 988 // Expect error as InfrastructureMachinePool not defined for MachinePool relevant to the Cluster. 989 wantErr: true, 990 }, 991 { 992 name: "Pass reading a full Cluster with MachineHealthChecks for ControlPlane and MachineDeployment)", 993 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 994 WithInfrastructureCluster(infraCluster). 995 WithControlPlane(controlPlaneWithInfra). 996 WithTopology(builder.ClusterTopology(). 997 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 998 Class: "mdClass", 999 Name: "md1", 1000 }). 1001 Build()). 1002 Build(), 1003 blueprint: &scope.ClusterBlueprint{ 1004 ClusterClass: clusterClassWithControlPlaneInfra, 1005 InfrastructureClusterTemplate: infraClusterTemplate, 1006 ControlPlane: &scope.ControlPlaneBlueprint{ 1007 Template: controlPlaneTemplateWithInfrastructureMachine, 1008 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 1009 }, 1010 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 1011 "mdClass": { 1012 BootstrapTemplate: machineDeploymentBootstrap, 1013 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 1014 }, 1015 }, 1016 }, 1017 objects: []client.Object{ 1018 infraCluster, 1019 clusterClassWithControlPlaneInfra, 1020 controlPlaneInfrastructureMachineTemplate, 1021 controlPlaneWithInfra, 1022 machineDeploymentInfrastructure, 1023 machineDeploymentBootstrap, 1024 machineDeployment, 1025 machineHealthCheckForMachineDeployment, 1026 machineHealthCheckForControlPlane, 1027 }, 1028 // Expect valid return of full ClusterState with MachineHealthChecks for both ControlPlane and MachineDeployment. 1029 want: &scope.ClusterState{ 1030 Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 1031 WithInfrastructureCluster(infraCluster). 1032 WithControlPlane(controlPlaneWithInfra). 1033 WithTopology(builder.ClusterTopology(). 1034 WithMachineDeployment(clusterv1.MachineDeploymentTopology{ 1035 Class: "mdClass", 1036 Name: "md1", 1037 }). 1038 Build()). 1039 Build(), 1040 ControlPlane: &scope.ControlPlaneState{ 1041 Object: controlPlaneWithInfra, 1042 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 1043 MachineHealthCheck: machineHealthCheckForControlPlane, 1044 }, 1045 InfrastructureCluster: infraCluster, 1046 MachineDeployments: map[string]*scope.MachineDeploymentState{ 1047 "md1": { 1048 Object: machineDeployment, 1049 BootstrapTemplate: machineDeploymentBootstrap, 1050 InfrastructureMachineTemplate: machineDeploymentInfrastructure, 1051 MachineHealthCheck: machineHealthCheckForMachineDeployment, 1052 }, 1053 }, 1054 MachinePools: emptyMachinePools, 1055 }, 1056 }, 1057 } 1058 for _, tt := range tests { 1059 t.Run(tt.name, func(t *testing.T) { 1060 g := NewWithT(t) 1061 1062 // Sets up a scope with a Blueprint. 1063 s := scope.New(tt.cluster) 1064 s.Blueprint = tt.blueprint 1065 1066 // Sets up the fakeClient for the test case. 1067 objs := []client.Object{} 1068 objs = append(objs, crds...) 1069 objs = append(objs, tt.objects...) 1070 if tt.cluster != nil { 1071 objs = append(objs, tt.cluster) 1072 } 1073 fakeClient := fake.NewClientBuilder(). 1074 WithScheme(fakeScheme). 1075 WithObjects(objs...). 1076 Build() 1077 1078 // Calls getCurrentState. 1079 r := &Reconciler{ 1080 Client: fakeClient, 1081 APIReader: fakeClient, 1082 UnstructuredCachingClient: fakeClient, 1083 patchHelperFactory: dryRunPatchHelperFactory(fakeClient), 1084 } 1085 got, err := r.getCurrentState(ctx, s) 1086 1087 // Checks the return error. 1088 if tt.wantErr { 1089 g.Expect(err).To(HaveOccurred()) 1090 } else { 1091 g.Expect(err).ToNot(HaveOccurred()) 1092 } 1093 if tt.want == nil { 1094 g.Expect(got).To(BeNil()) 1095 return 1096 } 1097 1098 // Use EqualObject where the compared object is passed through the fakeClient. Elsewhere the Equal method is 1099 // good enough to establish equality. 1100 g.Expect(got.Cluster).To(EqualObject(tt.want.Cluster, IgnoreAutogeneratedMetadata)) 1101 g.Expect(got.InfrastructureCluster).To(EqualObject(tt.want.InfrastructureCluster)) 1102 g.Expect(got.ControlPlane).To(BeComparableTo(tt.want.ControlPlane), cmp.Diff(got.ControlPlane, tt.want.ControlPlane)) 1103 g.Expect(got.MachineDeployments).To(BeComparableTo(tt.want.MachineDeployments), cmp.Diff(got.MachineDeployments, tt.want.MachineDeployments)) 1104 g.Expect(got.MachinePools).To(BeComparableTo(tt.want.MachinePools), cmp.Diff(got.MachinePools, tt.want.MachinePools)) 1105 }) 1106 } 1107 } 1108 1109 func TestAlignRefAPIVersion(t *testing.T) { 1110 tests := []struct { 1111 name string 1112 templateFromClusterClass *unstructured.Unstructured 1113 currentRef *corev1.ObjectReference 1114 want *corev1.ObjectReference 1115 wantErr bool 1116 }{ 1117 { 1118 name: "Error for invalid apiVersion", 1119 templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{ 1120 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1121 "kind": "DockerCluster", 1122 }}, 1123 currentRef: &corev1.ObjectReference{ 1124 APIVersion: "invalid/api/version", 1125 Kind: "DockerCluster", 1126 Name: "my-cluster-abc", 1127 Namespace: metav1.NamespaceDefault, 1128 }, 1129 wantErr: true, 1130 }, 1131 { 1132 name: "Use apiVersion from ClusterClass: group and kind is the same", 1133 templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{ 1134 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1135 "kind": "DockerCluster", 1136 }}, 1137 currentRef: &corev1.ObjectReference{ 1138 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta2", 1139 Kind: "DockerCluster", 1140 Name: "my-cluster-abc", 1141 Namespace: metav1.NamespaceDefault, 1142 }, 1143 want: &corev1.ObjectReference{ 1144 // Group & kind is the same => apiVersion is taken from ClusterClass. 1145 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1146 Kind: "DockerCluster", 1147 Name: "my-cluster-abc", 1148 Namespace: metav1.NamespaceDefault, 1149 }, 1150 }, 1151 { 1152 name: "Use apiVersion from currentRef: kind is different", 1153 templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{ 1154 "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha4", 1155 "kind": "DifferentConfigTemplate", 1156 }}, 1157 currentRef: &corev1.ObjectReference{ 1158 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1159 Kind: "KubeadmConfigTemplate", 1160 Name: "my-cluster-abc", 1161 Namespace: metav1.NamespaceDefault, 1162 }, 1163 want: &corev1.ObjectReference{ 1164 // kind is different => apiVersion is taken from currentRef. 1165 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1166 Kind: "KubeadmConfigTemplate", 1167 Name: "my-cluster-abc", 1168 Namespace: metav1.NamespaceDefault, 1169 }, 1170 }, 1171 { 1172 name: "Use apiVersion from currentRef: group is different", 1173 templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{ 1174 "apiVersion": "bootstrap2.cluster.x-k8s.io/v1beta1", 1175 "kind": "KubeadmConfigTemplate", 1176 }}, 1177 currentRef: &corev1.ObjectReference{ 1178 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1179 Kind: "KubeadmConfigTemplate", 1180 Name: "my-cluster-abc", 1181 Namespace: metav1.NamespaceDefault, 1182 }, 1183 want: &corev1.ObjectReference{ 1184 // group is different => apiVersion is taken from currentRef. 1185 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1186 Kind: "KubeadmConfigTemplate", 1187 Name: "my-cluster-abc", 1188 Namespace: metav1.NamespaceDefault, 1189 }, 1190 }, 1191 } 1192 for _, tt := range tests { 1193 t.Run(tt.name, func(t *testing.T) { 1194 g := NewWithT(t) 1195 1196 got, err := alignRefAPIVersion(tt.templateFromClusterClass, tt.currentRef) 1197 if tt.wantErr { 1198 g.Expect(err).To(HaveOccurred()) 1199 return 1200 } 1201 g.Expect(err).ToNot(HaveOccurred()) 1202 1203 g.Expect(got).To(BeComparableTo(tt.want)) 1204 }) 1205 } 1206 }