k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/util/runtime/runtime_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // expected error occurs, impossible to test runtime further 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, // Test case 1 220 fakeOK, fakeOK, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeOK, // Test case 2 221 fakeErr, fakeErr, fakeErr, fakeErr, fakeErr, fakeOK, fakeOK, fakeOK, fakeOK, // Test case 3 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}, // Test case 1 236 {"invalid: CRI rmp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true}, // Test case 2 237 {"invalid: CRI stopp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true}, // Test case 3 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 // If the pull fails, it will be retried 5 times (see PullImageRetry in constants/constants.go) 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 // If the pull fails, it will be retried 5 times (see PullImageRetry in constants/constants.go) 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 // this test is not expected to work on Windows 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 }