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 }