1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package helper 18 19import ( 20 "fmt" 21 "reflect" 22 "testing" 23 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/api/resource" 26 "k8s.io/apimachinery/pkg/labels" 27) 28 29func TestIsNativeResource(t *testing.T) { 30 testCases := []struct { 31 resourceName v1.ResourceName 32 expectVal bool 33 }{ 34 { 35 resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", 36 expectVal: true, 37 }, 38 { 39 resourceName: "kubernetes.io/resource-foo", 40 expectVal: true, 41 }, 42 { 43 resourceName: "foo", 44 expectVal: true, 45 }, 46 { 47 resourceName: "a/b", 48 expectVal: false, 49 }, 50 { 51 resourceName: "", 52 expectVal: true, 53 }, 54 } 55 56 for _, tc := range testCases { 57 t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { 58 t.Parallel() 59 v := IsNativeResource(tc.resourceName) 60 if v != tc.expectVal { 61 t.Errorf("Got %v but expected %v", v, tc.expectVal) 62 } 63 }) 64 } 65} 66 67func TestHugePageSizeFromResourceName(t *testing.T) { 68 expected100m, _ := resource.ParseQuantity("100m") 69 testCases := []struct { 70 resourceName v1.ResourceName 71 expectVal resource.Quantity 72 expectErr bool 73 }{ 74 { 75 resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", 76 expectVal: resource.Quantity{}, 77 expectErr: true, 78 }, 79 { 80 resourceName: "hugepages-", 81 expectVal: resource.Quantity{}, 82 expectErr: true, 83 }, 84 { 85 resourceName: "hugepages-100m", 86 expectVal: expected100m, 87 expectErr: false, 88 }, 89 { 90 resourceName: "", 91 expectVal: resource.Quantity{}, 92 expectErr: true, 93 }, 94 } 95 96 for i, tc := range testCases { 97 t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { 98 t.Parallel() 99 v, err := HugePageSizeFromResourceName(tc.resourceName) 100 if err == nil && tc.expectErr { 101 t.Errorf("[%v]expected error but got none.", i) 102 } 103 if err != nil && !tc.expectErr { 104 t.Errorf("[%v]did not expect error but got: %v", i, err) 105 } 106 if v != tc.expectVal { 107 t.Errorf("Got %v but expected %v", v, tc.expectVal) 108 } 109 }) 110 } 111} 112 113func TestHugePageSizeFromMedium(t *testing.T) { 114 testCases := []struct { 115 description string 116 medium v1.StorageMedium 117 expectVal resource.Quantity 118 expectErr bool 119 }{ 120 { 121 description: "Invalid hugepages medium", 122 medium: "Memory", 123 expectVal: resource.Quantity{}, 124 expectErr: true, 125 }, 126 { 127 description: "Invalid hugepages medium", 128 medium: "Memory", 129 expectVal: resource.Quantity{}, 130 expectErr: true, 131 }, 132 { 133 description: "Invalid: HugePages without size", 134 medium: "HugePages", 135 expectVal: resource.Quantity{}, 136 expectErr: true, 137 }, 138 { 139 description: "Invalid: HugePages without size", 140 medium: "HugePages", 141 expectVal: resource.Quantity{}, 142 expectErr: true, 143 }, 144 { 145 description: "Valid: HugePages-1Gi", 146 medium: "HugePages-1Gi", 147 expectVal: resource.MustParse("1Gi"), 148 expectErr: false, 149 }, 150 { 151 description: "Valid: HugePages-2Mi", 152 medium: "HugePages-2Mi", 153 expectVal: resource.MustParse("2Mi"), 154 expectErr: false, 155 }, 156 { 157 description: "Valid: HugePages-64Ki", 158 medium: "HugePages-64Ki", 159 expectVal: resource.MustParse("64Ki"), 160 expectErr: false, 161 }, 162 } 163 for i, tc := range testCases { 164 t.Run(tc.description, func(t *testing.T) { 165 t.Parallel() 166 v, err := HugePageSizeFromMedium(tc.medium) 167 if err == nil && tc.expectErr { 168 t.Errorf("[%v]expected error but got none.", i) 169 } 170 if err != nil && !tc.expectErr { 171 t.Errorf("[%v]did not expect error but got: %v", i, err) 172 } 173 if v != tc.expectVal { 174 t.Errorf("Got %v but expected %v", v, tc.expectVal) 175 } 176 }) 177 } 178} 179 180func TestIsOvercommitAllowed(t *testing.T) { 181 testCases := []struct { 182 resourceName v1.ResourceName 183 expectVal bool 184 }{ 185 { 186 resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", 187 expectVal: true, 188 }, 189 { 190 resourceName: "kubernetes.io/resource-foo", 191 expectVal: true, 192 }, 193 { 194 resourceName: "hugepages-100m", 195 expectVal: false, 196 }, 197 { 198 resourceName: "", 199 expectVal: true, 200 }, 201 } 202 203 for _, tc := range testCases { 204 t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { 205 t.Parallel() 206 v := IsOvercommitAllowed(tc.resourceName) 207 if v != tc.expectVal { 208 t.Errorf("Got %v but expected %v", v, tc.expectVal) 209 } 210 }) 211 } 212} 213 214func TestGetAccessModesFromString(t *testing.T) { 215 modes := GetAccessModesFromString("ROX") 216 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 217 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 218 } 219 220 modes = GetAccessModesFromString("ROX,RWX") 221 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 222 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 223 } 224 if !ContainsAccessMode(modes, v1.ReadWriteMany) { 225 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) 226 } 227 228 modes = GetAccessModesFromString("RWO,ROX,RWX") 229 if !ContainsAccessMode(modes, v1.ReadWriteOnce) { 230 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes) 231 } 232 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 233 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 234 } 235 if !ContainsAccessMode(modes, v1.ReadWriteMany) { 236 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) 237 } 238 239 modes = GetAccessModesFromString("RWO,ROX,RWX,RWOP") 240 if !ContainsAccessMode(modes, v1.ReadWriteOnce) { 241 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes) 242 } 243 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 244 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 245 } 246 if !ContainsAccessMode(modes, v1.ReadWriteMany) { 247 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) 248 } 249 if !ContainsAccessMode(modes, v1.ReadWriteOncePod) { 250 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOncePod, modes) 251 } 252} 253 254func TestRemoveDuplicateAccessModes(t *testing.T) { 255 modes := []v1.PersistentVolumeAccessMode{ 256 v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadOnlyMany, v1.ReadOnlyMany, 257 } 258 modes = removeDuplicateAccessModes(modes) 259 if len(modes) != 2 { 260 t.Errorf("Expected 2 distinct modes in set but found %v", len(modes)) 261 } 262} 263 264func TestTopologySelectorRequirementsAsSelector(t *testing.T) { 265 mustParse := func(s string) labels.Selector { 266 out, e := labels.Parse(s) 267 if e != nil { 268 panic(e) 269 } 270 return out 271 } 272 tc := []struct { 273 in []v1.TopologySelectorLabelRequirement 274 out labels.Selector 275 expectErr bool 276 }{ 277 {in: nil, out: labels.Nothing()}, 278 {in: []v1.TopologySelectorLabelRequirement{}, out: labels.Nothing()}, 279 { 280 in: []v1.TopologySelectorLabelRequirement{{ 281 Key: "foo", 282 Values: []string{"bar", "baz"}, 283 }}, 284 out: mustParse("foo in (baz,bar)"), 285 }, 286 { 287 in: []v1.TopologySelectorLabelRequirement{{ 288 Key: "foo", 289 Values: []string{}, 290 }}, 291 expectErr: true, 292 }, 293 { 294 in: []v1.TopologySelectorLabelRequirement{ 295 { 296 Key: "foo", 297 Values: []string{"bar", "baz"}, 298 }, 299 { 300 Key: "invalid", 301 Values: []string{}, 302 }, 303 }, 304 expectErr: true, 305 }, 306 { 307 in: []v1.TopologySelectorLabelRequirement{{ 308 Key: "/invalidkey", 309 Values: []string{"bar", "baz"}, 310 }}, 311 expectErr: true, 312 }, 313 } 314 315 for i, tc := range tc { 316 out, err := TopologySelectorRequirementsAsSelector(tc.in) 317 if err == nil && tc.expectErr { 318 t.Errorf("[%v]expected error but got none.", i) 319 } 320 if err != nil && !tc.expectErr { 321 t.Errorf("[%v]did not expect error but got: %v", i, err) 322 } 323 if !reflect.DeepEqual(out, tc.out) { 324 t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) 325 } 326 } 327} 328 329func TestMatchTopologySelectorTerms(t *testing.T) { 330 type args struct { 331 topologySelectorTerms []v1.TopologySelectorTerm 332 labels labels.Set 333 } 334 335 tests := []struct { 336 name string 337 args args 338 want bool 339 }{ 340 { 341 name: "nil term list", 342 args: args{ 343 topologySelectorTerms: nil, 344 labels: nil, 345 }, 346 want: true, 347 }, 348 { 349 name: "nil term", 350 args: args{ 351 topologySelectorTerms: []v1.TopologySelectorTerm{ 352 { 353 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{}, 354 }, 355 }, 356 labels: nil, 357 }, 358 want: false, 359 }, 360 { 361 name: "label matches MatchLabelExpressions terms", 362 args: args{ 363 topologySelectorTerms: []v1.TopologySelectorTerm{ 364 { 365 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 366 Key: "label_1", 367 Values: []string{"label_1_val"}, 368 }}, 369 }, 370 }, 371 labels: map[string]string{"label_1": "label_1_val"}, 372 }, 373 want: true, 374 }, 375 { 376 name: "label does not match MatchLabelExpressions terms", 377 args: args{ 378 topologySelectorTerms: []v1.TopologySelectorTerm{ 379 { 380 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 381 Key: "label_1", 382 Values: []string{"label_1_val"}, 383 }}, 384 }, 385 }, 386 labels: map[string]string{"label_1": "label_1_val-failed"}, 387 }, 388 want: false, 389 }, 390 { 391 name: "multi-values in one requirement, one matched", 392 args: args{ 393 topologySelectorTerms: []v1.TopologySelectorTerm{ 394 { 395 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 396 Key: "label_1", 397 Values: []string{"label_1_val1", "label_1_val2"}, 398 }}, 399 }, 400 }, 401 labels: map[string]string{"label_1": "label_1_val2"}, 402 }, 403 want: true, 404 }, 405 { 406 name: "multi-terms was set, one matched", 407 args: args{ 408 topologySelectorTerms: []v1.TopologySelectorTerm{ 409 { 410 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 411 Key: "label_1", 412 Values: []string{"label_1_val"}, 413 }}, 414 }, 415 { 416 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 417 Key: "label_2", 418 Values: []string{"label_2_val"}, 419 }}, 420 }, 421 }, 422 labels: map[string]string{ 423 "label_2": "label_2_val", 424 }, 425 }, 426 want: true, 427 }, 428 { 429 name: "multi-requirement in one term, fully matched", 430 args: args{ 431 topologySelectorTerms: []v1.TopologySelectorTerm{ 432 { 433 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ 434 { 435 Key: "label_1", 436 Values: []string{"label_1_val"}, 437 }, 438 { 439 Key: "label_2", 440 Values: []string{"label_2_val"}, 441 }, 442 }, 443 }, 444 }, 445 labels: map[string]string{ 446 "label_1": "label_1_val", 447 "label_2": "label_2_val", 448 }, 449 }, 450 want: true, 451 }, 452 { 453 name: "multi-requirement in one term, partial matched", 454 args: args{ 455 topologySelectorTerms: []v1.TopologySelectorTerm{ 456 { 457 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ 458 { 459 Key: "label_1", 460 Values: []string{"label_1_val"}, 461 }, 462 { 463 Key: "label_2", 464 Values: []string{"label_2_val"}, 465 }, 466 }, 467 }, 468 }, 469 labels: map[string]string{ 470 "label_1": "label_1_val-failed", 471 "label_2": "label_2_val", 472 }, 473 }, 474 want: false, 475 }, 476 } 477 478 for _, tt := range tests { 479 t.Run(tt.name, func(t *testing.T) { 480 if got := MatchTopologySelectorTerms(tt.args.topologySelectorTerms, tt.args.labels); got != tt.want { 481 t.Errorf("MatchTopologySelectorTermsORed() = %v, want %v", got, tt.want) 482 } 483 }) 484 } 485} 486 487func TestNodeSelectorRequirementKeyExistsInNodeSelectorTerms(t *testing.T) { 488 tests := []struct { 489 name string 490 reqs []v1.NodeSelectorRequirement 491 terms []v1.NodeSelectorTerm 492 exists bool 493 }{ 494 { 495 name: "empty set of keys in empty set of terms", 496 reqs: []v1.NodeSelectorRequirement{}, 497 terms: []v1.NodeSelectorTerm{}, 498 exists: false, 499 }, 500 { 501 name: "key existence in terms with all keys specified", 502 reqs: []v1.NodeSelectorRequirement{ 503 { 504 Key: "key1", 505 Operator: v1.NodeSelectorOpIn, 506 Values: []string{"test-value1"}, 507 }, 508 { 509 Key: "key2", 510 Operator: v1.NodeSelectorOpIn, 511 Values: []string{"test-value2"}, 512 }, 513 }, 514 terms: []v1.NodeSelectorTerm{ 515 { 516 MatchExpressions: []v1.NodeSelectorRequirement{ 517 { 518 Key: "key2", 519 Operator: v1.NodeSelectorOpIn, 520 Values: []string{"test-value2"}, 521 }, 522 { 523 Key: "key3", 524 Operator: v1.NodeSelectorOpIn, 525 Values: []string{"test-value3"}, 526 }, 527 }, 528 }, 529 { 530 MatchExpressions: []v1.NodeSelectorRequirement{ 531 { 532 Key: "key1", 533 Operator: v1.NodeSelectorOpIn, 534 Values: []string{"test-value11, test-value12"}, 535 }, 536 { 537 Key: "key4", 538 Operator: v1.NodeSelectorOpIn, 539 Values: []string{"test-value41, test-value42"}, 540 }, 541 }, 542 }, 543 }, 544 exists: true, 545 }, 546 { 547 name: "key existence in terms with one of the keys specfied", 548 reqs: []v1.NodeSelectorRequirement{ 549 { 550 Key: "key1", 551 Operator: v1.NodeSelectorOpIn, 552 Values: []string{"test-value1"}, 553 }, 554 { 555 Key: "key2", 556 Operator: v1.NodeSelectorOpIn, 557 Values: []string{"test-value2"}, 558 }, 559 { 560 Key: "key3", 561 Operator: v1.NodeSelectorOpIn, 562 Values: []string{"test-value3"}, 563 }, 564 { 565 Key: "key6", 566 Operator: v1.NodeSelectorOpIn, 567 Values: []string{"test-value6"}, 568 }, 569 }, 570 terms: []v1.NodeSelectorTerm{ 571 { 572 MatchExpressions: []v1.NodeSelectorRequirement{ 573 { 574 Key: "key2", 575 Operator: v1.NodeSelectorOpIn, 576 Values: []string{"test-value2"}, 577 }, { 578 Key: "key4", 579 Operator: v1.NodeSelectorOpIn, 580 Values: []string{"test-value4"}, 581 }, 582 }, 583 }, 584 { 585 MatchExpressions: []v1.NodeSelectorRequirement{ 586 { 587 Key: "key5", 588 Operator: v1.NodeSelectorOpIn, 589 Values: []string{"test-value5"}, 590 }, 591 }, 592 }, 593 }, 594 exists: true, 595 }, 596 { 597 name: "key existence in terms without any of the keys specified", 598 reqs: []v1.NodeSelectorRequirement{ 599 { 600 Key: "key2", 601 Operator: v1.NodeSelectorOpIn, 602 Values: []string{"test-value2"}, 603 }, 604 { 605 Key: "key3", 606 Operator: v1.NodeSelectorOpIn, 607 Values: []string{"test-value3"}, 608 }, 609 }, 610 terms: []v1.NodeSelectorTerm{ 611 { 612 MatchExpressions: []v1.NodeSelectorRequirement{ 613 { 614 Key: "key4", 615 Operator: v1.NodeSelectorOpIn, 616 Values: []string{"test-value"}, 617 }, 618 { 619 Key: "key5", 620 Operator: v1.NodeSelectorOpIn, 621 Values: []string{"test-value"}, 622 }, 623 }, 624 }, 625 { 626 MatchExpressions: []v1.NodeSelectorRequirement{ 627 { 628 Key: "key6", 629 Operator: v1.NodeSelectorOpIn, 630 Values: []string{"test-value"}, 631 }, 632 }, 633 }, 634 { 635 MatchExpressions: []v1.NodeSelectorRequirement{ 636 { 637 Key: "key7", 638 Operator: v1.NodeSelectorOpIn, 639 Values: []string{"test-value"}, 640 }, 641 { 642 Key: "key8", 643 Operator: v1.NodeSelectorOpIn, 644 Values: []string{"test-value"}, 645 }, 646 }, 647 }, 648 }, 649 exists: false, 650 }, 651 { 652 name: "key existence in empty set of terms", 653 reqs: []v1.NodeSelectorRequirement{ 654 { 655 Key: "key2", 656 Operator: v1.NodeSelectorOpIn, 657 Values: []string{"test-value2"}, 658 }, 659 { 660 Key: "key3", 661 Operator: v1.NodeSelectorOpIn, 662 Values: []string{"test-value3"}, 663 }, 664 }, 665 terms: []v1.NodeSelectorTerm{}, 666 exists: false, 667 }, 668 } 669 for _, test := range tests { 670 keyExists := NodeSelectorRequirementKeysExistInNodeSelectorTerms(test.reqs, test.terms) 671 if test.exists != keyExists { 672 t.Errorf("test %s failed. Expected %v but got %v", test.name, test.exists, keyExists) 673 } 674 } 675} 676 677func TestHugePageUnitSizeFromByteSize(t *testing.T) { 678 tests := []struct { 679 size int64 680 expected string 681 wantErr bool 682 }{ 683 { 684 size: 1024, 685 expected: "1KB", 686 wantErr: false, 687 }, 688 { 689 size: 33554432, 690 expected: "32MB", 691 wantErr: false, 692 }, 693 { 694 size: 3221225472, 695 expected: "3GB", 696 wantErr: false, 697 }, 698 { 699 size: 1024 * 1024 * 1023 * 3, 700 expected: "3069MB", 701 wantErr: true, 702 }, 703 } 704 for _, test := range tests { 705 size := test.size 706 result, err := HugePageUnitSizeFromByteSize(size) 707 if err != nil { 708 if test.wantErr { 709 t.Logf("HugePageUnitSizeFromByteSize() expected error = %v", err) 710 } else { 711 t.Errorf("HugePageUnitSizeFromByteSize() error = %v, wantErr %v", err, test.wantErr) 712 } 713 continue 714 } 715 if test.expected != result { 716 t.Errorf("HugePageUnitSizeFromByteSize() expected %v but got %v", test.expected, result) 717 } 718 } 719} 720 721func TestLoadBalancerStatusEqual(t *testing.T) { 722 723 testCases := []struct { 724 left *v1.LoadBalancerStatus 725 right *v1.LoadBalancerStatus 726 name string 727 expectVal bool 728 }{{ 729 name: "left equals right", 730 left: &v1.LoadBalancerStatus{ 731 Ingress: []v1.LoadBalancerIngress{{ 732 IP: "1.1.1.1", 733 Hostname: "host1", 734 }}, 735 }, 736 right: &v1.LoadBalancerStatus{ 737 Ingress: []v1.LoadBalancerIngress{{ 738 IP: "1.1.1.1", 739 Hostname: "host1", 740 }}, 741 }, 742 expectVal: true, 743 }, { 744 name: "length of LoadBalancerIngress slice is not equal", 745 left: &v1.LoadBalancerStatus{ 746 Ingress: []v1.LoadBalancerIngress{{ 747 IP: "1.1.1.1", 748 Hostname: "host1", 749 }, { 750 IP: "1.1.1.2", 751 Hostname: "host1", 752 }}, 753 }, 754 right: &v1.LoadBalancerStatus{ 755 Ingress: []v1.LoadBalancerIngress{{ 756 IP: "1.1.1.1", 757 Hostname: "host1", 758 }}, 759 }, 760 expectVal: false, 761 }, { 762 name: "LoadBalancerIngress ip is not equal", 763 left: &v1.LoadBalancerStatus{ 764 Ingress: []v1.LoadBalancerIngress{{ 765 IP: "1.1.1.2", 766 Hostname: "host1", 767 }}, 768 }, 769 right: &v1.LoadBalancerStatus{ 770 Ingress: []v1.LoadBalancerIngress{{ 771 IP: "1.1.1.1", 772 Hostname: "host1", 773 }}, 774 }, 775 expectVal: false, 776 }, { 777 name: "LoadBalancerIngress hostname is not equal", 778 left: &v1.LoadBalancerStatus{ 779 Ingress: []v1.LoadBalancerIngress{{ 780 IP: "1.1.1.1", 781 Hostname: "host2", 782 }}, 783 }, 784 right: &v1.LoadBalancerStatus{ 785 Ingress: []v1.LoadBalancerIngress{{ 786 IP: "1.1.1.1", 787 Hostname: "host1", 788 }}, 789 }, 790 expectVal: false, 791 }} 792 793 for _, tc := range testCases { 794 t.Run(tc.name, func(t *testing.T) { 795 v := LoadBalancerStatusEqual(tc.left, tc.right) 796 if v != tc.expectVal { 797 t.Errorf("test %s failed. left input=%v, right input=%v, Got %v but expected %v", 798 tc.name, tc.left, tc.right, v, tc.expectVal) 799 } 800 }) 801 } 802} 803