sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/blueprint_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 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 "sigs.k8s.io/controller-runtime/pkg/client/fake" 28 . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" 29 30 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 31 "sigs.k8s.io/cluster-api/exp/topology/scope" 32 "sigs.k8s.io/cluster-api/internal/test/builder" 33 ) 34 35 func TestGetBlueprint(t *testing.T) { 36 crds := []client.Object{ 37 builder.GenericInfrastructureClusterTemplateCRD, 38 builder.GenericInfrastructureMachineTemplateCRD, 39 builder.GenericInfrastructureMachineCRD, 40 builder.GenericInfrastructureMachinePoolTemplateCRD, 41 builder.GenericInfrastructureMachinePoolCRD, 42 builder.GenericControlPlaneTemplateCRD, 43 builder.GenericBootstrapConfigTemplateCRD, 44 } 45 46 // The following is a block creating a number of objects for use in the test cases. 47 48 infraClusterTemplate := builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infraclustertemplate1"). 49 Build() 50 controlPlaneTemplate := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "controlplanetemplate1"). 51 Build() 52 53 controlPlaneInfrastructureMachineTemplate := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "controlplaneinframachinetemplate1"). 54 Build() 55 controlPlaneTemplateWithInfrastructureMachine := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "controlplanetempaltewithinfrastructuremachine1"). 56 WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 57 Build() 58 59 workerInfrastructureMachineTemplate := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "workerinframachinetemplate1"). 60 Build() 61 workerInfrastructureMachinePoolTemplate := builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "workerinframachinepooltemplate1"). 62 Build() 63 workerBootstrapTemplate := builder.BootstrapTemplate(metav1.NamespaceDefault, "workerbootstraptemplate1"). 64 Build() 65 machineHealthCheck := &clusterv1.MachineHealthCheckClass{ 66 NodeStartupTimeout: &metav1.Duration{ 67 Duration: time.Duration(1)}, 68 } 69 70 machineDeployment := builder.MachineDeploymentClass("workerclass1"). 71 WithLabels(map[string]string{"foo": "bar"}). 72 WithAnnotations(map[string]string{"a": "b"}). 73 WithInfrastructureTemplate(workerInfrastructureMachineTemplate). 74 WithBootstrapTemplate(workerBootstrapTemplate). 75 WithMachineHealthCheckClass(machineHealthCheck). 76 Build() 77 78 mds := []clusterv1.MachineDeploymentClass{*machineDeployment} 79 80 machinePools := builder.MachinePoolClass("workerclass2"). 81 WithLabels(map[string]string{"foo": "bar"}). 82 WithAnnotations(map[string]string{"a": "b"}). 83 WithInfrastructureTemplate(workerInfrastructureMachinePoolTemplate). 84 WithBootstrapTemplate(workerBootstrapTemplate). 85 Build() 86 mps := []clusterv1.MachinePoolClass{*machinePools} 87 88 // Define test cases. 89 tests := []struct { 90 name string 91 clusterClass *clusterv1.ClusterClass 92 objects []client.Object 93 want *scope.ClusterBlueprint 94 wantErr bool 95 }{ 96 { 97 name: "Fails if ClusterClass does not have reference to the InfrastructureClusterTemplate", 98 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "clusterclass1"). 99 // No InfrastructureClusterTemplate reference! 100 Build(), 101 wantErr: true, 102 }, 103 { 104 name: "Fails if ClusterClass references an InfrastructureClusterTemplate that does not exist", 105 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "clusterclass1"). 106 WithInfrastructureClusterTemplate(infraClusterTemplate). 107 Build(), 108 objects: []client.Object{ 109 // infraClusterTemplate is missing! 110 }, 111 wantErr: true, 112 }, 113 { 114 name: "Fails if ClusterClass does not have reference to the ControlPlaneTemplate", 115 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 116 WithInfrastructureClusterTemplate(infraClusterTemplate). 117 // No ControlPlaneTemplate reference! 118 Build(), 119 objects: []client.Object{ 120 infraClusterTemplate, 121 }, 122 wantErr: true, 123 }, 124 { 125 name: "Fails if ClusterClass does not have reference to the ControlPlaneTemplate", 126 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 127 WithInfrastructureClusterTemplate(infraClusterTemplate). 128 WithControlPlaneTemplate(controlPlaneTemplate). 129 Build(), 130 objects: []client.Object{ 131 infraClusterTemplate, 132 // ControlPlaneTemplate is missing! 133 }, 134 wantErr: true, 135 }, 136 { 137 name: "Should read a ClusterClass without worker classes", 138 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 139 WithInfrastructureClusterTemplate(infraClusterTemplate). 140 WithControlPlaneTemplate(controlPlaneTemplate). 141 Build(), 142 objects: []client.Object{ 143 infraClusterTemplate, 144 controlPlaneTemplate, 145 }, 146 want: &scope.ClusterBlueprint{ 147 ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 148 WithInfrastructureClusterTemplate(infraClusterTemplate). 149 WithControlPlaneTemplate(controlPlaneTemplate). 150 Build(), 151 InfrastructureClusterTemplate: infraClusterTemplate, 152 ControlPlane: &scope.ControlPlaneBlueprint{ 153 Template: controlPlaneTemplate, 154 }, 155 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{}, 156 MachinePools: map[string]*scope.MachinePoolBlueprint{}, 157 }, 158 }, 159 { 160 name: "Should read a ClusterClass referencing an InfrastructureMachineTemplate for the ControlPlane (but without any worker class)", 161 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 162 WithInfrastructureClusterTemplate(infraClusterTemplate). 163 WithControlPlaneTemplate(controlPlaneTemplateWithInfrastructureMachine). 164 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 165 Build(), 166 objects: []client.Object{ 167 infraClusterTemplate, 168 controlPlaneTemplateWithInfrastructureMachine, 169 controlPlaneInfrastructureMachineTemplate, 170 }, 171 want: &scope.ClusterBlueprint{ 172 ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 173 WithInfrastructureClusterTemplate(infraClusterTemplate). 174 WithControlPlaneTemplate(controlPlaneTemplateWithInfrastructureMachine). 175 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 176 Build(), 177 InfrastructureClusterTemplate: infraClusterTemplate, 178 ControlPlane: &scope.ControlPlaneBlueprint{ 179 Template: controlPlaneTemplateWithInfrastructureMachine, 180 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 181 }, 182 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{}, 183 MachinePools: map[string]*scope.MachinePoolBlueprint{}, 184 }, 185 }, 186 { 187 name: "Fails if ClusterClass references an InfrastructureMachineTemplate for the ControlPlane that does not exist", 188 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 189 WithInfrastructureClusterTemplate(infraClusterTemplate). 190 WithControlPlaneTemplate(controlPlaneTemplate). 191 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 192 Build(), 193 objects: []client.Object{ 194 infraClusterTemplate, 195 controlPlaneTemplate, 196 // controlPlaneInfrastructureMachineTemplate is missing! 197 }, 198 wantErr: true, 199 }, 200 { 201 name: "Should read a ClusterClass with a MachineDeploymentClass", 202 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 203 WithInfrastructureClusterTemplate(infraClusterTemplate). 204 WithControlPlaneTemplate(controlPlaneTemplate). 205 WithWorkerMachineDeploymentClasses(mds...). 206 Build(), 207 objects: []client.Object{ 208 infraClusterTemplate, 209 controlPlaneTemplate, 210 workerInfrastructureMachineTemplate, 211 workerBootstrapTemplate, 212 }, 213 want: &scope.ClusterBlueprint{ 214 ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 215 WithInfrastructureClusterTemplate(infraClusterTemplate). 216 WithControlPlaneTemplate(controlPlaneTemplate). 217 WithWorkerMachineDeploymentClasses(mds...). 218 Build(), 219 InfrastructureClusterTemplate: infraClusterTemplate, 220 ControlPlane: &scope.ControlPlaneBlueprint{ 221 Template: controlPlaneTemplate, 222 }, 223 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{ 224 "workerclass1": { 225 Metadata: clusterv1.ObjectMeta{ 226 Labels: map[string]string{"foo": "bar"}, 227 Annotations: map[string]string{"a": "b"}, 228 }, 229 InfrastructureMachineTemplate: workerInfrastructureMachineTemplate, 230 BootstrapTemplate: workerBootstrapTemplate, 231 MachineHealthCheck: machineHealthCheck, 232 }, 233 }, 234 MachinePools: map[string]*scope.MachinePoolBlueprint{}, 235 }, 236 }, 237 { 238 name: "Fails if ClusterClass has a MachineDeploymentClass referencing a BootstrapConfigTemplate that does not exist", 239 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 240 WithInfrastructureClusterTemplate(infraClusterTemplate). 241 WithControlPlaneTemplate(controlPlaneTemplate). 242 WithWorkerMachineDeploymentClasses(mds...). 243 Build(), 244 objects: []client.Object{ 245 infraClusterTemplate, 246 controlPlaneTemplate, 247 workerInfrastructureMachineTemplate, 248 // workerBootstrapTemplate is missing! 249 }, 250 wantErr: true, 251 }, 252 { 253 name: "Fails if ClusterClass has a MachineDeploymentClass referencing a InfrastructureMachineTemplate that does not exist", 254 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 255 WithInfrastructureClusterTemplate(infraClusterTemplate). 256 WithControlPlaneTemplate(controlPlaneTemplate). 257 WithWorkerMachineDeploymentClasses(mds...). 258 Build(), 259 objects: []client.Object{ 260 infraClusterTemplate, 261 controlPlaneTemplate, 262 workerBootstrapTemplate, 263 // workerInfrastructureTemplate is missing! 264 }, 265 wantErr: true, 266 }, 267 { 268 name: "Should read a ClusterClass with a MachineHealthCheck in the ControlPlane", 269 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 270 WithInfrastructureClusterTemplate(infraClusterTemplate). 271 WithControlPlaneTemplate(controlPlaneTemplate). 272 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 273 WithControlPlaneMachineHealthCheck(machineHealthCheck). 274 Build(), 275 objects: []client.Object{ 276 infraClusterTemplate, 277 controlPlaneTemplate, 278 controlPlaneInfrastructureMachineTemplate, 279 }, 280 want: &scope.ClusterBlueprint{ 281 ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 282 WithInfrastructureClusterTemplate(infraClusterTemplate). 283 WithControlPlaneTemplate(controlPlaneTemplate). 284 WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate). 285 WithControlPlaneMachineHealthCheck(machineHealthCheck). 286 Build(), 287 InfrastructureClusterTemplate: infraClusterTemplate, 288 ControlPlane: &scope.ControlPlaneBlueprint{ 289 Template: controlPlaneTemplate, 290 InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate, 291 MachineHealthCheck: machineHealthCheck, 292 }, 293 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{}, 294 MachinePools: map[string]*scope.MachinePoolBlueprint{}, 295 }, 296 }, 297 { 298 name: "Should read a ClusterClass with a MachinePoolClass", 299 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 300 WithInfrastructureClusterTemplate(infraClusterTemplate). 301 WithControlPlaneTemplate(controlPlaneTemplate). 302 WithWorkerMachinePoolClasses(mps...). 303 Build(), 304 objects: []client.Object{ 305 infraClusterTemplate, 306 controlPlaneTemplate, 307 workerInfrastructureMachinePoolTemplate, 308 workerBootstrapTemplate, 309 }, 310 want: &scope.ClusterBlueprint{ 311 ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 312 WithInfrastructureClusterTemplate(infraClusterTemplate). 313 WithControlPlaneTemplate(controlPlaneTemplate). 314 WithWorkerMachinePoolClasses(mps...). 315 Build(), 316 InfrastructureClusterTemplate: infraClusterTemplate, 317 ControlPlane: &scope.ControlPlaneBlueprint{ 318 Template: controlPlaneTemplate, 319 }, 320 MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{}, 321 MachinePools: map[string]*scope.MachinePoolBlueprint{ 322 "workerclass2": { 323 Metadata: clusterv1.ObjectMeta{ 324 Labels: map[string]string{"foo": "bar"}, 325 Annotations: map[string]string{"a": "b"}, 326 }, 327 InfrastructureMachinePoolTemplate: workerInfrastructureMachinePoolTemplate, 328 BootstrapTemplate: workerBootstrapTemplate, 329 }, 330 }, 331 }, 332 }, 333 { 334 name: "Fails if ClusterClass has a MachinePoolClass referencing a BootstrapConfigTemplate that does not exist", 335 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 336 WithInfrastructureClusterTemplate(infraClusterTemplate). 337 WithControlPlaneTemplate(controlPlaneTemplate). 338 WithWorkerMachinePoolClasses(mps...). 339 Build(), 340 objects: []client.Object{ 341 infraClusterTemplate, 342 controlPlaneTemplate, 343 workerInfrastructureMachinePoolTemplate, 344 // workerBootstrapTemplate is missing! 345 }, 346 wantErr: true, 347 }, 348 { 349 name: "Fails if ClusterClass has a MachinePoolClass referencing a InfrastructureMachinePoolTemplate that does not exist", 350 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 351 WithInfrastructureClusterTemplate(infraClusterTemplate). 352 WithControlPlaneTemplate(controlPlaneTemplate). 353 WithWorkerMachinePoolClasses(mps...). 354 Build(), 355 objects: []client.Object{ 356 infraClusterTemplate, 357 controlPlaneTemplate, 358 workerBootstrapTemplate, 359 // workerInfrastructureMachinePoolTemplate is missing! 360 }, 361 wantErr: true, 362 }, 363 } 364 for _, tt := range tests { 365 t.Run(tt.name, func(t *testing.T) { 366 g := NewWithT(t) 367 368 // Set up a cluster using the ClusterClass, if any. 369 cluster := builder.Cluster(metav1.NamespaceDefault, "cluster1"). 370 WithTopology( 371 builder.ClusterTopology(). 372 WithClass("class1"). 373 Build()). 374 Build() 375 376 // If no clusterClass is defined in the test case fill in a dummy value "foo". 377 if tt.clusterClass == nil { 378 cluster.Spec.Topology.Class = "foo" 379 } 380 381 // Sets up the fakeClient for the test case. 382 objs := []client.Object{} 383 objs = append(objs, crds...) 384 objs = append(objs, tt.objects...) 385 if tt.clusterClass != nil { 386 objs = append(objs, tt.clusterClass) 387 } 388 fakeClient := fake.NewClientBuilder(). 389 WithScheme(fakeScheme). 390 WithObjects(objs...). 391 Build() 392 393 // Calls getBlueprint. 394 r := &Reconciler{ 395 Client: fakeClient, 396 patchHelperFactory: dryRunPatchHelperFactory(fakeClient), 397 UnstructuredCachingClient: fakeClient, 398 } 399 got, err := r.getBlueprint(ctx, scope.New(cluster).Current.Cluster, tt.clusterClass) 400 401 // Checks the return error. 402 if tt.wantErr { 403 g.Expect(err).To(HaveOccurred()) 404 } else { 405 g.Expect(err).ToNot(HaveOccurred()) 406 } 407 408 if tt.want == nil { 409 g.Expect(got).To(BeNil()) 410 return 411 } 412 413 // Use EqualObject where an object is created and passed through the fake client. Elsewhere the Equal method 414 // is enough to establish inequality. 415 g.Expect(tt.want.ClusterClass).To(EqualObject(got.ClusterClass, IgnoreAutogeneratedMetadata)) 416 g.Expect(tt.want.InfrastructureClusterTemplate).To(EqualObject(got.InfrastructureClusterTemplate), cmp.Diff(got.InfrastructureClusterTemplate, tt.want.InfrastructureClusterTemplate)) 417 g.Expect(got.ControlPlane).To(BeComparableTo(tt.want.ControlPlane), cmp.Diff(got.ControlPlane, tt.want.ControlPlane)) 418 g.Expect(tt.want.MachineDeployments).To(BeComparableTo(got.MachineDeployments), cmp.Diff(got.MachineDeployments, tt.want.MachineDeployments)) 419 g.Expect(tt.want.MachinePools).To(BeComparableTo(got.MachinePools), cmp.Diff(got.MachinePools, tt.want.MachinePools)) 420 }) 421 } 422 }