1
16
17 package util
18
19 import (
20 "fmt"
21 "reflect"
22 "testing"
23
24 v1 "k8s.io/api/core/v1"
25 extensions "k8s.io/api/extensions/v1beta1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 utilfeature "k8s.io/apiserver/pkg/util/feature"
28 "k8s.io/component-base/featuregate"
29 featuregatetesting "k8s.io/component-base/featuregate/testing"
30 "k8s.io/utils/pointer"
31 )
32
33 func newPod(podName string, nodeName string, label map[string]string) *v1.Pod {
34 pod := &v1.Pod{
35 TypeMeta: metav1.TypeMeta{APIVersion: "v1"},
36 ObjectMeta: metav1.ObjectMeta{
37 Labels: label,
38 Namespace: metav1.NamespaceDefault,
39 },
40 Spec: v1.PodSpec{
41 NodeName: nodeName,
42 Containers: []v1.Container{
43 {
44 Image: "foo/bar",
45 },
46 },
47 },
48 }
49 pod.Name = podName
50 return pod
51 }
52
53 func TestIsPodUpdated(t *testing.T) {
54 templateGeneration := pointer.Int64(12345)
55 badGeneration := pointer.Int64(12350)
56 hash := "55555"
57 labels := map[string]string{extensions.DaemonSetTemplateGenerationKey: fmt.Sprint(*templateGeneration), extensions.DefaultDaemonSetUniqueLabelKey: hash}
58 labelsNoHash := map[string]string{extensions.DaemonSetTemplateGenerationKey: fmt.Sprint(*templateGeneration)}
59 tests := []struct {
60 test string
61 templateGeneration *int64
62 pod *v1.Pod
63 hash string
64 isUpdated bool
65 }{
66 {
67 "templateGeneration and hash both match",
68 templateGeneration,
69 newPod("pod1", "node1", labels),
70 hash,
71 true,
72 },
73 {
74 "templateGeneration matches, hash doesn't",
75 templateGeneration,
76 newPod("pod1", "node1", labels),
77 hash + "123",
78 true,
79 },
80 {
81 "templateGeneration matches, no hash label, has hash",
82 templateGeneration,
83 newPod("pod1", "node1", labelsNoHash),
84 hash,
85 true,
86 },
87 {
88 "templateGeneration matches, no hash label, no hash",
89 templateGeneration,
90 newPod("pod1", "node1", labelsNoHash),
91 "",
92 true,
93 },
94 {
95 "templateGeneration matches, has hash label, no hash",
96 templateGeneration,
97 newPod("pod1", "node1", labels),
98 "",
99 true,
100 },
101 {
102 "templateGeneration doesn't match, hash does",
103 badGeneration,
104 newPod("pod1", "node1", labels),
105 hash,
106 true,
107 },
108 {
109 "templateGeneration and hash don't match",
110 badGeneration,
111 newPod("pod1", "node1", labels),
112 hash + "123",
113 false,
114 },
115 {
116 "empty labels, no hash",
117 templateGeneration,
118 newPod("pod1", "node1", map[string]string{}),
119 "",
120 false,
121 },
122 {
123 "empty labels",
124 templateGeneration,
125 newPod("pod1", "node1", map[string]string{}),
126 hash,
127 false,
128 },
129 {
130 "no labels",
131 templateGeneration,
132 newPod("pod1", "node1", nil),
133 hash,
134 false,
135 },
136 }
137 for _, test := range tests {
138 updated := IsPodUpdated(test.pod, test.hash, test.templateGeneration)
139 if updated != test.isUpdated {
140 t.Errorf("%s: IsPodUpdated returned wrong value. Expected %t, got %t", test.test, test.isUpdated, updated)
141 }
142 }
143 }
144
145 func TestCreatePodTemplate(t *testing.T) {
146 tests := []struct {
147 templateGeneration *int64
148 hash string
149 expectUniqueLabel bool
150 }{
151 {pointer.Int64(1), "", false},
152 {pointer.Int64(2), "3242341807", true},
153 }
154 for _, test := range tests {
155 podTemplateSpec := v1.PodTemplateSpec{}
156 newPodTemplate := CreatePodTemplate(podTemplateSpec, test.templateGeneration, test.hash)
157 val, exists := newPodTemplate.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey]
158 if !exists || val != fmt.Sprint(*test.templateGeneration) {
159 t.Errorf("Expected podTemplateSpec to have generation label value: %d, got: %s", *test.templateGeneration, val)
160 }
161 val, exists = newPodTemplate.ObjectMeta.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
162 if test.expectUniqueLabel && (!exists || val != test.hash) {
163 t.Errorf("Expected podTemplateSpec to have hash label value: %s, got: %s", test.hash, val)
164 }
165 if !test.expectUniqueLabel && exists {
166 t.Errorf("Expected podTemplateSpec to have no hash label, got: %s", val)
167 }
168 }
169 }
170
171 func TestReplaceDaemonSetPodNodeNameNodeAffinity(t *testing.T) {
172 tests := []struct {
173 affinity *v1.Affinity
174 hostname string
175 expected *v1.Affinity
176 }{
177 {
178 affinity: nil,
179 hostname: "host_1",
180 expected: &v1.Affinity{
181 NodeAffinity: &v1.NodeAffinity{
182 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
183 NodeSelectorTerms: []v1.NodeSelectorTerm{
184 {
185 MatchFields: []v1.NodeSelectorRequirement{
186 {
187 Key: metav1.ObjectNameField,
188 Operator: v1.NodeSelectorOpIn,
189 Values: []string{"host_1"},
190 },
191 },
192 },
193 },
194 },
195 },
196 },
197 },
198 {
199 affinity: &v1.Affinity{
200 NodeAffinity: &v1.NodeAffinity{
201 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
202 NodeSelectorTerms: []v1.NodeSelectorTerm{
203 {
204 MatchExpressions: []v1.NodeSelectorRequirement{
205 {
206 Key: v1.LabelHostname,
207 Operator: v1.NodeSelectorOpIn,
208 Values: []string{"host_1"},
209 },
210 },
211 },
212 },
213 },
214 },
215 },
216 hostname: "host_1",
217 expected: &v1.Affinity{
218 NodeAffinity: &v1.NodeAffinity{
219 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
220 NodeSelectorTerms: []v1.NodeSelectorTerm{
221 {
222 MatchFields: []v1.NodeSelectorRequirement{
223 {
224 Key: metav1.ObjectNameField,
225 Operator: v1.NodeSelectorOpIn,
226 Values: []string{"host_1"},
227 },
228 },
229 },
230 },
231 },
232 },
233 },
234 },
235 {
236 affinity: &v1.Affinity{
237 NodeAffinity: &v1.NodeAffinity{
238 PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
239 {
240 Preference: v1.NodeSelectorTerm{
241 MatchExpressions: []v1.NodeSelectorRequirement{
242 {
243 Key: v1.LabelHostname,
244 Operator: v1.NodeSelectorOpIn,
245 Values: []string{"host_1"},
246 },
247 },
248 },
249 },
250 },
251 },
252 },
253 hostname: "host_1",
254 expected: &v1.Affinity{
255 NodeAffinity: &v1.NodeAffinity{
256 PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
257 {
258 Preference: v1.NodeSelectorTerm{
259 MatchExpressions: []v1.NodeSelectorRequirement{
260 {
261 Key: v1.LabelHostname,
262 Operator: v1.NodeSelectorOpIn,
263 Values: []string{"host_1"},
264 },
265 },
266 },
267 },
268 },
269 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
270 NodeSelectorTerms: []v1.NodeSelectorTerm{
271 {
272 MatchFields: []v1.NodeSelectorRequirement{
273 {
274 Key: metav1.ObjectNameField,
275 Operator: v1.NodeSelectorOpIn,
276 Values: []string{"host_1"},
277 },
278 },
279 },
280 },
281 },
282 },
283 },
284 },
285 {
286 affinity: &v1.Affinity{
287 NodeAffinity: &v1.NodeAffinity{
288 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
289 NodeSelectorTerms: []v1.NodeSelectorTerm{
290 {
291 MatchFields: []v1.NodeSelectorRequirement{
292 {
293 Key: metav1.ObjectNameField,
294 Operator: v1.NodeSelectorOpIn,
295 Values: []string{"host_1", "host_2"},
296 },
297 },
298 },
299 },
300 },
301 },
302 },
303 hostname: "host_1",
304 expected: &v1.Affinity{
305 NodeAffinity: &v1.NodeAffinity{
306 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
307 NodeSelectorTerms: []v1.NodeSelectorTerm{
308 {
309 MatchFields: []v1.NodeSelectorRequirement{
310 {
311 Key: metav1.ObjectNameField,
312 Operator: v1.NodeSelectorOpIn,
313 Values: []string{"host_1"},
314 },
315 },
316 },
317 },
318 },
319 },
320 },
321 },
322 {
323 affinity: nil,
324 hostname: "host_1",
325 expected: &v1.Affinity{
326 NodeAffinity: &v1.NodeAffinity{
327 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
328 NodeSelectorTerms: []v1.NodeSelectorTerm{
329 {
330 MatchFields: []v1.NodeSelectorRequirement{
331 {
332 Key: metav1.ObjectNameField,
333 Operator: v1.NodeSelectorOpIn,
334 Values: []string{"host_1"},
335 },
336 },
337 },
338 },
339 },
340 },
341 },
342 },
343 {
344 affinity: &v1.Affinity{
345 NodeAffinity: &v1.NodeAffinity{
346 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
347 NodeSelectorTerms: []v1.NodeSelectorTerm{
348 {
349 MatchExpressions: []v1.NodeSelectorRequirement{
350 {
351 Key: "hostname",
352 Operator: v1.NodeSelectorOpIn,
353 Values: []string{"host_1"},
354 },
355 },
356 },
357 {
358 MatchFields: []v1.NodeSelectorRequirement{
359 {
360 Key: metav1.ObjectNameField,
361 Operator: v1.NodeSelectorOpIn,
362 Values: []string{"host_2"},
363 },
364 },
365 },
366 },
367 },
368 },
369 },
370 hostname: "host_1",
371 expected: &v1.Affinity{
372 NodeAffinity: &v1.NodeAffinity{
373 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
374 NodeSelectorTerms: []v1.NodeSelectorTerm{
375 {
376 MatchFields: []v1.NodeSelectorRequirement{
377 {
378 Key: metav1.ObjectNameField,
379 Operator: v1.NodeSelectorOpIn,
380 Values: []string{"host_1"},
381 },
382 },
383 },
384 },
385 },
386 },
387 },
388 },
389 {
390 affinity: &v1.Affinity{
391 NodeAffinity: &v1.NodeAffinity{
392 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
393 NodeSelectorTerms: []v1.NodeSelectorTerm{
394 {
395 MatchFields: []v1.NodeSelectorRequirement{
396 {
397 Key: metav1.ObjectNameField,
398 Operator: v1.NodeSelectorOpNotIn,
399 Values: []string{"host_2"},
400 },
401 },
402 },
403 },
404 },
405 },
406 },
407 hostname: "host_1",
408 expected: &v1.Affinity{
409 NodeAffinity: &v1.NodeAffinity{
410 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
411 NodeSelectorTerms: []v1.NodeSelectorTerm{
412 {
413 MatchFields: []v1.NodeSelectorRequirement{
414 {
415 Key: metav1.ObjectNameField,
416 Operator: v1.NodeSelectorOpIn,
417 Values: []string{"host_1"},
418 },
419 },
420 },
421 },
422 },
423 },
424 },
425 },
426 {
427 affinity: &v1.Affinity{
428 NodeAffinity: &v1.NodeAffinity{
429 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
430 NodeSelectorTerms: []v1.NodeSelectorTerm{
431 {
432 MatchFields: []v1.NodeSelectorRequirement{
433 {
434
435
436
437 Key: "metadata.foo",
438 Operator: v1.NodeSelectorOpIn,
439 Values: []string{"bar"},
440 },
441 },
442 },
443 },
444 },
445 },
446 },
447 hostname: "host_1",
448 expected: &v1.Affinity{
449 NodeAffinity: &v1.NodeAffinity{
450 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
451 NodeSelectorTerms: []v1.NodeSelectorTerm{
452 {
453 MatchFields: []v1.NodeSelectorRequirement{
454 {
455 Key: metav1.ObjectNameField,
456 Operator: v1.NodeSelectorOpIn,
457 Values: []string{"host_1"},
458 },
459 },
460 },
461 },
462 },
463 },
464 },
465 },
466 }
467
468 for i, test := range tests {
469 got := ReplaceDaemonSetPodNodeNameNodeAffinity(test.affinity, test.hostname)
470 if !reflect.DeepEqual(test.expected, got) {
471 t.Errorf("Failed to append NodeAffinity in case %d, got: %v, expected: %v",
472 i, got, test.expected)
473 }
474 }
475 }
476
477 func forEachFeatureGate(t *testing.T, tf func(t *testing.T), gates ...featuregate.Feature) {
478 for _, fg := range gates {
479 for _, f := range []bool{true, false} {
480 func() {
481 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, fg, f)()
482 t.Run(fmt.Sprintf("%v (%t)", fg, f), tf)
483 }()
484 }
485 }
486 }
487
488 func TestGetTargetNodeName(t *testing.T) {
489 testFun := func(t *testing.T) {
490 tests := []struct {
491 pod *v1.Pod
492 nodeName string
493 expectedErr bool
494 }{
495 {
496 pod: &v1.Pod{
497 ObjectMeta: metav1.ObjectMeta{
498 Name: "pod1",
499 Namespace: "default",
500 },
501 Spec: v1.PodSpec{
502 NodeName: "node-1",
503 },
504 },
505 nodeName: "node-1",
506 },
507 {
508 pod: &v1.Pod{
509 ObjectMeta: metav1.ObjectMeta{
510 Name: "pod2",
511 Namespace: "default",
512 },
513 Spec: v1.PodSpec{
514 Affinity: &v1.Affinity{
515 NodeAffinity: &v1.NodeAffinity{
516 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
517 NodeSelectorTerms: []v1.NodeSelectorTerm{
518 {
519 MatchFields: []v1.NodeSelectorRequirement{
520 {
521 Key: metav1.ObjectNameField,
522 Operator: v1.NodeSelectorOpIn,
523 Values: []string{"node-1"},
524 },
525 },
526 },
527 },
528 },
529 },
530 },
531 },
532 },
533 nodeName: "node-1",
534 },
535 {
536 pod: &v1.Pod{
537 ObjectMeta: metav1.ObjectMeta{
538 Name: "pod3",
539 Namespace: "default",
540 },
541 Spec: v1.PodSpec{
542 Affinity: &v1.Affinity{
543 NodeAffinity: &v1.NodeAffinity{
544 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
545 NodeSelectorTerms: []v1.NodeSelectorTerm{
546 {
547 MatchFields: []v1.NodeSelectorRequirement{
548 {
549 Key: metav1.ObjectNameField,
550 Operator: v1.NodeSelectorOpIn,
551 Values: []string{"node-1", "node-2"},
552 },
553 },
554 },
555 },
556 },
557 },
558 },
559 },
560 },
561 expectedErr: true,
562 },
563 {
564 pod: &v1.Pod{
565 ObjectMeta: metav1.ObjectMeta{
566 Name: "pod4",
567 Namespace: "default",
568 },
569 Spec: v1.PodSpec{},
570 },
571 expectedErr: true,
572 },
573 }
574
575 for _, test := range tests {
576 got, err := GetTargetNodeName(test.pod)
577 if test.expectedErr != (err != nil) {
578 t.Errorf("Unexpected error, expectedErr: %v, err: %v", test.expectedErr, err)
579 } else if !test.expectedErr {
580 if test.nodeName != got {
581 t.Errorf("Failed to get target node name, got: %v, expected: %v", got, test.nodeName)
582 }
583 }
584 }
585 }
586
587 forEachFeatureGate(t, testFun)
588 }
589
View as plain text