1
16
17 package kuberuntime
18
19 import (
20 "path/filepath"
21 "testing"
22
23 "github.com/stretchr/testify/assert"
24 "github.com/stretchr/testify/require"
25
26 v1 "k8s.io/api/core/v1"
27 utilfeature "k8s.io/apiserver/pkg/util/feature"
28 featuregatetesting "k8s.io/component-base/featuregate/testing"
29 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
30 "k8s.io/kubernetes/pkg/features"
31 "k8s.io/kubernetes/pkg/kubelet/cm"
32 )
33
34 func seccompLocalhostRef(profileName string) string {
35 return filepath.Join(fakeSeccompProfileRoot, profileName)
36 }
37
38 func TestMilliCPUToQuota(t *testing.T) {
39 for _, testCase := range []struct {
40 msg string
41 input int64
42 expected int64
43 period uint64
44 }{
45 {
46 msg: "all-zero",
47 input: int64(0),
48 expected: int64(0),
49 period: uint64(0),
50 },
51 {
52 msg: "5 input default quota and period",
53 input: int64(5),
54 expected: int64(1000),
55 period: uint64(100000),
56 },
57 {
58 msg: "9 input default quota and period",
59 input: int64(9),
60 expected: int64(1000),
61 period: uint64(100000),
62 },
63 {
64 msg: "10 input default quota and period",
65 input: int64(10),
66 expected: int64(1000),
67 period: uint64(100000),
68 },
69 {
70 msg: "200 input 20k quota and default period",
71 input: int64(200),
72 expected: int64(20000),
73 period: uint64(100000),
74 },
75 {
76 msg: "500 input 50k quota and default period",
77 input: int64(500),
78 expected: int64(50000),
79 period: uint64(100000),
80 },
81 {
82 msg: "1k input 100k quota and default period",
83 input: int64(1000),
84 expected: int64(100000),
85 period: uint64(100000),
86 },
87 {
88 msg: "1500 input 150k quota and default period",
89 input: int64(1500),
90 expected: int64(150000),
91 period: uint64(100000),
92 }} {
93 t.Run(testCase.msg, func(t *testing.T) {
94 quota := milliCPUToQuota(testCase.input, int64(testCase.period))
95 if quota != testCase.expected {
96 t.Errorf("Input %v and %v, expected quota %v, but got quota %v", testCase.input, testCase.period, testCase.expected, quota)
97 }
98 })
99 }
100 }
101
102 func TestMilliCPUToQuotaWithCustomCPUCFSQuotaPeriod(t *testing.T) {
103 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CPUCFSQuotaPeriod, true)()
104
105 for _, testCase := range []struct {
106 msg string
107 input int64
108 expected int64
109 period uint64
110 }{
111 {
112 msg: "all-zero",
113 input: int64(0),
114 expected: int64(0),
115 period: uint64(0),
116 },
117 {
118 msg: "5 input default quota and period",
119 input: int64(5),
120 expected: minQuotaPeriod,
121 period: uint64(100000),
122 },
123 {
124 msg: "9 input default quota and period",
125 input: int64(9),
126 expected: minQuotaPeriod,
127 period: uint64(100000),
128 },
129 {
130 msg: "10 input default quota and period",
131 input: int64(10),
132 expected: minQuotaPeriod,
133 period: uint64(100000),
134 },
135 {
136 msg: "200 input 20k quota and default period",
137 input: int64(200),
138 expected: int64(20000),
139 period: uint64(100000),
140 },
141 {
142 msg: "500 input 50k quota and default period",
143 input: int64(500),
144 expected: int64(50000),
145 period: uint64(100000),
146 },
147 {
148 msg: "1k input 100k quota and default period",
149 input: int64(1000),
150 expected: int64(100000),
151 period: uint64(100000),
152 },
153 {
154 msg: "1500 input 150k quota and default period",
155 input: int64(1500),
156 expected: int64(150000),
157 period: uint64(100000),
158 },
159 {
160 msg: "5 input 10k period and default quota expected",
161 input: int64(5),
162 period: uint64(10000),
163 expected: minQuotaPeriod,
164 },
165 {
166 msg: "5 input 5k period and default quota expected",
167 input: int64(5),
168 period: uint64(5000),
169 expected: minQuotaPeriod,
170 },
171 {
172 msg: "9 input 10k period and default quota expected",
173 input: int64(9),
174 period: uint64(10000),
175 expected: minQuotaPeriod,
176 },
177 {
178 msg: "10 input 200k period and 2000 quota expected",
179 input: int64(10),
180 period: uint64(200000),
181 expected: int64(2000),
182 },
183 {
184 msg: "200 input 200k period and 40k quota",
185 input: int64(200),
186 period: uint64(200000),
187 expected: int64(40000),
188 },
189 {
190 msg: "500 input 20k period and 20k expected quota",
191 input: int64(500),
192 period: uint64(20000),
193 expected: int64(10000),
194 },
195 {
196 msg: "1000 input 10k period and 10k expected quota",
197 input: int64(1000),
198 period: uint64(10000),
199 expected: int64(10000),
200 },
201 {
202 msg: "1500 input 5000 period and 7500 expected quota",
203 input: int64(1500),
204 period: uint64(5000),
205 expected: int64(7500),
206 }} {
207 t.Run(testCase.msg, func(t *testing.T) {
208 quota := milliCPUToQuota(testCase.input, int64(testCase.period))
209 if quota != testCase.expected {
210 t.Errorf("Input %v and %v, expected quota %v, but got quota %v", testCase.input, testCase.period, testCase.expected, quota)
211 }
212 })
213 }
214 }
215
216 func TestGetSeccompProfile(t *testing.T) {
217 _, _, m, err := createTestRuntimeManager()
218 require.NoError(t, err)
219
220 unconfinedProfile := &runtimeapi.SecurityProfile{
221 ProfileType: runtimeapi.SecurityProfile_Unconfined,
222 }
223
224 runtimeDefaultProfile := &runtimeapi.SecurityProfile{
225 ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
226 }
227
228 tests := []struct {
229 description string
230 annotation map[string]string
231 podSc *v1.PodSecurityContext
232 containerSc *v1.SecurityContext
233 containerName string
234 expectedProfile *runtimeapi.SecurityProfile
235 expectedError string
236 }{
237 {
238 description: "no seccomp should return unconfined",
239 expectedProfile: unconfinedProfile,
240 },
241 {
242 description: "pod seccomp profile set to unconfined returns unconfined",
243 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
244 expectedProfile: unconfinedProfile,
245 },
246 {
247 description: "container seccomp profile set to unconfined returns unconfined",
248 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
249 expectedProfile: unconfinedProfile,
250 },
251 {
252 description: "pod seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
253 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
254 expectedProfile: runtimeDefaultProfile,
255 },
256 {
257 description: "container seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
258 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
259 expectedProfile: runtimeDefaultProfile,
260 },
261 {
262 description: "pod seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
263 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename")}},
264 expectedProfile: &runtimeapi.SecurityProfile{
265 ProfileType: runtimeapi.SecurityProfile_Localhost,
266 LocalhostRef: seccompLocalhostRef("filename"),
267 },
268 },
269 {
270 description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
271 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
272 expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
273 },
274 {
275 description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
276 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
277 expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
278 },
279 {
280 description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
281 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename2")}},
282 expectedProfile: &runtimeapi.SecurityProfile{
283 ProfileType: runtimeapi.SecurityProfile_Localhost,
284 LocalhostRef: seccompLocalhostRef("filename2"),
285 },
286 },
287 {
288 description: "prioritise container field over pod field",
289 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
290 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
291 expectedProfile: runtimeDefaultProfile,
292 },
293 {
294 description: "prioritise container field over pod field",
295 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
296 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-cont-profile.json")}},
297 containerName: "container1",
298 expectedProfile: &runtimeapi.SecurityProfile{
299 ProfileType: runtimeapi.SecurityProfile_Localhost,
300 LocalhostRef: seccompLocalhostRef("field-cont-profile.json"),
301 },
302 },
303 }
304
305 for i, test := range tests {
306 seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false)
307 if test.expectedError != "" {
308 assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
309 } else {
310 assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
311 assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
312 }
313 }
314 }
315
316 func TestGetSeccompProfileDefaultSeccomp(t *testing.T) {
317 _, _, m, err := createTestRuntimeManager()
318 require.NoError(t, err)
319
320 unconfinedProfile := &runtimeapi.SecurityProfile{
321 ProfileType: runtimeapi.SecurityProfile_Unconfined,
322 }
323
324 runtimeDefaultProfile := &runtimeapi.SecurityProfile{
325 ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
326 }
327
328 tests := []struct {
329 description string
330 annotation map[string]string
331 podSc *v1.PodSecurityContext
332 containerSc *v1.SecurityContext
333 containerName string
334 expectedProfile *runtimeapi.SecurityProfile
335 expectedError string
336 }{
337 {
338 description: "no seccomp should return RuntimeDefault",
339 expectedProfile: runtimeDefaultProfile,
340 },
341 {
342 description: "pod seccomp profile set to unconfined returns unconfined",
343 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
344 expectedProfile: unconfinedProfile,
345 },
346 {
347 description: "container seccomp profile set to unconfined returns unconfined",
348 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
349 expectedProfile: unconfinedProfile,
350 },
351 {
352 description: "pod seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
353 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
354 expectedProfile: runtimeDefaultProfile,
355 },
356 {
357 description: "container seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
358 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
359 expectedProfile: runtimeDefaultProfile,
360 },
361 {
362 description: "pod seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
363 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename")}},
364 expectedProfile: &runtimeapi.SecurityProfile{
365 ProfileType: runtimeapi.SecurityProfile_Localhost,
366 LocalhostRef: seccompLocalhostRef("filename"),
367 },
368 },
369 {
370 description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
371 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
372 expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
373 },
374 {
375 description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
376 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
377 expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
378 },
379 {
380 description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
381 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename2")}},
382 expectedProfile: &runtimeapi.SecurityProfile{
383 ProfileType: runtimeapi.SecurityProfile_Localhost,
384 LocalhostRef: seccompLocalhostRef("filename2"),
385 },
386 },
387 {
388 description: "prioritise container field over pod field",
389 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
390 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
391 expectedProfile: runtimeDefaultProfile,
392 },
393 {
394 description: "prioritise container field over pod field",
395 podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
396 containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-cont-profile.json")}},
397 containerName: "container1",
398 expectedProfile: &runtimeapi.SecurityProfile{
399 ProfileType: runtimeapi.SecurityProfile_Localhost,
400 LocalhostRef: seccompLocalhostRef("field-cont-profile.json"),
401 },
402 },
403 }
404
405 for i, test := range tests {
406 seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, true)
407 if test.expectedError != "" {
408 assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
409 } else {
410 assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
411 assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
412 }
413 }
414 }
415
416 func getLocal(v string) *string {
417 return &v
418 }
419
420 func TestSharesToMilliCPU(t *testing.T) {
421 knownMilliCPUToShares := map[int64]int64{
422 0: 2,
423 1: 2,
424 2: 2,
425 3: 3,
426 4: 4,
427 32: 32,
428 64: 65,
429 100: 102,
430 250: 256,
431 500: 512,
432 1000: 1024,
433 1500: 1536,
434 2000: 2048,
435 }
436
437 t.Run("sharesToMilliCPUTest", func(t *testing.T) {
438 var testMilliCPU int64
439 for testMilliCPU = 0; testMilliCPU <= 2000; testMilliCPU++ {
440 shares := int64(cm.MilliCPUToShares(testMilliCPU))
441 if expectedShares, found := knownMilliCPUToShares[testMilliCPU]; found {
442 if shares != expectedShares {
443 t.Errorf("Test milliCPIToShares: Input milliCPU %v, expected shares %v, but got %v", testMilliCPU, expectedShares, shares)
444 }
445 }
446 expectedMilliCPU := testMilliCPU
447 if testMilliCPU < 2 {
448 expectedMilliCPU = 2
449 }
450 milliCPU := sharesToMilliCPU(shares)
451 if milliCPU != expectedMilliCPU {
452 t.Errorf("Test sharesToMilliCPU: Input shares %v, expected milliCPU %v, but got %v", shares, expectedMilliCPU, milliCPU)
453 }
454 }
455 })
456 }
457
458 func TestQuotaToMilliCPU(t *testing.T) {
459 for _, tc := range []struct {
460 name string
461 quota int64
462 period int64
463 expected int64
464 }{
465 {
466 name: "50m",
467 quota: int64(5000),
468 period: int64(100000),
469 expected: int64(50),
470 },
471 {
472 name: "750m",
473 quota: int64(75000),
474 period: int64(100000),
475 expected: int64(750),
476 },
477 {
478 name: "1000m",
479 quota: int64(100000),
480 period: int64(100000),
481 expected: int64(1000),
482 },
483 {
484 name: "1500m",
485 quota: int64(150000),
486 period: int64(100000),
487 expected: int64(1500),
488 }} {
489 t.Run(tc.name, func(t *testing.T) {
490 milliCPU := quotaToMilliCPU(tc.quota, tc.period)
491 if milliCPU != tc.expected {
492 t.Errorf("Test %s: Input quota %v and period %v, expected milliCPU %v, but got %v", tc.name, tc.quota, tc.period, tc.expected, milliCPU)
493 }
494 })
495 }
496 }
497
View as plain text