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  }