1
16
17 package runtime
18
19 import (
20 "net"
21 "os"
22 "reflect"
23 "runtime"
24 "testing"
25
26 "github.com/pkg/errors"
27
28 "k8s.io/utils/exec"
29 fakeexec "k8s.io/utils/exec/testing"
30
31 "k8s.io/kubernetes/cmd/kubeadm/app/constants"
32 )
33
34 func TestNewContainerRuntime(t *testing.T) {
35 execLookPathOK := &fakeexec.FakeExec{
36 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
37 }
38 execLookPathErr := &fakeexec.FakeExec{
39 LookPathFunc: func(cmd string) (string, error) { return "", errors.Errorf("%s not found", cmd) },
40 }
41 cases := []struct {
42 name string
43 execer *fakeexec.FakeExec
44 isError bool
45 }{
46 {"valid: crictl present", execLookPathOK, false},
47 {"invalid: no crictl", execLookPathErr, true},
48 }
49
50 for _, tc := range cases {
51 t.Run(tc.name, func(t *testing.T) {
52 _, err := NewContainerRuntime(tc.execer, "unix:///some/socket.sock")
53 if err != nil {
54 if !tc.isError {
55 t.Fatalf("unexpected NewContainerRuntime error. error: %v", err)
56 }
57 return
58 }
59 if tc.isError && err == nil {
60 t.Fatal("unexpected NewContainerRuntime success")
61 }
62 })
63 }
64 }
65
66 func genFakeActions(fcmd *fakeexec.FakeCmd, num int) []fakeexec.FakeCommandAction {
67 var actions []fakeexec.FakeCommandAction
68 for i := 0; i < num; i++ {
69 actions = append(actions, func(cmd string, args ...string) exec.Cmd {
70 return fakeexec.InitFakeCmd(fcmd, cmd, args...)
71 })
72 }
73 return actions
74 }
75
76 func TestIsRunning(t *testing.T) {
77 fcmd := fakeexec.FakeCmd{
78 CombinedOutputScript: []fakeexec.FakeAction{
79 func() ([]byte, []byte, error) { return nil, nil, nil },
80 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
81 func() ([]byte, []byte, error) { return nil, nil, nil },
82 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
83 },
84 }
85
86 criExecer := &fakeexec.FakeExec{
87 CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
88 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
89 }
90
91 cases := []struct {
92 name string
93 criSocket string
94 execer *fakeexec.FakeExec
95 isError bool
96 }{
97 {"valid: CRI-O is running", "unix:///var/run/crio/crio.sock", criExecer, false},
98 {"invalid: CRI-O is not running", "unix:///var/run/crio/crio.sock", criExecer, true},
99 }
100
101 for _, tc := range cases {
102 t.Run(tc.name, func(t *testing.T) {
103 runtime, err := NewContainerRuntime(tc.execer, tc.criSocket)
104 if err != nil {
105 t.Fatalf("unexpected NewContainerRuntime error: %v", err)
106 }
107 isRunning := runtime.IsRunning()
108 if tc.isError && isRunning == nil {
109 t.Error("unexpected IsRunning() success")
110 }
111 if !tc.isError && isRunning != nil {
112 t.Error("unexpected IsRunning() error")
113 }
114 })
115 }
116 }
117
118 func TestListKubeContainers(t *testing.T) {
119 fcmd := fakeexec.FakeCmd{
120 CombinedOutputScript: []fakeexec.FakeAction{
121 func() ([]byte, []byte, error) { return []byte("k8s_p1\nk8s_p2"), nil, nil },
122 func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
123 func() ([]byte, []byte, error) { return []byte("k8s_p1\nk8s_p2"), nil, nil },
124 },
125 }
126 execer := &fakeexec.FakeExec{
127 CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
128 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
129 }
130
131 cases := []struct {
132 name string
133 criSocket string
134 isError bool
135 }{
136 {"valid: list containers using CRI socket url", "unix:///var/run/crio/crio.sock", false},
137 {"invalid: list containers using CRI socket url", "unix:///var/run/crio/crio.sock", true},
138 }
139
140 for _, tc := range cases {
141 t.Run(tc.name, func(t *testing.T) {
142 runtime, err := NewContainerRuntime(execer, tc.criSocket)
143 if err != nil {
144 t.Fatalf("unexpected NewContainerRuntime error: %v", err)
145 }
146
147 containers, err := runtime.ListKubeContainers()
148 if tc.isError {
149 if err == nil {
150 t.Errorf("unexpected ListKubeContainers success")
151 }
152 return
153 } else if err != nil {
154 t.Errorf("unexpected ListKubeContainers error: %v", err)
155 }
156
157 if !reflect.DeepEqual(containers, []string{"k8s_p1", "k8s_p2"}) {
158 t.Errorf("unexpected ListKubeContainers output: %v", containers)
159 }
160 })
161 }
162 }
163
164 func TestSandboxImage(t *testing.T) {
165 fcmd := fakeexec.FakeCmd{
166 CombinedOutputScript: []fakeexec.FakeAction{
167 func() ([]byte, []byte, error) { return []byte("registry.k8s.io/pause:3.9"), nil, nil },
168 func() ([]byte, []byte, error) { return []byte("registry.k8s.io/pause:3.9\n"), nil, nil },
169 func() ([]byte, []byte, error) { return nil, nil, nil },
170 func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
171 },
172 }
173
174 execer := &fakeexec.FakeExec{
175 CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
176 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
177 }
178
179 cases := []struct {
180 name string
181 expected string
182 isError bool
183 }{
184 {"valid: read sandbox image normally", "registry.k8s.io/pause:3.9", false},
185 {"valid: read sandbox image with leading/trailing white spaces", "registry.k8s.io/pause:3.9", false},
186 {"invalid: read empty sandbox image", "", true},
187 {"invalid: failed to read sandbox image", "", true},
188 }
189
190 for _, tc := range cases {
191 t.Run(tc.name, func(t *testing.T) {
192 runtime, err := NewContainerRuntime(execer, "unix:///some/socket.sock")
193 if err != nil {
194 t.Fatalf("unexpected NewContainerRuntime error: %v", err)
195 }
196
197 sandboxImage, err := runtime.SandboxImage()
198 if tc.isError {
199 if err == nil {
200 t.Errorf("unexpected SandboxImage success")
201 }
202 return
203 } else if err != nil {
204 t.Errorf("unexpected SandboxImage error: %v", err)
205 }
206
207 if sandboxImage != tc.expected {
208 t.Errorf("expected sandbox image %v, but got %v", tc.expected, sandboxImage)
209 }
210 })
211 }
212 }
213
214 func TestRemoveContainers(t *testing.T) {
215 fakeOK := func() ([]byte, []byte, error) { return nil, nil, nil }
216 fakeErr := func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} }
217 fcmd := fakeexec.FakeCmd{
218 CombinedOutputScript: []fakeexec.FakeAction{
219 fakeOK, fakeOK, fakeOK, fakeOK, fakeOK, fakeOK,
220 fakeOK, fakeOK, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeOK,
221 fakeErr, fakeErr, fakeErr, fakeErr, fakeErr, fakeOK, fakeOK, fakeOK, fakeOK,
222 },
223 }
224 execer := &fakeexec.FakeExec{
225 CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
226 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
227 }
228
229 cases := []struct {
230 name string
231 criSocket string
232 containers []string
233 isError bool
234 }{
235 {"valid: remove containers using CRI", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, false},
236 {"invalid: CRI rmp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true},
237 {"invalid: CRI stopp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true},
238 }
239
240 for _, tc := range cases {
241 t.Run(tc.name, func(t *testing.T) {
242 runtime, err := NewContainerRuntime(execer, tc.criSocket)
243 if err != nil {
244 t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
245 }
246
247 err = runtime.RemoveContainers(tc.containers)
248 if !tc.isError && err != nil {
249 t.Errorf("unexpected RemoveContainers errors: %v, criSocket: %s, containers: %v", err, tc.criSocket, tc.containers)
250 }
251 if tc.isError && err == nil {
252 t.Errorf("unexpected RemoveContainers success, criSocket: %s, containers: %v", tc.criSocket, tc.containers)
253 }
254 })
255 }
256 }
257
258 func TestPullImage(t *testing.T) {
259 fcmd := fakeexec.FakeCmd{
260 CombinedOutputScript: []fakeexec.FakeAction{
261 func() ([]byte, []byte, error) { return nil, nil, nil },
262
263 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
264 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
265 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
266 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
267 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
268 func() ([]byte, []byte, error) { return nil, nil, nil },
269
270 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
271 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
272 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
273 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
274 func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
275 },
276 }
277 execer := &fakeexec.FakeExec{
278 CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
279 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
280 }
281
282 cases := []struct {
283 name string
284 criSocket string
285 image string
286 isError bool
287 }{
288 {"valid: pull image using CRI", "unix:///var/run/crio/crio.sock", "image1", false},
289 {"invalid: CRI pull error", "unix:///var/run/crio/crio.sock", "image2", true},
290 }
291
292 for _, tc := range cases {
293 t.Run(tc.name, func(t *testing.T) {
294 runtime, err := NewContainerRuntime(execer, tc.criSocket)
295 if err != nil {
296 t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
297 }
298
299 err = runtime.PullImage(tc.image)
300 if !tc.isError && err != nil {
301 t.Errorf("unexpected PullImage error: %v, criSocket: %s, image: %s", err, tc.criSocket, tc.image)
302 }
303 if tc.isError && err == nil {
304 t.Errorf("unexpected PullImage success, criSocket: %s, image: %s", tc.criSocket, tc.image)
305 }
306 })
307 }
308 }
309
310 func TestImageExists(t *testing.T) {
311 fcmd := fakeexec.FakeCmd{
312 RunScript: []fakeexec.FakeAction{
313 func() ([]byte, []byte, error) { return nil, nil, nil },
314 func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
315 func() ([]byte, []byte, error) { return nil, nil, nil },
316 func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
317 },
318 }
319 execer := &fakeexec.FakeExec{
320 CommandScript: genFakeActions(&fcmd, len(fcmd.RunScript)),
321 LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
322 }
323
324 cases := []struct {
325 name string
326 criSocket string
327 image string
328 result bool
329 }{
330 {"valid: test if image exists using CRI", "unix:///var/run/crio/crio.sock", "image1", false},
331 {"invalid: CRI inspect failure", "unix:///var/run/crio/crio.sock", "image2", true},
332 }
333
334 for _, tc := range cases {
335 t.Run(tc.name, func(t *testing.T) {
336 runtime, err := NewContainerRuntime(execer, tc.criSocket)
337 if err != nil {
338 t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
339 }
340
341 result, err := runtime.ImageExists(tc.image)
342 if !tc.result != result {
343 t.Errorf("unexpected ImageExists result: %t, criSocket: %s, image: %s, expected result: %t", err, tc.criSocket, tc.image, tc.result)
344 }
345 })
346 }
347 }
348
349 func TestIsExistingSocket(t *testing.T) {
350
351 if runtime.GOOS == "windows" {
352 return
353 }
354
355 const tempPrefix = "test.kubeadm.runtime.isExistingSocket."
356 tests := []struct {
357 name string
358 proc func(*testing.T)
359 }{
360 {
361 name: "Valid domain socket is detected as such",
362 proc: func(t *testing.T) {
363 tmpFile, err := os.CreateTemp("", tempPrefix)
364 if err != nil {
365 t.Fatalf("unexpected error by TempFile: %v", err)
366 }
367 theSocket := tmpFile.Name()
368 os.Remove(theSocket)
369 tmpFile.Close()
370
371 con, err := net.Listen("unix", theSocket)
372 if err != nil {
373 t.Fatalf("unexpected error while dialing a socket: %v", err)
374 }
375 defer con.Close()
376
377 if !isExistingSocket("unix://" + theSocket) {
378 t.Fatalf("isExistingSocket(%q) gave unexpected result. Should have been true, instead of false", theSocket)
379 }
380 },
381 },
382 {
383 name: "Regular file is not a domain socket",
384 proc: func(t *testing.T) {
385 tmpFile, err := os.CreateTemp("", tempPrefix)
386 if err != nil {
387 t.Fatalf("unexpected error by TempFile: %v", err)
388 }
389 theSocket := tmpFile.Name()
390 defer os.Remove(theSocket)
391 tmpFile.Close()
392
393 if isExistingSocket(theSocket) {
394 t.Fatalf("isExistingSocket(%q) gave unexpected result. Should have been false, instead of true", theSocket)
395 }
396 },
397 },
398 {
399 name: "Non existent socket is not a domain socket",
400 proc: func(t *testing.T) {
401 const theSocket = "/non/existent/socket"
402 if isExistingSocket(theSocket) {
403 t.Fatalf("isExistingSocket(%q) gave unexpected result. Should have been false, instead of true", theSocket)
404 }
405 },
406 },
407 }
408
409 for _, test := range tests {
410 t.Run(test.name, test.proc)
411 }
412 }
413
414 func TestDetectCRISocketImpl(t *testing.T) {
415 tests := []struct {
416 name string
417 existingSockets []string
418 expectedError bool
419 expectedSocket string
420 }{
421 {
422 name: "No existing sockets, use default",
423 existingSockets: []string{},
424 expectedError: false,
425 expectedSocket: constants.DefaultCRISocket,
426 },
427 {
428 name: "One valid CRI socket leads to success",
429 existingSockets: []string{"unix:///foo/bar.sock"},
430 expectedError: false,
431 expectedSocket: "unix:///foo/bar.sock",
432 },
433 {
434 name: "Multiple CRI sockets lead to an error",
435 existingSockets: []string{
436 "unix:///foo/bar.sock",
437 "unix:///foo/baz.sock",
438 },
439 expectedError: true,
440 },
441 }
442
443 for _, test := range tests {
444 t.Run(test.name, func(t *testing.T) {
445 socket, err := detectCRISocketImpl(func(path string) bool {
446 for _, existing := range test.existingSockets {
447 if path == existing {
448 return true
449 }
450 }
451 return false
452 }, test.existingSockets)
453
454 if (err != nil) != test.expectedError {
455 t.Fatalf("detectCRISocketImpl returned unexpected result\n\tExpected error: %t\n\tGot error: %t", test.expectedError, err != nil)
456 }
457 if !test.expectedError && socket != test.expectedSocket {
458 t.Fatalf("detectCRISocketImpl returned unexpected CRI socket\n\tExpected socket: %s\n\tReturned socket: %s",
459 test.expectedSocket, socket)
460 }
461 })
462 }
463 }
464
465 func TestPullImagesInParallelImpl(t *testing.T) {
466 testError := errors.New("error")
467
468 tests := []struct {
469 name string
470 images []string
471 ifNotPresent bool
472 imageExistsFunc func(string) (bool, error)
473 pullImageFunc func(string) error
474 expectedErrors int
475 }{
476 {
477 name: "all images exist, no errors",
478 images: []string{"foo", "bar", "baz"},
479 ifNotPresent: true,
480 imageExistsFunc: func(string) (bool, error) {
481 return true, nil
482 },
483 pullImageFunc: nil,
484 expectedErrors: 0,
485 },
486 {
487 name: "cannot check if one image exists due to error",
488 images: []string{"foo", "bar", "baz"},
489 ifNotPresent: true,
490 imageExistsFunc: func(image string) (bool, error) {
491 if image == "baz" {
492 return false, testError
493 }
494 return true, nil
495 },
496 pullImageFunc: nil,
497 expectedErrors: 1,
498 },
499 {
500 name: "cannot pull two images",
501 images: []string{"foo", "bar", "baz"},
502 ifNotPresent: true,
503 imageExistsFunc: func(string) (bool, error) {
504 return false, nil
505 },
506 pullImageFunc: func(image string) error {
507 if image == "foo" {
508 return nil
509 }
510 return testError
511 },
512 expectedErrors: 2,
513 },
514 {
515 name: "pull all images",
516 images: []string{"foo", "bar", "baz"},
517 ifNotPresent: true,
518 imageExistsFunc: func(string) (bool, error) {
519 return false, nil
520 },
521 pullImageFunc: func(string) error {
522 return nil
523 },
524 expectedErrors: 0,
525 },
526 }
527
528 for _, tc := range tests {
529 t.Run(tc.name, func(t *testing.T) {
530 actual := pullImagesInParallelImpl(tc.images, tc.ifNotPresent,
531 tc.imageExistsFunc, tc.pullImageFunc)
532 if len(actual) != tc.expectedErrors {
533 t.Fatalf("expected non-nil errors: %v, got: %v, full list of errors: %v",
534 tc.expectedErrors, len(actual), actual)
535 }
536 })
537 }
538 }
539
View as plain text