sigs.k8s.io/cluster-api@v1.7.1/exp/internal/controllers/machinepool_controller_noderef_test.go (about)

     1  /*
     2  Copyright 2019 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 controllers
    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/client-go/tools/record"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    30  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    31  )
    32  
    33  func TestMachinePoolGetNodeReference(t *testing.T) {
    34  	r := &MachinePoolReconciler{
    35  		Client:   fake.NewClientBuilder().Build(),
    36  		recorder: record.NewFakeRecorder(32),
    37  	}
    38  
    39  	nodeList := []client.Object{
    40  		&corev1.Node{
    41  			ObjectMeta: metav1.ObjectMeta{
    42  				Name: "node-1",
    43  			},
    44  			Spec: corev1.NodeSpec{
    45  				ProviderID: "aws://us-east-1/id-node-1",
    46  			},
    47  		},
    48  		&corev1.Node{
    49  			ObjectMeta: metav1.ObjectMeta{
    50  				Name: "node-2",
    51  			},
    52  			Spec: corev1.NodeSpec{
    53  				ProviderID: "aws://us-west-2/id-node-2",
    54  			},
    55  		},
    56  		&corev1.Node{
    57  			ObjectMeta: metav1.ObjectMeta{
    58  				Name: "gce-node-2",
    59  			},
    60  			Spec: corev1.NodeSpec{
    61  				ProviderID: "gce://us-central1/gce-id-node-2",
    62  			},
    63  		},
    64  		&corev1.Node{
    65  			ObjectMeta: metav1.ObjectMeta{
    66  				Name: "azure-node-4",
    67  			},
    68  			Spec: corev1.NodeSpec{
    69  				ProviderID: "azure://westus2/id-node-4",
    70  			},
    71  		},
    72  		&corev1.Node{
    73  			ObjectMeta: metav1.ObjectMeta{
    74  				Name: "azure-nodepool1-0",
    75  			},
    76  			Spec: corev1.NodeSpec{
    77  				ProviderID: "azure://westus2/id-nodepool1/0",
    78  			},
    79  		},
    80  		&corev1.Node{
    81  			ObjectMeta: metav1.ObjectMeta{
    82  				Name: "azure-nodepool2-0",
    83  			},
    84  			Spec: corev1.NodeSpec{
    85  				ProviderID: "azure://westus2/id-nodepool2/0",
    86  			},
    87  		},
    88  	}
    89  
    90  	client := fake.NewClientBuilder().WithObjects(nodeList...).Build()
    91  
    92  	testCases := []struct {
    93  		name           string
    94  		providerIDList []string
    95  		expected       *getNodeReferencesResult
    96  		err            error
    97  	}{
    98  		{
    99  			name:           "valid provider id, valid aws node",
   100  			providerIDList: []string{"aws://us-east-1/id-node-1"},
   101  			expected: &getNodeReferencesResult{
   102  				references: []corev1.ObjectReference{
   103  					{Name: "node-1"},
   104  				},
   105  			},
   106  		},
   107  		{
   108  			name:           "valid provider id, valid aws node",
   109  			providerIDList: []string{"aws://us-west-2/id-node-2"},
   110  			expected: &getNodeReferencesResult{
   111  				references: []corev1.ObjectReference{
   112  					{Name: "node-2"},
   113  				},
   114  			},
   115  		},
   116  		{
   117  			name:           "valid provider id, valid gce node",
   118  			providerIDList: []string{"gce://us-central1/gce-id-node-2"},
   119  			expected: &getNodeReferencesResult{
   120  				references: []corev1.ObjectReference{
   121  					{Name: "gce-node-2"},
   122  				},
   123  			},
   124  		},
   125  		{
   126  			name:           "valid provider id, valid azure node",
   127  			providerIDList: []string{"azure://westus2/id-node-4"},
   128  			expected: &getNodeReferencesResult{
   129  				references: []corev1.ObjectReference{
   130  					{Name: "azure-node-4"},
   131  				},
   132  			},
   133  		},
   134  		{
   135  			name:           "valid provider ids, valid azure and aws nodes",
   136  			providerIDList: []string{"aws://us-east-1/id-node-1", "azure://westus2/id-node-4"},
   137  			expected: &getNodeReferencesResult{
   138  				references: []corev1.ObjectReference{
   139  					{Name: "node-1"},
   140  					{Name: "azure-node-4"},
   141  				},
   142  			},
   143  		},
   144  		{
   145  			name:           "valid provider id, no node found",
   146  			providerIDList: []string{"aws:///id-node-100"},
   147  			expected:       nil,
   148  			err:            errNoAvailableNodes,
   149  		},
   150  		{
   151  			name:           "no provider id, no node found",
   152  			providerIDList: []string{},
   153  			expected: &getNodeReferencesResult{
   154  				references: []corev1.ObjectReference{},
   155  				available:  0,
   156  				ready:      0,
   157  			},
   158  		},
   159  		{
   160  			name:           "valid provider id with non-unique instance id, valid azure node",
   161  			providerIDList: []string{"azure://westus2/id-nodepool1/0"},
   162  			expected: &getNodeReferencesResult{
   163  				references: []corev1.ObjectReference{
   164  					{Name: "azure-nodepool1-0"},
   165  				},
   166  			},
   167  		},
   168  		{
   169  			name:           "valid provider ids with same instance ids, valid azure nodes",
   170  			providerIDList: []string{"azure://westus2/id-nodepool1/0", "azure://westus2/id-nodepool2/0"},
   171  			expected: &getNodeReferencesResult{
   172  				references: []corev1.ObjectReference{
   173  					{Name: "azure-nodepool1-0"},
   174  					{Name: "azure-nodepool2-0"},
   175  				},
   176  			},
   177  		},
   178  	}
   179  
   180  	for _, test := range testCases {
   181  		t.Run(test.name, func(t *testing.T) {
   182  			g := NewWithT(t)
   183  
   184  			result, err := r.getNodeReferences(ctx, client, test.providerIDList)
   185  			if test.err == nil {
   186  				g.Expect(err).ToNot(HaveOccurred())
   187  			} else {
   188  				g.Expect(err).To(HaveOccurred())
   189  				g.Expect(err).To(Equal(test.err), "Expected error %v, got %v", test.err, err)
   190  			}
   191  
   192  			if test.expected == nil && len(result.references) == 0 {
   193  				return
   194  			}
   195  
   196  			g.Expect(result.references).To(HaveLen(len(test.expected.references)), "Expected NodeRef count to be %v, got %v", len(result.references), len(test.expected.references))
   197  
   198  			for n := range test.expected.references {
   199  				g.Expect(result.references[n].Name).To(Equal(test.expected.references[n].Name), "Expected NodeRef's name to be %v, got %v", result.references[n].Name, test.expected.references[n].Name)
   200  				g.Expect(result.references[n].Namespace).To(Equal(test.expected.references[n].Namespace), "Expected NodeRef's namespace to be %v, got %v", result.references[n].Namespace, test.expected.references[n].Namespace)
   201  			}
   202  		})
   203  	}
   204  }
   205  
   206  func TestMachinePoolPatchNodes(t *testing.T) {
   207  	r := &MachinePoolReconciler{
   208  		Client:   fake.NewClientBuilder().Build(),
   209  		recorder: record.NewFakeRecorder(32),
   210  	}
   211  
   212  	nodeList := []client.Object{
   213  		&corev1.Node{
   214  			ObjectMeta: metav1.ObjectMeta{
   215  				Name: "node-1",
   216  			},
   217  			Spec: corev1.NodeSpec{
   218  				ProviderID: "aws://us-east-1/id-node-1",
   219  				Taints: []corev1.Taint{
   220  					clusterv1.NodeUninitializedTaint,
   221  				},
   222  			},
   223  		},
   224  		&corev1.Node{
   225  			ObjectMeta: metav1.ObjectMeta{
   226  				Name: "node-2",
   227  				Annotations: map[string]string{
   228  					"foo": "bar",
   229  				},
   230  			},
   231  			Spec: corev1.NodeSpec{
   232  				ProviderID: "aws://us-west-2/id-node-2",
   233  				Taints: []corev1.Taint{
   234  					{
   235  						Key:   "some-other-taint",
   236  						Value: "SomeEffect",
   237  					},
   238  					clusterv1.NodeUninitializedTaint,
   239  				},
   240  			},
   241  		},
   242  		&corev1.Node{
   243  			ObjectMeta: metav1.ObjectMeta{
   244  				Name: "node-3",
   245  				Annotations: map[string]string{
   246  					"cluster.x-k8s.io/cluster-name":      "cluster-1",
   247  					"cluster.x-k8s.io/cluster-namespace": "my-namespace",
   248  					"cluster.x-k8s.io/owner-kind":        "MachinePool",
   249  					"cluster.x-k8s.io/owner-name":        "machinepool-3",
   250  				},
   251  			},
   252  			Spec: corev1.NodeSpec{
   253  				ProviderID: "aws://us-west-2/id-node-3",
   254  				Taints: []corev1.Taint{
   255  					{
   256  						Key:   "some-other-taint",
   257  						Value: "SomeEffect",
   258  					},
   259  					clusterv1.NodeUninitializedTaint,
   260  				},
   261  			},
   262  		},
   263  		&corev1.Node{
   264  			ObjectMeta: metav1.ObjectMeta{
   265  				Name: "node-4",
   266  				Annotations: map[string]string{
   267  					"cluster.x-k8s.io/cluster-name":      "cluster-1",
   268  					"cluster.x-k8s.io/cluster-namespace": "my-namespace",
   269  				},
   270  			},
   271  			Spec: corev1.NodeSpec{
   272  				ProviderID: "aws://us-west-2/id-node-4",
   273  				Taints: []corev1.Taint{
   274  					{
   275  						Key:   "some-other-taint",
   276  						Value: "SomeEffect",
   277  					},
   278  				},
   279  			},
   280  		},
   281  	}
   282  
   283  	testCases := []struct {
   284  		name          string
   285  		machinePool   *expv1.MachinePool
   286  		nodeRefs      []corev1.ObjectReference
   287  		expectedNodes []corev1.Node
   288  		err           error
   289  	}{
   290  		{
   291  			name: "Node with uninitialized taint should be patched",
   292  			machinePool: &expv1.MachinePool{
   293  				TypeMeta: metav1.TypeMeta{
   294  					Kind: "MachinePool",
   295  				},
   296  				ObjectMeta: metav1.ObjectMeta{
   297  					Name:      "machinepool-1",
   298  					Namespace: "my-namespace",
   299  				},
   300  				Spec: expv1.MachinePoolSpec{
   301  					ClusterName:    "cluster-1",
   302  					ProviderIDList: []string{"aws://us-east-1/id-node-1"},
   303  				},
   304  			},
   305  			nodeRefs: []corev1.ObjectReference{
   306  				{Name: "node-1"},
   307  			},
   308  			expectedNodes: []corev1.Node{
   309  				{
   310  					ObjectMeta: metav1.ObjectMeta{
   311  						Name: "node-1",
   312  						Annotations: map[string]string{
   313  							"cluster.x-k8s.io/cluster-name":      "cluster-1",
   314  							"cluster.x-k8s.io/cluster-namespace": "my-namespace",
   315  							"cluster.x-k8s.io/owner-kind":        "MachinePool",
   316  							"cluster.x-k8s.io/owner-name":        "machinepool-1",
   317  						},
   318  					},
   319  					Spec: corev1.NodeSpec{
   320  						Taints: nil,
   321  					},
   322  				},
   323  			},
   324  		},
   325  		{
   326  			name: "Node with existing annotations and taints should be patched",
   327  			machinePool: &expv1.MachinePool{
   328  				TypeMeta: metav1.TypeMeta{
   329  					Kind: "MachinePool",
   330  				},
   331  				ObjectMeta: metav1.ObjectMeta{
   332  					Name:      "machinepool-2",
   333  					Namespace: "my-namespace",
   334  				},
   335  				Spec: expv1.MachinePoolSpec{
   336  					ClusterName:    "cluster-1",
   337  					ProviderIDList: []string{"aws://us-west-2/id-node-2"},
   338  				},
   339  			},
   340  			nodeRefs: []corev1.ObjectReference{
   341  				{Name: "node-2"},
   342  				{Name: "node-3"},
   343  				{Name: "node-4"},
   344  			},
   345  			expectedNodes: []corev1.Node{
   346  				{
   347  					ObjectMeta: metav1.ObjectMeta{
   348  						Name: "node-2",
   349  						Annotations: map[string]string{
   350  							"cluster.x-k8s.io/cluster-name":      "cluster-1",
   351  							"cluster.x-k8s.io/cluster-namespace": "my-namespace",
   352  							"cluster.x-k8s.io/owner-kind":        "MachinePool",
   353  							"cluster.x-k8s.io/owner-name":        "machinepool-2",
   354  							"foo":                                "bar",
   355  						},
   356  					},
   357  					Spec: corev1.NodeSpec{
   358  						Taints: []corev1.Taint{
   359  							{
   360  								Key:   "some-other-taint",
   361  								Value: "SomeEffect",
   362  							},
   363  						},
   364  					},
   365  				},
   366  				{
   367  					ObjectMeta: metav1.ObjectMeta{
   368  						Name: "node-3",
   369  						Annotations: map[string]string{
   370  							"cluster.x-k8s.io/cluster-name":      "cluster-1",
   371  							"cluster.x-k8s.io/cluster-namespace": "my-namespace",
   372  							"cluster.x-k8s.io/owner-kind":        "MachinePool",
   373  							"cluster.x-k8s.io/owner-name":        "machinepool-2",
   374  						},
   375  					},
   376  					Spec: corev1.NodeSpec{
   377  						Taints: []corev1.Taint{
   378  							{
   379  								Key:   "some-other-taint",
   380  								Value: "SomeEffect",
   381  							},
   382  						},
   383  					},
   384  				},
   385  				{
   386  					ObjectMeta: metav1.ObjectMeta{
   387  						Name: "node-4",
   388  						Annotations: map[string]string{
   389  							"cluster.x-k8s.io/cluster-name":      "cluster-1",
   390  							"cluster.x-k8s.io/cluster-namespace": "my-namespace",
   391  							"cluster.x-k8s.io/owner-kind":        "MachinePool",
   392  							"cluster.x-k8s.io/owner-name":        "machinepool-2",
   393  						},
   394  					},
   395  					Spec: corev1.NodeSpec{
   396  						Taints: []corev1.Taint{
   397  							{
   398  								Key:   "some-other-taint",
   399  								Value: "SomeEffect",
   400  							},
   401  						},
   402  					},
   403  				},
   404  			},
   405  		},
   406  	}
   407  
   408  	for _, test := range testCases {
   409  		t.Run(test.name, func(t *testing.T) {
   410  			g := NewWithT(t)
   411  
   412  			fakeClient := fake.NewClientBuilder().WithObjects(nodeList...).Build()
   413  
   414  			err := r.patchNodes(ctx, fakeClient, test.nodeRefs, test.machinePool)
   415  			if test.err == nil {
   416  				g.Expect(err).ToNot(HaveOccurred())
   417  			} else {
   418  				g.Expect(err).To(HaveOccurred())
   419  				g.Expect(err).To(Equal(test.err), "Expected error %v, got %v", test.err, err)
   420  			}
   421  
   422  			// Check that the nodes have the desired taints and annotations
   423  			for _, expected := range test.expectedNodes {
   424  				node := &corev1.Node{}
   425  				err := fakeClient.Get(ctx, client.ObjectKey{Name: expected.Name}, node)
   426  				g.Expect(err).ToNot(HaveOccurred())
   427  				g.Expect(node.Annotations).To(Equal(expected.Annotations))
   428  				g.Expect(node.Spec.Taints).To(BeComparableTo(expected.Spec.Taints))
   429  			}
   430  		})
   431  	}
   432  }