Files
kata-containers/cli/run_test.go
James O. D. Hunt 3a1bbd0271 tracing: Add initial opentracing support
Add initial support for opentracing by using the `jaeger` package.
Since opentracing uses the `context` package, add a `context.Context`
as the first parameter to all the functions that we might want to
trace. Trace "spans" (trace points) are then added by extracting the
trace details from the specified context parameter.

Notes:

- Although the tracer is created in `main()`, the "root span"
  (aka the first trace point) is not added until `beforeSubcommands()`.

  This is by design and is a compromise: by delaying the creation of the
  root span, the spans become much more readable since using the web-based
  JaegerUI, you will see traces like this:

  ```
  kata-runtime: kata-runtime create
  ------------  -------------------
       ^                ^
       |                |
  Trace name        First span name
                    (which clearly shows the CLI command that was run)
  ```

  Creating the span earlier means it is necessary to expand 'n' spans in
  the UI before you get to see the name of the CLI command that was run.
  In adding support, this became very tedious, hence my design decision to
  defer the creation of the root span until after signal handling has been
  setup and after CLI options have been parsed, but still very early in
  the code path.

  - At this stage, the tracing stops at the `virtcontainers` call
  boundary.

- Tracing is "always on" as there doesn't appear to be a way to toggle
  it. However, its resolves to a "nop" unless the tracer can talk to a
  jaeger agent.

Note that this commit required a bit of rework to `beforeSubcommands()`
to reduce the cyclomatic complexity.

Fixes #557.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
2018-08-10 16:13:48 +01:00

629 lines
18 KiB
Go

// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
vc "github.com/kata-containers/runtime/virtcontainers"
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
func TestRunCliAction(t *testing.T) {
assert := assert.New(t)
flagSet := flag.NewFlagSet("flag", flag.ContinueOnError)
flagSet.Parse([]string{"runtime"})
// create a new fake context
ctx := createCLIContext(flagSet)
// get Action function
actionFunc, ok := runCLICommand.Action.(func(ctx *cli.Context) error)
assert.True(ok)
err := actionFunc(ctx)
assert.Error(err, "missing runtime configuration")
// temporal dir to place container files
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
// create a new runtime config
runtimeConfig, err := newTestRuntimeConfig(tmpdir, "/dev/ptmx", true)
assert.NoError(err)
ctx.App.Metadata = map[string]interface{}{
"runtimeConfig": runtimeConfig,
}
err = actionFunc(ctx)
assert.Error(err, "run without args")
}
func TestRunInvalidArgs(t *testing.T) {
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
MockContainers: []*vcmock.Container{
{MockID: testContainerID},
},
}
// fake functions used to run containers
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return sandbox, nil
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
defer func() {
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
}()
// temporal dir to place container files
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
// create a new bundle
bundlePath := filepath.Join(tmpdir, "bundle")
err = os.MkdirAll(bundlePath, testDirMode)
assert.NoError(err)
err = makeOCIBundle(bundlePath)
assert.NoError(err)
// pid file
pidFilePath := filepath.Join(tmpdir, "pid")
// console file
consolePath := "/dev/ptmx"
// inexistent path
inexistentPath := "/this/path/does/not/exist"
runtimeConfig, err := newTestRuntimeConfig(tmpdir, consolePath, true)
assert.NoError(err)
type testArgs struct {
containerID string
bundle string
console string
consoleSocket string
pidFile string
detach bool
runtimeConfig oci.RuntimeConfig
}
args := []testArgs{
{"", "", "", "", "", true, oci.RuntimeConfig{}},
{"", "", "", "", "", false, oci.RuntimeConfig{}},
{"", "", "", "", "", true, runtimeConfig},
{"", "", "", "", "", false, runtimeConfig},
{"", "", "", "", pidFilePath, false, runtimeConfig},
{"", "", "", "", inexistentPath, false, runtimeConfig},
{"", "", "", "", pidFilePath, false, runtimeConfig},
{"", "", "", inexistentPath, pidFilePath, false, runtimeConfig},
{"", "", inexistentPath, inexistentPath, pidFilePath, false, runtimeConfig},
{"", "", inexistentPath, "", pidFilePath, false, runtimeConfig},
{"", "", consolePath, "", pidFilePath, false, runtimeConfig},
{"", bundlePath, consolePath, "", pidFilePath, false, runtimeConfig},
{testContainerID, inexistentPath, consolePath, "", pidFilePath, false, oci.RuntimeConfig{}},
{testContainerID, inexistentPath, consolePath, "", inexistentPath, false, oci.RuntimeConfig{}},
{testContainerID, bundlePath, consolePath, "", pidFilePath, false, oci.RuntimeConfig{}},
{testContainerID, inexistentPath, consolePath, "", pidFilePath, false, runtimeConfig},
{testContainerID, inexistentPath, consolePath, "", inexistentPath, false, runtimeConfig},
{testContainerID, bundlePath, consolePath, "", pidFilePath, false, runtimeConfig},
}
for i, a := range args {
err := run(context.Background(), a.containerID, a.bundle, a.console, a.consoleSocket, a.pidFile, a.detach, a.runtimeConfig)
assert.Errorf(err, "test %d (%+v)", i, a)
}
}
type runContainerData struct {
pidFilePath string
consolePath string
bundlePath string
configJSON string
sandbox *vcmock.Sandbox
runtimeConfig oci.RuntimeConfig
process *os.Process
tmpDir string
}
func testRunContainerSetup(t *testing.T) runContainerData {
assert := assert.New(t)
// create a fake container workload
workload := []string{"/bin/sleep", "10"}
cmd := exec.Command(workload[0], workload[1:]...)
err := cmd.Start()
assert.NoError(err, "unable to start fake container workload %+v: %s", workload, err)
// temporal dir to place container files
// Note - it is returned to the caller, who does the defer remove to clean up.
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
// pid file
pidFilePath := filepath.Join(tmpdir, "pid")
// console file
consolePath := "/dev/ptmx"
// create a new bundle
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
// config json path
configPath := filepath.Join(bundlePath, specConfig)
// sandbox id and container id must be the same otherwise delete will not works
sandbox := &vcmock.Sandbox{
MockID: testContainerID,
}
sandbox.MockContainers = []*vcmock.Container{
{
MockID: testContainerID,
MockPid: cmd.Process.Pid,
MockSandbox: sandbox,
},
}
// create a new runtime config
runtimeConfig, err := newTestRuntimeConfig(tmpdir, consolePath, true)
assert.NoError(err)
configJSON, err := readOCIConfigJSON(configPath)
assert.NoError(err)
return runContainerData{
pidFilePath: pidFilePath,
consolePath: consolePath,
bundlePath: bundlePath,
configJSON: configJSON,
sandbox: sandbox,
runtimeConfig: runtimeConfig,
process: cmd.Process,
tmpDir: tmpdir,
}
}
func TestRunContainerSuccessful(t *testing.T) {
assert := assert.New(t)
d := testRunContainerSetup(t)
defer os.RemoveAll(d.tmpDir)
// this flags is used to detect if createSandboxFunc was called
flagCreate := false
// fake functions used to run containers
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
flagCreate = true
return d.sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return d.sandbox, nil
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
// return an empty list on create
if !flagCreate {
return vc.ContainerStatus{}, nil
}
// return a sandboxStatus with the container status
return vc.ContainerStatus{
ID: d.sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
vcAnnotations.ConfigJSONKey: d.configJSON,
},
}, nil
}
testingImpl.StartContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
// now we can kill the fake container workload
err := d.process.Kill()
assert.NoError(err)
return d.sandbox.MockContainers[0], nil
}
testingImpl.DeleteSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return d.sandbox, nil
}
testingImpl.DeleteContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
return d.sandbox.MockContainers[0], nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
testingImpl.StatusContainerFunc = nil
testingImpl.StartContainerFunc = nil
testingImpl.DeleteSandboxFunc = nil
testingImpl.DeleteContainerFunc = nil
}()
err = run(context.Background(), d.sandbox.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
// should return ExitError with the message and exit code
e, ok := err.(*cli.ExitError)
assert.True(ok, "error should be a cli.ExitError: %s", err)
assert.Empty(e.Error())
assert.NotZero(e.ExitCode())
}
func TestRunContainerDetachSuccessful(t *testing.T) {
assert := assert.New(t)
d := testRunContainerSetup(t)
defer os.RemoveAll(d.tmpDir)
// this flags is used to detect if createSandboxFunc was called
flagCreate := false
// fake functions used to run containers
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
flagCreate = true
return d.sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return d.sandbox, nil
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
// return an empty list on create
if !flagCreate {
return vc.ContainerStatus{}, nil
}
// return a sandboxStatus with the container status
return vc.ContainerStatus{
ID: d.sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
vcAnnotations.ConfigJSONKey: d.configJSON,
},
}, nil
}
testingImpl.StartContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
// now we can kill the fake container workload
err := d.process.Kill()
assert.NoError(err)
return d.sandbox.MockContainers[0], nil
}
testingImpl.DeleteSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return d.sandbox, nil
}
testingImpl.DeleteContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
return d.sandbox.MockContainers[0], nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
testingImpl.StatusContainerFunc = nil
testingImpl.StartContainerFunc = nil
testingImpl.DeleteSandboxFunc = nil
testingImpl.DeleteContainerFunc = nil
}()
err = run(context.Background(), d.sandbox.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, true, d.runtimeConfig)
// should not return ExitError
assert.NoError(err)
}
func TestRunContainerDeleteFail(t *testing.T) {
assert := assert.New(t)
d := testRunContainerSetup(t)
defer os.RemoveAll(d.tmpDir)
// this flags is used to detect if createSandboxFunc was called
flagCreate := false
// fake functions used to run containers
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
flagCreate = true
return d.sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return d.sandbox, nil
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
// return an empty list on create
if !flagCreate {
return vc.ContainerStatus{}, nil
}
// return a sandboxStatus with the container status
return vc.ContainerStatus{
ID: d.sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
vcAnnotations.ConfigJSONKey: d.configJSON,
},
}, nil
}
testingImpl.StartContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
// now we can kill the fake container workload
err := d.process.Kill()
assert.NoError(err)
return d.sandbox.MockContainers[0], nil
}
testingImpl.DeleteSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
// return an error to provoke a failure in delete
return nil, fmt.Errorf("DeleteSandboxFunc")
}
testingImpl.DeleteContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
// return an error to provoke a failure in delete
return d.sandbox.MockContainers[0], fmt.Errorf("DeleteContainerFunc")
}
defer func() {
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
testingImpl.StatusContainerFunc = nil
testingImpl.StartContainerFunc = nil
testingImpl.DeleteSandboxFunc = nil
testingImpl.DeleteContainerFunc = nil
}()
err = run(context.Background(), d.sandbox.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
// should not return ExitError
err, ok := err.(*cli.ExitError)
assert.False(ok, "error should not be a cli.ExitError: %s", err)
}
func TestRunContainerWaitFail(t *testing.T) {
assert := assert.New(t)
d := testRunContainerSetup(t)
defer os.RemoveAll(d.tmpDir)
// this flags is used to detect if createSandboxFunc was called
flagCreate := false
// fake functions used to run containers
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
flagCreate = true
return d.sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
return d.sandbox, nil
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
// return an empty list on create
if !flagCreate {
return vc.ContainerStatus{}, nil
}
// return a sandboxStatus with the container status
return vc.ContainerStatus{
ID: d.sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
vcAnnotations.ConfigJSONKey: d.configJSON,
},
}, nil
}
testingImpl.StartContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
// now we can kill the fake container workload
err := d.process.Kill()
assert.NoError(err)
// change PID to provoke a failure in Wait
d.sandbox.MockContainers[0].MockPid = -1
return d.sandbox.MockContainers[0], nil
}
testingImpl.DeleteSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
// return an error to provoke a failure in delete
return nil, fmt.Errorf("DeleteSandboxFunc")
}
testingImpl.DeleteContainerFunc = func(sandboxID, containerID string) (vc.VCContainer, error) {
// return an error to provoke a failure in delete
return d.sandbox.MockContainers[0], fmt.Errorf("DeleteContainerFunc")
}
defer func() {
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
testingImpl.StatusContainerFunc = nil
testingImpl.StartContainerFunc = nil
testingImpl.DeleteSandboxFunc = nil
testingImpl.DeleteContainerFunc = nil
}()
err = run(context.Background(), d.sandbox.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
// should not return ExitError
err, ok := err.(*cli.ExitError)
assert.False(ok, "error should not be a cli.ExitError: %s", err)
}
func TestRunContainerStartFail(t *testing.T) {
assert := assert.New(t)
d := testRunContainerSetup(t)
defer os.RemoveAll(d.tmpDir)
// now we can kill the fake container workload
err := d.process.Kill()
assert.NoError(err)
// this flags is used to detect if createSandboxFunc was called
flagCreate := false
// fake functions used to run containers
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
flagCreate = true
return d.sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
// start fails
return nil, fmt.Errorf("StartSandbox")
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
// return an empty list on create
if !flagCreate {
return vc.ContainerStatus{}, nil
}
// return a sandboxStatus with the container status
return vc.ContainerStatus{
ID: d.sandbox.ID(),
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
vcAnnotations.ConfigJSONKey: d.configJSON,
},
}, nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
testingImpl.StatusContainerFunc = nil
}()
err = run(context.Background(), d.sandbox.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
// should not return ExitError
err, ok := err.(*cli.ExitError)
assert.False(ok, "error should not be a cli.ExitError: %s", err)
}
func TestRunContainerStartFailExistingContainer(t *testing.T) {
assert := assert.New(t)
d := testRunContainerSetup(t)
defer os.RemoveAll(d.tmpDir)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
}
sandbox.MockContainers = []*vcmock.Container{
{
MockID: testContainerID,
MockSandbox: sandbox,
},
}
path, err := createTempContainerIDMapping(testContainerID, sandbox.ID())
assert.NoError(err)
defer os.RemoveAll(path)
testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) {
// return the container status
return vc.ContainerStatus{
ID: testContainerID,
Annotations: map[string]string{
vcAnnotations.ContainerTypeKey: string(vc.PodContainer),
},
}, nil
}
testingImpl.CreateSandboxFunc = func(sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
testingImpl.StartSandboxFunc = func(sandboxID string) (vc.VCSandbox, error) {
// force no containers
sandbox.MockContainers = nil
return sandbox, nil
}
defer func() {
testingImpl.StatusContainerFunc = nil
testingImpl.CreateSandboxFunc = nil
testingImpl.StartSandboxFunc = nil
}()
err = run(context.Background(), d.sandbox.ID(), d.bundlePath, d.consolePath, "", d.pidFilePath, false, d.runtimeConfig)
assert.Error(err)
assert.False(vcmock.IsMockError(err))
}