1
16
17 package kuberuntime
18
19 import (
20 "context"
21 "testing"
22
23 "github.com/stretchr/testify/assert"
24
25 v1 "k8s.io/api/core/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/types"
28 utilfeature "k8s.io/apiserver/pkg/util/feature"
29 featuregatetesting "k8s.io/component-base/featuregate/testing"
30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
31 runtimetesting "k8s.io/cri-api/pkg/apis/testing"
32 "k8s.io/kubernetes/pkg/features"
33 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
34 "k8s.io/utils/ptr"
35 )
36
37 type podStatusProviderFunc func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error)
38
39 func (f podStatusProviderFunc) GetPodStatus(_ context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) {
40 return f(uid, name, namespace)
41 }
42
43 func TestIsInitContainerFailed(t *testing.T) {
44 tests := []struct {
45 status *kubecontainer.Status
46 isFailed bool
47 description string
48 }{
49 {
50 status: &kubecontainer.Status{
51 State: kubecontainer.ContainerStateExited,
52 ExitCode: 1,
53 },
54 isFailed: true,
55 description: "Init container in exited state and non-zero exit code should return true",
56 },
57 {
58 status: &kubecontainer.Status{
59 State: kubecontainer.ContainerStateUnknown,
60 },
61 isFailed: true,
62 description: "Init container in unknown state should return true",
63 },
64 {
65 status: &kubecontainer.Status{
66 Reason: "OOMKilled",
67 ExitCode: 0,
68 },
69 isFailed: true,
70 description: "Init container which reason is OOMKilled should return true",
71 },
72 {
73 status: &kubecontainer.Status{
74 State: kubecontainer.ContainerStateExited,
75 ExitCode: 0,
76 },
77 isFailed: false,
78 description: "Init container in exited state and zero exit code should return false",
79 },
80 {
81 status: &kubecontainer.Status{
82 State: kubecontainer.ContainerStateRunning,
83 },
84 isFailed: false,
85 description: "Init container in running state should return false",
86 },
87 {
88 status: &kubecontainer.Status{
89 State: kubecontainer.ContainerStateCreated,
90 },
91 isFailed: false,
92 description: "Init container in created state should return false",
93 },
94 }
95 for i, test := range tests {
96 isFailed := isInitContainerFailed(test.status)
97 assert.Equal(t, test.isFailed, isFailed, "TestCase[%d]: %s", i, test.description)
98 }
99 }
100
101 func TestStableKey(t *testing.T) {
102 container := &v1.Container{
103 Name: "test_container",
104 Image: "foo/image:v1",
105 }
106 pod := &v1.Pod{
107 ObjectMeta: metav1.ObjectMeta{
108 Name: "test_pod",
109 Namespace: "test_pod_namespace",
110 UID: "test_pod_uid",
111 },
112 Spec: v1.PodSpec{
113 Containers: []v1.Container{*container},
114 },
115 }
116 oldKey := getStableKey(pod, container)
117
118
119 container.Image = "foo/image:v2"
120 newKey := getStableKey(pod, container)
121 assert.NotEqual(t, oldKey, newKey)
122 }
123
124 func TestToKubeContainer(t *testing.T) {
125 c := &runtimeapi.Container{
126 Id: "test-id",
127 Metadata: &runtimeapi.ContainerMetadata{
128 Name: "test-name",
129 Attempt: 1,
130 },
131 Image: &runtimeapi.ImageSpec{Image: "test-image"},
132 ImageId: "test-image-id",
133 ImageRef: "test-image-ref",
134 State: runtimeapi.ContainerState_CONTAINER_RUNNING,
135 Annotations: map[string]string{
136 containerHashLabel: "1234",
137 },
138 }
139 expect := &kubecontainer.Container{
140 ID: kubecontainer.ContainerID{
141 Type: runtimetesting.FakeRuntimeName,
142 ID: "test-id",
143 },
144 Name: "test-name",
145 ImageID: "test-image-id",
146 ImageRef: "test-image-ref",
147 Image: "test-image",
148 ImageRuntimeHandler: "",
149 Hash: uint64(0x1234),
150 State: kubecontainer.ContainerStateRunning,
151 }
152
153 _, _, m, err := createTestRuntimeManager()
154 assert.NoError(t, err)
155 got, err := m.toKubeContainer(c)
156 assert.NoError(t, err)
157 assert.Equal(t, expect, got)
158
159
160 _, err = m.toKubeContainer(nil)
161 assert.Error(t, err)
162 _, err = m.sandboxToKubeContainer(nil)
163 assert.Error(t, err)
164 }
165
166 func TestToKubeContainerWithRuntimeHandlerInImageSpecCri(t *testing.T) {
167 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClassInImageCriAPI, true)()
168 c := &runtimeapi.Container{
169 Id: "test-id",
170 Metadata: &runtimeapi.ContainerMetadata{
171 Name: "test-name",
172 Attempt: 1,
173 },
174 Image: &runtimeapi.ImageSpec{Image: "test-image", RuntimeHandler: "test-runtimeHandler"},
175 ImageId: "test-image-id",
176 ImageRef: "test-image-ref",
177 State: runtimeapi.ContainerState_CONTAINER_RUNNING,
178 Annotations: map[string]string{
179 containerHashLabel: "1234",
180 },
181 }
182 expect := &kubecontainer.Container{
183 ID: kubecontainer.ContainerID{
184 Type: runtimetesting.FakeRuntimeName,
185 ID: "test-id",
186 },
187 Name: "test-name",
188 ImageID: "test-image-id",
189 ImageRef: "test-image-ref",
190 Image: "test-image",
191 ImageRuntimeHandler: "test-runtimeHandler",
192 Hash: uint64(0x1234),
193 State: kubecontainer.ContainerStateRunning,
194 }
195
196 _, _, m, err := createTestRuntimeManager()
197 assert.NoError(t, err)
198 got, err := m.toKubeContainer(c)
199 assert.NoError(t, err)
200 assert.Equal(t, expect, got)
201
202
203 _, err = m.toKubeContainer(nil)
204 assert.Error(t, err)
205 _, err = m.sandboxToKubeContainer(nil)
206 assert.Error(t, err)
207 }
208
209 func TestGetImageUser(t *testing.T) {
210 _, i, m, err := createTestRuntimeManager()
211 assert.NoError(t, err)
212
213 type image struct {
214 name string
215 uid *runtimeapi.Int64Value
216 username string
217 }
218
219 type imageUserValues struct {
220
221
222 uid interface{}
223 username string
224 err error
225 }
226
227 tests := []struct {
228 description string
229 originalImage image
230 expectedImageUserValues imageUserValues
231 }{
232 {
233 "image without username and uid should return (new(int64), \"\", nil)",
234 image{
235 name: "test-image-ref1",
236 uid: (*runtimeapi.Int64Value)(nil),
237 username: "",
238 },
239 imageUserValues{
240 uid: int64(0),
241 username: "",
242 err: nil,
243 },
244 },
245 {
246 "image with username and no uid should return ((*int64)nil, imageStatus.Username, nil)",
247 image{
248 name: "test-image-ref2",
249 uid: (*runtimeapi.Int64Value)(nil),
250 username: "testUser",
251 },
252 imageUserValues{
253 uid: (*int64)(nil),
254 username: "testUser",
255 err: nil,
256 },
257 },
258 {
259 "image with uid should return (*int64, \"\", nil)",
260 image{
261 name: "test-image-ref3",
262 uid: &runtimeapi.Int64Value{
263 Value: 2,
264 },
265 username: "whatever",
266 },
267 imageUserValues{
268 uid: int64(2),
269 username: "",
270 err: nil,
271 },
272 },
273 }
274
275 i.SetFakeImages([]string{"test-image-ref1", "test-image-ref2", "test-image-ref3"})
276 for j, test := range tests {
277 ctx := context.Background()
278 i.Images[test.originalImage.name].Username = test.originalImage.username
279 i.Images[test.originalImage.name].Uid = test.originalImage.uid
280
281 uid, username, err := m.getImageUser(ctx, test.originalImage.name)
282 assert.NoError(t, err, "TestCase[%d]", j)
283
284 if test.expectedImageUserValues.uid == (*int64)(nil) {
285 assert.Equal(t, test.expectedImageUserValues.uid, uid, "TestCase[%d]", j)
286 } else {
287 assert.Equal(t, test.expectedImageUserValues.uid, *uid, "TestCase[%d]", j)
288 }
289 assert.Equal(t, test.expectedImageUserValues.username, username, "TestCase[%d]", j)
290 }
291 }
292
293 func TestToRuntimeProtocol(t *testing.T) {
294 for _, test := range []struct {
295 name string
296 protocol string
297 expected runtimeapi.Protocol
298 }{
299 {
300 name: "TCP protocol",
301 protocol: "TCP",
302 expected: runtimeapi.Protocol_TCP,
303 },
304 {
305 name: "UDP protocol",
306 protocol: "UDP",
307 expected: runtimeapi.Protocol_UDP,
308 },
309 {
310 name: "SCTP protocol",
311 protocol: "SCTP",
312 expected: runtimeapi.Protocol_SCTP,
313 },
314 {
315 name: "unknown protocol",
316 protocol: "unknown",
317 expected: runtimeapi.Protocol_TCP,
318 },
319 } {
320 t.Run(test.name, func(t *testing.T) {
321 if result := toRuntimeProtocol(v1.Protocol(test.protocol)); result != test.expected {
322 t.Errorf("expected %d but got %d", test.expected, result)
323 }
324 })
325 }
326 }
327
328 func TestToKubeContainerState(t *testing.T) {
329 for _, test := range []struct {
330 name string
331 state int32
332 expected kubecontainer.State
333 }{
334 {
335 name: "container created",
336 state: 0,
337 expected: kubecontainer.ContainerStateCreated,
338 },
339 {
340 name: "container running",
341 state: 1,
342 expected: kubecontainer.ContainerStateRunning,
343 },
344 {
345 name: "container exited",
346 state: 2,
347 expected: kubecontainer.ContainerStateExited,
348 },
349 {
350 name: "unknown state",
351 state: 3,
352 expected: kubecontainer.ContainerStateUnknown,
353 },
354 {
355 name: "not supported state",
356 state: 4,
357 expected: kubecontainer.ContainerStateUnknown,
358 },
359 } {
360 t.Run(test.name, func(t *testing.T) {
361 if result := toKubeContainerState(runtimeapi.ContainerState(test.state)); result != test.expected {
362 t.Errorf("expected %s but got %s", test.expected, result)
363 }
364 })
365 }
366 }
367
368 func TestGetAppArmorProfile(t *testing.T) {
369 tests := []struct {
370 name string
371 podProfile *v1.AppArmorProfile
372 expectedProfile *runtimeapi.SecurityProfile
373 expectedOldProfile string
374 expectError bool
375 }{{
376 name: "no appArmor",
377 expectedProfile: nil,
378 }, {
379 name: "runtime default",
380 podProfile: &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeRuntimeDefault},
381 expectedProfile: &runtimeapi.SecurityProfile{
382 ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
383 },
384 expectedOldProfile: "runtime/default",
385 }, {
386 name: "unconfined",
387 podProfile: &v1.AppArmorProfile{Type: v1.AppArmorProfileTypeUnconfined},
388 expectedProfile: &runtimeapi.SecurityProfile{
389 ProfileType: runtimeapi.SecurityProfile_Unconfined,
390 },
391 expectedOldProfile: "unconfined",
392 }, {
393 name: "localhost",
394 podProfile: &v1.AppArmorProfile{
395 Type: v1.AppArmorProfileTypeLocalhost,
396 LocalhostProfile: ptr.To("test"),
397 },
398 expectedProfile: &runtimeapi.SecurityProfile{
399 ProfileType: runtimeapi.SecurityProfile_Localhost,
400 LocalhostRef: "test",
401 },
402 expectedOldProfile: "localhost/test",
403 }, {
404 name: "invalid localhost",
405 podProfile: &v1.AppArmorProfile{
406 Type: v1.AppArmorProfileTypeLocalhost,
407 },
408 expectError: true,
409 }, {
410 name: "invalid type",
411 podProfile: &v1.AppArmorProfile{
412 Type: "foo",
413 },
414 expectError: true,
415 }}
416
417 for _, test := range tests {
418 t.Run(test.name, func(t *testing.T) {
419 pod := v1.Pod{
420 ObjectMeta: metav1.ObjectMeta{
421 Name: "bar",
422 },
423 Spec: v1.PodSpec{
424 SecurityContext: &v1.PodSecurityContext{
425 AppArmorProfile: test.podProfile,
426 },
427 Containers: []v1.Container{{Name: "foo"}},
428 },
429 }
430
431 actual, actualOld, err := getAppArmorProfile(&pod, &pod.Spec.Containers[0])
432
433 if test.expectError {
434 assert.Error(t, err)
435 } else {
436 assert.NoError(t, err)
437 }
438
439 assert.Equal(t, test.expectedProfile, actual, "AppArmor profile")
440 assert.Equal(t, test.expectedOldProfile, actualOld, "old (deprecated) profile string")
441 })
442 }
443 }
444
View as plain text