sigs.k8s.io/cluster-api@v1.7.1/util/util_test.go (about) 1 /* 2 Copyright 2018 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 util 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "github.com/blang/semver/v4" 25 . "github.com/onsi/gomega" 26 corev1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/meta" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 ctrl "sigs.k8s.io/controller-runtime" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 35 "sigs.k8s.io/controller-runtime/pkg/client/fake" 36 "sigs.k8s.io/controller-runtime/pkg/reconcile" 37 38 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 39 "sigs.k8s.io/cluster-api/util/labels/format" 40 ) 41 42 func TestMachineToInfrastructureMapFunc(t *testing.T) { 43 g := NewWithT(t) 44 45 testcases := []struct { 46 name string 47 input schema.GroupVersionKind 48 request client.Object 49 output []reconcile.Request 50 }{ 51 { 52 name: "should reconcile infra-1", 53 input: schema.GroupVersionKind{ 54 Group: "foo.cluster.x-k8s.io", 55 Version: "v1alpha4", 56 Kind: "TestMachine", 57 }, 58 request: &clusterv1.Machine{ 59 ObjectMeta: metav1.ObjectMeta{ 60 Namespace: metav1.NamespaceDefault, 61 Name: "test-1", 62 }, 63 Spec: clusterv1.MachineSpec{ 64 InfrastructureRef: corev1.ObjectReference{ 65 APIVersion: "foo.cluster.x-k8s.io/v1beta1", 66 Kind: "TestMachine", 67 Name: "infra-1", 68 }, 69 }, 70 }, 71 output: []reconcile.Request{ 72 { 73 NamespacedName: client.ObjectKey{ 74 Namespace: metav1.NamespaceDefault, 75 Name: "infra-1", 76 }, 77 }, 78 }, 79 }, 80 { 81 name: "should return no matching reconcile requests", 82 input: schema.GroupVersionKind{ 83 Group: "foo.cluster.x-k8s.io", 84 Version: "v1beta1", 85 Kind: "TestMachine", 86 }, 87 request: &clusterv1.Machine{ 88 ObjectMeta: metav1.ObjectMeta{ 89 Namespace: metav1.NamespaceDefault, 90 Name: "test-1", 91 }, 92 Spec: clusterv1.MachineSpec{ 93 InfrastructureRef: corev1.ObjectReference{ 94 APIVersion: "bar.cluster.x-k8s.io/v1beta1", 95 Kind: "TestMachine", 96 Name: "bar-1", 97 }, 98 }, 99 }, 100 output: nil, 101 }, 102 } 103 104 for _, tc := range testcases { 105 t.Run(tc.name, func(*testing.T) { 106 fn := MachineToInfrastructureMapFunc(tc.input) 107 out := fn(ctx, tc.request) 108 g.Expect(out).To(BeComparableTo(tc.output)) 109 }) 110 } 111 } 112 113 func TestClusterToInfrastructureMapFunc(t *testing.T) { 114 testcases := []struct { 115 name string 116 input schema.GroupVersionKind 117 request *clusterv1.Cluster 118 infrastructure client.Object 119 output []reconcile.Request 120 }{ 121 { 122 name: "should reconcile infra-1", 123 input: schema.GroupVersionKind{ 124 Group: "foo.cluster.x-k8s.io", 125 Version: "v1alpha4", 126 Kind: "TestCluster", 127 }, 128 request: &clusterv1.Cluster{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Namespace: metav1.NamespaceDefault, 131 Name: "test-1", 132 }, 133 Spec: clusterv1.ClusterSpec{ 134 InfrastructureRef: &corev1.ObjectReference{ 135 APIVersion: "foo.cluster.x-k8s.io/v1beta1", 136 Kind: "TestCluster", 137 Name: "infra-1", 138 }, 139 }, 140 }, 141 infrastructure: &unstructured.Unstructured{Object: map[string]interface{}{ 142 "apiVersion": "foo.cluster.x-k8s.io/v1beta1", 143 "kind": "TestCluster", 144 "metadata": map[string]interface{}{ 145 "namespace": metav1.NamespaceDefault, 146 "name": "infra-1", 147 }, 148 }}, 149 output: []reconcile.Request{ 150 { 151 NamespacedName: client.ObjectKey{ 152 Namespace: metav1.NamespaceDefault, 153 Name: "infra-1", 154 }, 155 }, 156 }, 157 }, 158 { 159 name: "should return no matching reconcile requests", 160 input: schema.GroupVersionKind{ 161 Group: "foo.cluster.x-k8s.io", 162 Version: "v1beta1", 163 Kind: "TestCluster", 164 }, 165 request: &clusterv1.Cluster{ 166 ObjectMeta: metav1.ObjectMeta{ 167 Namespace: metav1.NamespaceDefault, 168 Name: "test-1", 169 }, 170 Spec: clusterv1.ClusterSpec{ 171 InfrastructureRef: &corev1.ObjectReference{ 172 APIVersion: "bar.cluster.x-k8s.io/v1beta1", 173 Kind: "TestCluster", 174 Name: "bar-1", 175 }, 176 }, 177 }, 178 output: nil, 179 }, 180 { 181 name: "Externally managed provider cluster is excluded", 182 input: schema.GroupVersionKind{ 183 Group: "foo.cluster.x-k8s.io", 184 Version: "v1alpha4", 185 Kind: "TestCluster", 186 }, 187 request: &clusterv1.Cluster{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Namespace: metav1.NamespaceDefault, 190 Name: "test-1", 191 }, 192 Spec: clusterv1.ClusterSpec{ 193 InfrastructureRef: &corev1.ObjectReference{ 194 APIVersion: "foo.cluster.x-k8s.io/v1beta1", 195 Kind: "TestCluster", 196 Name: "infra-1", 197 }, 198 }, 199 }, 200 infrastructure: &unstructured.Unstructured{Object: map[string]interface{}{ 201 "apiVersion": "foo.cluster.x-k8s.io/v1beta1", 202 "kind": "TestCluster", 203 "metadata": map[string]interface{}{ 204 "namespace": metav1.NamespaceDefault, 205 "name": "infra-1", 206 "annotations": map[string]interface{}{ 207 clusterv1.ManagedByAnnotation: "", 208 }, 209 }, 210 }}, 211 }, 212 } 213 214 for _, tc := range testcases { 215 t.Run(tc.name, func(t *testing.T) { 216 g := NewWithT(t) 217 clientBuilder := fake.NewClientBuilder() 218 if tc.infrastructure != nil { 219 clientBuilder.WithObjects(tc.infrastructure) 220 } 221 222 // Unstructured simplifies testing but should not be used in real usage, because it will 223 // likely result in a duplicate cache in an unstructured projection. 224 referenceObject := &unstructured.Unstructured{} 225 referenceObject.SetAPIVersion(tc.request.Spec.InfrastructureRef.APIVersion) 226 referenceObject.SetKind(tc.request.Spec.InfrastructureRef.Kind) 227 228 fn := ClusterToInfrastructureMapFunc(context.Background(), tc.input, clientBuilder.Build(), referenceObject) 229 out := fn(ctx, tc.request) 230 g.Expect(out).To(BeComparableTo(tc.output)) 231 }) 232 } 233 } 234 235 func TestHasOwner(t *testing.T) { 236 g := NewWithT(t) 237 238 tests := []struct { 239 name string 240 refList []metav1.OwnerReference 241 expected bool 242 }{ 243 { 244 name: "no ownership", 245 }, 246 { 247 name: "owned by cluster", 248 refList: []metav1.OwnerReference{ 249 { 250 Kind: "Cluster", 251 APIVersion: clusterv1.GroupVersion.String(), 252 }, 253 }, 254 expected: true, 255 }, 256 { 257 name: "owned by cluster from older version", 258 refList: []metav1.OwnerReference{ 259 { 260 Kind: "Cluster", 261 APIVersion: "cluster.x-k8s.io/v1alpha2", 262 }, 263 }, 264 expected: true, 265 }, 266 { 267 name: "owned by a MachineDeployment from older version", 268 refList: []metav1.OwnerReference{ 269 { 270 Kind: "MachineDeployment", 271 APIVersion: "cluster.x-k8s.io/v1alpha2", 272 }, 273 }, 274 expected: true, 275 }, 276 { 277 name: "owned by something else", 278 refList: []metav1.OwnerReference{ 279 { 280 Kind: "Pod", 281 APIVersion: "v1", 282 }, 283 { 284 Kind: "Deployment", 285 APIVersion: "apps/v1", 286 }, 287 }, 288 }, 289 { 290 name: "owner by a deployment", 291 refList: []metav1.OwnerReference{ 292 { 293 Kind: "MachineDeployment", 294 APIVersion: clusterv1.GroupVersion.String(), 295 }, 296 }, 297 expected: true, 298 }, 299 { 300 name: "right kind, wrong apiversion", 301 refList: []metav1.OwnerReference{ 302 { 303 Kind: "MachineDeployment", 304 APIVersion: "wrong/v2", 305 }, 306 }, 307 }, 308 { 309 name: "right apiversion, wrong kind", 310 refList: []metav1.OwnerReference{ 311 { 312 Kind: "Machine", 313 APIVersion: clusterv1.GroupVersion.String(), 314 }, 315 }, 316 }, 317 } 318 319 for _, test := range tests { 320 t.Run(test.name, func(*testing.T) { 321 result := HasOwner( 322 test.refList, 323 clusterv1.GroupVersion.String(), 324 []string{"MachineDeployment", "Cluster"}, 325 ) 326 g.Expect(result).To(Equal(test.expected)) 327 }) 328 } 329 } 330 331 type fakeMeta struct { 332 metav1.ObjectMeta 333 metav1.TypeMeta 334 } 335 336 var _ runtime.Object = &fakeMeta{} 337 338 func (*fakeMeta) DeepCopyObject() runtime.Object { 339 panic("not implemented") 340 } 341 342 func TestIsOwnedByObject(t *testing.T) { 343 g := NewWithT(t) 344 345 targetGroup := "ponies.info" 346 targetKind := "Rainbow" 347 targetName := "fri3ndsh1p" 348 349 meta := fakeMeta{ 350 metav1.ObjectMeta{ 351 Name: targetName, 352 }, 353 metav1.TypeMeta{ 354 APIVersion: "ponies.info/v1", 355 Kind: targetKind, 356 }, 357 } 358 359 tests := []struct { 360 name string 361 refs []metav1.OwnerReference 362 expected bool 363 }{ 364 { 365 name: "empty owner list", 366 }, 367 { 368 name: "single wrong name owner ref", 369 refs: []metav1.OwnerReference{{ 370 APIVersion: targetGroup + "/v1", 371 Kind: targetKind, 372 Name: "m4g1c", 373 }}, 374 }, 375 { 376 name: "single wrong group owner ref", 377 refs: []metav1.OwnerReference{{ 378 APIVersion: "dazzlings.info/v1", 379 Kind: "Twilight", 380 Name: "m4g1c", 381 }}, 382 }, 383 { 384 name: "single wrong kind owner ref", 385 refs: []metav1.OwnerReference{{ 386 APIVersion: targetGroup + "/v1", 387 Kind: "Twilight", 388 Name: "m4g1c", 389 }}, 390 }, 391 { 392 name: "single right owner ref", 393 refs: []metav1.OwnerReference{{ 394 APIVersion: targetGroup + "/v1", 395 Kind: targetKind, 396 Name: targetName, 397 }}, 398 expected: true, 399 }, 400 { 401 name: "single right owner ref (different version)", 402 refs: []metav1.OwnerReference{{ 403 APIVersion: targetGroup + "/v2alpha2", 404 Kind: targetKind, 405 Name: targetName, 406 }}, 407 expected: true, 408 }, 409 { 410 name: "multiple wrong refs", 411 refs: []metav1.OwnerReference{{ 412 APIVersion: targetGroup + "/v1", 413 Kind: targetKind, 414 Name: "m4g1c", 415 }, { 416 APIVersion: targetGroup + "/v1", 417 Kind: targetKind, 418 Name: "h4rm0ny", 419 }}, 420 }, 421 { 422 name: "multiple refs one right", 423 refs: []metav1.OwnerReference{{ 424 APIVersion: targetGroup + "/v1", 425 Kind: targetKind, 426 Name: "m4g1c", 427 }, { 428 APIVersion: targetGroup + "/v1", 429 Kind: targetKind, 430 Name: targetName, 431 }}, 432 expected: true, 433 }, 434 } 435 436 for _, test := range tests { 437 t.Run(test.name, func(*testing.T) { 438 pointer := &metav1.ObjectMeta{ 439 OwnerReferences: test.refs, 440 } 441 442 g.Expect(IsOwnedByObject(pointer, &meta)).To(Equal(test.expected), "Could not find a ref to %+v in %+v", meta, test.refs) 443 }) 444 } 445 } 446 447 func TestGetOwnerClusterSuccessByName(t *testing.T) { 448 g := NewWithT(t) 449 450 myCluster := &clusterv1.Cluster{ 451 ObjectMeta: metav1.ObjectMeta{ 452 Name: "my-cluster", 453 Namespace: metav1.NamespaceDefault, 454 }, 455 } 456 457 c := fake.NewClientBuilder(). 458 WithObjects(myCluster). 459 Build() 460 461 objm := metav1.ObjectMeta{ 462 OwnerReferences: []metav1.OwnerReference{ 463 { 464 Kind: "Cluster", 465 APIVersion: clusterv1.GroupVersion.String(), 466 Name: "my-cluster", 467 }, 468 }, 469 Namespace: metav1.NamespaceDefault, 470 Name: "my-resource-owned-by-cluster", 471 } 472 cluster, err := GetOwnerCluster(ctx, c, objm) 473 g.Expect(err).ToNot(HaveOccurred()) 474 g.Expect(cluster).NotTo(BeNil()) 475 476 // Make sure API version does not matter 477 objm.OwnerReferences[0].APIVersion = "cluster.x-k8s.io/v1alpha1234" 478 cluster, err = GetOwnerCluster(ctx, c, objm) 479 g.Expect(err).ToNot(HaveOccurred()) 480 g.Expect(cluster).NotTo(BeNil()) 481 } 482 483 func TestGetOwnerMachineSuccessByName(t *testing.T) { 484 g := NewWithT(t) 485 486 myMachine := &clusterv1.Machine{ 487 ObjectMeta: metav1.ObjectMeta{ 488 Name: "my-machine", 489 Namespace: metav1.NamespaceDefault, 490 }, 491 } 492 493 c := fake.NewClientBuilder(). 494 WithObjects(myMachine). 495 Build() 496 497 objm := metav1.ObjectMeta{ 498 OwnerReferences: []metav1.OwnerReference{ 499 { 500 Kind: "Machine", 501 APIVersion: clusterv1.GroupVersion.String(), 502 Name: "my-machine", 503 }, 504 }, 505 Namespace: metav1.NamespaceDefault, 506 Name: "my-resource-owned-by-machine", 507 } 508 machine, err := GetOwnerMachine(ctx, c, objm) 509 g.Expect(err).ToNot(HaveOccurred()) 510 g.Expect(machine).NotTo(BeNil()) 511 } 512 513 func TestGetOwnerMachineSuccessByNameFromDifferentVersion(t *testing.T) { 514 g := NewWithT(t) 515 516 myMachine := &clusterv1.Machine{ 517 ObjectMeta: metav1.ObjectMeta{ 518 Name: "my-machine", 519 Namespace: metav1.NamespaceDefault, 520 }, 521 } 522 523 c := fake.NewClientBuilder(). 524 WithObjects(myMachine). 525 Build() 526 527 objm := metav1.ObjectMeta{ 528 OwnerReferences: []metav1.OwnerReference{ 529 { 530 Kind: "Machine", 531 APIVersion: clusterv1.GroupVersion.Group + "/v1alpha2", 532 Name: "my-machine", 533 }, 534 }, 535 Namespace: metav1.NamespaceDefault, 536 Name: "my-resource-owned-by-machine", 537 } 538 machine, err := GetOwnerMachine(ctx, c, objm) 539 g.Expect(err).ToNot(HaveOccurred()) 540 g.Expect(machine).NotTo(BeNil()) 541 } 542 543 func TestIsExternalManagedControlPlane(t *testing.T) { 544 g := NewWithT(t) 545 546 t.Run("should return true if control plane status externalManagedControlPlane is true", func(*testing.T) { 547 controlPlane := &unstructured.Unstructured{ 548 Object: map[string]interface{}{ 549 "status": map[string]interface{}{ 550 "externalManagedControlPlane": true, 551 }, 552 }, 553 } 554 result := IsExternalManagedControlPlane(controlPlane) 555 g.Expect(result).Should(BeTrue()) 556 }) 557 558 t.Run("should return false if control plane status externalManagedControlPlane is false", func(*testing.T) { 559 controlPlane := &unstructured.Unstructured{ 560 Object: map[string]interface{}{ 561 "status": map[string]interface{}{ 562 "externalManagedControlPlane": false, 563 }, 564 }, 565 } 566 result := IsExternalManagedControlPlane(controlPlane) 567 g.Expect(result).Should(BeFalse()) 568 }) 569 570 t.Run("should return false if control plane status externalManagedControlPlane is not set", func(*testing.T) { 571 controlPlane := &unstructured.Unstructured{ 572 Object: map[string]interface{}{ 573 "status": map[string]interface{}{ 574 "someOtherStatusField": "someValue", 575 }, 576 }, 577 } 578 result := IsExternalManagedControlPlane(controlPlane) 579 g.Expect(result).Should(BeFalse()) 580 }) 581 } 582 583 func TestEnsureOwnerRef(t *testing.T) { 584 g := NewWithT(t) 585 586 t.Run("should set ownerRef on an empty list", func(*testing.T) { 587 obj := &clusterv1.Machine{} 588 ref := metav1.OwnerReference{ 589 APIVersion: clusterv1.GroupVersion.String(), 590 Kind: "Cluster", 591 Name: "test-cluster", 592 } 593 obj.OwnerReferences = EnsureOwnerRef(obj.OwnerReferences, ref) 594 g.Expect(obj.OwnerReferences).Should(ContainElement(ref)) 595 }) 596 597 t.Run("should not duplicate owner references", func(*testing.T) { 598 obj := &clusterv1.Machine{ 599 ObjectMeta: metav1.ObjectMeta{ 600 OwnerReferences: []metav1.OwnerReference{ 601 { 602 APIVersion: clusterv1.GroupVersion.String(), 603 Kind: "Cluster", 604 Name: "test-cluster", 605 }, 606 }, 607 }, 608 } 609 ref := metav1.OwnerReference{ 610 APIVersion: clusterv1.GroupVersion.String(), 611 Kind: "Cluster", 612 Name: "test-cluster", 613 } 614 obj.OwnerReferences = EnsureOwnerRef(obj.OwnerReferences, ref) 615 g.Expect(obj.OwnerReferences).Should(ContainElement(ref)) 616 g.Expect(obj.OwnerReferences).Should(HaveLen(1)) 617 }) 618 619 t.Run("should update the APIVersion if duplicate", func(*testing.T) { 620 oldgvk := schema.GroupVersion{ 621 Group: clusterv1.GroupVersion.Group, 622 Version: "v1alpha2", 623 } 624 obj := &clusterv1.Machine{ 625 ObjectMeta: metav1.ObjectMeta{ 626 OwnerReferences: []metav1.OwnerReference{ 627 { 628 APIVersion: oldgvk.String(), 629 Kind: "Cluster", 630 Name: "test-cluster", 631 }, 632 }, 633 }, 634 } 635 ref := metav1.OwnerReference{ 636 APIVersion: clusterv1.GroupVersion.String(), 637 Kind: "Cluster", 638 Name: "test-cluster", 639 } 640 obj.OwnerReferences = EnsureOwnerRef(obj.OwnerReferences, ref) 641 g.Expect(obj.OwnerReferences).Should(ContainElement(ref)) 642 g.Expect(obj.OwnerReferences).Should(HaveLen(1)) 643 }) 644 } 645 646 func TestClusterToObjectsMapper(t *testing.T) { 647 g := NewWithT(t) 648 649 cluster := &clusterv1.Cluster{ 650 ObjectMeta: metav1.ObjectMeta{ 651 Name: "test1", 652 }, 653 } 654 655 table := []struct { 656 name string 657 objects []client.Object 658 input client.ObjectList 659 output []ctrl.Request 660 expectError bool 661 }{ 662 { 663 name: "should return a list of requests with labelled machines", 664 input: &clusterv1.MachineList{}, 665 objects: []client.Object{ 666 &clusterv1.Machine{ 667 ObjectMeta: metav1.ObjectMeta{ 668 Name: "machine1", 669 Labels: map[string]string{ 670 clusterv1.ClusterNameLabel: "test1", 671 }, 672 }, 673 }, 674 &clusterv1.Machine{ 675 ObjectMeta: metav1.ObjectMeta{ 676 Name: "machine2", 677 Labels: map[string]string{ 678 clusterv1.ClusterNameLabel: "test1", 679 }, 680 }, 681 }, 682 }, 683 output: []ctrl.Request{ 684 {NamespacedName: client.ObjectKey{Name: "machine1"}}, 685 {NamespacedName: client.ObjectKey{Name: "machine2"}}, 686 }, 687 }, 688 { 689 name: "should return a list of requests with labelled MachineDeployments", 690 input: &clusterv1.MachineDeploymentList{}, 691 objects: []client.Object{ 692 &clusterv1.MachineDeployment{ 693 ObjectMeta: metav1.ObjectMeta{ 694 Name: "md1", 695 Labels: map[string]string{ 696 clusterv1.ClusterNameLabel: "test1", 697 }, 698 }, 699 }, 700 &clusterv1.MachineDeployment{ 701 ObjectMeta: metav1.ObjectMeta{ 702 Name: "md2", 703 Labels: map[string]string{ 704 clusterv1.ClusterNameLabel: "test2", 705 }, 706 }, 707 }, 708 &clusterv1.MachineDeployment{ 709 ObjectMeta: metav1.ObjectMeta{ 710 Name: "md3", 711 Labels: map[string]string{ 712 clusterv1.ClusterNameLabel: "test1", 713 }, 714 }, 715 }, 716 &clusterv1.MachineDeployment{ 717 ObjectMeta: metav1.ObjectMeta{ 718 Name: "md4", 719 }, 720 }, 721 }, 722 output: []ctrl.Request{ 723 {NamespacedName: client.ObjectKey{Name: "md1"}}, 724 {NamespacedName: client.ObjectKey{Name: "md3"}}, 725 }, 726 }, 727 } 728 729 for _, tc := range table { 730 tc.objects = append(tc.objects, cluster) 731 732 scheme := runtime.NewScheme() 733 _ = clusterv1.AddToScheme(scheme) 734 735 restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{clusterv1.GroupVersion}) 736 737 // Add tc.input gvk to the restMapper. 738 gvk, err := apiutil.GVKForObject(tc.input, scheme) 739 g.Expect(err).ToNot(HaveOccurred()) 740 restMapper.Add(gvk, meta.RESTScopeNamespace) 741 742 client := fake.NewClientBuilder().WithObjects(tc.objects...).WithRESTMapper(restMapper).Build() 743 f, err := ClusterToTypedObjectsMapper(client, tc.input, scheme) 744 g.Expect(err != nil, err).To(Equal(tc.expectError)) 745 g.Expect(f(ctx, cluster)).To(ConsistOf(tc.output)) 746 } 747 } 748 749 func TestMachineDeploymentToObjectsMapper(t *testing.T) { 750 g := NewWithT(t) 751 752 machineDeployment := &clusterv1.MachineDeployment{ 753 ObjectMeta: metav1.ObjectMeta{ 754 Name: "cluster-md-0", 755 }, 756 } 757 758 table := []struct { 759 name string 760 objects []client.Object 761 output []ctrl.Request 762 expectError bool 763 }{ 764 { 765 name: "should return a list of requests with labelled machines", 766 objects: []client.Object{ 767 &clusterv1.Machine{ 768 ObjectMeta: metav1.ObjectMeta{ 769 Name: "machine1", 770 Labels: map[string]string{ 771 clusterv1.MachineDeploymentNameLabel: machineDeployment.GetName(), 772 }, 773 }, 774 }, 775 &clusterv1.Machine{ 776 ObjectMeta: metav1.ObjectMeta{ 777 Name: "machine2", 778 Labels: map[string]string{ 779 clusterv1.MachineDeploymentNameLabel: machineDeployment.GetName(), 780 }, 781 }, 782 }, 783 }, 784 output: []ctrl.Request{ 785 {NamespacedName: client.ObjectKey{Name: "machine1"}}, 786 {NamespacedName: client.ObjectKey{Name: "machine2"}}, 787 }, 788 }, 789 } 790 791 for _, tc := range table { 792 tc.objects = append(tc.objects, machineDeployment) 793 794 scheme := runtime.NewScheme() 795 _ = clusterv1.AddToScheme(scheme) 796 797 restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{clusterv1.GroupVersion}) 798 799 // Add tc.input gvk to the restMapper. 800 gvk, err := apiutil.GVKForObject(&clusterv1.MachineList{}, scheme) 801 g.Expect(err).ToNot(HaveOccurred()) 802 restMapper.Add(gvk, meta.RESTScopeNamespace) 803 804 client := fake.NewClientBuilder().WithObjects(tc.objects...).WithRESTMapper(restMapper).Build() 805 f, err := MachineDeploymentToObjectsMapper(client, &clusterv1.MachineList{}, scheme) 806 g.Expect(err != nil, err).To(Equal(tc.expectError)) 807 g.Expect(f(ctx, machineDeployment)).To(ConsistOf(tc.output)) 808 } 809 } 810 811 func TestMachineSetToObjectsMapper(t *testing.T) { 812 g := NewWithT(t) 813 814 table := []struct { 815 name string 816 machineSet *clusterv1.MachineSet 817 objects []client.Object 818 output []ctrl.Request 819 expectError bool 820 }{ 821 { 822 name: "should return a list of requests with labelled machines", 823 machineSet: &clusterv1.MachineSet{ObjectMeta: metav1.ObjectMeta{ 824 Name: "cluster-ms-0", 825 }}, 826 objects: []client.Object{ 827 &clusterv1.Machine{ 828 ObjectMeta: metav1.ObjectMeta{ 829 Name: "machine1", 830 Labels: map[string]string{ 831 clusterv1.MachineSetNameLabel: format.MustFormatValue("cluster-ms-0"), 832 }, 833 }, 834 }, 835 &clusterv1.Machine{ 836 ObjectMeta: metav1.ObjectMeta{ 837 Name: "machine2", 838 Labels: map[string]string{ 839 clusterv1.MachineSetNameLabel: format.MustFormatValue("cluster-ms-0"), 840 }, 841 }, 842 }, 843 }, 844 output: []ctrl.Request{ 845 {NamespacedName: client.ObjectKey{Name: "machine1"}}, 846 {NamespacedName: client.ObjectKey{Name: "machine2"}}, 847 }, 848 }, 849 { 850 name: "should return a list of requests with labelled machines when the machineset name is hashed in the label", 851 machineSet: &clusterv1.MachineSet{ObjectMeta: metav1.ObjectMeta{ 852 Name: "cluster-ms-0-looooooooooooooooooooooooooooooooooooooooooooong-name", 853 }}, 854 objects: []client.Object{ 855 &clusterv1.Machine{ 856 ObjectMeta: metav1.ObjectMeta{ 857 Name: "machine1", 858 Labels: map[string]string{ 859 clusterv1.MachineSetNameLabel: format.MustFormatValue("cluster-ms-0-looooooooooooooooooooooooooooooooooooooooooooong-name"), 860 }, 861 }, 862 }, 863 &clusterv1.Machine{ 864 ObjectMeta: metav1.ObjectMeta{ 865 Name: "machine2", 866 Labels: map[string]string{ 867 clusterv1.MachineSetNameLabel: format.MustFormatValue("cluster-ms-0-looooooooooooooooooooooooooooooooooooooooooooong-name"), 868 }, 869 }, 870 }, 871 }, 872 output: []ctrl.Request{ 873 {NamespacedName: client.ObjectKey{Name: "machine1"}}, 874 {NamespacedName: client.ObjectKey{Name: "machine2"}}, 875 }, 876 }, 877 } 878 879 for _, tc := range table { 880 tc.objects = append(tc.objects, tc.machineSet) 881 882 scheme := runtime.NewScheme() 883 _ = clusterv1.AddToScheme(scheme) 884 885 restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{clusterv1.GroupVersion}) 886 887 // Add tc.input gvk to the restMapper. 888 gvk, err := apiutil.GVKForObject(&clusterv1.MachineList{}, scheme) 889 g.Expect(err).ToNot(HaveOccurred()) 890 restMapper.Add(gvk, meta.RESTScopeNamespace) 891 892 client := fake.NewClientBuilder().WithObjects(tc.objects...).WithRESTMapper(restMapper).Build() 893 f, err := MachineSetToObjectsMapper(client, &clusterv1.MachineList{}, scheme) 894 g.Expect(err != nil, err).To(Equal(tc.expectError)) 895 g.Expect(f(ctx, tc.machineSet)).To(ConsistOf(tc.output)) 896 } 897 } 898 899 func TestOrdinalize(t *testing.T) { 900 tests := []struct { 901 input int 902 expected string 903 }{ 904 {0, "0th"}, 905 {1, "1st"}, 906 {2, "2nd"}, 907 {43, "43rd"}, 908 {5, "5th"}, 909 {6, "6th"}, 910 {207, "207th"}, 911 {1008, "1008th"}, 912 {-109, "-109th"}, 913 {-0, "0th"}, 914 } 915 916 for _, tt := range tests { 917 t.Run(fmt.Sprintf("ordinalize %d", tt.input), func(t *testing.T) { 918 g := NewWithT(t) 919 g.Expect(Ordinalize(tt.input)).To(Equal(tt.expected)) 920 }) 921 } 922 } 923 924 func TestIsSupportedVersionSkew(t *testing.T) { 925 type args struct { 926 a semver.Version 927 b semver.Version 928 } 929 tests := []struct { 930 name string 931 args args 932 want bool 933 }{ 934 { 935 name: "same version", 936 args: args{ 937 a: semver.MustParse("1.10.0"), 938 b: semver.MustParse("1.10.0"), 939 }, 940 want: true, 941 }, 942 { 943 name: "different patch version", 944 args: args{ 945 a: semver.MustParse("1.10.0"), 946 b: semver.MustParse("1.10.2"), 947 }, 948 want: true, 949 }, 950 { 951 name: "a + 1 minor version", 952 args: args{ 953 a: semver.MustParse("1.11.0"), 954 b: semver.MustParse("1.10.2"), 955 }, 956 want: true, 957 }, 958 { 959 name: "b + 1 minor version", 960 args: args{ 961 a: semver.MustParse("1.10.0"), 962 b: semver.MustParse("1.11.2"), 963 }, 964 want: true, 965 }, 966 { 967 name: "a + 2 minor versions", 968 args: args{ 969 a: semver.MustParse("1.12.0"), 970 b: semver.MustParse("1.10.0"), 971 }, 972 want: false, 973 }, 974 { 975 name: "b + 2 minor versions", 976 args: args{ 977 a: semver.MustParse("1.10.0"), 978 b: semver.MustParse("1.12.0"), 979 }, 980 want: false, 981 }, 982 } 983 for _, tt := range tests { 984 t.Run(tt.name, func(t *testing.T) { 985 if got := IsSupportedVersionSkew(tt.args.a, tt.args.b); got != tt.want { 986 t.Errorf("IsSupportedVersionSkew() = %v, want %v", got, tt.want) 987 } 988 }) 989 } 990 } 991 992 func TestRemoveOwnerRef(t *testing.T) { 993 g := NewWithT(t) 994 makeOwnerRefs := func() []metav1.OwnerReference { 995 return []metav1.OwnerReference{ 996 { 997 APIVersion: "dazzlings.info/v1", 998 Kind: "Twilight", 999 Name: "m4g1c", 1000 }, 1001 { 1002 APIVersion: "bar.cluster.x-k8s.io/v1beta1", 1003 Kind: "TestCluster", 1004 Name: "bar-1", 1005 }, 1006 } 1007 } 1008 1009 tests := []struct { 1010 name string 1011 toBeRemoved metav1.OwnerReference 1012 }{ 1013 { 1014 name: "owner reference present", 1015 toBeRemoved: metav1.OwnerReference{ 1016 APIVersion: "dazzlings.info/v1", 1017 Kind: "Twilight", 1018 Name: "m4g1c", 1019 }, 1020 }, 1021 { 1022 name: "owner reference not present", 1023 toBeRemoved: metav1.OwnerReference{ 1024 APIVersion: "dazzlings.info/v1", 1025 Kind: "Twilight", 1026 Name: "abcdef", 1027 }, 1028 }, 1029 } 1030 for _, tt := range tests { 1031 t.Run(tt.name, func(*testing.T) { 1032 // Use a fresh ownerRefs slice for each test, because RemoveOwnerRef may modify the underlying array. 1033 ownerRefs := makeOwnerRefs() 1034 ownerRefs = RemoveOwnerRef(ownerRefs, tt.toBeRemoved) 1035 g.Expect(HasOwnerRef(ownerRefs, tt.toBeRemoved)).NotTo(BeTrue()) 1036 }) 1037 } 1038 } 1039 1040 func TestUnstructuredUnmarshalField(t *testing.T) { 1041 tests := []struct { 1042 name string 1043 obj *unstructured.Unstructured 1044 v interface{} 1045 fields []string 1046 wantErr bool 1047 }{ 1048 { 1049 "return error if object is nil", 1050 nil, 1051 nil, 1052 nil, 1053 true, 1054 }, 1055 } 1056 for _, tt := range tests { 1057 t.Run(tt.name, func(t *testing.T) { 1058 if err := UnstructuredUnmarshalField(tt.obj, tt.v, tt.fields...); (err != nil) != tt.wantErr { 1059 t.Errorf("UnstructuredUnmarshalField() error = %v, wantErr %v", err, tt.wantErr) 1060 } 1061 }) 1062 } 1063 }