sigs.k8s.io/cluster-api@v1.6.3/internal/topology/check/compatibility_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 check 18 19 import ( 20 "testing" 21 22 . "github.com/onsi/gomega" 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 28 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 29 "sigs.k8s.io/cluster-api/internal/test/builder" 30 ) 31 32 type referencedObjectsCompatibilityTestCase struct { 33 name string 34 current *unstructured.Unstructured 35 desired *unstructured.Unstructured 36 wantErr bool 37 } 38 39 var referencedObjectsCompatibilityTestCases = []referencedObjectsCompatibilityTestCase{ 40 { 41 name: "Fails if group changes", 42 current: &unstructured.Unstructured{ 43 Object: map[string]interface{}{ 44 "apiVersion": "foo/v1beta1", 45 }, 46 }, 47 desired: &unstructured.Unstructured{ 48 Object: map[string]interface{}{ 49 "apiVersion": "bar/v1beta1", 50 }, 51 }, 52 wantErr: true, 53 }, 54 { 55 name: "Fails if kind changes", 56 current: &unstructured.Unstructured{ 57 Object: map[string]interface{}{ 58 "kind": "foo", 59 }, 60 }, 61 desired: &unstructured.Unstructured{ 62 Object: map[string]interface{}{ 63 "kind": "bar", 64 }, 65 }, 66 wantErr: true, 67 }, 68 { 69 name: "Pass if gvk remains the same", 70 current: &unstructured.Unstructured{ 71 Object: map[string]interface{}{ 72 "apiVersion": "infrastructure.cluster.x-k8s.io/foo", 73 "kind": "foo", 74 }, 75 }, 76 desired: &unstructured.Unstructured{ 77 Object: map[string]interface{}{ 78 "apiVersion": "infrastructure.cluster.x-k8s.io/foo", 79 "kind": "foo", 80 }, 81 }, 82 wantErr: false, 83 }, 84 { 85 name: "Pass if version changes but group and kind remains the same", 86 current: &unstructured.Unstructured{ 87 Object: map[string]interface{}{ 88 "apiVersion": "infrastructure.cluster.x-k8s.io/foo", 89 "kind": "foo", 90 }, 91 }, 92 desired: &unstructured.Unstructured{ 93 Object: map[string]interface{}{ 94 "apiVersion": "infrastructure.cluster.x-k8s.io/bar", 95 "kind": "foo", 96 }, 97 }, 98 wantErr: false, 99 }, 100 { 101 name: "Fails if namespace changes", 102 current: &unstructured.Unstructured{ 103 Object: map[string]interface{}{ 104 "metadata": map[string]interface{}{ 105 "namespace": "foo", 106 }, 107 }, 108 }, 109 desired: &unstructured.Unstructured{ 110 Object: map[string]interface{}{ 111 "metadata": map[string]interface{}{ 112 "namespace": "bar", 113 }, 114 }, 115 }, 116 wantErr: true, 117 }, 118 } 119 120 func TestObjectsAreCompatible(t *testing.T) { 121 for _, tt := range referencedObjectsCompatibilityTestCases { 122 t.Run(tt.name, func(t *testing.T) { 123 g := NewWithT(t) 124 allErrs := ObjectsAreCompatible(tt.current, tt.desired) 125 if tt.wantErr { 126 g.Expect(allErrs).ToNot(BeEmpty()) 127 return 128 } 129 g.Expect(allErrs).To(BeEmpty()) 130 }) 131 } 132 } 133 134 func TestObjectsAreStrictlyCompatible(t *testing.T) { 135 referencedObjectsStrictCompatibilityTestCases := append(referencedObjectsCompatibilityTestCases, []referencedObjectsCompatibilityTestCase{ 136 { 137 name: "Fails if name changes", 138 current: &unstructured.Unstructured{ 139 Object: map[string]interface{}{ 140 "metadata": map[string]interface{}{ 141 "name": "foo", 142 }, 143 }, 144 }, 145 desired: &unstructured.Unstructured{ 146 Object: map[string]interface{}{ 147 "metadata": map[string]interface{}{ 148 "name": "bar", 149 }, 150 }, 151 }, 152 wantErr: true, 153 }, 154 { 155 name: "Pass if name remains the same", 156 current: &unstructured.Unstructured{ 157 Object: map[string]interface{}{ 158 "metadata": map[string]interface{}{ 159 "name": "foo", 160 }, 161 }, 162 }, 163 desired: &unstructured.Unstructured{ 164 Object: map[string]interface{}{ 165 "metadata": map[string]interface{}{ 166 "name": "foo", 167 }, 168 }, 169 }, 170 wantErr: false, 171 }, 172 }...) 173 174 for _, tt := range referencedObjectsStrictCompatibilityTestCases { 175 t.Run(tt.name, func(t *testing.T) { 176 g := NewWithT(t) 177 178 allErrs := ObjectsAreStrictlyCompatible(tt.current, tt.desired) 179 if tt.wantErr { 180 g.Expect(allErrs).ToNot(BeEmpty()) 181 return 182 } 183 g.Expect(allErrs).To(BeEmpty()) 184 }) 185 } 186 } 187 188 func TestLocalObjectTemplatesAreCompatible(t *testing.T) { 189 template := clusterv1.LocalObjectTemplate{ 190 Ref: &corev1.ObjectReference{ 191 Namespace: "default", 192 Name: "foo", 193 Kind: "bar", 194 APIVersion: "test.group.io/versionone", 195 }, 196 } 197 compatibleNameChangeTemplate := clusterv1.LocalObjectTemplate{ 198 Ref: &corev1.ObjectReference{ 199 Namespace: "default", 200 Name: "newFoo", 201 Kind: "bar", 202 APIVersion: "test.group.io/versionone", 203 }, 204 } 205 compatibleAPIVersionChangeTemplate := clusterv1.LocalObjectTemplate{ 206 Ref: &corev1.ObjectReference{ 207 Namespace: "default", 208 Name: "foo", 209 Kind: "bar", 210 APIVersion: "test.group.io/versiontwo", 211 }, 212 } 213 incompatibleNamespaceChangeTemplate := clusterv1.LocalObjectTemplate{ 214 Ref: &corev1.ObjectReference{ 215 Namespace: "different", 216 Name: "foo", 217 Kind: "bar", 218 APIVersion: "test.group.io/versionone", 219 }, 220 } 221 incompatibleAPIGroupChangeTemplate := clusterv1.LocalObjectTemplate{ 222 Ref: &corev1.ObjectReference{ 223 Namespace: "default", 224 Name: "foo", 225 Kind: "bar", 226 APIVersion: "production.group.io/versionone", 227 }, 228 } 229 incompatibleAPIKindChangeTemplate := clusterv1.LocalObjectTemplate{ 230 Ref: &corev1.ObjectReference{ 231 Namespace: "default", 232 Name: "foo", 233 Kind: "notabar", 234 APIVersion: "test.group.io/versionone", 235 }, 236 } 237 tests := []struct { 238 name string 239 current clusterv1.LocalObjectTemplate 240 desired clusterv1.LocalObjectTemplate 241 wantErr bool 242 }{ 243 { 244 name: "Allow change to template name", 245 current: template, 246 desired: compatibleNameChangeTemplate, 247 wantErr: false, 248 }, 249 { 250 name: "Allow change to template APIVersion", 251 current: template, 252 desired: compatibleAPIVersionChangeTemplate, 253 wantErr: false, 254 }, 255 { 256 name: "Block change to template API Group", 257 current: template, 258 desired: incompatibleAPIGroupChangeTemplate, 259 wantErr: true, 260 }, 261 { 262 name: "Block change to template namespace", 263 current: template, 264 desired: incompatibleNamespaceChangeTemplate, 265 wantErr: true, 266 }, 267 { 268 name: "Block change to template API Kind", 269 current: template, 270 desired: incompatibleAPIKindChangeTemplate, 271 wantErr: true, 272 }, 273 } 274 for _, tt := range tests { 275 t.Run(tt.name, func(t *testing.T) { 276 g := NewWithT(t) 277 allErrs := LocalObjectTemplatesAreCompatible(tt.current, tt.desired, field.NewPath("spec")) 278 if tt.wantErr { 279 g.Expect(allErrs).ToNot(BeEmpty()) 280 return 281 } 282 g.Expect(allErrs).To(BeEmpty()) 283 }) 284 } 285 } 286 287 func TestLocalObjectTemplateIsValid(t *testing.T) { 288 namespace := metav1.NamespaceDefault 289 pathPrefix := field.NewPath("this", "is", "a", "prefix") 290 291 validTemplate := &clusterv1.LocalObjectTemplate{ 292 Ref: &corev1.ObjectReference{ 293 Namespace: "default", 294 Name: "valid", 295 Kind: "barTemplate", 296 APIVersion: "test.group.io/versionone", 297 }, 298 } 299 300 nilTemplate := &clusterv1.LocalObjectTemplate{ 301 Ref: nil, 302 } 303 emptyNameTemplate := &clusterv1.LocalObjectTemplate{ 304 Ref: &corev1.ObjectReference{ 305 Namespace: "default", 306 Name: "", 307 Kind: "barTemplate", 308 APIVersion: "test.group.io/versionone", 309 }, 310 } 311 wrongNamespaceTemplate := &clusterv1.LocalObjectTemplate{ 312 Ref: &corev1.ObjectReference{ 313 Namespace: "wrongNamespace", 314 Name: "foo", 315 Kind: "barTemplate", 316 APIVersion: "test.group.io/versiontwo", 317 }, 318 } 319 notTemplateKindTemplate := &clusterv1.LocalObjectTemplate{ 320 Ref: &corev1.ObjectReference{ 321 Namespace: "default", 322 Name: "foo", 323 Kind: "bar", 324 APIVersion: "test.group.io/versionone", 325 }, 326 } 327 invalidAPIVersionTemplate := &clusterv1.LocalObjectTemplate{ 328 Ref: &corev1.ObjectReference{ 329 Namespace: "default", 330 Name: "foo", 331 Kind: "barTemplate", 332 APIVersion: "this/has/too/many/slashes", 333 }, 334 } 335 emptyAPIVersionTemplate := &clusterv1.LocalObjectTemplate{ 336 Ref: &corev1.ObjectReference{ 337 Namespace: "default", 338 Name: "foo", 339 Kind: "barTemplate", 340 APIVersion: "", 341 }, 342 } 343 344 tests := []struct { 345 template *clusterv1.LocalObjectTemplate 346 name string 347 wantErr bool 348 }{ 349 { 350 name: "No error with valid Template", 351 template: validTemplate, 352 wantErr: false, 353 }, 354 355 { 356 name: "Invalid if ref is nil", 357 template: nilTemplate, 358 wantErr: true, 359 }, 360 { 361 name: "Invalid if name is empty", 362 template: emptyNameTemplate, 363 wantErr: true, 364 }, 365 { 366 name: "Invalid if namespace doesn't match", 367 template: wrongNamespaceTemplate, 368 wantErr: true, 369 }, 370 { 371 name: "Invalid if Kind doesn't have Template suffix", 372 template: notTemplateKindTemplate, 373 wantErr: true, 374 }, 375 { 376 name: "Invalid if apiVersion is not valid", 377 template: invalidAPIVersionTemplate, 378 wantErr: true, 379 }, 380 { 381 name: "Empty apiVersion is not valid", 382 template: emptyAPIVersionTemplate, 383 wantErr: true, 384 }, 385 } 386 for _, tt := range tests { 387 t.Run(tt.name, func(t *testing.T) { 388 g := NewWithT(t) 389 allErrs := LocalObjectTemplateIsValid(tt.template, namespace, pathPrefix) 390 if tt.wantErr { 391 g.Expect(allErrs).ToNot(BeEmpty()) 392 return 393 } 394 g.Expect(allErrs).To(BeEmpty()) 395 }) 396 } 397 } 398 399 func TestClusterClassesAreCompatible(t *testing.T) { 400 ref := &corev1.ObjectReference{ 401 APIVersion: "group.test.io/foo", 402 Kind: "barTemplate", 403 Name: "baz", 404 Namespace: "default", 405 } 406 incompatibleRef := &corev1.ObjectReference{ 407 APIVersion: "group.test.io/foo", 408 Kind: "another-barTemplate", 409 Name: "baz", 410 Namespace: "default", 411 } 412 compatibleRef := &corev1.ObjectReference{ 413 APIVersion: "group.test.io/another-foo", 414 Kind: "barTemplate", 415 Name: "another-baz", 416 Namespace: "default", 417 } 418 419 tests := []struct { 420 name string 421 current *clusterv1.ClusterClass 422 desired *clusterv1.ClusterClass 423 wantErr bool 424 }{ 425 { 426 name: "error if current is nil", 427 current: nil, 428 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 429 WithInfrastructureClusterTemplate( 430 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 431 WithControlPlaneTemplate( 432 refToUnstructured(ref)). 433 WithControlPlaneInfrastructureMachineTemplate( 434 refToUnstructured(ref)). 435 Build(), 436 wantErr: true, 437 }, 438 { 439 name: "error if desired is nil", 440 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 441 WithInfrastructureClusterTemplate( 442 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 443 WithControlPlaneTemplate( 444 refToUnstructured(ref)). 445 WithControlPlaneInfrastructureMachineTemplate( 446 refToUnstructured(ref)). 447 Build(), 448 desired: nil, 449 wantErr: true, 450 }, 451 452 { 453 name: "pass for compatible clusterClasses", 454 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 455 WithInfrastructureClusterTemplate( 456 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 457 WithControlPlaneTemplate( 458 refToUnstructured(ref)). 459 WithControlPlaneInfrastructureMachineTemplate( 460 refToUnstructured(ref)). 461 WithWorkerMachineDeploymentClasses( 462 *builder.MachineDeploymentClass("aa"). 463 WithInfrastructureTemplate( 464 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 465 WithBootstrapTemplate( 466 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 467 Build()). 468 Build(), 469 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 470 WithInfrastructureClusterTemplate( 471 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 472 WithControlPlaneTemplate( 473 refToUnstructured(compatibleRef)). 474 WithControlPlaneInfrastructureMachineTemplate( 475 refToUnstructured(compatibleRef)). 476 WithWorkerMachineDeploymentClasses( 477 *builder.MachineDeploymentClass("aa"). 478 WithInfrastructureTemplate( 479 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 480 WithBootstrapTemplate( 481 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 482 Build()). 483 Build(), 484 wantErr: false, 485 }, 486 { 487 name: "error if clusterClass has incompatible ControlPlane ref", 488 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 489 WithInfrastructureClusterTemplate( 490 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 491 WithControlPlaneTemplate( 492 refToUnstructured(ref)). 493 WithControlPlaneInfrastructureMachineTemplate( 494 refToUnstructured(ref)). 495 WithWorkerMachineDeploymentClasses( 496 *builder.MachineDeploymentClass("aa"). 497 WithInfrastructureTemplate( 498 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 499 WithBootstrapTemplate( 500 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 501 Build()). 502 Build(), 503 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 504 WithInfrastructureClusterTemplate( 505 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 506 WithControlPlaneTemplate( 507 refToUnstructured(incompatibleRef)). 508 WithControlPlaneInfrastructureMachineTemplate( 509 refToUnstructured(ref)). 510 WithWorkerMachineDeploymentClasses( 511 *builder.MachineDeploymentClass("aa"). 512 WithInfrastructureTemplate( 513 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 514 WithBootstrapTemplate( 515 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 516 Build()). 517 Build(), 518 wantErr: true, 519 }, 520 { 521 name: "pass for incompatible ref in MachineDeploymentClass bootstrapTemplate clusterClasses", 522 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 523 WithInfrastructureClusterTemplate( 524 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 525 WithControlPlaneTemplate( 526 refToUnstructured(ref)). 527 WithControlPlaneInfrastructureMachineTemplate( 528 refToUnstructured(ref)). 529 WithWorkerMachineDeploymentClasses( 530 *builder.MachineDeploymentClass("aa"). 531 WithInfrastructureTemplate( 532 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 533 WithBootstrapTemplate( 534 refToUnstructured(ref)).Build()). 535 Build(), 536 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 537 WithInfrastructureClusterTemplate( 538 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 539 WithControlPlaneTemplate( 540 refToUnstructured(ref)). 541 WithControlPlaneInfrastructureMachineTemplate( 542 refToUnstructured(ref)). 543 WithWorkerMachineDeploymentClasses( 544 *builder.MachineDeploymentClass("aa"). 545 WithInfrastructureTemplate( 546 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 547 WithBootstrapTemplate( 548 refToUnstructured(incompatibleRef)).Build()). 549 Build(), 550 wantErr: false, 551 }, 552 { 553 name: "pass if machineDeploymentClass is removed from ClusterClass", 554 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 555 WithInfrastructureClusterTemplate( 556 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 557 WithControlPlaneTemplate( 558 refToUnstructured(ref)). 559 WithControlPlaneInfrastructureMachineTemplate( 560 refToUnstructured(ref)). 561 WithWorkerMachineDeploymentClasses( 562 *builder.MachineDeploymentClass("aa"). 563 WithInfrastructureTemplate( 564 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 565 WithBootstrapTemplate( 566 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 567 Build(), 568 *builder.MachineDeploymentClass("bb"). 569 WithInfrastructureTemplate( 570 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 571 WithBootstrapTemplate( 572 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 573 Build()). 574 Build(), 575 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 576 WithInfrastructureClusterTemplate( 577 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 578 WithControlPlaneTemplate( 579 refToUnstructured(ref)). 580 WithControlPlaneInfrastructureMachineTemplate( 581 refToUnstructured(ref)). 582 WithWorkerMachineDeploymentClasses( 583 *builder.MachineDeploymentClass("aa"). 584 WithInfrastructureTemplate( 585 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 586 WithBootstrapTemplate( 587 refToUnstructured(incompatibleRef)).Build()). 588 Build(), 589 wantErr: false, 590 }, 591 } 592 for _, tt := range tests { 593 g := NewWithT(t) 594 t.Run(tt.name, func(t *testing.T) { 595 allErrs := ClusterClassesAreCompatible(tt.current, tt.desired) 596 if tt.wantErr { 597 g.Expect(allErrs).ToNot(BeEmpty()) 598 return 599 } 600 g.Expect(allErrs).To(BeEmpty()) 601 }) 602 } 603 } 604 605 func TestMachineDeploymentClassesAreCompatible(t *testing.T) { 606 ref := &corev1.ObjectReference{ 607 APIVersion: "group.test.io/foo", 608 Kind: "barTemplate", 609 Name: "baz", 610 Namespace: "default", 611 } 612 compatibleRef := &corev1.ObjectReference{ 613 APIVersion: "group.test.io/another-foo", 614 Kind: "barTemplate", 615 Name: "another-baz", 616 Namespace: "default", 617 } 618 incompatibleRef := &corev1.ObjectReference{ 619 APIVersion: "group.test.io/foo", 620 Kind: "another-barTemplate", 621 Name: "baz", 622 Namespace: "default", 623 } 624 625 tests := []struct { 626 name string 627 current *clusterv1.ClusterClass 628 desired *clusterv1.ClusterClass 629 wantErr bool 630 }{ 631 { 632 name: "pass if machineDeploymentClasses are compatible", 633 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 634 WithInfrastructureClusterTemplate( 635 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 636 WithControlPlaneTemplate( 637 refToUnstructured(ref)). 638 WithControlPlaneInfrastructureMachineTemplate( 639 refToUnstructured(ref)). 640 WithWorkerMachineDeploymentClasses( 641 *builder.MachineDeploymentClass("aa"). 642 WithInfrastructureTemplate( 643 refToUnstructured(ref)). 644 WithBootstrapTemplate( 645 refToUnstructured(ref)). 646 Build()). 647 Build(), 648 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 649 WithInfrastructureClusterTemplate( 650 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 651 WithControlPlaneTemplate( 652 refToUnstructured(ref)). 653 WithControlPlaneInfrastructureMachineTemplate( 654 refToUnstructured(ref)). 655 WithWorkerMachineDeploymentClasses( 656 *builder.MachineDeploymentClass("aa"). 657 WithInfrastructureTemplate( 658 refToUnstructured(compatibleRef)). 659 WithBootstrapTemplate( 660 refToUnstructured(incompatibleRef)).Build()). 661 Build(), 662 wantErr: false, 663 }, 664 { 665 name: "pass if machineDeploymentClass is removed from ClusterClass", 666 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 667 WithInfrastructureClusterTemplate( 668 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 669 WithControlPlaneTemplate( 670 refToUnstructured(ref)). 671 WithControlPlaneInfrastructureMachineTemplate( 672 refToUnstructured(ref)). 673 WithWorkerMachineDeploymentClasses( 674 *builder.MachineDeploymentClass("aa"). 675 WithInfrastructureTemplate( 676 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 677 WithBootstrapTemplate( 678 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 679 Build(), 680 *builder.MachineDeploymentClass("bb"). 681 WithInfrastructureTemplate( 682 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 683 WithBootstrapTemplate( 684 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 685 Build()). 686 Build(), 687 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 688 WithInfrastructureClusterTemplate( 689 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 690 WithControlPlaneTemplate( 691 refToUnstructured(ref)). 692 WithControlPlaneInfrastructureMachineTemplate( 693 refToUnstructured(ref)). 694 WithWorkerMachineDeploymentClasses( 695 *builder.MachineDeploymentClass("aa"). 696 WithInfrastructureTemplate( 697 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 698 WithBootstrapTemplate( 699 refToUnstructured(incompatibleRef)).Build()). 700 Build(), 701 wantErr: false, 702 }, 703 { 704 name: "error if machineDeploymentClass has multiple incompatible references", 705 current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 706 WithInfrastructureClusterTemplate( 707 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 708 WithControlPlaneTemplate( 709 refToUnstructured(ref)). 710 WithControlPlaneInfrastructureMachineTemplate( 711 refToUnstructured(ref)). 712 WithWorkerMachineDeploymentClasses( 713 *builder.MachineDeploymentClass("aa"). 714 WithInfrastructureTemplate( 715 refToUnstructured(ref)). 716 WithBootstrapTemplate( 717 refToUnstructured(ref)). 718 Build(), 719 ). 720 Build(), 721 desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 722 WithInfrastructureClusterTemplate( 723 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 724 WithControlPlaneTemplate( 725 refToUnstructured(incompatibleRef)). 726 WithControlPlaneInfrastructureMachineTemplate( 727 refToUnstructured(incompatibleRef)). 728 WithWorkerMachineDeploymentClasses( 729 *builder.MachineDeploymentClass("aa"). 730 WithInfrastructureTemplate( 731 refToUnstructured(incompatibleRef)). 732 WithBootstrapTemplate( 733 refToUnstructured(compatibleRef)).Build()). 734 Build(), 735 wantErr: true, 736 }, 737 } 738 for _, tt := range tests { 739 t.Run(tt.name, func(t *testing.T) { 740 g := NewWithT(t) 741 allErrs := MachineDeploymentClassesAreCompatible(tt.current, tt.desired) 742 if tt.wantErr { 743 g.Expect(allErrs).ToNot(BeEmpty()) 744 return 745 } 746 g.Expect(allErrs).To(BeEmpty()) 747 }) 748 } 749 } 750 751 func TestMachineDeploymentClassesAreUnique(t *testing.T) { 752 tests := []struct { 753 name string 754 clusterClass *clusterv1.ClusterClass 755 wantErr bool 756 }{ 757 { 758 name: "pass if MachineDeploymentClasses are unique", 759 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 760 WithInfrastructureClusterTemplate( 761 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 762 WithControlPlaneTemplate( 763 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 764 WithControlPlaneInfrastructureMachineTemplate( 765 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 766 WithWorkerMachineDeploymentClasses( 767 *builder.MachineDeploymentClass("aa"). 768 WithInfrastructureTemplate( 769 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 770 WithBootstrapTemplate( 771 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 772 Build(), 773 *builder.MachineDeploymentClass("bb"). 774 WithInfrastructureTemplate( 775 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 776 WithBootstrapTemplate( 777 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 778 Build()). 779 Build(), 780 wantErr: false, 781 }, 782 { 783 name: "fail if MachineDeploymentClasses are duplicated", 784 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 785 WithInfrastructureClusterTemplate( 786 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 787 WithControlPlaneTemplate( 788 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 789 WithControlPlaneInfrastructureMachineTemplate( 790 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 791 WithWorkerMachineDeploymentClasses( 792 *builder.MachineDeploymentClass("aa"). 793 WithInfrastructureTemplate( 794 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 795 WithBootstrapTemplate( 796 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 797 Build(), 798 *builder.MachineDeploymentClass("aa"). 799 WithInfrastructureTemplate( 800 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 801 WithBootstrapTemplate( 802 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 803 Build()). 804 Build(), 805 wantErr: true, 806 }, 807 { 808 name: "fail if multiple MachineDeploymentClasses are identical", 809 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 810 WithInfrastructureClusterTemplate( 811 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 812 WithControlPlaneTemplate( 813 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 814 WithControlPlaneInfrastructureMachineTemplate( 815 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 816 WithWorkerMachineDeploymentClasses( 817 *builder.MachineDeploymentClass("aa"). 818 WithInfrastructureTemplate( 819 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 820 WithBootstrapTemplate( 821 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 822 Build(), 823 *builder.MachineDeploymentClass("aa"). 824 WithInfrastructureTemplate( 825 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 826 WithBootstrapTemplate( 827 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 828 Build(), 829 *builder.MachineDeploymentClass("aa"). 830 WithInfrastructureTemplate( 831 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 832 WithBootstrapTemplate( 833 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 834 Build(), 835 *builder.MachineDeploymentClass("aa"). 836 WithInfrastructureTemplate( 837 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 838 WithBootstrapTemplate( 839 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 840 Build(), 841 ). 842 Build(), 843 wantErr: true, 844 }, 845 } 846 for _, tt := range tests { 847 t.Run(tt.name, func(t *testing.T) { 848 g := NewWithT(t) 849 allErrs := MachineDeploymentClassesAreUnique(tt.clusterClass) 850 if tt.wantErr { 851 g.Expect(allErrs).ToNot(BeEmpty()) 852 return 853 } 854 g.Expect(allErrs).To(BeEmpty()) 855 }) 856 } 857 } 858 859 func TestMachineDeploymentTopologiesAreUniqueAndDefinedInClusterClass(t *testing.T) { 860 tests := []struct { 861 name string 862 clusterClass *clusterv1.ClusterClass 863 cluster *clusterv1.Cluster 864 wantErr bool 865 }{ 866 { 867 name: "fail if MachineDeploymentTopologies name is empty", 868 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 869 WithInfrastructureClusterTemplate( 870 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 871 WithControlPlaneTemplate( 872 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 873 WithWorkerMachineDeploymentClasses( 874 *builder.MachineDeploymentClass("aa"). 875 WithInfrastructureTemplate( 876 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 877 WithBootstrapTemplate( 878 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 879 Build()). 880 Build(), 881 cluster: builder.Cluster("fooboo", "cluster1"). 882 WithTopology(builder.ClusterTopology(). 883 WithClass("foo"). 884 WithVersion("v1.19.1"). 885 WithMachineDeployment( 886 // The name should not be empty. 887 builder.MachineDeploymentTopology(""). 888 WithClass("aa"). 889 Build()). 890 Build()). 891 Build(), 892 wantErr: true, 893 }, 894 { 895 name: "pass if MachineDeploymentTopologies are unique and defined in ClusterClass", 896 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 897 WithInfrastructureClusterTemplate( 898 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 899 WithControlPlaneTemplate( 900 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 901 WithControlPlaneInfrastructureMachineTemplate( 902 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 903 WithWorkerMachineDeploymentClasses( 904 *builder.MachineDeploymentClass("aa"). 905 WithInfrastructureTemplate( 906 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 907 WithBootstrapTemplate( 908 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 909 Build()). 910 Build(), 911 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 912 WithTopology( 913 builder.ClusterTopology(). 914 WithClass("class1"). 915 WithVersion("v1.22.2"). 916 WithMachineDeployment( 917 builder.MachineDeploymentTopology("workers1"). 918 WithClass("aa"). 919 Build()). 920 Build()). 921 Build(), 922 wantErr: false, 923 }, 924 { 925 name: "fail if MachineDeploymentTopologies are unique but not defined in ClusterClass", 926 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 927 WithInfrastructureClusterTemplate( 928 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 929 WithControlPlaneTemplate( 930 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 931 WithControlPlaneInfrastructureMachineTemplate( 932 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 933 WithWorkerMachineDeploymentClasses( 934 *builder.MachineDeploymentClass("aa"). 935 WithInfrastructureTemplate( 936 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 937 WithBootstrapTemplate( 938 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 939 Build()). 940 Build(), 941 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 942 WithTopology( 943 builder.ClusterTopology(). 944 WithClass("class1"). 945 WithVersion("v1.22.2"). 946 WithMachineDeployment( 947 builder.MachineDeploymentTopology("workers1"). 948 WithClass("bb"). 949 Build()). 950 Build()). 951 Build(), 952 wantErr: true, 953 }, 954 { 955 name: "fail if MachineDeploymentTopologies are not unique but is defined in ClusterClass", 956 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 957 WithInfrastructureClusterTemplate( 958 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 959 WithControlPlaneTemplate( 960 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 961 WithControlPlaneInfrastructureMachineTemplate( 962 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 963 WithWorkerMachineDeploymentClasses( 964 *builder.MachineDeploymentClass("aa"). 965 WithInfrastructureTemplate( 966 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 967 WithBootstrapTemplate( 968 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 969 Build()). 970 Build(), 971 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 972 WithTopology( 973 builder.ClusterTopology(). 974 WithClass("class1"). 975 WithVersion("v1.22.2"). 976 WithMachineDeployment( 977 builder.MachineDeploymentTopology("workers1"). 978 WithClass("aa"). 979 Build()). 980 WithMachineDeployment( 981 builder.MachineDeploymentTopology("workers1"). 982 WithClass("aa"). 983 Build()). 984 Build()). 985 Build(), 986 wantErr: true, 987 }, 988 { 989 name: "pass if MachineDeploymentTopologies are unique and share a class that is defined in ClusterClass", 990 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 991 WithInfrastructureClusterTemplate( 992 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 993 WithControlPlaneTemplate( 994 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 995 WithControlPlaneInfrastructureMachineTemplate( 996 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 997 WithWorkerMachineDeploymentClasses( 998 *builder.MachineDeploymentClass("aa"). 999 WithInfrastructureTemplate( 1000 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 1001 WithBootstrapTemplate( 1002 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 1003 Build()). 1004 Build(), 1005 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 1006 WithTopology( 1007 builder.ClusterTopology(). 1008 WithClass("class1"). 1009 WithVersion("v1.22.2"). 1010 WithMachineDeployment( 1011 builder.MachineDeploymentTopology("workers1"). 1012 WithClass("aa"). 1013 Build()). 1014 WithMachineDeployment( 1015 builder.MachineDeploymentTopology("workers2"). 1016 WithClass("aa"). 1017 Build()). 1018 Build()). 1019 Build(), 1020 wantErr: false, 1021 }, 1022 { 1023 name: "fail if MachineDeploymentTopology name is longer than 63 characters", 1024 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1025 WithInfrastructureClusterTemplate( 1026 builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). 1027 WithControlPlaneTemplate( 1028 builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). 1029 WithControlPlaneInfrastructureMachineTemplate( 1030 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). 1031 WithWorkerMachineDeploymentClasses( 1032 *builder.MachineDeploymentClass("aa"). 1033 WithInfrastructureTemplate( 1034 builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()). 1035 WithBootstrapTemplate( 1036 builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). 1037 Build()). 1038 Build(), 1039 cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). 1040 WithTopology( 1041 builder.ClusterTopology(). 1042 WithClass("class1"). 1043 WithVersion("v1.22.2"). 1044 WithMachineDeployment( 1045 builder.MachineDeploymentTopology("machine-deployment-topology-name-that-has-longerthan63characterlooooooooooooooooooooooongname"). 1046 WithClass("aa"). 1047 Build()). 1048 Build()). 1049 Build(), 1050 wantErr: true, 1051 }, 1052 } 1053 for _, tt := range tests { 1054 t.Run(tt.name, func(t *testing.T) { 1055 g := NewWithT(t) 1056 allErrs := MachineDeploymentTopologiesAreValidAndDefinedInClusterClass(tt.cluster, tt.clusterClass) 1057 if tt.wantErr { 1058 g.Expect(allErrs).ToNot(BeEmpty()) 1059 return 1060 } 1061 g.Expect(allErrs).To(BeEmpty()) 1062 }) 1063 } 1064 } 1065 1066 func TestClusterClassReferencesAreValid(t *testing.T) { 1067 ref := &corev1.ObjectReference{ 1068 APIVersion: "group.test.io/foo", 1069 Kind: "barTemplate", 1070 Name: "baz", 1071 Namespace: "default", 1072 } 1073 invalidRef := &corev1.ObjectReference{ 1074 APIVersion: "group.test.io/foo", 1075 Kind: "another-barTemplate", 1076 Name: "baz", 1077 Namespace: "", 1078 } 1079 1080 tests := []struct { 1081 name string 1082 clusterClass *clusterv1.ClusterClass 1083 wantErr bool 1084 }{ 1085 { 1086 name: "pass for clusterClass with valid references", 1087 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1088 WithInfrastructureClusterTemplate( 1089 refToUnstructured(ref)). 1090 WithControlPlaneTemplate( 1091 refToUnstructured(ref)). 1092 WithControlPlaneInfrastructureMachineTemplate( 1093 refToUnstructured(ref)). 1094 WithWorkerMachineDeploymentClasses( 1095 *builder.MachineDeploymentClass("aa"). 1096 WithInfrastructureTemplate( 1097 refToUnstructured(ref)). 1098 WithBootstrapTemplate( 1099 refToUnstructured(ref)). 1100 Build()). 1101 Build(), 1102 wantErr: false, 1103 }, 1104 { 1105 name: "error if clusterClass has multiple invalid refs", 1106 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1107 WithInfrastructureClusterTemplate( 1108 refToUnstructured(invalidRef)). 1109 WithControlPlaneTemplate( 1110 refToUnstructured(invalidRef)). 1111 WithControlPlaneInfrastructureMachineTemplate( 1112 refToUnstructured(invalidRef)). 1113 WithWorkerMachineDeploymentClasses( 1114 *builder.MachineDeploymentClass("a"). 1115 WithInfrastructureTemplate( 1116 refToUnstructured(invalidRef)). 1117 WithBootstrapTemplate( 1118 refToUnstructured(invalidRef)). 1119 Build(), 1120 *builder.MachineDeploymentClass("b"). 1121 WithInfrastructureTemplate( 1122 refToUnstructured(invalidRef)). 1123 WithBootstrapTemplate( 1124 refToUnstructured(invalidRef)). 1125 Build(), 1126 *builder.MachineDeploymentClass("c"). 1127 WithInfrastructureTemplate( 1128 refToUnstructured(invalidRef)). 1129 WithBootstrapTemplate( 1130 refToUnstructured(invalidRef)). 1131 Build()). 1132 Build(), 1133 wantErr: true, 1134 }, 1135 1136 { 1137 name: "error if clusterClass has invalid ControlPlane ref", 1138 clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). 1139 WithInfrastructureClusterTemplate( 1140 refToUnstructured(ref)). 1141 WithControlPlaneTemplate( 1142 refToUnstructured(invalidRef)). 1143 WithControlPlaneInfrastructureMachineTemplate( 1144 refToUnstructured(ref)). 1145 WithWorkerMachineDeploymentClasses( 1146 *builder.MachineDeploymentClass("aa"). 1147 WithInfrastructureTemplate( 1148 refToUnstructured(ref)). 1149 WithBootstrapTemplate( 1150 refToUnstructured(ref)). 1151 Build()). 1152 Build(), 1153 wantErr: true, 1154 }, 1155 } 1156 for _, tt := range tests { 1157 t.Run(tt.name, func(t *testing.T) { 1158 g := NewWithT(t) 1159 allErrs := ClusterClassReferencesAreValid(tt.clusterClass) 1160 if tt.wantErr { 1161 g.Expect(allErrs).ToNot(BeEmpty()) 1162 return 1163 } 1164 g.Expect(allErrs).To(BeEmpty()) 1165 }) 1166 } 1167 } 1168 1169 func refToUnstructured(ref *corev1.ObjectReference) *unstructured.Unstructured { 1170 gvk := ref.GetObjectKind().GroupVersionKind() 1171 output := &unstructured.Unstructured{} 1172 output.SetKind(gvk.Kind) 1173 output.SetAPIVersion(gvk.GroupVersion().String()) 1174 output.SetName(ref.Name) 1175 output.SetNamespace(ref.Namespace) 1176 return output 1177 }