k8s.io/kubernetes@v1.29.3/pkg/controller/daemon/util/daemonset_util_test.go (about) 1 /* 2 Copyright 2017 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 "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 // NOTE: Only `metadata.name` is valid key in `MatchFields` in 1.11; 435 // added this case for compatibility: the feature works as normal 436 // when new Keys introduced. 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 }