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