Files
kata-containers/cli/create_test.go
Wei Zhang 2ed94cbd9d Config: Remove ConfigJSONKey from annotations
Fixes: #2023

We can get OCI spec config from bundle instead of annotations, so this
field isn't necessary.

Signed-off-by: Wei Zhang <weizhang555.zw@gmail.com>
2019-09-17 11:47:06 +08:00

725 lines
19 KiB
Go

// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"testing"
ktu "github.com/kata-containers/runtime/pkg/katatestutils"
"github.com/kata-containers/runtime/pkg/katautils"
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/kata-containers/runtime/virtcontainers/pkg/compatoci"
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
const (
testPID = 100
testConsole = "/dev/pts/999"
testContainerTypeAnnotation = "io.kubernetes.cri-o.ContainerType"
testContainerTypeSandbox = "sandbox"
testContainerTypeContainer = "container"
)
var (
testStrPID = fmt.Sprintf("%d", testPID)
ctrsMapTreePath = "/var/run/kata-containers/containers-mapping"
)
func TestCreatePIDFileSuccessful(t *testing.T) {
pidDirPath, err := ioutil.TempDir(testDir, "pid-path-")
if err != nil {
t.Fatalf("Could not create temporary PID directory: %s", err)
}
pidFilePath := filepath.Join(pidDirPath, "pid-file-path")
if err := createPIDFile(context.Background(), pidFilePath, testPID); err != nil {
t.Fatal(err)
}
fileBytes, err := ioutil.ReadFile(pidFilePath)
if err != nil {
os.RemoveAll(pidFilePath)
t.Fatalf("Could not read %q: %s", pidFilePath, err)
}
if string(fileBytes) != testStrPID {
os.RemoveAll(pidFilePath)
t.Fatalf("PID %s read from %q different from expected PID %s", string(fileBytes), pidFilePath, testStrPID)
}
os.RemoveAll(pidFilePath)
}
func TestCreatePIDFileEmptyPathSuccessful(t *testing.T) {
file := ""
if err := createPIDFile(context.Background(), file, testPID); err != nil {
t.Fatalf("This test should not fail (pidFilePath %q, pid %d)", file, testPID)
}
}
func TestCreatePIDFileUnableToRemove(t *testing.T) {
if tc.NotValid(ktu.NeedNonRoot()) {
// The os.FileMode(0000) trick doesn't work for root.
t.Skip(ktu.TestDisabledNeedNonRoot)
}
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
subdir := filepath.Join(tmpdir, "dir")
file := filepath.Join(subdir, "pidfile")
// stop non-root user from removing the directory later
err = os.MkdirAll(subdir, os.FileMode(0000))
assert.NoError(err)
err = createPIDFile(context.Background(), file, testPID)
assert.Error(err)
// let it be deleted
os.Chmod(subdir, testDirMode)
}
func TestCreatePIDFileUnableToCreate(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
subdir := filepath.Join(tmpdir, "dir")
file := filepath.Join(subdir, "pidfile")
err = createPIDFile(context.Background(), file, testPID)
// subdir doesn't exist
assert.Error(err)
os.Chmod(subdir, testDirMode)
}
func TestCreateCLIFunctionNoRuntimeConfig(t *testing.T) {
assert := assert.New(t)
ctx := createCLIContext(nil)
ctx.App.Name = "foo"
ctx.App.Metadata["foo"] = "bar"
fn, ok := createCLICommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err := fn(ctx)
// no runtime config in the Metadata
assert.Error(err)
}
func TestCreateCLIFunctionSetupConsoleFail(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
subdir := filepath.Join(tmpdir, "dir")
// does not exist
consoleSocketPath := filepath.Join(subdir, "console")
set := flag.NewFlagSet("", 0)
set.String("console-socket", consoleSocketPath, "")
ctx := createCLIContext(set)
ctx.App.Name = "foo"
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok := createCLICommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
// failed to setup console
assert.Error(err)
}
func TestCreateCLIFunctionCreateFail(t *testing.T) {
assert := assert.New(t)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
set := flag.NewFlagSet("", 0)
set.String("console-socket", "", "")
ctx := createCLIContext(set)
ctx.App.Name = "foo"
ctx.App.Metadata["runtimeConfig"] = runtimeConfig
fn, ok := createCLICommand.Action.(func(context *cli.Context) error)
assert.True(ok)
err = fn(ctx)
// create() failed
assert.Error(err)
}
func TestCreateInvalidArgs(t *testing.T) {
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
MockContainers: []*vcmock.Container{
{MockID: testContainerID},
{MockID: testContainerID},
{MockID: testContainerID},
},
}
testingImpl.CreateSandboxFunc = func(ctx context.Context, sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
defer func() {
testingImpl.CreateSandboxFunc = nil
}()
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
type testData struct {
containerID string
bundlePath string
console string
pidFilePath string
detach bool
systemdCgroup bool
runtimeConfig oci.RuntimeConfig
}
data := []testData{
{"", "", "", "", false, false, oci.RuntimeConfig{}},
{"", "", "", "", true, true, oci.RuntimeConfig{}},
{"foo", "", "", "", true, false, oci.RuntimeConfig{}},
{testContainerID, bundlePath, testConsole, pidFilePath, false, false, runtimeConfig},
{testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig},
}
for i, d := range data {
err := create(context.Background(), d.containerID, d.bundlePath, d.console, d.pidFilePath, d.detach, d.systemdCgroup, d.runtimeConfig)
assert.Errorf(err, "test %d (%+v)", i, d)
}
}
func TestCreateInvalidConfigJSON(t *testing.T) {
assert := assert.New(t)
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
f, err := os.OpenFile(ociConfigFile, os.O_APPEND|os.O_WRONLY, testFileMode)
assert.NoError(err)
// invalidate the JSON
_, err = f.WriteString("{")
assert.NoError(err)
f.Close()
for detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig)
assert.Errorf(err, "%+v", detach)
assert.False(vcmock.IsMockError(err))
os.RemoveAll(path)
}
}
func TestCreateInvalidContainerType(t *testing.T) {
assert := assert.New(t)
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
spec, err := compatoci.ParseConfigJSON(bundlePath)
assert.NoError(err)
// Force an invalid container type
spec.Annotations = make(map[string]string)
spec.Annotations[testContainerTypeAnnotation] = "I-am-not-a-valid-container-type"
// rewrite the file
err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err)
for detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig)
assert.Errorf(err, "%+v", detach)
assert.False(vcmock.IsMockError(err))
os.RemoveAll(path)
}
}
func TestCreateContainerInvalid(t *testing.T) {
assert := assert.New(t)
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
spec, err := compatoci.ParseConfigJSON(bundlePath)
assert.NoError(err)
// Force createContainer() to be called.
spec.Annotations = make(map[string]string)
spec.Annotations[testContainerTypeAnnotation] = testContainerTypeContainer
// rewrite the file
err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err)
for detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig)
assert.Errorf(err, "%+v", detach)
assert.False(vcmock.IsMockError(err))
os.RemoveAll(path)
}
}
func TestCreateProcessCgroupsPathSuccessful(t *testing.T) {
if tc.NotValid(ktu.NeedRoot()) {
t.Skip(ktu.TestDisabledNeedRoot)
}
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
MockContainers: []*vcmock.Container{
{MockID: testContainerID},
},
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
testingImpl.CreateSandboxFunc = func(ctx context.Context, sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
}()
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
spec, err := compatoci.ParseConfigJSON(bundlePath)
assert.NoError(err)
// Force sandbox-type container
spec.Annotations = make(map[string]string)
spec.Annotations[testContainerTypeAnnotation] = testContainerTypeSandbox
// Set a limit to ensure processCgroupsPath() considers the
// cgroup part of the spec
limit := int64(1024 * 1024)
spec.Linux.Resources.Memory = &specs.LinuxMemory{
Limit: &limit,
}
// Set an absolute path
spec.Linux.CgroupsPath = "/this/is/a/cgroup/path"
var mounts []specs.Mount
foundMount := false
// Replace the standard cgroup destination with a temporary one.
for _, mount := range spec.Mounts {
if mount.Type == "cgroup" {
foundMount = true
cgroupDir, err := ioutil.TempDir("", "cgroup")
assert.NoError(err)
defer os.RemoveAll(cgroupDir)
mount.Destination = cgroupDir
}
mounts = append(mounts, mount)
}
assert.True(foundMount)
// Replace mounts with the newly created one.
spec.Mounts = mounts
// Rewrite the file
err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err)
err = create(context.Background(), testContainerID, "", testConsole, pidFilePath, false, true, runtimeConfig)
assert.Error(err, "bundle path not set")
re := regexp.MustCompile("config.json.*no such file or directory")
matches := re.FindAllStringSubmatch(err.Error(), -1)
assert.NotEmpty(matches)
for _, detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, detach, true, runtimeConfig)
assert.NoError(err, "detached: %+v", detach)
os.RemoveAll(path)
}
}
func TestCreateCreateCgroupsFilesFail(t *testing.T) {
if tc.NotValid(ktu.NeedNonRoot()) {
// The os.FileMode(0000) trick doesn't work for root.
t.Skip(ktu.TestDisabledNeedNonRoot)
}
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
MockContainers: []*vcmock.Container{
{MockID: testContainerID},
},
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
testingImpl.CreateSandboxFunc = func(ctx context.Context, sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
}()
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
spec, err := compatoci.ParseConfigJSON(bundlePath)
assert.NoError(err)
// Force sandbox-type container
spec.Annotations = make(map[string]string)
spec.Annotations[testContainerTypeAnnotation] = testContainerTypeSandbox
// Set a limit to ensure processCgroupsPath() considers the
// cgroup part of the spec
limit := int64(1024 * 1024)
spec.Linux.Resources.Memory = &specs.LinuxMemory{
Limit: &limit,
}
// Override
cgroupsDirPath = filepath.Join(tmpdir, "cgroups")
err = os.MkdirAll(cgroupsDirPath, testDirMode)
assert.NoError(err)
// Set a relative path
spec.Linux.CgroupsPath = "./a/relative/path"
dir := filepath.Join(cgroupsDirPath, "memory")
// Stop directory from being created
err = os.MkdirAll(dir, os.FileMode(0000))
assert.NoError(err)
// Rewrite the file
err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err)
for detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig)
assert.Errorf(err, "%+v", detach)
assert.False(vcmock.IsMockError(err))
os.RemoveAll(path)
}
}
func TestCreateCreateCreatePidFileFail(t *testing.T) {
if tc.NotValid(ktu.NeedNonRoot()) {
// The os.FileMode(0000) trick doesn't work for root.
t.Skip(ktu.TestDisabledNeedNonRoot)
}
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
MockContainers: []*vcmock.Container{
{MockID: testContainerID},
},
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
testingImpl.CreateSandboxFunc = func(ctx context.Context, sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
}()
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidDir := filepath.Join(tmpdir, "pid")
pidFilePath := filepath.Join(pidDir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
spec, err := compatoci.ParseConfigJSON(bundlePath)
assert.NoError(err)
// Force sandbox-type container
spec.Annotations = make(map[string]string)
spec.Annotations[testContainerTypeAnnotation] = testContainerTypeSandbox
// Set a limit to ensure processCgroupsPath() considers the
// cgroup part of the spec
limit := int64(1024 * 1024)
spec.Linux.Resources.Memory = &specs.LinuxMemory{
Limit: &limit,
}
// Rewrite the file
err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err)
// stop the pidfile from being created
err = os.MkdirAll(pidDir, os.FileMode(0000))
assert.NoError(err)
for detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig)
assert.Errorf(err, "%+v", detach)
assert.False(vcmock.IsMockError(err))
os.RemoveAll(path)
}
}
func TestCreate(t *testing.T) {
if tc.NotValid(ktu.NeedRoot()) {
t.Skip(ktu.TestDisabledNeedRoot)
}
assert := assert.New(t)
sandbox := &vcmock.Sandbox{
MockID: testSandboxID,
MockContainers: []*vcmock.Container{
{MockID: testContainerID},
},
}
path, err := ioutil.TempDir("", "containers-mapping")
assert.NoError(err)
defer os.RemoveAll(path)
ctrsMapTreePath = path
katautils.SetCtrsMapTreePath(ctrsMapTreePath)
testingImpl.CreateSandboxFunc = func(ctx context.Context, sandboxConfig vc.SandboxConfig) (vc.VCSandbox, error) {
return sandbox, nil
}
defer func() {
testingImpl.CreateSandboxFunc = nil
}()
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)
runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
assert.NoError(err)
bundlePath := filepath.Join(tmpdir, "bundle")
err = makeOCIBundle(bundlePath)
assert.NoError(err)
pidFilePath := filepath.Join(tmpdir, "pidfile.txt")
ociConfigFile := filepath.Join(bundlePath, "config.json")
assert.True(katautils.FileExists(ociConfigFile))
spec, err := compatoci.ParseConfigJSON(bundlePath)
assert.NoError(err)
// Force sandbox-type container
spec.Annotations = make(map[string]string)
spec.Annotations[testContainerTypeAnnotation] = testContainerTypeSandbox
// Set a limit to ensure processCgroupsPath() considers the
// cgroup part of the spec
limit := int64(1024 * 1024)
spec.Linux.Resources.Memory = &specs.LinuxMemory{
Limit: &limit,
}
// Rewrite the file
err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err)
for detach := range []bool{true, false} {
err := create(context.Background(), testContainerID, bundlePath, testConsole, pidFilePath, true, true, runtimeConfig)
assert.NoError(err, "%+v", detach)
os.RemoveAll(path)
}
}