From 7683b37005b087d90249fa43d9a8119e169cfe38 Mon Sep 17 00:00:00 2001 From: Salvador Fuentes Date: Wed, 17 Jan 2018 21:57:08 +0000 Subject: [PATCH] tests: Add initial docker integration tests This commmit adds inital docker run tests. Fixes #26. Signed-off-by: Salvador Fuentes --- .gitignore | 1 + Makefile | 15 ++ command.go | 84 +++++++ integration/docker/docker.go | 396 ++++++++++++++++++++++++++++++++ integration/docker/main_test.go | 43 ++++ integration/docker/run_test.go | 300 ++++++++++++++++++++++++ log.go | 18 ++ rand.go | 29 +++ vm.go | 54 +++++ 9 files changed, 940 insertions(+) create mode 100644 .gitignore create mode 100644 command.go create mode 100644 integration/docker/docker.go create mode 100644 integration/docker/main_test.go create mode 100644 integration/docker/run_test.go create mode 100644 log.go create mode 100644 rand.go create mode 100644 vm.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..dbf813f29 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ginkgo diff --git a/Makefile b/Makefile index dbd935b6c..e0a710ccd 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,22 @@ # SPDX-License-Identifier: Apache-2.0 # +# The time limit in seconds for each test +TIMEOUT ?= 60 + default: checkcommits checkcommits: make -C cmd/checkcommits + +ginkgo: + ln -sf . vendor/src + GOPATH=$(PWD)/vendor go build ./vendor/github.com/onsi/ginkgo/ginkgo + unlink vendor/src + +integration: ginkgo +ifeq ($(RUNTIME),) + $(error RUNTIME is not set) +else + ./ginkgo -v -focus "${FOCUS}" ./integration/docker/ -- -runtime=${RUNTIME} -timeout=${TIMEOUT} +endif diff --git a/command.go b/command.go new file mode 100644 index 000000000..b14618549 --- /dev/null +++ b/command.go @@ -0,0 +1,84 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package tests + +import ( + "bytes" + "flag" + "os/exec" + "syscall" + "time" +) + +// Runtime is the path of a Kata Containers Runtime +var Runtime string + +// Timeout specifies the time limit in seconds for each test +var Timeout int + +// Command contains the information of the command to run +type Command struct { + // cmd exec.Cmd + cmd *exec.Cmd + + // Timeout is the time limit of seconds of the command + Timeout time.Duration +} + +func init() { + flag.StringVar(&Runtime, "runtime", "", "Path of the desired Kata Runtime") + flag.IntVar(&Timeout, "timeout", 5, "Time limit in seconds for each test") + + flag.Parse() +} + +// NewCommand returns a new instance of Command +func NewCommand(path string, args ...string) *Command { + c := new(Command) + c.cmd = exec.Command(path, args...) + c.Timeout = time.Duration(Timeout) + + return c +} + +// Run runs a command returning its stdout, stderr and exit code +func (c *Command) Run() (string, string, int) { + LogIfFail("Running command '%s %s'\n", c.cmd.Path, c.cmd.Args) + + var stdout, stderr bytes.Buffer + c.cmd.Stdout = &stdout + c.cmd.Stderr = &stderr + + if err := c.cmd.Start(); err != nil { + LogIfFail("could no start command: %v\n", err) + } + + done := make(chan error) + go func() { done <- c.cmd.Wait() }() + + var timeout <-chan time.Time + if c.Timeout > 0 { + timeout = time.After(c.Timeout * time.Second) + } + + select { + case <-timeout: + LogIfFail("Killing process timeout reached '%d' seconds\n", c.Timeout) + _ = c.cmd.Process.Kill() + return "", "", -1 + + case err := <-done: + if err != nil { + LogIfFail("command failed error '%s'\n", err) + } + + exitCode := c.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + + LogIfFail("%+v\nTimeout: %d seconds\nExit Code: %d\nStdout: %s\nStderr: %s\n", + c.cmd.Args, c.Timeout, exitCode, stdout.String(), stderr.String()) + + return stdout.String(), stderr.String(), exitCode + } +} diff --git a/integration/docker/docker.go b/integration/docker/docker.go new file mode 100644 index 000000000..8c5852376 --- /dev/null +++ b/integration/docker/docker.go @@ -0,0 +1,396 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package docker + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/kata-containers/tests" +) + +const ( + // Docker command + Docker = "docker" + + // Image used to run containers + Image = "busybox" + + // AlpineImage is the alpine image + AlpineImage = "alpine" + + // PostgresImage is the postgres image + PostgresImage = "postgres" + + // DebianImage is the debian image + DebianImage = "debian" + + // FedoraImage is the fedora image + FedoraImage = "fedora" +) + +func runDockerCommandWithTimeout(timeout time.Duration, command string, args ...string) (string, string, int) { + a := []string{command} + a = append(a, args...) + + cmd := tests.NewCommand(Docker, a...) + cmd.Timeout = timeout + + return cmd.Run() +} + +func runDockerCommand(command string, args ...string) (string, string, int) { + return runDockerCommandWithTimeout(time.Duration(tests.Timeout), command, args...) +} + +// LogsDockerContainer returns the container logs +func LogsDockerContainer(name string) (string, error) { + args := []string{name} + + stdout, _, exitCode := runDockerCommand("logs", args...) + + if exitCode != 0 { + return "", fmt.Errorf("failed to run docker logs command") + } + + return strings.TrimSpace(stdout), nil +} + +// StatusDockerContainer returns the container status +func StatusDockerContainer(name string) string { + args := []string{"-a", "-f", "name=" + name, "--format", "{{.Status}}"} + + stdout, _, exitCode := runDockerCommand("ps", args...) + + if exitCode != 0 || stdout == "" { + return "" + } + + state := strings.Split(stdout, " ") + return state[0] +} + +// hasExitedDockerContainer checks if the container has exited. +func hasExitedDockerContainer(name string) (bool, error) { + args := []string{"--format={{.State.Status}}", name} + + stdout, _, exitCode := runDockerCommand("inspect", args...) + + if exitCode != 0 || stdout == "" { + return false, fmt.Errorf("failed to run docker inspect command") + } + + status := strings.TrimSpace(stdout) + + if status == "exited" { + return true, nil + } + + return false, nil +} + +// ExitCodeDockerContainer returns the container exit code +func ExitCodeDockerContainer(name string, waitForExit bool) (int, error) { + // It makes no sense to try to retrieve the exit code of the container + // if it is still running. That's why this infinite loop takes care of + // waiting for the status to become "exited" before to ask for the exit + // code. + // However, we might want to bypass this check on purpose, that's why + // we check waitForExit boolean. + if waitForExit { + errCh := make(chan error) + exitCh := make(chan bool) + + go func() { + for { + exited, err := hasExitedDockerContainer(name) + if err != nil { + errCh <- err + } + + if exited { + break + } + + time.Sleep(time.Second) + } + + close(exitCh) + }() + + select { + case <-exitCh: + break + case err := <-errCh: + return -1, err + case <-time.After(time.Duration(tests.Timeout) * time.Second): + return -1, fmt.Errorf("Timeout reached after %ds", tests.Timeout) + } + } + + args := []string{"--format={{.State.ExitCode}}", name} + + stdout, _, exitCode := runDockerCommand("inspect", args...) + + if exitCode != 0 || stdout == "" { + return -1, fmt.Errorf("failed to run docker inspect command") + } + + return strconv.Atoi(strings.TrimSpace(stdout)) +} + +// WaitForRunningDockerContainer verifies if a docker container +// is running for a certain period of time +// returns an error if the timeout is reached. +func WaitForRunningDockerContainer(name string, running bool) error { + ch := make(chan bool) + go func() { + if IsRunningDockerContainer(name) == running { + close(ch) + return + } + + time.Sleep(time.Second) + }() + + select { + case <-ch: + case <-time.After(time.Duration(tests.Timeout) * time.Second): + return fmt.Errorf("Timeout reached after %ds", tests.Timeout) + } + + return nil +} + +// IsRunningDockerContainer inspects a container +// returns true if is running +func IsRunningDockerContainer(name string) bool { + stdout, _, exitCode := runDockerCommand("inspect", "--format={{.State.Running}}", name) + + if exitCode != 0 { + return false + } + + output := strings.TrimSpace(stdout) + tests.LogIfFail("container running: " + output) + if output == "false" { + return false + } + + return true +} + +// ExistDockerContainer returns true if any of next cases is true: +// - 'docker ps -a' command shows the container +// - the VM is running (qemu) +// else false is returned +func ExistDockerContainer(name string) bool { + state := StatusDockerContainer(name) + if state != "" { + return true + } + + return tests.IsVMRunning(name) +} + +// RemoveDockerContainer removes a container using docker rm -f +func RemoveDockerContainer(name string) bool { + _, _, exitCode := dockerRm("-f", name) + if exitCode != 0 { + return false + } + + return true +} + +// StopDockerContainer stops a container +func StopDockerContainer(name string) bool { + _, _, exitCode := dockerStop(name) + if exitCode != 0 { + return false + } + + return true +} + +// KillDockerContainer kills a container +func KillDockerContainer(name string) bool { + _, _, exitCode := dockerKill(name) + if exitCode != 0 { + return false + } + + return true +} + +// dockerRm removes a container +func dockerRm(args ...string) (string, string, int) { + return runDockerCommand("rm", args...) +} + +// dockerStop stops a container +// returns true on success else false +func dockerStop(args ...string) (string, string, int) { + // docker stop takes ~15 seconds + return runDockerCommand("stop", args...) +} + +// dockerPull downloads the specific image +func dockerPull(args ...string) (string, string, int) { + // 10 minutes should be enough to download a image + return runDockerCommandWithTimeout(600, "pull", args...) +} + +// dockerRun runs a container +func dockerRun(args ...string) (string, string, int) { + if tests.Runtime != "" { + args = append(args, []string{"", ""}...) + copy(args[2:], args[:]) + args[0] = "--runtime" + args[1] = tests.Runtime + } + + return runDockerCommand("run", args...) +} + +// doockerKill kills a container +func dockerKill(args ...string) (string, string, int) { + return runDockerCommand("kill", args...) +} + +// dockerVolume manages volumes +func dockerVolume(args ...string) (string, string, int) { + return runDockerCommand("volume", args...) +} + +// dockerAttach attach to a running container +func dockerAttach(args ...string) (string, string, int) { + return runDockerCommand("attach", args...) +} + +// dockerCommit creates a new image from a container's changes +func dockerCommit(args ...string) (string, string, int) { + return runDockerCommand("commit", args...) +} + +// dockerImages list images +func dockerImages(args ...string) (string, string, int) { + return runDockerCommand("images", args...) +} + +// dockerImport imports the contents from a tarball to create a filesystem image +func dockerImport(args ...string) (string, string, int) { + return runDockerCommand("import", args...) +} + +// dockerRmi removes one or more images +func dockerRmi(args ...string) (string, string, int) { + // docker takes more than 5 seconds to remove an image, it depends + // of the image size and this operation does not involve to the + // runtime + return runDockerCommand("rmi", args...) +} + +// dockerCp copies files/folders between a container and the local filesystem +func dockerCp(args ...string) (string, string, int) { + return runDockerCommand("cp", args...) +} + +// dockerExec runs a command in a running container +func dockerExec(args ...string) (string, string, int) { + return runDockerCommand("exec", args...) +} + +// dockerPs list containers +func dockerPs(args ...string) (string, string, int) { + return runDockerCommand("ps", args...) +} + +// dockerSearch searches docker hub images +func dockerSearch(args ...string) (string, string, int) { + return runDockerCommand("search", args...) +} + +// dockerCreate creates a new container +func dockerCreate(args ...string) (string, string, int) { + return runDockerCommand("create", args...) +} + +// dockerDiff inspect changes to files or directories on a container’s filesystem +func dockerDiff(args ...string) (string, string, int) { + return runDockerCommand("diff", args...) +} + +// dockerBuild builds an image from a Dockerfile +func dockerBuild(args ...string) (string, string, int) { + return runDockerCommand("build", args...) +} + +// dockerNetwork manages networks +func dockerNetwork(args ...string) (string, string, int) { + return runDockerCommand("network", args...) +} + +// dockerExport will export a container’s filesystem as a tar archive +func dockerExport(args ...string) (string, string, int) { + return runDockerCommand("export", args...) +} + +// dockerInfo displays system-wide information +func dockerInfo() (string, string, int) { + return runDockerCommand("info") +} + +// dockerLoad loads a tarred repository +func dockerLoad(args ...string) (string, string, int) { + return runDockerCommand("load", args...) +} + +// dockerPort starts one or more stopped containers +func dockerPort(args ...string) (string, string, int) { + return runDockerCommand("port", args...) +} + +// dockerRestart starts one or more stopped containers +func dockerRestart(args ...string) (string, string, int) { + return runDockerCommand("restart", args...) +} + +// dockerSwarm manages swarm +func dockerSwarm(args ...string) (string, string, int) { + return runDockerCommand("swarm", args...) +} + +// dockerSave saves one or more images +func dockerSave(args ...string) (string, string, int) { + return runDockerCommand("save", args...) +} + +// dockerService manages services +func dockerService(args ...string) (string, string, int) { + return runDockerCommand("service", args...) +} + +// dockerStart starts one or more stopped containers +func dockerStart(args ...string) (string, string, int) { + return runDockerCommand("start", args...) +} + +// dockerPause pauses all processes within one or more containers +func dockerPause(args ...string) (string, string, int) { + return runDockerCommand("pause", args...) +} + +// dockerUnpause unpauses all processes within one or more containers +func dockerUnpause(args ...string) (string, string, int) { + return runDockerCommand("unpause", args...) +} + +// dockerTop displays the running processes of a container +func dockerTop(args ...string) (string, string, int) { + return runDockerCommand("top", args...) +} diff --git a/integration/docker/main_test.go b/integration/docker/main_test.go new file mode 100644 index 000000000..e216ac23c --- /dev/null +++ b/integration/docker/main_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package docker + +import ( + "testing" + + . "github.com/kata-containers/tests" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + shouldFail = true + shouldNotFail = false +) + +func randomDockerName() string { + return RandID(30) +} + +func TestIntegration(t *testing.T) { + // before start we have to download the docker images + images := []string{ + Image, + AlpineImage, + PostgresImage, + DebianImage, + FedoraImage, + } + + for _, i := range images { + _, _, exitCode := dockerPull(i) + if exitCode != 0 { + t.Fatalf("failed to pull docker image: %s\n", i) + } + } + + RegisterFailHandler(Fail) + RunSpecs(t, "Integration Suite") +} diff --git a/integration/docker/run_test.go b/integration/docker/run_test.go new file mode 100644 index 000000000..a1392acba --- /dev/null +++ b/integration/docker/run_test.go @@ -0,0 +1,300 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package docker + +import ( + "fmt" + "io/ioutil" + "math" + "os" + "strings" + + . "github.com/kata-containers/tests" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +// some machines support until 32 loop devices +var losetupMaxTries = 32 + +// number of loop devices to hotplug +var loopDevices = 10 + +func withWorkload(workload string, expectedExitCode int) TableEntry { + return Entry(fmt.Sprintf("with '%v' as workload", workload), workload, expectedExitCode) +} + +var _ = Describe("run", func() { + var ( + args []string + id string + ) + + BeforeEach(func() { + id = randomDockerName() + args = []string{"--rm", "--name", id, Image, "sh", "-c"} + }) + + AfterEach(func() { + Expect(ExistDockerContainer(id)).NotTo(BeTrue()) + }) + + DescribeTable("container with docker", + func(workload string, expectedExitCode int) { + args = append(args, workload) + _, _, exitCode := dockerRun(args...) + Expect(expectedExitCode).To(Equal(exitCode)) + }, + withWorkload("true", 0), + withWorkload("false", 1), + withWorkload("exit 0", 0), + withWorkload("exit 1", 1), + withWorkload("exit 15", 15), + withWorkload("exit 123", 123), + ) +}) + +var _ = Describe("run", func() { + var ( + args []string + id string + ) + + BeforeEach(func() { + id = randomDockerName() + args = []string{"--name", id} + }) + + AfterEach(func() { + Expect(RemoveDockerContainer(id)).To(BeTrue()) + Expect(ExistDockerContainer(id)).NotTo(BeTrue()) + }) + + DescribeTable("container with docker", + func(options, expectedStatus string) { + args = append(args, options, Image, "sh") + + _, _, exitCode := dockerRun(args...) + Expect(exitCode).To(BeZero()) + Expect(StatusDockerContainer(id)).To(Equal(expectedStatus)) + Expect(ExistDockerContainer(id)).To(BeTrue()) + }, + Entry("in background and interactive", "-di", "Up"), + Entry("in background, interactive and with a tty", "-dit", "Up"), + ) +}) + +// creates a new disk file using 'dd' command, returns the path to disk file and +// its loop device representation +func createLoopDevice() (string, string, error) { + f, err := ioutil.TempFile("", "dd") + if err != nil { + return "", "", err + } + defer f.Close() + + // create disk file + ddArgs := []string{"if=/dev/zero", fmt.Sprintf("of=%s", f.Name()), "count=1", "bs=5M"} + ddCmd := NewCommand("dd", ddArgs...) + if _, stderr, exitCode := ddCmd.Run(); exitCode != 0 { + return "", "", fmt.Errorf("%s", stderr) + } + + // partitioning disk file + fdiskArgs := []string{"-c", fmt.Sprintf(`printf "g\nn\n\n\n\nw\n" | fdisk %s`, f.Name())} + fdiskCmd := NewCommand("bash", fdiskArgs...) + if _, stderr, exitCode := fdiskCmd.Run(); exitCode != 0 { + return "", "", fmt.Errorf("%s", stderr) + } + + // create loop device + for i := 0; i < losetupMaxTries; i++ { + loopPath := fmt.Sprintf("/dev/loop%d", i) + losetupCmd := NewCommand("losetup", "-P", loopPath, f.Name()) + _, _, exitCode := losetupCmd.Run() + if exitCode == 0 { + return f.Name(), loopPath, nil + } + } + + return "", "", fmt.Errorf("unable to create loop device for disk %s", f.Name()) +} + +func deleteLoopDevice(loopFile string) error { + partxCmd := NewCommand("losetup", "-d", loopFile) + _, stderr, exitCode := partxCmd.Run() + if exitCode != 0 { + return fmt.Errorf("%s", stderr) + } + + return nil +} + +var _ = Describe("run", func() { + var ( + err error + diskFiles []string + diskFile string + loopFiles []string + loopFile string + dockerArgs []string + id string + ) + + BeforeEach(func() { + if os.Getuid() != 0 { + Skip("only root user can create loop devices") + } + id = RandID(30) + + for i := 0; i < loopDevices; i++ { + diskFile, loopFile, err = createLoopDevice() + Expect(err).ToNot(HaveOccurred()) + + diskFiles = append(diskFiles, diskFile) + loopFiles = append(loopFiles, loopFile) + dockerArgs = append(dockerArgs, "--device", loopFile) + } + + dockerArgs = append(dockerArgs, "--rm", "--name", id, Image, "stat") + + for _, lf := range loopFiles { + dockerArgs = append(dockerArgs, lf) + } + }) + + AfterEach(func() { + Expect(ExistDockerContainer(id)).NotTo(BeTrue()) + for _, lf := range loopFiles { + err = deleteLoopDevice(lf) + Expect(err).ToNot(HaveOccurred()) + } + for _, df := range diskFiles { + err = os.Remove(df) + Expect(err).ToNot(HaveOccurred()) + } + }) + + Context("hot plug block devices", func() { + It("should be attached", func() { + _, _, exitCode := dockerRun(dockerArgs...) + Expect(exitCode).To(BeZero()) + }) + }) +}) + +func withCPUPeriodAndQuota(quota, period int, fail bool) TableEntry { + var msg string + + if fail { + msg = "should fail" + } else { + msg = fmt.Sprintf("should have %d CPUs", (quota+period-1)/period) + } + + return Entry(msg, quota, period, fail) +} + +func withCPUConstraint(cpus float64, fail bool) TableEntry { + var msg string + c := int(math.Ceil(cpus)) + + if fail { + msg = "should fail" + } else { + msg = fmt.Sprintf("should have %d CPUs", c) + } + + return Entry(msg, c, fail) +} + +var _ = Describe("run", func() { + var ( + args []string + id string + vCPUs int + ) + + BeforeEach(func() { + id = RandID(30) + args = []string{"--rm", "--name", id} + }) + + AfterEach(func() { + Expect(ExistDockerContainer(id)).NotTo(BeTrue()) + }) + + DescribeTable("container with CPU period and quota", + func(quota, period int, fail bool) { + args = append(args, "--cpu-quota", fmt.Sprintf("%d", quota), + "--cpu-period", fmt.Sprintf("%d", period), Image, "nproc") + vCPUs = (quota + period - 1) / period + stdout, _, exitCode := dockerRun(args...) + if fail { + Expect(exitCode).ToNot(BeZero()) + return + } + Expect(exitCode).To(BeZero()) + Expect(strings.Trim(stdout, "\n\t ")).To(Equal(fmt.Sprintf("%d", vCPUs))) + }, + withCPUPeriodAndQuota(30000, 20000, false), + withCPUPeriodAndQuota(30000, 10000, false), + withCPUPeriodAndQuota(10000, 10000, false), + withCPUPeriodAndQuota(10000, 100, true), + ) + + DescribeTable("container with CPU constraint", + func(cpus int, fail bool) { + args = append(args, "--cpus", fmt.Sprintf("%d", cpus), Image, "nproc") + stdout, _, exitCode := dockerRun(args...) + if fail { + Expect(exitCode).ToNot(BeZero()) + return + } + Expect(exitCode).To(BeZero()) + Expect(strings.Trim(stdout, "\n\t ")).To(Equal(fmt.Sprintf("%d", cpus))) + }, + withCPUConstraint(1, false), + withCPUConstraint(1.5, false), + withCPUConstraint(2, false), + withCPUConstraint(2.5, false), + withCPUConstraint(-5, true), + ) +}) + +var _ = Describe("run", func() { + var ( + args []string + id string + ) + + BeforeEach(func() { + id = randomDockerName() + }) + + AfterEach(func() { + Expect(ExistDockerContainer(id)).NotTo(BeTrue()) + }) + + Context("stdout using run", func() { + It("should not display the output", func() { + args = []string{"--rm", "--name", id, Image, "sh", "-c", "ls /etc/resolv.conf"} + stdout, _, exitCode := dockerRun(args...) + Expect(exitCode).To(Equal(0)) + Expect(stdout).To(ContainSubstring("/etc/resolv.conf")) + }) + }) + + Context("stderr using run", func() { + It("should not display the output", func() { + args = []string{"--rm", "--name", id, Image, "sh", "-c", "ls /etc/foo"} + stdout, stderr, exitCode := dockerRun(args...) + Expect(exitCode).To(Equal(1)) + Expect(stdout).To(BeEmpty()) + Expect(stderr).To(ContainSubstring("ls: /etc/foo: No such file or directory")) + }) + }) +}) diff --git a/log.go b/log.go new file mode 100644 index 000000000..4fb232c1b --- /dev/null +++ b/log.go @@ -0,0 +1,18 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package tests + +import ( + "fmt" + + "github.com/onsi/ginkgo" +) + +// LogIfFail will output the message online if the test fails. This can be used +// for information that would be useful to debug a failure. +func LogIfFail(format string, args ...interface{}) { + str := fmt.Sprintf(format, args...) + _, _ = ginkgo.GinkgoWriter.Write([]byte(str)) +} diff --git a/rand.go b/rand.go new file mode 100644 index 000000000..33ec44f43 --- /dev/null +++ b/rand.go @@ -0,0 +1,29 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package tests + +import ( + "math/rand" + "time" +) + +const letters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +const lettersMask = 63 + +var randSrc = rand.NewSource(time.Now().UnixNano()) + +// RandID returns a random string +func RandID(n int) string { + b := make([]byte, n) + for i := 0; i < n; { + if j := int(randSrc.Int63() & lettersMask); j < len(letters) { + b[i] = letters[j] + i++ + } + } + + return string(b) +} diff --git a/vm.go b/vm.go new file mode 100644 index 000000000..237d3ddca --- /dev/null +++ b/vm.go @@ -0,0 +1,54 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +package tests + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "regexp" +) + +const procPath = "/proc" + +var errFound = errors.New("found") + +// IsVMRunning looks in /proc for a hypervisor process that contains +// the containerID in its command line +func IsVMRunning(containerID string) bool { + err := filepath.Walk(procPath, func(path string, _ os.FileInfo, _ error) error { + if path == "" { + return filepath.SkipDir + } + + info, err := os.Stat(path) + if err != nil { + return filepath.SkipDir + } + + if !info.IsDir() { + return filepath.SkipDir + } + + content, err := ioutil.ReadFile(filepath.Join(path, "cmdline")) + if err != nil { + return filepath.SkipDir + } + + hypervisorRegexs := []string{".*/qemu.*-name.*" + containerID + ".*-qmp.*unix:.*/" + containerID + "/.*"} + + for _, regex := range hypervisorRegexs { + matcher := regexp.MustCompile(regex) + if matcher.MatchString(string(content)) { + return errFound + } + } + + return nil + }) + + return err == errFound +}