github.com/dmaizel/tests@v0.0.0-20210728163746-cae6a2d9cee8/integration/docker/docker.go (about) 1 // Copyright (c) 2018 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package docker 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "log" 12 "os" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 "time" 18 19 "gopkg.in/yaml.v2" 20 21 "github.com/kata-containers/tests" 22 ginkgoconf "github.com/onsi/ginkgo/config" 23 ) 24 25 const ( 26 // Docker command 27 Docker = "docker" 28 29 // Image used to run containers 30 Image = "busybox" 31 32 // DebianImage is the debian image 33 DebianImage = "debian" 34 35 // FedoraImage is the fedora image 36 FedoraImage = "fedora" 37 38 // Fedora30Image is the fedora 30 image 39 // This Fedora version is used mainly because of https://github.com/kata-containers/tests/issues/2358 40 Fedora30Image = "fedora:30" 41 42 // StressImage is the vish/stress image 43 StressImage = "vish/stress" 44 45 // StressDockerFile is the dockerfile to build vish/stress image 46 StressDockerFile = "src/github.com/kata-containers/tests/stress/." 47 48 // VersionsPath is the path for the versions.yaml 49 VersionsPath = "src/github.com/kata-containers/tests/versions.yaml" 50 ) 51 52 // cidDirectory is the directory where container ID files are created. 53 var cidDirectory string 54 55 // AlpineImage is the alpine image 56 var AlpineImage string 57 58 var images []string 59 60 // versionDockerImage is the definition in the yaml for the Alpine image 61 type versionDockerImage struct { 62 Description string `yaml:"description"` 63 URL string `yaml:"url"` 64 Version string `yaml:"version"` 65 } 66 67 // versionDockerImages is the complete information for docker images in the versions yaml 68 type versionDockerImages struct { 69 Description string `yaml:"description"` 70 Alpine versionDockerImage 71 } 72 73 // Versions will be used to parse the versions yaml 74 type Versions struct { 75 Docker versionDockerImages `yaml:"docker_images"` 76 } 77 78 func init() { 79 var err error 80 cidDirectory, err = ioutil.TempDir("", "cid") 81 if err != nil { 82 log.Fatalf("Could not create cid directory: %v\n", err) 83 } 84 85 // Check versions.yaml 86 gopath := os.Getenv("GOPATH") 87 entirePath := filepath.Join(gopath, VersionsPath) 88 89 // Read versions.yaml 90 data, err := ioutil.ReadFile(entirePath) 91 if err != nil { 92 log.Fatalf("Could not read versions.yaml") 93 } 94 95 // Parse versions.yaml 96 var versions Versions 97 err = yaml.Unmarshal(data, &versions) 98 if err != nil { 99 log.Fatalf("Could not get alpine version") 100 } 101 102 // Define Alpine image with its proper version 103 AlpineImage = "alpine:" + versions.Docker.Alpine.Version 104 105 images = []string{ 106 Image, 107 AlpineImage, 108 DebianImage, 109 FedoraImage, 110 Fedora30Image, 111 CentosImage, 112 StressImage, 113 } 114 } 115 116 func cidFilePath(containerName string) string { 117 return filepath.Join(cidDirectory, containerName) 118 } 119 120 func runDockerCommandWithTimeout(timeout time.Duration, command string, args ...string) (string, string, int) { 121 return runDockerCommandWithTimeoutAndPipe(nil, timeout, command, args...) 122 } 123 124 func runDockerCommandWithTimeoutAndPipe(stdin *bytes.Buffer, timeout time.Duration, command string, args ...string) (string, string, int) { 125 a := []string{command} 126 127 // --cidfile must be specified when the container is created (run/create) 128 if command == "run" || command == "create" { 129 for i := 0; i < len(args); i++ { 130 // looks for container name 131 if args[i] == "--name" && i+1 < len(args) { 132 a = append(a, "--cidfile", cidFilePath(args[i+1])) 133 } 134 } 135 } 136 137 a = append(a, args...) 138 139 cmd := tests.NewCommand(Docker, a...) 140 cmd.Timeout = timeout 141 142 return cmd.RunWithPipe(stdin) 143 } 144 145 func runDockerCommand(command string, args ...string) (string, string, int) { 146 return runDockerCommandWithTimeout(time.Duration(tests.Timeout), command, args...) 147 } 148 149 func runDockerCommandWithPipe(stdin *bytes.Buffer, command string, args ...string) (string, string, int) { 150 return runDockerCommandWithTimeoutAndPipe(stdin, time.Duration(tests.Timeout), command, args...) 151 } 152 153 // LogsDockerContainer returns the container logs 154 func LogsDockerContainer(name string) (string, error) { 155 args := []string{name} 156 157 stdout, _, exitCode := runDockerCommand("logs", args...) 158 159 if exitCode != 0 { 160 return "", fmt.Errorf("failed to run docker logs command") 161 } 162 163 return strings.TrimSpace(stdout), nil 164 } 165 166 // StatusDockerContainer returns the container status 167 func StatusDockerContainer(name string) string { 168 args := []string{"-a", "-f", "name=" + name, "--format", "{{.Status}}"} 169 170 stdout, _, exitCode := runDockerCommand("ps", args...) 171 172 if exitCode != 0 || stdout == "" { 173 return "" 174 } 175 176 state := strings.Split(stdout, " ") 177 return state[0] 178 } 179 180 // hasExitedDockerContainer checks if the container has exited. 181 func hasExitedDockerContainer(name string) (bool, error) { 182 args := []string{"--format={{.State.Status}}", name} 183 184 stdout, _, exitCode := runDockerCommand("inspect", args...) 185 186 if exitCode != 0 || stdout == "" { 187 return false, fmt.Errorf("failed to run docker inspect command") 188 } 189 190 status := strings.TrimSpace(stdout) 191 192 if status == "exited" { 193 return true, nil 194 } 195 196 return false, nil 197 } 198 199 // ExitCodeDockerContainer returns the container exit code 200 func ExitCodeDockerContainer(name string, waitForExit bool) (int, error) { 201 // It makes no sense to try to retrieve the exit code of the container 202 // if it is still running. That's why this infinite loop takes care of 203 // waiting for the status to become "exited" before to ask for the exit 204 // code. 205 // However, we might want to bypass this check on purpose, that's why 206 // we check waitForExit boolean. 207 if waitForExit { 208 errCh := make(chan error) 209 exitCh := make(chan bool) 210 211 go func() { 212 for { 213 exited, err := hasExitedDockerContainer(name) 214 if err != nil { 215 errCh <- err 216 } 217 218 if exited { 219 break 220 } 221 222 time.Sleep(time.Second) 223 } 224 225 close(exitCh) 226 }() 227 228 select { 229 case <-exitCh: 230 break 231 case err := <-errCh: 232 return -1, err 233 case <-time.After(time.Duration(tests.Timeout) * time.Second): 234 return -1, fmt.Errorf("Timeout reached after %ds", tests.Timeout) 235 } 236 } 237 238 args := []string{"--format={{.State.ExitCode}}", name} 239 240 stdout, _, exitCode := runDockerCommand("inspect", args...) 241 242 if exitCode != 0 || stdout == "" { 243 return -1, fmt.Errorf("failed to run docker inspect command") 244 } 245 246 return strconv.Atoi(strings.TrimSpace(stdout)) 247 } 248 249 // WaitForRunningDockerContainer verifies if a docker container 250 // is running for a certain period of time 251 // returns an error if the timeout is reached. 252 func WaitForRunningDockerContainer(name string, running bool) error { 253 ch := make(chan bool) 254 go func() { 255 if IsRunningDockerContainer(name) == running { 256 close(ch) 257 return 258 } 259 260 time.Sleep(time.Second) 261 }() 262 263 select { 264 case <-ch: 265 case <-time.After(time.Duration(tests.Timeout) * time.Second): 266 return fmt.Errorf("Timeout reached after %ds", tests.Timeout) 267 } 268 269 return nil 270 } 271 272 // IsRunningDockerContainer inspects a container 273 // returns true if is running 274 func IsRunningDockerContainer(name string) bool { 275 stdout, _, exitCode := runDockerCommand("inspect", "--format={{.State.Running}}", name) 276 277 if exitCode != 0 { 278 return false 279 } 280 281 output := strings.TrimSpace(stdout) 282 tests.LogIfFail("container running: " + output) 283 return !(output == "false") 284 } 285 286 // ExistDockerContainer returns true if any of next cases is true: 287 // - 'docker ps -a' command shows the container 288 // - the VM is running (qemu) 289 // - the proxy is running 290 // - the shim is running 291 // else false is returned 292 func ExistDockerContainer(name string) bool { 293 if name == "" { 294 tests.LogIfFail("Container name is empty") 295 return false 296 } 297 298 state := StatusDockerContainer(name) 299 if state != "" { 300 return true 301 } 302 303 // If we reach this point means that the container doesn't exist in docker, 304 // but we have to check that the components (qemu, shim, proxy) are not running. 305 // Read container ID from file created by run/create 306 path := cidFilePath(name) 307 defer os.Remove(path) 308 content, err := ioutil.ReadFile(path) 309 if err != nil { 310 tests.LogIfFail("Could not read container ID file: %v\n", err) 311 return false 312 } 313 314 // Use container ID to check if kata components are still running. 315 cid := string(content) 316 exitCh := make(chan bool) 317 go func() { 318 for { 319 if !tests.HypervisorRunning(cid) && 320 !tests.ProxyRunning(cid) && 321 !tests.ShimRunning(cid) { 322 close(exitCh) 323 return 324 } 325 time.Sleep(time.Second) 326 } 327 }() 328 329 select { 330 case <-exitCh: 331 return false 332 case <-time.After(time.Duration(tests.Timeout) * time.Second): 333 tests.LogIfFail("Timeout reached after %ds", tests.Timeout) 334 return true 335 } 336 } 337 338 // RemoveDockerContainer removes a container using docker rm -f 339 func RemoveDockerContainer(name string) bool { 340 _, _, exitCode := dockerRm("-f", name) 341 return (exitCode == 0) 342 } 343 344 // StopDockerContainer stops a container 345 func StopDockerContainer(name string) bool { 346 _, _, exitCode := dockerStop(name) 347 return (exitCode == 0) 348 } 349 350 // KillDockerContainer kills a container 351 func KillDockerContainer(name string) bool { 352 _, _, exitCode := dockerKill(name) 353 return (exitCode == 0) 354 } 355 356 func randomDockerName() string { 357 return tests.RandID(29) + fmt.Sprint(ginkgoconf.GinkgoConfig.ParallelNode) 358 } 359 360 // returns a random and valid repository name 361 func randomDockerRepoName() string { 362 return strings.ToLower(tests.RandID(14)) + fmt.Sprint(ginkgoconf.GinkgoConfig.ParallelNode) 363 } 364 365 // dockerRm removes a container 366 func dockerRm(args ...string) (string, string, int) { 367 return runDockerCommand("rm", args...) 368 } 369 370 // dockerStop stops a container 371 // returns true on success else false 372 func dockerStop(args ...string) (string, string, int) { 373 // docker stop takes ~15 seconds 374 return runDockerCommand("stop", args...) 375 } 376 377 // dockerPull downloads the specific image 378 func dockerPull(args ...string) (string, string, int) { 379 // 10 minutes should be enough to download a image 380 return runDockerCommandWithTimeout(600, "pull", args...) 381 } 382 383 // dockerRun runs a container 384 func dockerRun(args ...string) (string, string, int) { 385 if tests.Runtime != "" { 386 args = append(args, []string{"", ""}...) 387 copy(args[2:], args[:]) 388 args[0] = "--runtime" 389 args[1] = tests.Runtime 390 } 391 392 return runDockerCommand("run", args...) 393 } 394 395 // Runs a container with stdin 396 func dockerRunWithPipe(stdin *bytes.Buffer, args ...string) (string, string, int) { 397 if tests.Runtime != "" { 398 args = append(args, []string{"", ""}...) 399 copy(args[2:], args[:]) 400 args[0] = "--runtime" 401 args[1] = tests.Runtime 402 } 403 404 return runDockerCommandWithPipe(stdin, "run", args...) 405 } 406 407 // dockerKill kills a container 408 func dockerKill(args ...string) (string, string, int) { 409 return runDockerCommand("kill", args...) 410 } 411 412 // dockerVolume manages volumes 413 func dockerVolume(args ...string) (string, string, int) { 414 return runDockerCommand("volume", args...) 415 } 416 417 // dockerAttach attach to a running container 418 func dockerAttach(args ...string) (string, string, int) { 419 return runDockerCommand("attach", args...) 420 } 421 422 // dockerCommit creates a new image from a container's changes 423 func dockerCommit(args ...string) (string, string, int) { 424 return runDockerCommand("commit", args...) 425 } 426 427 // dockerImages list images 428 func dockerImages(args ...string) (string, string, int) { 429 return runDockerCommand("images", args...) 430 } 431 432 // dockerImport imports the contents from a tarball to create a filesystem image 433 func dockerImport(args ...string) (string, string, int) { 434 return runDockerCommand("import", args...) 435 } 436 437 // dockerRmi removes one or more images 438 func dockerRmi(args ...string) (string, string, int) { 439 // docker takes more than 5 seconds to remove an image, it depends 440 // of the image size and this operation does not involve to the 441 // runtime 442 return runDockerCommand("rmi", args...) 443 } 444 445 // dockerCp copies files/folders between a container and the local filesystem 446 func dockerCp(args ...string) (string, string, int) { 447 return runDockerCommand("cp", args...) 448 } 449 450 // dockerExec runs a command in a running container 451 func dockerExec(args ...string) (string, string, int) { 452 return runDockerCommand("exec", args...) 453 } 454 455 // dockerPs list containers 456 func dockerPs(args ...string) (string, string, int) { 457 return runDockerCommand("ps", args...) 458 } 459 460 // dockerSearch searches docker hub images 461 func dockerSearch(args ...string) (string, string, int) { 462 return runDockerCommand("search", args...) 463 } 464 465 // dockerCreate creates a new container 466 func dockerCreate(args ...string) (string, string, int) { 467 return runDockerCommand("create", args...) 468 } 469 470 // dockerDiff inspect changes to files or directories on a container’s filesystem 471 func dockerDiff(args ...string) (string, string, int) { 472 return runDockerCommand("diff", args...) 473 } 474 475 // dockerBuild builds an image from a Dockerfile 476 func dockerBuild(args ...string) (string, string, int) { 477 // 10 minutes should be enough to build a image 478 return runDockerCommandWithTimeout(600, "build", args...) 479 } 480 481 // dockerExport will export a container’s filesystem as a tar archive 482 func dockerExport(args ...string) (string, string, int) { 483 return runDockerCommand("export", args...) 484 } 485 486 // dockerInfo displays system-wide information 487 func dockerInfo() (string, string, int) { 488 return runDockerCommand("info") 489 } 490 491 // dockerInspect returns low-level information on Docker objects 492 func dockerInspect(args ...string) (string, string, int) { 493 return runDockerCommand("inspect", args...) 494 } 495 496 // dockerLoad loads a tarred repository 497 func dockerLoad(args ...string) (string, string, int) { 498 return runDockerCommand("load", args...) 499 } 500 501 // dockerPort starts one or more stopped containers 502 func dockerPort(args ...string) (string, string, int) { 503 return runDockerCommand("port", args...) 504 } 505 506 // dockerRestart starts one or more stopped containers 507 func dockerRestart(args ...string) (string, string, int) { 508 return runDockerCommand("restart", args...) 509 } 510 511 // dockerSave saves one or more images 512 func dockerSave(args ...string) (string, string, int) { 513 return runDockerCommand("save", args...) 514 } 515 516 // dockerPause pauses all processes within one or more containers 517 func dockerPause(args ...string) (string, string, int) { 518 return runDockerCommand("pause", args...) 519 } 520 521 // dockerUnpause unpauses all processes within one or more containers 522 func dockerUnpause(args ...string) (string, string, int) { 523 return runDockerCommand("unpause", args...) 524 } 525 526 // dockerTop displays the running processes of a container 527 //nolint:unused 528 func dockerTop(args ...string) (string, string, int) { 529 return runDockerCommand("top", args...) 530 } 531 532 // dockerUpdate updates configuration of one or more containers 533 func dockerUpdate(args ...string) (string, string, int) { 534 return runDockerCommand("update", args...) 535 } 536 537 // createLoopDevice creates a new disk file using 'dd' command, returns the path to disk file and 538 // its loop device representation 539 func createLoopDevice() (string, string, error) { 540 f, err := ioutil.TempFile("", "dd") 541 if err != nil { 542 return "", "", err 543 } 544 defer f.Close() 545 546 // create disk file 547 ddArgs := []string{"if=/dev/zero", fmt.Sprintf("of=%s", f.Name()), "count=1", "bs=50M"} 548 ddCmd := tests.NewCommand("dd", ddArgs...) 549 if _, stderr, exitCode := ddCmd.Run(); exitCode != 0 { 550 return "", "", fmt.Errorf("%s", stderr) 551 } 552 553 // partitioning disk file 554 fdiskArgs := []string{"-c", fmt.Sprintf(`printf "g\nn\n\n\n\nw\n" | fdisk %s`, f.Name())} 555 fdiskCmd := tests.NewCommand("bash", fdiskArgs...) 556 if _, stderr, exitCode := fdiskCmd.Run(); exitCode != 0 { 557 return "", "", fmt.Errorf("%s", stderr) 558 } 559 560 // create loop device 561 losetupCmd := tests.NewCommand("losetup", "-fP", f.Name()) 562 if _, stderr, exitCode := losetupCmd.Run(); exitCode != 0 { 563 return "", "", fmt.Errorf("%s", stderr) 564 } 565 566 // get loop device path 567 getLoopPath := tests.NewCommand("losetup", "-j", f.Name()) 568 stdout, stderr, exitCode := getLoopPath.Run() 569 if exitCode != 0 { 570 return "", "", fmt.Errorf("exitCode: %d, stdout: %s, stderr: %s ", exitCode, stdout, stderr) 571 } 572 re := regexp.MustCompile("/dev/loop[0-9]+") 573 loopPath := re.FindStringSubmatch(stdout) 574 if len(loopPath) == 0 { 575 return "", "", fmt.Errorf("Unable to get loop device path, stdout: %s, stderr: %s", stdout, stderr) 576 } 577 return f.Name(), loopPath[0], nil 578 } 579 580 // deleteLoopDevice removes loopdevices 581 func deleteLoopDevice(loopFile string) error { 582 partxCmd := tests.NewCommand("losetup", "-d", loopFile) 583 _, stderr, exitCode := partxCmd.Run() 584 if exitCode != 0 { 585 return fmt.Errorf("%s", stderr) 586 } 587 588 return nil 589 }