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