1/*
2Copyright 2019 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 topologymanager
18
19import (
20	"fmt"
21	"strings"
22	"testing"
23
24	"k8s.io/api/core/v1"
25	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
26	"k8s.io/kubernetes/pkg/kubelet/lifecycle"
27)
28
29func NewTestBitMask(sockets ...int) bitmask.BitMask {
30	s, _ := bitmask.NewBitMask(sockets...)
31	return s
32}
33
34func TestNewManager(t *testing.T) {
35	tcases := []struct {
36		description    string
37		policyName     string
38		expectedPolicy string
39		expectedError  error
40	}{
41		{
42			description:    "Policy is set to none",
43			policyName:     "none",
44			expectedPolicy: "none",
45		},
46		{
47			description:    "Policy is set to best-effort",
48			policyName:     "best-effort",
49			expectedPolicy: "best-effort",
50		},
51		{
52			description:    "Policy is set to restricted",
53			policyName:     "restricted",
54			expectedPolicy: "restricted",
55		},
56		{
57			description:    "Policy is set to single-numa-node",
58			policyName:     "single-numa-node",
59			expectedPolicy: "single-numa-node",
60		},
61		{
62			description:   "Policy is set to unknown",
63			policyName:    "unknown",
64			expectedError: fmt.Errorf("unknown policy: \"unknown\""),
65		},
66	}
67
68	for _, tc := range tcases {
69		mngr, err := NewManager(nil, tc.policyName, "container")
70
71		if tc.expectedError != nil {
72			if !strings.Contains(err.Error(), tc.expectedError.Error()) {
73				t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error())
74			}
75		} else {
76			rawMgr := mngr.(*manager)
77			rawScope := rawMgr.scope.(*containerScope)
78			if rawScope.policy.Name() != tc.expectedPolicy {
79				t.Errorf("Unexpected policy name. Have: %q wants %q", rawScope.policy.Name(), tc.expectedPolicy)
80			}
81		}
82	}
83}
84
85func TestManagerScope(t *testing.T) {
86	tcases := []struct {
87		description   string
88		scopeName     string
89		expectedScope string
90		expectedError error
91	}{
92		{
93			description:   "Topology Manager Scope is set to container",
94			scopeName:     "container",
95			expectedScope: "container",
96		},
97		{
98			description:   "Topology Manager Scope is set to pod",
99			scopeName:     "pod",
100			expectedScope: "pod",
101		},
102		{
103			description:   "Topology Manager Scope is set to unknown",
104			scopeName:     "unknown",
105			expectedError: fmt.Errorf("unknown scope: \"unknown\""),
106		},
107	}
108
109	for _, tc := range tcases {
110		mngr, err := NewManager(nil, "best-effort", tc.scopeName)
111
112		if tc.expectedError != nil {
113			if !strings.Contains(err.Error(), tc.expectedError.Error()) {
114				t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error())
115			}
116		} else {
117			rawMgr := mngr.(*manager)
118			if rawMgr.scope.Name() != tc.expectedScope {
119				t.Errorf("Unexpected scope name. Have: %q wants %q", rawMgr.scope, tc.expectedScope)
120			}
121		}
122	}
123}
124
125type mockHintProvider struct {
126	th map[string][]TopologyHint
127	//TODO: Add this field and add some tests to make sure things error out
128	//appropriately on allocation errors.
129	//allocateError error
130}
131
132func (m *mockHintProvider) GetTopologyHints(pod *v1.Pod, container *v1.Container) map[string][]TopologyHint {
133	return m.th
134}
135
136func (m *mockHintProvider) GetPodTopologyHints(pod *v1.Pod) map[string][]TopologyHint {
137	return m.th
138}
139
140func (m *mockHintProvider) Allocate(pod *v1.Pod, container *v1.Container) error {
141	//return allocateError
142	return nil
143}
144
145type mockPolicy struct {
146	nonePolicy
147	ph []map[string][]TopologyHint
148}
149
150func (p *mockPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) {
151	p.ph = providersHints
152	return TopologyHint{}, true
153}
154
155func TestAddHintProvider(t *testing.T) {
156	tcases := []struct {
157		name string
158		hp   []HintProvider
159	}{
160		{
161			name: "Add HintProvider",
162			hp: []HintProvider{
163				&mockHintProvider{},
164				&mockHintProvider{},
165				&mockHintProvider{},
166			},
167		},
168	}
169	mngr := manager{}
170	mngr.scope = NewContainerScope(NewNonePolicy())
171	for _, tc := range tcases {
172		for _, hp := range tc.hp {
173			mngr.AddHintProvider(hp)
174		}
175		if len(tc.hp) != len(mngr.scope.(*containerScope).hintProviders) {
176			t.Errorf("error")
177		}
178	}
179}
180
181func TestAdmit(t *testing.T) {
182	numaNodes := []int{0, 1}
183
184	tcases := []struct {
185		name     string
186		result   lifecycle.PodAdmitResult
187		qosClass v1.PodQOSClass
188		policy   Policy
189		hp       []HintProvider
190		expected bool
191	}{
192		{
193			name:     "QOSClass set as BestEffort. None Policy. No Hints.",
194			qosClass: v1.PodQOSBestEffort,
195			policy:   NewNonePolicy(),
196			hp:       []HintProvider{},
197			expected: true,
198		},
199		{
200			name:     "QOSClass set as Guaranteed. None Policy. No Hints.",
201			qosClass: v1.PodQOSGuaranteed,
202			policy:   NewNonePolicy(),
203			hp:       []HintProvider{},
204			expected: true,
205		},
206		{
207			name:     "QOSClass set as BestEffort. single-numa-node Policy. No Hints.",
208			qosClass: v1.PodQOSBestEffort,
209			policy:   NewRestrictedPolicy(numaNodes),
210			hp: []HintProvider{
211				&mockHintProvider{},
212			},
213			expected: true,
214		},
215		{
216			name:     "QOSClass set as BestEffort. Restricted Policy. No Hints.",
217			qosClass: v1.PodQOSBestEffort,
218			policy:   NewRestrictedPolicy(numaNodes),
219			hp: []HintProvider{
220				&mockHintProvider{},
221			},
222			expected: true,
223		},
224		{
225			name:     "QOSClass set as Guaranteed. BestEffort Policy. Preferred Affinity.",
226			qosClass: v1.PodQOSGuaranteed,
227			policy:   NewBestEffortPolicy(numaNodes),
228			hp: []HintProvider{
229				&mockHintProvider{
230					map[string][]TopologyHint{
231						"resource": {
232							{
233								NUMANodeAffinity: NewTestBitMask(0),
234								Preferred:        true,
235							},
236							{
237								NUMANodeAffinity: NewTestBitMask(0, 1),
238								Preferred:        false,
239							},
240						},
241					},
242				},
243			},
244			expected: true,
245		},
246		{
247			name:     "QOSClass set as Guaranteed. BestEffort Policy. More than one Preferred Affinity.",
248			qosClass: v1.PodQOSGuaranteed,
249			policy:   NewBestEffortPolicy(numaNodes),
250			hp: []HintProvider{
251				&mockHintProvider{
252					map[string][]TopologyHint{
253						"resource": {
254							{
255								NUMANodeAffinity: NewTestBitMask(0),
256								Preferred:        true,
257							},
258							{
259								NUMANodeAffinity: NewTestBitMask(1),
260								Preferred:        true,
261							},
262							{
263								NUMANodeAffinity: NewTestBitMask(0, 1),
264								Preferred:        false,
265							},
266						},
267					},
268				},
269			},
270			expected: true,
271		},
272		{
273			name:     "QOSClass set as Burstable. BestEffort Policy. More than one Preferred Affinity.",
274			qosClass: v1.PodQOSBurstable,
275			policy:   NewBestEffortPolicy(numaNodes),
276			hp: []HintProvider{
277				&mockHintProvider{
278					map[string][]TopologyHint{
279						"resource": {
280							{
281								NUMANodeAffinity: NewTestBitMask(0),
282								Preferred:        true,
283							},
284							{
285								NUMANodeAffinity: NewTestBitMask(1),
286								Preferred:        true,
287							},
288							{
289								NUMANodeAffinity: NewTestBitMask(0, 1),
290								Preferred:        false,
291							},
292						},
293					},
294				},
295			},
296			expected: true,
297		},
298		{
299			name:     "QOSClass set as Guaranteed. BestEffort Policy. No Preferred Affinity.",
300			qosClass: v1.PodQOSGuaranteed,
301			policy:   NewBestEffortPolicy(numaNodes),
302			hp: []HintProvider{
303				&mockHintProvider{
304					map[string][]TopologyHint{
305						"resource": {
306							{
307								NUMANodeAffinity: NewTestBitMask(0, 1),
308								Preferred:        false,
309							},
310						},
311					},
312				},
313			},
314			expected: true,
315		},
316		{
317			name:     "QOSClass set as Guaranteed. Restricted Policy. Preferred Affinity.",
318			qosClass: v1.PodQOSGuaranteed,
319			policy:   NewRestrictedPolicy(numaNodes),
320			hp: []HintProvider{
321				&mockHintProvider{
322					map[string][]TopologyHint{
323						"resource": {
324							{
325								NUMANodeAffinity: NewTestBitMask(0),
326								Preferred:        true,
327							},
328							{
329								NUMANodeAffinity: NewTestBitMask(0, 1),
330								Preferred:        false,
331							},
332						},
333					},
334				},
335			},
336			expected: true,
337		},
338		{
339			name:     "QOSClass set as Burstable. Restricted Policy. Preferred Affinity.",
340			qosClass: v1.PodQOSBurstable,
341			policy:   NewRestrictedPolicy(numaNodes),
342			hp: []HintProvider{
343				&mockHintProvider{
344					map[string][]TopologyHint{
345						"resource": {
346							{
347								NUMANodeAffinity: NewTestBitMask(0),
348								Preferred:        true,
349							},
350							{
351								NUMANodeAffinity: NewTestBitMask(0, 1),
352								Preferred:        false,
353							},
354						},
355					},
356				},
357			},
358			expected: true,
359		},
360		{
361			name:     "QOSClass set as Guaranteed. Restricted Policy. More than one Preferred affinity.",
362			qosClass: v1.PodQOSGuaranteed,
363			policy:   NewRestrictedPolicy(numaNodes),
364			hp: []HintProvider{
365				&mockHintProvider{
366					map[string][]TopologyHint{
367						"resource": {
368							{
369								NUMANodeAffinity: NewTestBitMask(0),
370								Preferred:        true,
371							},
372							{
373								NUMANodeAffinity: NewTestBitMask(1),
374								Preferred:        true,
375							},
376							{
377								NUMANodeAffinity: NewTestBitMask(0, 1),
378								Preferred:        false,
379							},
380						},
381					},
382				},
383			},
384			expected: true,
385		},
386		{
387			name:     "QOSClass set as Burstable. Restricted Policy. More than one Preferred affinity.",
388			qosClass: v1.PodQOSBurstable,
389			policy:   NewRestrictedPolicy(numaNodes),
390			hp: []HintProvider{
391				&mockHintProvider{
392					map[string][]TopologyHint{
393						"resource": {
394							{
395								NUMANodeAffinity: NewTestBitMask(0),
396								Preferred:        true,
397							},
398							{
399								NUMANodeAffinity: NewTestBitMask(1),
400								Preferred:        true,
401							},
402							{
403								NUMANodeAffinity: NewTestBitMask(0, 1),
404								Preferred:        false,
405							},
406						},
407					},
408				},
409			},
410			expected: true,
411		},
412		{
413			name:     "QOSClass set as Guaranteed. Restricted Policy. No Preferred affinity.",
414			qosClass: v1.PodQOSGuaranteed,
415			policy:   NewRestrictedPolicy(numaNodes),
416			hp: []HintProvider{
417				&mockHintProvider{
418					map[string][]TopologyHint{
419						"resource": {
420							{
421								NUMANodeAffinity: NewTestBitMask(0, 1),
422								Preferred:        false,
423							},
424						},
425					},
426				},
427			},
428			expected: false,
429		},
430		{
431			name:     "QOSClass set as Burstable. Restricted Policy. No Preferred affinity.",
432			qosClass: v1.PodQOSBurstable,
433			policy:   NewRestrictedPolicy(numaNodes),
434			hp: []HintProvider{
435				&mockHintProvider{
436					map[string][]TopologyHint{
437						"resource": {
438							{
439								NUMANodeAffinity: NewTestBitMask(0, 1),
440								Preferred:        false,
441							},
442						},
443					},
444				},
445			},
446			expected: false,
447		},
448	}
449	for _, tc := range tcases {
450		ctnScopeManager := manager{}
451		ctnScopeManager.scope = NewContainerScope(tc.policy)
452		ctnScopeManager.scope.(*containerScope).hintProviders = tc.hp
453
454		podScopeManager := manager{}
455		podScopeManager.scope = NewPodScope(tc.policy)
456		podScopeManager.scope.(*podScope).hintProviders = tc.hp
457
458		pod := &v1.Pod{
459			Spec: v1.PodSpec{
460				Containers: []v1.Container{
461					{
462						Resources: v1.ResourceRequirements{},
463					},
464				},
465			},
466			Status: v1.PodStatus{
467				QOSClass: tc.qosClass,
468			},
469		}
470
471		podAttr := lifecycle.PodAdmitAttributes{
472			Pod: pod,
473		}
474
475		// Container scope Admit
476		ctnActual := ctnScopeManager.Admit(&podAttr)
477		if ctnActual.Admit != tc.expected {
478			t.Errorf("Error occurred, expected Admit in result to be %v got %v", tc.expected, ctnActual.Admit)
479		}
480		if !ctnActual.Admit && ctnActual.Reason != ErrorTopologyAffinity {
481			t.Errorf("Error occurred, expected Reason in result to be %v got %v", ErrorTopologyAffinity, ctnActual.Reason)
482		}
483
484		// Pod scope Admit
485		podActual := podScopeManager.Admit(&podAttr)
486		if podActual.Admit != tc.expected {
487			t.Errorf("Error occurred, expected Admit in result to be %v got %v", tc.expected, podActual.Admit)
488		}
489		if !ctnActual.Admit && ctnActual.Reason != ErrorTopologyAffinity {
490			t.Errorf("Error occurred, expected Reason in result to be %v got %v", ErrorTopologyAffinity, ctnActual.Reason)
491		}
492	}
493}
494