From 3c53ed233dd61a68471f77e9ed9ede933a4d89df Mon Sep 17 00:00:00 2001 From: fupan Date: Wed, 19 Sep 2018 16:34:27 +0800 Subject: [PATCH] containerd-shim-kata: add unit test cases Add unit test cases. Signed-off-by: fupan --- containerd-shim-v2/container_test.go | 40 +++ containerd-shim-v2/create_test.go | 362 ++++++++++++++++++++++++++ containerd-shim-v2/delete_test.go | 61 +++++ containerd-shim-v2/exec_test.go | 50 ++++ containerd-shim-v2/pause_test.go | 202 +++++++++++++++ containerd-shim-v2/start_test.go | 177 +++++++++++++ containerd-shim-v2/utils_test.go | 375 +++++++++++++++++++++++++++ 7 files changed, 1267 insertions(+) create mode 100644 containerd-shim-v2/container_test.go create mode 100644 containerd-shim-v2/create_test.go create mode 100644 containerd-shim-v2/delete_test.go create mode 100644 containerd-shim-v2/exec_test.go create mode 100644 containerd-shim-v2/pause_test.go create mode 100644 containerd-shim-v2/start_test.go create mode 100644 containerd-shim-v2/utils_test.go diff --git a/containerd-shim-v2/container_test.go b/containerd-shim-v2/container_test.go new file mode 100644 index 0000000000..06734d0843 --- /dev/null +++ b/containerd-shim-v2/container_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + taskAPI "github.com/containerd/containerd/runtime/v2/task" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewContainer(t *testing.T) { + assert := assert.New(t) + + _, err := newContainer(nil, nil, "", nil) + + assert.Error(err) +} + +func TestGetExec(t *testing.T) { + assert := assert.New(t) + + r := &taskAPI.CreateTaskRequest{} + + c, err := newContainer(nil, r, "", nil) + assert.NoError(err) + + _, err = c.getExec("") + assert.Error(err) + + c.execs = make(map[string]*exec) + _, err = c.getExec("") + assert.Error(err) + + c.execs[TestID] = &exec{} + _, err = c.getExec(TestID) + assert.NoError(err) +} diff --git a/containerd-shim-v2/create_test.go b/containerd-shim-v2/create_test.go new file mode 100644 index 0000000000..ad2c1387f2 --- /dev/null +++ b/containerd-shim-v2/create_test.go @@ -0,0 +1,362 @@ +// Copyright (c) 2017 Intel Corporation +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + "context" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/containerd/containerd/namespaces" + taskAPI "github.com/containerd/containerd/runtime/v2/task" + + vc "github.com/kata-containers/runtime/virtcontainers" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" + + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/assert" +) + +func TestCreateSandboxSuccess(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip(testDisabledNeedRoot) + } + + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + MockContainers: []*vcmock.Container{ + {MockID: testContainerID}, + }, + } + + 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) + + ociConfigFile := filepath.Join(bundlePath, "config.json") + assert.True(fileExists(ociConfigFile)) + + spec, err := readOCIConfigFile(ociConfigFile) + 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) + + s := &service{ + id: testSandboxID, + containers: make(map[string]*container), + config: &runtimeConfig, + } + + req := &taskAPI.CreateTaskRequest{ + ID: testSandboxID, + Bundle: bundlePath, + Terminal: true, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Create(ctx, req) + assert.NoError(err) +} + +func TestCreateSandboxFail(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip(testDisabledNeedRoot) + } + assert := assert.New(t) + + 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) + + ociConfigFile := filepath.Join(bundlePath, "config.json") + assert.True(fileExists(ociConfigFile)) + + spec, err := readOCIConfigFile(ociConfigFile) + assert.NoError(err) + + err = writeOCIConfigFile(spec, ociConfigFile) + assert.NoError(err) + + s := &service{ + id: testSandboxID, + containers: make(map[string]*container), + config: &runtimeConfig, + } + + req := &taskAPI.CreateTaskRequest{ + ID: testSandboxID, + Bundle: bundlePath, + Terminal: true, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Create(ctx, req) + assert.Error(err) + assert.True(vcmock.IsMockError(err)) +} + +func TestCreateSandboxConfigFail(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip(testDisabledNeedRoot) + } + + assert := assert.New(t) + + 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) + + ociConfigFile := filepath.Join(bundlePath, "config.json") + assert.True(fileExists(ociConfigFile)) + + spec, err := readOCIConfigFile(ociConfigFile) + assert.NoError(err) + + quota := int64(0) + limit := int64(0) + + spec.Linux.Resources.Memory = &specs.LinuxMemory{ + Limit: &limit, + } + + // specify an invalid spec + spec.Linux.Resources.CPU = &specs.LinuxCPU{ + Quota: "a, + } + + s := &service{ + id: testSandboxID, + containers: make(map[string]*container), + config: &runtimeConfig, + } + + req := &taskAPI.CreateTaskRequest{ + ID: testSandboxID, + Bundle: bundlePath, + Terminal: true, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Create(ctx, req) + assert.Error(err) + assert.True(vcmock.IsMockError(err)) +} + +func TestCreateContainerSuccess(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.CreateContainerFunc = func(ctx context.Context, sandboxID string, containerConfig vc.ContainerConfig) (vc.VCSandbox, vc.VCContainer, error) { + return sandbox, &vcmock.Container{}, nil + } + + defer func() { + testingImpl.CreateContainerFunc = 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) + + ociConfigFile := filepath.Join(bundlePath, "config.json") + assert.True(fileExists(ociConfigFile)) + + spec, err := readOCIConfigFile(ociConfigFile) + assert.NoError(err) + + // set expected container type and sandboxID + spec.Annotations = make(map[string]string) + spec.Annotations[testContainerTypeAnnotation] = testContainerTypeContainer + spec.Annotations[testSandboxIDAnnotation] = testSandboxID + + // rewrite file + err = writeOCIConfigFile(spec, ociConfigFile) + assert.NoError(err) + + s := &service{ + id: testContainerID, + sandbox: sandbox, + containers: make(map[string]*container), + config: &runtimeConfig, + } + + req := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + Bundle: bundlePath, + Terminal: true, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Create(ctx, req) + assert.NoError(err) +} + +func TestCreateContainerFail(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) + + bundlePath := filepath.Join(tmpdir, "bundle") + + err = makeOCIBundle(bundlePath) + assert.NoError(err) + + ociConfigFile := filepath.Join(bundlePath, "config.json") + assert.True(fileExists(ociConfigFile)) + + spec, err := readOCIConfigFile(ociConfigFile) + assert.NoError(err) + + spec.Annotations = make(map[string]string) + spec.Annotations[testContainerTypeAnnotation] = testContainerTypeContainer + spec.Annotations[testSandboxIDAnnotation] = testSandboxID + + err = writeOCIConfigFile(spec, ociConfigFile) + assert.NoError(err) + + // doesn't create sandbox first + s := &service{ + id: testContainerID, + containers: make(map[string]*container), + config: &runtimeConfig, + } + + req := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + Bundle: bundlePath, + Terminal: true, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Create(ctx, req) + assert.Error(err) + assert.False(vcmock.IsMockError(err)) +} + +func TestCreateContainerConfigFail(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.CreateContainerFunc = func(ctx context.Context, sandboxID string, containerConfig vc.ContainerConfig) (vc.VCSandbox, vc.VCContainer, error) { + return sandbox, &vcmock.Container{}, nil + } + + defer func() { + testingImpl.CreateContainerFunc = 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) + + ociConfigFile := filepath.Join(bundlePath, "config.json") + assert.True(fileExists(ociConfigFile)) + + spec, err := readOCIConfigFile(ociConfigFile) + assert.NoError(err) + + // set the error containerType + spec.Annotations = make(map[string]string) + spec.Annotations[testContainerTypeAnnotation] = "errorType" + spec.Annotations[testSandboxIDAnnotation] = testSandboxID + + err = writeOCIConfigFile(spec, ociConfigFile) + assert.NoError(err) + + s := &service{ + id: testContainerID, + sandbox: sandbox, + containers: make(map[string]*container), + config: &runtimeConfig, + } + + req := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + Bundle: bundlePath, + Terminal: true, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Create(ctx, req) + assert.Error(err) +} diff --git a/containerd-shim-v2/delete_test.go b/containerd-shim-v2/delete_test.go new file mode 100644 index 0000000000..995e3d84ee --- /dev/null +++ b/containerd-shim-v2/delete_test.go @@ -0,0 +1,61 @@ +// Copyright (c) 2017 Intel Corporation +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + taskAPI "github.com/containerd/containerd/runtime/v2/task" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" + "github.com/stretchr/testify/assert" +) + +func TestDeleteContainerSuccessAndFail(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + rootPath, configPath := testConfigSetup(t) + defer os.RemoveAll(rootPath) + _, err := readOCIConfigJSON(configPath) + assert.NoError(err) + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + } + s.containers[testContainerID], err = newContainer(s, reqCreate, "", nil) + assert.NoError(err) +} + +func testConfigSetup(t *testing.T) (rootPath string, configPath string) { + assert := assert.New(t) + + tmpdir, err := ioutil.TempDir("", "") + assert.NoError(err) + + bundlePath := filepath.Join(tmpdir, "bundle") + err = os.MkdirAll(bundlePath, testDirMode) + assert.NoError(err) + + err = createOCIConfig(bundlePath) + assert.NoError(err) + + // config json path + configPath = filepath.Join(bundlePath, "config.json") + return tmpdir, configPath +} diff --git a/containerd-shim-v2/exec_test.go b/containerd-shim-v2/exec_test.go new file mode 100644 index 0000000000..254ec576e6 --- /dev/null +++ b/containerd-shim-v2/exec_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2017 Intel Corporation +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + "context" + "testing" + + "github.com/containerd/containerd/namespaces" + + taskAPI "github.com/containerd/containerd/runtime/v2/task" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" + + "github.com/stretchr/testify/assert" +) + +func TestExecNoSpecFail(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + } + + var err error + s.containers[testContainerID], err = newContainer(s, reqCreate, "", nil) + assert.NoError(err) + + reqExec := &taskAPI.ExecProcessRequest{ + ID: testContainerID, + ExecID: testContainerID, + } + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + + _, err = s.Exec(ctx, reqExec) + assert.Error(err) +} diff --git a/containerd-shim-v2/pause_test.go b/containerd-shim-v2/pause_test.go new file mode 100644 index 0000000000..552e5baa2b --- /dev/null +++ b/containerd-shim-v2/pause_test.go @@ -0,0 +1,202 @@ +// Copyright (c) 2017 Intel Corporation +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + "context" + "testing" + + "github.com/containerd/containerd/namespaces" + taskAPI "github.com/containerd/containerd/runtime/v2/task" + + vc "github.com/kata-containers/runtime/virtcontainers" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" + + "github.com/stretchr/testify/assert" +) + +func TestPauseContainerSuccess(t *testing.T) { + assert := assert.New(t) + var err error + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.PauseContainerFunc = func(ctx context.Context, sandboxID, containerID string) error { + return nil + } + defer func() { + testingImpl.PauseContainerFunc = nil + }() + + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: testContainerID, + Annotations: make(map[string]string), + State: vc.State{ + State: vc.StateRunning, + }, + }, nil + } + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + } + s.containers[testContainerID], err = newContainer(s, reqCreate, "", nil) + assert.NoError(err) + + reqPause := &taskAPI.PauseRequest{ + ID: testContainerID, + } + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + + _, err = s.Pause(ctx, reqPause) + assert.NoError(err) +} + +func TestPauseContainerFail(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.PauseContainerFunc = func(ctx context.Context, sandboxID, containerID string) error { + return nil + } + defer func() { + testingImpl.PauseContainerFunc = nil + }() + + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: testContainerID, + Annotations: make(map[string]string), + State: vc.State{ + State: vc.StateRunning, + }, + }, nil + } + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqPause := &taskAPI.PauseRequest{ + ID: testContainerID, + } + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + + _, err := s.Pause(ctx, reqPause) + assert.Error(err) +} + +func TestResumeContainerSuccess(t *testing.T) { + assert := assert.New(t) + var err error + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.ResumeContainerFunc = func(ctx context.Context, sandboxID, containerID string) error { + return nil + } + defer func() { + testingImpl.ResumeContainerFunc = nil + }() + + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: testContainerID, + Annotations: make(map[string]string), + State: vc.State{ + State: vc.StateRunning, + }, + }, nil + } + + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + } + s.containers[testContainerID], err = newContainer(s, reqCreate, "", nil) + assert.NoError(err) + + reqResume := &taskAPI.ResumeRequest{ + ID: testContainerID, + } + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + + _, err = s.Resume(ctx, reqResume) + assert.NoError(err) +} + +func TestResumeContainerFail(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.ResumeContainerFunc = func(ctx context.Context, sandboxID, containerID string) error { + return nil + } + defer func() { + testingImpl.ResumeContainerFunc = nil + }() + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: testContainerID, + Annotations: make(map[string]string), + State: vc.State{ + State: vc.StateRunning, + }, + }, nil + } + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqResume := &taskAPI.ResumeRequest{ + ID: testContainerID, + } + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + + _, err := s.Resume(ctx, reqResume) + assert.Error(err) +} diff --git a/containerd-shim-v2/start_test.go b/containerd-shim-v2/start_test.go new file mode 100644 index 0000000000..994576eb38 --- /dev/null +++ b/containerd-shim-v2/start_test.go @@ -0,0 +1,177 @@ +// Copyright (c) 2017 Intel Corporation +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + "context" + "testing" + + "github.com/containerd/containerd/namespaces" + taskAPI "github.com/containerd/containerd/runtime/v2/task" + + vc "github.com/kata-containers/runtime/virtcontainers" + vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" + + "github.com/stretchr/testify/assert" +) + +func TestStartStartSandboxSuccess(t *testing.T) { + assert := assert.New(t) + var err error + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: sandbox.ID(), + Annotations: map[string]string{ + vcAnnotations.ContainerTypeKey: string(vc.PodSandbox), + }, + }, nil + } + + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testSandboxID, + } + s.containers[testSandboxID], err = newContainer(s, reqCreate, vc.PodSandbox, nil) + assert.NoError(err) + + reqStart := &taskAPI.StartRequest{ + ID: testSandboxID, + } + + testingImpl.StartSandboxFunc = func(ctx context.Context, sandboxID string) (vc.VCSandbox, error) { + return sandbox, nil + } + + defer func() { + testingImpl.StartSandboxFunc = nil + }() + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Start(ctx, reqStart) + assert.NoError(err) +} + +func TestStartMissingAnnotation(t *testing.T) { + assert := assert.New(t) + var err error + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: sandbox.ID(), + Annotations: map[string]string{}, + }, nil + } + + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testSandboxID, + } + s.containers[testSandboxID], err = newContainer(s, reqCreate, "", nil) + assert.NoError(err) + + reqStart := &taskAPI.StartRequest{ + ID: testSandboxID, + } + + testingImpl.StartSandboxFunc = func(ctx context.Context, sandboxID string) (vc.VCSandbox, error) { + return sandbox, nil + } + + defer func() { + testingImpl.StartSandboxFunc = nil + }() + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Start(ctx, reqStart) + assert.Error(err) + assert.False(vcmock.IsMockError(err)) +} + +func TestStartStartContainerSucess(t *testing.T) { + assert := assert.New(t) + var err error + + sandbox := &vcmock.Sandbox{ + MockID: testSandboxID, + } + + sandbox.MockContainers = []*vcmock.Container{ + { + MockID: testContainerID, + MockSandbox: sandbox, + }, + } + + testingImpl.StatusContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: testContainerID, + Annotations: map[string]string{ + vcAnnotations.ContainerTypeKey: string(vc.PodContainer), + }, + }, nil + } + + defer func() { + testingImpl.StatusContainerFunc = nil + }() + + testingImpl.StartContainerFunc = func(ctx context.Context, sandboxID, containerID string) (vc.VCContainer, error) { + return sandbox.MockContainers[0], nil + } + + defer func() { + testingImpl.StartContainerFunc = nil + }() + + s := &service{ + id: testSandboxID, + sandbox: sandbox, + containers: make(map[string]*container), + } + + reqCreate := &taskAPI.CreateTaskRequest{ + ID: testContainerID, + } + s.containers[testContainerID], err = newContainer(s, reqCreate, vc.PodContainer, nil) + assert.NoError(err) + + reqStart := &taskAPI.StartRequest{ + ID: testContainerID, + } + + ctx := namespaces.WithNamespace(context.Background(), "UnitTest") + _, err = s.Start(ctx, reqStart) + assert.NoError(err) +} diff --git a/containerd-shim-v2/utils_test.go b/containerd-shim-v2/utils_test.go new file mode 100644 index 0000000000..4ae413ef93 --- /dev/null +++ b/containerd-shim-v2/utils_test.go @@ -0,0 +1,375 @@ +// Copyright (c) 2017 Intel Corporation +// Copyright (c) 2018 HyperHQ Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package containerdshim + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + sysExec "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/kata-containers/runtime/pkg/katautils" + vc "github.com/kata-containers/runtime/virtcontainers" + "github.com/kata-containers/runtime/virtcontainers/pkg/oci" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" +) + +const ( + // specConf is the name of the file holding the containers configuration + specConf = "config.json" + + TestID = "container_test" + + testDirMode = os.FileMode(0750) + testFileMode = os.FileMode(0640) + // testExeFileMode = os.FileMode(0750) + + // small docker image used to create root filesystems from + testDockerImage = "busybox" + + testSandboxID = "777-77-77777777" + testContainerID = "42" + testBundle = "bundle" + testConsole = "/dev/pts/888" + + testDisabledNeedRoot = "Test disabled as requires root user" + + testContainerTypeAnnotation = "io.kubernetes.cri.container-type" + testSandboxIDAnnotation = "io.kubernetes.cri.sandbox-id" + testContainerTypeSandbox = "sandbox" + testContainerTypeContainer = "container" +) + +var ( + // package variables set by calling TestMain() + testDir = "" + testBundleDir = "" +) + +// testingImpl is a concrete mock RVC implementation used for testing +var testingImpl = &vcmock.VCMock{} + +func init() { + fmt.Printf("INFO: running as actual user %v (effective %v), actual group %v (effective %v)\n", + os.Getuid(), os.Geteuid(), os.Getgid(), os.Getegid()) + + fmt.Printf("INFO: switching to fake virtcontainers implementation for testing\n") + vci = testingImpl + + var err error + + fmt.Printf("INFO: creating test directory\n") + testDir, err = ioutil.TempDir("", fmt.Sprintf("shimV2-")) + if err != nil { + panic(fmt.Sprintf("ERROR: failed to create test directory: %v", err)) + } + + fmt.Printf("INFO: test directory is %v\n", testDir) + + fmt.Printf("INFO: ensuring docker is running\n") + output, err := katautils.RunCommandFull([]string{"docker", "version"}, true) + if err != nil { + panic(fmt.Sprintf("ERROR: docker daemon is not installed, not running, or not accessible to current user: %v (error %v)", + output, err)) + } + + // Do this now to avoid hitting the test timeout value due to + // slow network response. + fmt.Printf("INFO: ensuring required docker image (%v) is available\n", testDockerImage) + + // Only hit the network if the image doesn't exist locally + _, err = katautils.RunCommand([]string{"docker", "inspect", "--type=image", testDockerImage}) + if err == nil { + fmt.Printf("INFO: docker image %v already exists locally\n", testDockerImage) + } else { + _, err = katautils.RunCommand([]string{"docker", "pull", testDockerImage}) + if err != nil { + panic(err) + } + } + + testBundleDir = filepath.Join(testDir, testBundle) + err = os.MkdirAll(testBundleDir, testDirMode) + if err != nil { + panic(fmt.Sprintf("ERROR: failed to create bundle directory %v: %v", testBundleDir, err)) + } + + fmt.Printf("INFO: creating OCI bundle in %v for tests to use\n", testBundleDir) + err = realMakeOCIBundle(testBundleDir) + if err != nil { + panic(fmt.Sprintf("ERROR: failed to create OCI bundle: %v", err)) + } +} + +// createOCIConfig creates an OCI configuration (spec) file in +// the bundle directory specified (which must exist). +func createOCIConfig(bundleDir string) error { + if bundleDir == "" { + return errors.New("BUG: Need bundle directory") + } + + if !katautils.FileExists(bundleDir) { + return fmt.Errorf("BUG: Bundle directory %s does not exist", bundleDir) + } + + var configCmd string + + // Search for a suitable version of runc to use to generate + // the OCI config file. + for _, cmd := range []string{"docker-runc", "runc"} { + fullPath, err := sysExec.LookPath(cmd) + if err == nil { + configCmd = fullPath + break + } + } + + if configCmd == "" { + return errors.New("Cannot find command to generate OCI config file") + } + + _, err := katautils.RunCommand([]string{configCmd, "spec", "--bundle", bundleDir}) + if err != nil { + return err + } + + specFile := filepath.Join(bundleDir, specConf) + if !katautils.FileExists(specFile) { + return fmt.Errorf("generated OCI config file does not exist: %v", specFile) + } + + return nil +} + +func createEmptyFile(path string) (err error) { + return ioutil.WriteFile(path, []byte(""), testFileMode) +} + +// newTestHypervisorConfig creaets a new virtcontainers +// HypervisorConfig, ensuring that the required resources are also +// created. +// +// Note: no parameter validation in case caller wishes to create an invalid +// object. +func newTestHypervisorConfig(dir string, create bool) (vc.HypervisorConfig, error) { + kernelPath := path.Join(dir, "kernel") + imagePath := path.Join(dir, "image") + hypervisorPath := path.Join(dir, "hypervisor") + + if create { + for _, file := range []string{kernelPath, imagePath, hypervisorPath} { + err := createEmptyFile(file) + if err != nil { + return vc.HypervisorConfig{}, err + } + } + } + + return vc.HypervisorConfig{ + KernelPath: kernelPath, + ImagePath: imagePath, + HypervisorPath: hypervisorPath, + HypervisorMachineType: "pc-lite", + }, nil +} + +// newTestRuntimeConfig creates a new RuntimeConfig +func newTestRuntimeConfig(dir, consolePath string, create bool) (oci.RuntimeConfig, error) { + if dir == "" { + return oci.RuntimeConfig{}, errors.New("BUG: need directory") + } + + hypervisorConfig, err := newTestHypervisorConfig(dir, create) + if err != nil { + return oci.RuntimeConfig{}, err + } + + return oci.RuntimeConfig{ + HypervisorType: vc.QemuHypervisor, + HypervisorConfig: hypervisorConfig, + AgentType: vc.KataContainersAgent, + ProxyType: vc.KataBuiltInProxyType, + ShimType: vc.KataBuiltInShimType, + Console: consolePath, + }, nil +} + +// readOCIConfig returns an OCI spec. +func readOCIConfigFile(configPath string) (oci.CompatOCISpec, error) { + if configPath == "" { + return oci.CompatOCISpec{}, errors.New("BUG: need config file path") + } + + data, err := ioutil.ReadFile(configPath) + if err != nil { + return oci.CompatOCISpec{}, err + } + + var ociSpec oci.CompatOCISpec + if err := json.Unmarshal(data, &ociSpec); err != nil { + return oci.CompatOCISpec{}, err + } + caps, err := oci.ContainerCapabilities(ociSpec) + if err != nil { + return oci.CompatOCISpec{}, err + } + ociSpec.Process.Capabilities = caps + return ociSpec, nil +} + +// realMakeOCIBundle will create an OCI bundle (including the "config.json" +// config file) in the directory specified (which must already exist). +// +// XXX: Note that tests should *NOT* call this function - they should +// XXX: instead call makeOCIBundle(). +func realMakeOCIBundle(bundleDir string) error { + if bundleDir == "" { + return errors.New("BUG: Need bundle directory") + } + + if !katautils.FileExists(bundleDir) { + return fmt.Errorf("BUG: Bundle directory %v does not exist", bundleDir) + } + + err := createOCIConfig(bundleDir) + if err != nil { + return err + } + + // Note the unusual parameter (a directory, not the config + // file to parse!) + spec, err := oci.ParseConfigJSON(bundleDir) + if err != nil { + return err + } + + // Determine the rootfs directory name the OCI config refers to + ociRootPath := spec.Root.Path + + rootfsDir := filepath.Join(bundleDir, ociRootPath) + + if strings.HasPrefix(ociRootPath, "/") { + return fmt.Errorf("Cannot handle absolute rootfs as bundle must be unique to each test") + } + + err = createRootfs(rootfsDir) + if err != nil { + return err + } + + return nil +} + +// Create an OCI bundle in the specified directory. +// +// Note that the directory will be created, but it's parent is expected to exist. +// +// This function works by copying the already-created test bundle. Ideally, +// the bundle would be recreated for each test, but createRootfs() uses +// docker which on some systems is too slow, resulting in the tests timing +// out. +func makeOCIBundle(bundleDir string) error { + from := testBundleDir + to := bundleDir + + // only the basename of bundleDir needs to exist as bundleDir + // will get created by cp(1). + base := filepath.Dir(bundleDir) + + for _, dir := range []string{from, base} { + if !katautils.FileExists(dir) { + return fmt.Errorf("BUG: directory %v should exist", dir) + } + } + + output, err := katautils.RunCommandFull([]string{"cp", "-a", from, to}, true) + if err != nil { + return fmt.Errorf("failed to copy test OCI bundle from %v to %v: %v (output: %v)", from, to, err, output) + } + + return nil +} + +// createRootfs creates a minimal root filesystem below the specified +// directory. +func createRootfs(dir string) error { + err := os.MkdirAll(dir, testDirMode) + if err != nil { + return err + } + + container, err := katautils.RunCommand([]string{"docker", "create", testDockerImage}) + if err != nil { + return err + } + + cmd1 := sysExec.Command("docker", "export", container) + cmd2 := sysExec.Command("tar", "-C", dir, "-xvf", "-") + + cmd1Stdout, err := cmd1.StdoutPipe() + if err != nil { + return err + } + + cmd2.Stdin = cmd1Stdout + + err = cmd2.Start() + if err != nil { + return err + } + + err = cmd1.Run() + if err != nil { + return err + } + + err = cmd2.Wait() + if err != nil { + return err + } + + // Clean up + _, err = katautils.RunCommand([]string{"docker", "rm", container}) + if err != nil { + return err + } + + return nil +} + +func writeOCIConfigFile(spec oci.CompatOCISpec, configPath string) error { + if configPath == "" { + return errors.New("BUG: need config file path") + } + + bytes, err := json.MarshalIndent(spec, "", "\t") + if err != nil { + return err + } + + return ioutil.WriteFile(configPath, bytes, testFileMode) +} + +// Read fail that should contain a CompatOCISpec and +// return its JSON representation on success +func readOCIConfigJSON(configFile string) (string, error) { + bundlePath := filepath.Dir(configFile) + ociSpec, err := oci.ParseConfigJSON(bundlePath) + if err != nil { + return "", nil + } + ociSpecJSON, err := json.Marshal(ociSpec) + if err != nil { + return "", err + } + return string(ociSpecJSON), err +}