Add unit test.

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu 2017-05-16 19:49:07 +00:00
parent 6ac71e5862
commit 322b6ef333
11 changed files with 1633 additions and 15 deletions

View File

@ -0,0 +1,157 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"errors"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
)
func TestCreateContainer(t *testing.T) {
testSandboxID := "test-sandbox-id"
testNameMeta := &runtime.ContainerMetadata{
Name: "test-name",
Attempt: 1,
}
testSandboxNameMeta := &runtime.PodSandboxMetadata{
Name: "test-sandbox-name",
Uid: "test-sandbox-uid",
Namespace: "test-sandbox-namespace",
Attempt: 2,
}
testConfig := &runtime.ContainerConfig{
Metadata: testNameMeta,
Image: &runtime.ImageSpec{
Image: "test-image",
},
Labels: map[string]string{"a": "b"},
Annotations: map[string]string{"c": "d"},
}
testSandboxConfig := &runtime.PodSandboxConfig{
Metadata: testSandboxNameMeta,
}
for desc, test := range map[string]struct {
sandboxMetadata *metadata.SandboxMetadata
reserveNameErr bool
createRootDirErr error
createMetadataErr bool
expectErr bool
expectMeta *metadata.ContainerMetadata
}{
"should return error if sandbox does not exist": {
sandboxMetadata: nil,
expectErr: true,
},
"should return error if name is reserved": {
sandboxMetadata: &metadata.SandboxMetadata{
ID: testSandboxID,
Name: makeSandboxName(testSandboxNameMeta),
Config: testSandboxConfig,
},
reserveNameErr: true,
expectErr: true,
},
"should return error if fail to create root directory": {
sandboxMetadata: &metadata.SandboxMetadata{
ID: testSandboxID,
Name: makeSandboxName(testSandboxNameMeta),
Config: testSandboxConfig,
},
createRootDirErr: errors.New("random error"),
expectErr: true,
},
"should be able to create container successfully": {
sandboxMetadata: &metadata.SandboxMetadata{
ID: testSandboxID,
Name: makeSandboxName(testSandboxNameMeta),
Config: testSandboxConfig,
},
expectErr: false,
expectMeta: &metadata.ContainerMetadata{
Name: makeContainerName(testNameMeta, testSandboxNameMeta),
SandboxID: testSandboxID,
Config: testConfig,
},
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fakeOS := c.os.(*ostesting.FakeOS)
if test.sandboxMetadata != nil {
assert.NoError(t, c.sandboxStore.Create(*test.sandboxMetadata))
}
containerName := makeContainerName(testNameMeta, testSandboxNameMeta)
if test.reserveNameErr {
assert.NoError(t, c.containerNameIndex.Reserve(containerName, "random id"))
}
rootExists := false
rootPath := ""
fakeOS.MkdirAllFn = func(path string, perm os.FileMode) error {
assert.Equal(t, os.FileMode(0755), perm)
rootPath = path
if test.createRootDirErr == nil {
rootExists = true
}
return test.createRootDirErr
}
fakeOS.RemoveAllFn = func(path string) error {
assert.Equal(t, rootPath, path)
rootExists = false
return nil
}
resp, err := c.CreateContainer(context.Background(), &runtime.CreateContainerRequest{
PodSandboxId: testSandboxID,
Config: testConfig,
SandboxConfig: testSandboxConfig,
})
if test.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
assert.False(t, rootExists, "root directory should be cleaned up")
if !test.reserveNameErr {
assert.NoError(t, c.containerNameIndex.Reserve(containerName, "random id"),
"container name should be released")
}
metas, err := c.containerStore.List()
assert.NoError(t, err)
assert.Empty(t, metas, "container metadata should not be created")
continue
}
assert.NoError(t, err)
assert.NotNil(t, resp)
id := resp.GetContainerId()
assert.True(t, rootExists)
assert.Equal(t, getContainerRootDir(c.rootDir, id), rootPath, "root directory should be created")
meta, err := c.containerStore.Get(id)
assert.NoError(t, err)
require.NotNil(t, meta)
test.expectMeta.ID = id
// TODO(random-liu): Use fake clock to test CreatedAt.
test.expectMeta.CreatedAt = meta.CreatedAt
assert.Equal(t, test.expectMeta, meta, "container metadata should be created")
}
}

View File

@ -0,0 +1,229 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
)
func TestToCRIContainer(t *testing.T) {
config := &runtime.ContainerConfig{
Metadata: &runtime.ContainerMetadata{
Name: "test-name",
Attempt: 1,
},
Image: &runtime.ImageSpec{Image: "test-image"},
Labels: map[string]string{"a": "b"},
Annotations: map[string]string{"c": "d"},
}
createdAt := time.Now().UnixNano()
meta := &metadata.ContainerMetadata{
ID: "test-id",
Name: "test-name",
SandboxID: "test-sandbox-id",
Config: config,
ImageRef: "test-image-ref",
Pid: 1234,
CreatedAt: createdAt,
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
ExitCode: 1,
Reason: "test-reason",
Message: "test-message",
}
expect := &runtime.Container{
Id: "test-id",
PodSandboxId: "test-sandbox-id",
Metadata: config.GetMetadata(),
Image: config.GetImage(),
ImageRef: "test-image-ref",
State: runtime.ContainerState_CONTAINER_EXITED,
CreatedAt: createdAt,
Labels: config.GetLabels(),
Annotations: config.GetAnnotations(),
}
c := toCRIContainer(meta)
assert.Equal(t, expect, c)
}
func TestFilterContainers(t *testing.T) {
c := newTestCRIContainerdService()
testContainers := []*runtime.Container{
{
Id: "1",
PodSandboxId: "s-1",
Metadata: &runtime.ContainerMetadata{Name: "name-1", Attempt: 1},
State: runtime.ContainerState_CONTAINER_RUNNING,
},
{
Id: "2",
PodSandboxId: "s-2",
Metadata: &runtime.ContainerMetadata{Name: "name-2", Attempt: 2},
State: runtime.ContainerState_CONTAINER_EXITED,
Labels: map[string]string{"a": "b"},
},
{
Id: "3",
PodSandboxId: "s-2",
Metadata: &runtime.ContainerMetadata{Name: "name-2", Attempt: 3},
State: runtime.ContainerState_CONTAINER_CREATED,
Labels: map[string]string{"c": "d"},
},
}
for desc, test := range map[string]struct {
filter *runtime.ContainerFilter
expect []*runtime.Container
}{
"no filter": {
expect: testContainers,
},
"id filter": {
filter: &runtime.ContainerFilter{Id: "2"},
expect: []*runtime.Container{testContainers[1]},
},
"state filter": {
filter: &runtime.ContainerFilter{
State: &runtime.ContainerStateValue{
State: runtime.ContainerState_CONTAINER_EXITED,
},
},
expect: []*runtime.Container{testContainers[1]},
},
"label filter": {
filter: &runtime.ContainerFilter{
LabelSelector: map[string]string{"a": "b"},
},
expect: []*runtime.Container{testContainers[1]},
},
"sandbox id filter": {
filter: &runtime.ContainerFilter{PodSandboxId: "s-2"},
expect: []*runtime.Container{testContainers[1], testContainers[2]},
},
"mixed filter not matched": {
filter: &runtime.ContainerFilter{
Id: "1",
PodSandboxId: "s-2",
LabelSelector: map[string]string{"a": "b"},
},
expect: []*runtime.Container{},
},
"mixed filter matched": {
filter: &runtime.ContainerFilter{
PodSandboxId: "s-2",
State: &runtime.ContainerStateValue{
State: runtime.ContainerState_CONTAINER_CREATED,
},
LabelSelector: map[string]string{"c": "d"},
},
expect: []*runtime.Container{testContainers[2]},
},
} {
filtered := c.filterCRIContainers(testContainers, test.filter)
assert.Equal(t, test.expect, filtered, desc)
}
}
func TestListContainers(t *testing.T) {
c := newTestCRIContainerdService()
createdAt := time.Now().UnixNano()
startedAt := time.Now().UnixNano()
finishedAt := time.Now().UnixNano()
containersInStore := []metadata.ContainerMetadata{
{
ID: "1",
Name: "name-1",
SandboxID: "s-1",
Config: &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-1"}},
CreatedAt: createdAt,
},
{
ID: "2",
Name: "name-2",
SandboxID: "s-1",
Config: &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-2"}},
CreatedAt: createdAt,
StartedAt: startedAt,
},
{
ID: "3",
Name: "name-3",
SandboxID: "s-1",
Config: &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-3"}},
CreatedAt: createdAt,
StartedAt: startedAt,
FinishedAt: finishedAt,
},
{
ID: "4",
Name: "name-4",
SandboxID: "s-2",
Config: &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-4"}},
CreatedAt: createdAt,
},
}
filter := &runtime.ContainerFilter{
PodSandboxId: "s-1",
}
expect := []*runtime.Container{
{
Id: "1",
PodSandboxId: "s-1",
Metadata: &runtime.ContainerMetadata{Name: "name-1"},
State: runtime.ContainerState_CONTAINER_CREATED,
CreatedAt: createdAt,
},
{
Id: "2",
PodSandboxId: "s-1",
Metadata: &runtime.ContainerMetadata{Name: "name-2"},
State: runtime.ContainerState_CONTAINER_RUNNING,
CreatedAt: createdAt,
},
{
Id: "3",
PodSandboxId: "s-1",
Metadata: &runtime.ContainerMetadata{Name: "name-3"},
State: runtime.ContainerState_CONTAINER_EXITED,
CreatedAt: createdAt,
},
}
// Inject test metadata
for _, cntr := range containersInStore {
c.containerStore.Create(cntr)
}
resp, err := c.ListContainers(context.Background(), &runtime.ListContainersRequest{Filter: filter})
assert.NoError(t, err)
require.NotNil(t, resp)
containers := resp.GetContainers()
assert.Len(t, containers, len(expect))
for _, cntr := range expect {
assert.Contains(t, containers, cntr)
}
}

View File

@ -0,0 +1,175 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
)
// TestSetContainerRemoving tests setContainerRemoving sets removing
// state correctly.
func TestSetContainerRemoving(t *testing.T) {
testID := "test-id"
for desc, test := range map[string]struct {
metadata *metadata.ContainerMetadata
expectErr bool
}{
"should return error when container is in running state": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
expectErr: true,
},
"should return error when container is in removing state": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
Removing: true,
},
expectErr: true,
},
"should not return error when container is not running and removing": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
},
expectErr: false,
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
if test.metadata != nil {
assert.NoError(t, c.containerStore.Create(*test.metadata))
}
err := c.setContainerRemoving(testID)
meta, getErr := c.containerStore.Get(testID)
assert.NoError(t, getErr)
if test.expectErr {
assert.Error(t, err)
assert.Equal(t, test.metadata, meta, "metadata should not be updated")
} else {
assert.NoError(t, err)
assert.True(t, meta.Removing, "removing should be set")
}
}
}
func TestRemoveContainer(t *testing.T) {
testID := "test-id"
testName := "test-name"
for desc, test := range map[string]struct {
metadata *metadata.ContainerMetadata
removeDirErr error
expectErr bool
expectUnsetRemoving bool
}{
"should return error when container is still running": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
expectErr: true,
},
"should return error when there is ongoing removing": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
Removing: true,
},
expectErr: true,
},
"should not return error if container does not exist": {
metadata: nil,
expectErr: false,
},
"should return error if remove container root fails": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
},
removeDirErr: errors.New("random error"),
expectErr: true,
expectUnsetRemoving: true,
},
"should be able to remove container successfully": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
},
expectErr: false,
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fakeOS := c.os.(*ostesting.FakeOS)
if test.metadata != nil {
assert.NoError(t, c.containerNameIndex.Reserve(testName, testID))
assert.NoError(t, c.containerStore.Create(*test.metadata))
}
fakeOS.RemoveAllFn = func(path string) error {
assert.Equal(t, getContainerRootDir(c.rootDir, testID), path)
return test.removeDirErr
}
resp, err := c.RemoveContainer(context.Background(), &runtime.RemoveContainerRequest{
ContainerId: testID,
})
if test.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
if !test.expectUnsetRemoving {
continue
}
meta, err := c.containerStore.Get(testID)
assert.NoError(t, err)
require.NotNil(t, meta)
// Also covers resetContainerRemoving.
assert.False(t, meta.Removing, "removing state should be unset")
continue
}
assert.NoError(t, err)
assert.NotNil(t, resp)
meta, err := c.containerStore.Get(testID)
assert.Error(t, err)
assert.True(t, metadata.IsNotExistError(err))
assert.Nil(t, meta, "container metadata should be removed")
assert.NoError(t, c.containerNameIndex.Reserve(testName, testID),
"container name should be released")
}
}

View File

@ -0,0 +1,402 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"encoding/json"
"errors"
"io"
"os"
"testing"
"time"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/container"
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
)
func getStartContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandboxConfig,
func(*testing.T, string, uint32, *runtimespec.Spec)) {
config := &runtime.ContainerConfig{
Metadata: &runtime.ContainerMetadata{
Name: "test-name",
Attempt: 1,
},
Command: []string{"test", "command"},
Args: []string{"test", "args"},
WorkingDir: "test-cwd",
Envs: []*runtime.KeyValue{
{Key: "k1", Value: "v1"},
{Key: "k2", Value: "v2"},
},
Mounts: []*runtime.Mount{
{
ContainerPath: "container-path-1",
HostPath: "host-path-1",
},
{
ContainerPath: "container-path-2",
HostPath: "host-path-2",
Readonly: true,
},
},
Labels: map[string]string{"a": "b"},
Annotations: map[string]string{"c": "d"},
Linux: &runtime.LinuxContainerConfig{
Resources: &runtime.LinuxContainerResources{
CpuPeriod: 100,
CpuQuota: 200,
CpuShares: 300,
MemoryLimitInBytes: 400,
OomScoreAdj: 500,
},
SecurityContext: &runtime.LinuxContainerSecurityContext{
Capabilities: &runtime.Capability{
AddCapabilities: []string{"CAP_SYS_ADMIN"},
DropCapabilities: []string{"CAP_CHOWN"},
},
SupplementalGroups: []int64{1111, 2222},
},
},
}
sandboxConfig := &runtime.PodSandboxConfig{
Metadata: &runtime.PodSandboxMetadata{
Name: "test-sandbox-name",
Uid: "test-sandbox-uid",
Namespace: "test-sandbox-ns",
Attempt: 2,
},
Linux: &runtime.LinuxPodSandboxConfig{
CgroupParent: "/test/cgroup/parent",
},
}
specCheck := func(t *testing.T, id string, sandboxPid uint32, spec *runtimespec.Spec) {
assert.Equal(t, relativeRootfsPath, spec.Root.Path)
assert.Equal(t, []string{"test", "command", "test", "args"}, spec.Process.Args)
assert.Equal(t, "test-cwd", spec.Process.Cwd)
assert.Contains(t, spec.Process.Env, "k1=v1", "k2=v2")
t.Logf("Check bind mount")
found1, found2 := false, false
for _, m := range spec.Mounts {
if m.Source == "host-path-1" {
assert.Equal(t, m.Destination, "container-path-1")
assert.Contains(t, m.Options, "rw")
found1 = true
}
if m.Source == "host-path-2" {
assert.Equal(t, m.Destination, "container-path-2")
assert.Contains(t, m.Options, "ro")
found2 = true
}
}
assert.True(t, found1)
assert.True(t, found2)
t.Logf("Check resource limits")
assert.EqualValues(t, *spec.Linux.Resources.CPU.Period, 100)
assert.EqualValues(t, *spec.Linux.Resources.CPU.Quota, 200)
assert.EqualValues(t, *spec.Linux.Resources.CPU.Shares, 300)
assert.EqualValues(t, *spec.Linux.Resources.Memory.Limit, 400)
assert.EqualValues(t, *spec.Linux.Resources.OOMScoreAdj, 500)
t.Logf("Check capabilities")
assert.Contains(t, spec.Process.Capabilities.Bounding, "CAP_SYS_ADMIN")
assert.Contains(t, spec.Process.Capabilities.Effective, "CAP_SYS_ADMIN")
assert.Contains(t, spec.Process.Capabilities.Inheritable, "CAP_SYS_ADMIN")
assert.Contains(t, spec.Process.Capabilities.Permitted, "CAP_SYS_ADMIN")
assert.Contains(t, spec.Process.Capabilities.Ambient, "CAP_SYS_ADMIN")
assert.NotContains(t, spec.Process.Capabilities.Bounding, "CAP_CHOWN")
assert.NotContains(t, spec.Process.Capabilities.Effective, "CAP_CHOWN")
assert.NotContains(t, spec.Process.Capabilities.Inheritable, "CAP_CHOWN")
assert.NotContains(t, spec.Process.Capabilities.Permitted, "CAP_CHOWN")
assert.NotContains(t, spec.Process.Capabilities.Ambient, "CAP_CHOWN")
t.Logf("Check supplemental groups")
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
t.Logf("Check cgroup path")
assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath)
t.Logf("Check namespaces")
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
Type: runtimespec.NetworkNamespace,
Path: getNetworkNamespace(sandboxPid),
})
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
Type: runtimespec.IPCNamespace,
Path: getIPCNamespace(sandboxPid),
})
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
Type: runtimespec.UTSNamespace,
Path: getUTSNamespace(sandboxPid),
})
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
Type: runtimespec.PIDNamespace,
Path: getPIDNamespace(sandboxPid),
})
}
return config, sandboxConfig, specCheck
}
func TestGeneralContainerSpec(t *testing.T) {
testID := "test-id"
testPid := uint32(1234)
config, sandboxConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService()
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig)
assert.NoError(t, err)
specCheck(t, testID, testPid, spec)
}
func TestContainerSpecTty(t *testing.T) {
testID := "test-id"
testPid := uint32(1234)
config, sandboxConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService()
for _, tty := range []bool{true, false} {
config.Tty = tty
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig)
assert.NoError(t, err)
specCheck(t, testID, testPid, spec)
assert.Equal(t, tty, spec.Process.Terminal)
}
}
func TestContainerSpecReadonlyRootfs(t *testing.T) {
testID := "test-id"
testPid := uint32(1234)
config, sandboxConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService()
for _, readonly := range []bool{true, false} {
config.Linux.SecurityContext.ReadonlyRootfs = readonly
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig)
assert.NoError(t, err)
specCheck(t, testID, testPid, spec)
assert.Equal(t, readonly, spec.Root.Readonly)
}
}
func TestStartContainer(t *testing.T) {
testID := "test-id"
testSandboxID := "test-sandbox-id"
testSandboxPid := uint32(4321)
config, sandboxConfig, specCheck := getStartContainerTestData()
testMetadata := &metadata.ContainerMetadata{
ID: testID,
Name: "test-name",
SandboxID: testSandboxID,
Config: config,
CreatedAt: time.Now().UnixNano(),
}
testSandboxMetadata := &metadata.SandboxMetadata{
ID: testSandboxID,
Name: "test-sandbox-name",
Config: sandboxConfig,
}
testSandboxContainer := &container.Container{
ID: testSandboxID,
Pid: testSandboxPid,
Status: container.Status_RUNNING,
}
for desc, test := range map[string]struct {
containerMetadata *metadata.ContainerMetadata
sandboxMetadata *metadata.SandboxMetadata
sandboxContainerdContainer *container.Container
prepareFIFOErr error
createContainerErr error
startContainerErr error
expectStateChange bool
expectCalls []string
expectErr bool
}{
"should return error when container does not exist": {
containerMetadata: nil,
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
expectCalls: []string{},
expectErr: true,
},
"should return error when container is not in created state": {
containerMetadata: &metadata.ContainerMetadata{
ID: testID,
Name: "test-name",
SandboxID: testSandboxID,
Config: config,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
expectCalls: []string{},
expectErr: true,
},
"should return error when container is in removing state": {
containerMetadata: &metadata.ContainerMetadata{
ID: testID,
Name: "test-name",
SandboxID: testSandboxID,
Config: config,
CreatedAt: time.Now().UnixNano(),
Removing: true,
},
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
expectCalls: []string{},
expectErr: true,
},
"should return error when sandbox does not exist": {
containerMetadata: testMetadata,
sandboxMetadata: nil,
sandboxContainerdContainer: testSandboxContainer,
expectStateChange: true,
expectCalls: []string{},
expectErr: true,
},
"should return error when sandbox is not running": {
containerMetadata: testMetadata,
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: &container.Container{
ID: testSandboxID,
Pid: testSandboxPid,
Status: container.Status_STOPPED,
},
expectStateChange: true,
expectCalls: []string{"info"},
expectErr: true,
},
"should return error when fail to open streaming pipes": {
containerMetadata: testMetadata,
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
prepareFIFOErr: errors.New("open error"),
expectStateChange: true,
expectCalls: []string{"info"},
expectErr: true,
},
"should return error when fail to create container": {
containerMetadata: testMetadata,
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
createContainerErr: errors.New("create error"),
expectStateChange: true,
expectCalls: []string{"info", "create"},
expectErr: true,
},
"should return error when fail to start container": {
containerMetadata: testMetadata,
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
startContainerErr: errors.New("start error"),
expectStateChange: true,
// cleanup the containerd container.
expectCalls: []string{"info", "create", "start", "delete"},
expectErr: true,
},
"should be able to start container successfully": {
containerMetadata: testMetadata,
sandboxMetadata: testSandboxMetadata,
sandboxContainerdContainer: testSandboxContainer,
expectStateChange: true,
expectCalls: []string{"info", "create", "start"},
expectErr: false,
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fake := c.containerService.(*servertesting.FakeExecutionClient)
fakeOS := c.os.(*ostesting.FakeOS)
if test.containerMetadata != nil {
assert.NoError(t, c.containerStore.Create(*test.containerMetadata))
}
if test.sandboxMetadata != nil {
assert.NoError(t, c.sandboxStore.Create(*test.sandboxMetadata))
}
if test.sandboxContainerdContainer != nil {
fake.SetFakeContainers([]container.Container{*test.sandboxContainerdContainer})
}
// TODO(random-liu): Test behavior with different streaming config.
fakeOS.OpenFifoFn = func(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error) {
return nopReadWriteCloser{}, test.prepareFIFOErr
}
if test.createContainerErr != nil {
fake.InjectError("create", test.createContainerErr)
}
if test.startContainerErr != nil {
fake.InjectError("start", test.startContainerErr)
}
resp, err := c.StartContainer(context.Background(), &runtime.StartContainerRequest{
ContainerId: testID,
})
// Check containerd functions called.
assert.Equal(t, test.expectCalls, fake.GetCalledNames())
// Check results returned.
if test.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
// Check container state.
meta, err := c.containerStore.Get(testID)
if !test.expectStateChange {
// Do not check the error, because container may not exist
// in the test case.
assert.Equal(t, meta, test.containerMetadata)
continue
}
assert.NoError(t, err)
require.NotNil(t, meta)
if test.expectErr {
t.Logf("container state should be in exited state when fail to start")
assert.Equal(t, runtime.ContainerState_CONTAINER_EXITED, meta.State())
assert.Zero(t, meta.Pid)
assert.EqualValues(t, errorStartExitCode, meta.ExitCode)
assert.Equal(t, errorStartReason, meta.Reason)
assert.NotEmpty(t, meta.Message)
_, err := fake.Info(context.Background(), &execution.InfoRequest{ID: testID})
assert.True(t, isContainerdContainerNotExistError(err),
"containerd container should be cleaned up after when fail to start")
continue
}
t.Logf("container state should be running when start successfully")
assert.Equal(t, runtime.ContainerState_CONTAINER_RUNNING, meta.State())
info, err := fake.Info(context.Background(), &execution.InfoRequest{ID: testID})
assert.NoError(t, err)
pid := info.Pid
assert.Equal(t, pid, meta.Pid)
assert.Equal(t, container.Status_RUNNING, info.Status)
// Check runtime spec
calls := fake.GetCalledDetails()
createOpts, ok := calls[1].Argument.(*execution.CreateRequest)
assert.True(t, ok, "2nd call should be create")
// TODO(random-liu): Test other create options.
spec := &runtimespec.Spec{}
assert.NoError(t, json.Unmarshal(createOpts.Spec.Value, spec))
specCheck(t, testID, testSandboxPid, spec)
}
}

View File

@ -0,0 +1,173 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
)
func getContainerStatusTestData() (*metadata.ContainerMetadata, *runtime.ContainerStatus) {
testID := "test-id"
config := &runtime.ContainerConfig{
Metadata: &runtime.ContainerMetadata{
Name: "test-name",
Attempt: 1,
},
Image: &runtime.ImageSpec{Image: "test-image"},
Mounts: []*runtime.Mount{{
ContainerPath: "test-container-path",
HostPath: "test-host-path",
}},
Labels: map[string]string{"a": "b"},
Annotations: map[string]string{"c": "d"},
}
createdAt := time.Now().UnixNano()
startedAt := time.Now().UnixNano()
metadata := &metadata.ContainerMetadata{
ID: testID,
Name: "test-long-name",
SandboxID: "test-sandbox-id",
Config: config,
ImageRef: "test-image-ref",
Pid: 1234,
CreatedAt: createdAt,
StartedAt: startedAt,
}
expected := &runtime.ContainerStatus{
Id: testID,
Metadata: config.GetMetadata(),
State: runtime.ContainerState_CONTAINER_RUNNING,
CreatedAt: createdAt,
StartedAt: startedAt,
Image: config.GetImage(),
ImageRef: "test-image-ref",
Reason: completeExitReason,
Labels: config.GetLabels(),
Annotations: config.GetAnnotations(),
Mounts: config.GetMounts(),
}
return metadata, expected
}
func TestToCRIContainerStatus(t *testing.T) {
for desc, test := range map[string]struct {
finishedAt int64
exitCode int32
reason string
message string
expectedState runtime.ContainerState
expectedReason string
}{
"container running": {
expectedState: runtime.ContainerState_CONTAINER_RUNNING,
},
"container exited with reason": {
finishedAt: time.Now().UnixNano(),
exitCode: 1,
reason: "test-reason",
message: "test-message",
expectedState: runtime.ContainerState_CONTAINER_EXITED,
expectedReason: "test-reason",
},
"container exited with exit code 0 without reason": {
finishedAt: time.Now().UnixNano(),
exitCode: 0,
message: "test-message",
expectedState: runtime.ContainerState_CONTAINER_EXITED,
expectedReason: completeExitReason,
},
"container exited with non-zero exit code without reason": {
finishedAt: time.Now().UnixNano(),
exitCode: 1,
message: "test-message",
expectedState: runtime.ContainerState_CONTAINER_EXITED,
expectedReason: errorExitReason,
},
} {
meta, expected := getContainerStatusTestData()
// Update metadata with test case.
meta.FinishedAt = test.finishedAt
meta.ExitCode = test.exitCode
meta.Reason = test.reason
meta.Message = test.message
// Set expectation based on test case.
expected.State = test.expectedState
expected.Reason = test.expectedReason
expected.FinishedAt = test.finishedAt
expected.ExitCode = test.exitCode
expected.Message = test.message
assert.Equal(t, expected, toCRIContainerStatus(meta), desc)
}
}
func TestContainerStatus(t *testing.T) {
for desc, test := range map[string]struct {
exist bool
finishedAt int64
reason string
expectedState runtime.ContainerState
expectErr bool
}{
"container running": {
exist: true,
expectedState: runtime.ContainerState_CONTAINER_RUNNING,
},
"container exited": {
exist: true,
finishedAt: time.Now().UnixNano(),
reason: "test-reason",
expectedState: runtime.ContainerState_CONTAINER_EXITED,
},
"container not exist": {
exist: false,
expectErr: true,
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
meta, expected := getContainerStatusTestData()
// Update metadata with test case.
meta.FinishedAt = test.finishedAt
meta.Reason = test.reason
if test.exist {
assert.NoError(t, c.containerStore.Create(*meta))
}
resp, err := c.ContainerStatus(context.Background(), &runtime.ContainerStatusRequest{ContainerId: meta.ID})
if test.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
continue
}
// Set expectation based on test case.
expected.FinishedAt = test.finishedAt
expected.Reason = test.reason
expected.State = test.expectedState
assert.Equal(t, expected, resp.GetStatus())
}
}

View File

@ -0,0 +1,199 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"errors"
"testing"
"time"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/container"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
)
func TestWaitContainerStop(t *testing.T) {
id := "test-id"
timeout := 2 * stopCheckPollInterval
for desc, test := range map[string]struct {
metadata *metadata.ContainerMetadata
expectErr bool
}{
"should return error if timeout exceeds": {
metadata: &metadata.ContainerMetadata{
ID: id,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
expectErr: true,
},
"should not return error if container is removed before timeout": {
metadata: nil,
expectErr: false,
},
"should not return error if container is stopped before timeout": {
metadata: &metadata.ContainerMetadata{
ID: id,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
},
expectErr: false,
},
} {
c := newTestCRIContainerdService()
if test.metadata != nil {
assert.NoError(t, c.containerStore.Create(*test.metadata))
}
err := c.waitContainerStop(id, timeout)
assert.Equal(t, test.expectErr, err != nil, desc)
}
}
func TestStopContainer(t *testing.T) {
testID := "test-id"
testPid := uint32(1234)
testMetadata := metadata.ContainerMetadata{
ID: testID,
Pid: testPid,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
}
testContainer := container.Container{
ID: testID,
Pid: testPid,
Status: container.Status_RUNNING,
}
for desc, test := range map[string]struct {
metadata *metadata.ContainerMetadata
containerdContainer *container.Container
killErr error
deleteErr error
discardEvents int
expectErr bool
expectCalls []string
}{
"should return error when container does not exist": {
metadata: nil,
expectErr: true,
expectCalls: []string{},
},
"should not return error when container is not running": {
metadata: &metadata.ContainerMetadata{
ID: testID,
CreatedAt: time.Now().UnixNano(),
},
expectErr: false,
expectCalls: []string{},
},
"should not return error if containerd container does not exist": {
metadata: &testMetadata,
expectErr: false,
expectCalls: []string{"kill"},
},
"should not return error if containerd container is killed": {
metadata: &testMetadata,
containerdContainer: &testContainer,
expectErr: false,
// deleted by the event monitor.
expectCalls: []string{"kill", "delete"},
},
"should not return error if containerd container is deleted": {
metadata: &testMetadata,
containerdContainer: &testContainer,
// discard killed events to force a delete. This is only
// for testing. Actually real containerd should only generate
// one EXIT event.
discardEvents: 1,
expectErr: false,
// one more delete from the event monitor.
expectCalls: []string{"kill", "delete", "delete"},
},
"should return error if kill failed": {
metadata: &testMetadata,
containerdContainer: &testContainer,
killErr: errors.New("random error"),
expectErr: true,
expectCalls: []string{"kill"},
},
"should return error if delete failed": {
metadata: &testMetadata,
containerdContainer: &testContainer,
deleteErr: errors.New("random error"),
discardEvents: 1,
expectErr: true,
expectCalls: []string{"kill", "delete"},
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fake := servertesting.NewFakeExecutionClient().WithEvents()
defer fake.Stop()
c.containerService = fake
// Inject metadata.
if test.metadata != nil {
assert.NoError(t, c.containerStore.Create(*test.metadata))
}
// Inject containerd container.
if test.containerdContainer != nil {
fake.SetFakeContainers([]container.Container{*test.containerdContainer})
}
if test.killErr != nil {
fake.InjectError("kill", test.killErr)
}
if test.deleteErr != nil {
fake.InjectError("delete", test.deleteErr)
}
eventClient, err := fake.Events(context.Background(), &execution.EventsRequest{})
assert.NoError(t, err)
// Start a simple test event monitor.
go func(e execution.ContainerService_EventsClient, discard int) {
for {
e, err := e.Recv() // nolint: vetshadow
if err != nil {
return
}
if discard > 0 {
discard--
continue
}
c.handleEvent(e)
}
}(eventClient, test.discardEvents)
fake.ClearCalls()
// 1 second timeout should be enough for the unit test.
// TODO(random-liu): Use fake clock for this test.
resp, err := c.StopContainer(context.Background(), &runtime.StopContainerRequest{
ContainerId: testID,
Timeout: 1,
})
if test.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
assert.Equal(t, test.expectCalls, fake.GetCalledNames())
}
}

View File

@ -28,6 +28,10 @@ import (
// startEventMonitor starts an event monitor which monitors and handles all // startEventMonitor starts an event monitor which monitors and handles all
// container events. // container events.
// TODO(random-liu): [P1] Figure out:
// 1) Is it possible to drop event during containerd is running?
// 2) How to deal with containerd down? We should restart event monitor, and
// we should recover all container state.
func (c *criContainerdService) startEventMonitor() error { func (c *criContainerdService) startEventMonitor() error {
events, err := c.containerService.Events(context.Background(), &execution.EventsRequest{}) events, err := c.containerService.Events(context.Background(), &execution.EventsRequest{})
if err != nil { if err != nil {
@ -35,25 +39,33 @@ func (c *criContainerdService) startEventMonitor() error {
} }
go func() { go func() {
for { for {
c.handleEvent(events) c.handleEventStream(events)
} }
}() }()
return nil return nil
} }
// handleEvent receives an event from contaienrd and handles the event. // handleEventStream receives an event from containerd and handles the event.
func (c *criContainerdService) handleEvent(events execution.ContainerService_EventsClient) { func (c *criContainerdService) handleEventStream(events execution.ContainerService_EventsClient) {
// TODO(random-liu): [P1] Should backoff on this error, or else this will
// cause a busy loop.
e, err := events.Recv() e, err := events.Recv()
if err != nil { if err != nil {
glog.Errorf("Failed to receive event: %v", err) glog.Errorf("Failed to receive event: %v", err)
return return
} }
glog.V(2).Infof("Received container event: %+v", e) glog.V(2).Infof("Received container event: %+v", e)
c.handleEvent(e)
return
}
// handleEvent handles a containerd event.
func (c *criContainerdService) handleEvent(e *container.Event) {
switch e.Type { switch e.Type {
// If containerd-shim exits unexpectedly, there will be no corresponding event. // If containerd-shim exits unexpectedly, there will be no corresponding event.
// However, containerd could not retrieve container state in that case, so it's // However, containerd could not retrieve container state in that case, so it's
// fine to leave out that case for now. // fine to leave out that case for now.
// TODO(random-liu): [P2] Handle container-shim exit. // TODO(random-liu): [P2] Handle containerd-shim exit.
case container.Event_EXIT: case container.Event_EXIT:
meta, err := c.containerStore.Get(e.ID) meta, err := c.containerStore.Get(e.ID)
if err != nil { if err != nil {
@ -61,7 +73,7 @@ func (c *criContainerdService) handleEvent(events execution.ContainerService_Eve
return return
} }
if e.Pid != meta.Pid { if e.Pid != meta.Pid {
// Not init process dies, ignore the event. // Non-init process died, ignore the event.
return return
} }
// Delete the container from containerd. // Delete the container from containerd.
@ -90,5 +102,4 @@ func (c *criContainerdService) handleEvent(events execution.ContainerService_Eve
case container.Event_OOM: case container.Event_OOM:
// TODO(random-liu): [P1] Handle OOM event. // TODO(random-liu): [P1] Handle OOM event.
} }
return
} }

154
pkg/server/events_test.go Normal file
View File

@ -0,0 +1,154 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"fmt"
"testing"
"time"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/container"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
)
func TestHandleEvent(t *testing.T) {
testID := "test-id"
testPid := uint32(1234)
testCreatedAt := time.Now().UnixNano()
testStartedAt := time.Now().UnixNano()
// Container metadata in running state.
testMetadata := metadata.ContainerMetadata{
ID: testID,
Name: "test-name",
SandboxID: "test-sandbox-id",
Pid: testPid,
CreatedAt: testCreatedAt,
StartedAt: testStartedAt,
}
testExitedAt := time.Now()
testExitEvent := container.Event{
ID: testID,
Type: container.Event_EXIT,
Pid: testPid,
ExitStatus: 1,
ExitedAt: testExitedAt,
}
testFinishedMetadata := metadata.ContainerMetadata{
ID: testID,
Name: "test-name",
SandboxID: "test-sandbox-id",
Pid: 0,
CreatedAt: testCreatedAt,
StartedAt: testStartedAt,
FinishedAt: testExitedAt.UnixNano(),
ExitCode: 1,
}
assert.Equal(t, runtime.ContainerState_CONTAINER_RUNNING, testMetadata.State())
testContainerdContainer := container.Container{
ID: testID,
Pid: testPid,
Status: container.Status_RUNNING,
}
for desc, test := range map[string]struct {
event *container.Event
metadata *metadata.ContainerMetadata
containerdContainer *container.Container
containerdErr error
expected *metadata.ContainerMetadata
}{
"should not update state when no corresponding metadata for event": {
event: &testExitEvent,
expected: nil,
},
"should not update state when exited process is not init process": {
event: &container.Event{
ID: testID,
Type: container.Event_EXIT,
Pid: 9999,
ExitStatus: 1,
ExitedAt: testExitedAt,
},
metadata: &testMetadata,
containerdContainer: &testContainerdContainer,
expected: &testMetadata,
},
"should not update state when fail to delete containerd container": {
event: &testExitEvent,
metadata: &testMetadata,
containerdContainer: &testContainerdContainer,
containerdErr: fmt.Errorf("random error"),
expected: &testMetadata,
},
"should not update state for non-exited events": {
event: &container.Event{
ID: testID,
Type: container.Event_OOM,
Pid: testPid,
ExitStatus: 1,
ExitedAt: testExitedAt,
},
metadata: &testMetadata,
containerdContainer: &testContainerdContainer,
expected: &testMetadata,
},
"should update state when containerd container is already deleted": {
event: &testExitEvent,
metadata: &testMetadata,
expected: &testFinishedMetadata,
},
"should update state when delete containerd container successfully": {
event: &testExitEvent,
metadata: &testMetadata,
containerdContainer: &testContainerdContainer,
expected: &testFinishedMetadata,
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fake := c.containerService.(*servertesting.FakeExecutionClient)
e, err := fake.Events(context.Background(), &execution.EventsRequest{})
assert.NoError(t, err)
fakeEvents := e.(*servertesting.EventClient)
// Inject event.
if test.event != nil {
fakeEvents.Events <- test.event
}
// Inject metadata.
if test.metadata != nil {
// Make sure that original data will not be changed.
assert.NoError(t, c.containerStore.Create(*test.metadata))
}
// Inject containerd container.
if test.containerdContainer != nil {
fake.SetFakeContainers([]container.Container{*test.containerdContainer})
}
// Inject containerd delete error.
if test.containerdErr != nil {
fake.InjectError("delete", test.containerdErr)
}
c.handleEventStream(e)
got, _ := c.containerStore.Get(testID)
assert.Equal(t, test.expected, got)
}
}

View File

@ -17,13 +17,106 @@ limitations under the License.
package server package server
import ( import (
"fmt"
"io"
"os"
"syscall"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata" "github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing"
) )
func TestPrepareStreamingPipes(t *testing.T) {
for desc, test := range map[string]struct {
stdin string
stdout string
stderr string
}{
"empty stdin": {
stdout: "/test/stdout",
stderr: "/test/stderr",
},
"empty stdout/stderr": {
stdin: "/test/stdin",
},
"non-empty stdio": {
stdin: "/test/stdin",
stdout: "/test/stdout",
stderr: "/test/stderr",
},
"empty stdio": {},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fakeOS := c.os.(*ostesting.FakeOS)
fakeOS.OpenFifoFn = func(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
expectFlag := syscall.O_RDONLY | syscall.O_CREAT | syscall.O_NONBLOCK
if fn == test.stdin {
expectFlag = syscall.O_WRONLY | syscall.O_CREAT | syscall.O_NONBLOCK
}
assert.Equal(t, expectFlag, flag)
assert.Equal(t, os.FileMode(0700), perm)
return nopReadWriteCloser{}, nil
}
i, o, e, err := c.prepareStreamingPipes(context.Background(), test.stdin, test.stdout, test.stderr)
assert.NoError(t, err)
assert.Equal(t, test.stdin != "", i != nil)
assert.Equal(t, test.stdout != "", o != nil)
assert.Equal(t, test.stderr != "", e != nil)
}
}
type closeTestReadWriteCloser struct {
CloseFn func() error
nopReadWriteCloser
}
func (c closeTestReadWriteCloser) Close() error {
return c.CloseFn()
}
func TestPrepareStreamingPipesError(t *testing.T) {
stdin, stdout, stderr := "/test/stdin", "/test/stdout", "/test/stderr"
for desc, inject := range map[string]map[string]error{
"should cleanup on stdin error": {stdin: fmt.Errorf("stdin error")},
"should cleanup on stdout error": {stdout: fmt.Errorf("stdout error")},
"should cleanup on stderr error": {stderr: fmt.Errorf("stderr error")},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fakeOS := c.os.(*ostesting.FakeOS)
openFlags := map[string]bool{
stdin: false,
stdout: false,
stderr: false,
}
fakeOS.OpenFifoFn = func(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
if inject[fn] != nil {
return nil, inject[fn]
}
openFlags[fn] = !openFlags[fn]
testCloser := closeTestReadWriteCloser{}
testCloser.CloseFn = func() error {
openFlags[fn] = !openFlags[fn]
return nil
}
return testCloser, nil
}
i, o, e, err := c.prepareStreamingPipes(context.Background(), stdin, stdout, stderr)
assert.Error(t, err)
assert.Nil(t, i)
assert.Nil(t, o)
assert.Nil(t, e)
assert.False(t, openFlags[stdin])
assert.False(t, openFlags[stdout])
assert.False(t, openFlags[stderr])
}
}
func TestGetSandbox(t *testing.T) { func TestGetSandbox(t *testing.T) {
c := newTestCRIContainerdService() c := newTestCRIContainerdService()
testID := "abcdefg" testID := "abcdefg"

View File

@ -39,8 +39,9 @@ import (
type nopReadWriteCloser struct{} type nopReadWriteCloser struct{}
func (nopReadWriteCloser) Read(p []byte) (n int, err error) { return len(p), nil } // Return error directly to avoid read/write.
func (nopReadWriteCloser) Write(p []byte) (n int, err error) { return len(p), nil } func (nopReadWriteCloser) Read(p []byte) (n int, err error) { return 0, io.EOF }
func (nopReadWriteCloser) Write(p []byte) (n int, err error) { return 0, io.ErrShortWrite }
func (nopReadWriteCloser) Close() error { return nil } func (nopReadWriteCloser) Close() error { return nil }
const testRootDir = "/test/rootfs" const testRootDir = "/test/rootfs"
@ -48,12 +49,14 @@ const testRootDir = "/test/rootfs"
// newTestCRIContainerdService creates a fake criContainerdService for test. // newTestCRIContainerdService creates a fake criContainerdService for test.
func newTestCRIContainerdService() *criContainerdService { func newTestCRIContainerdService() *criContainerdService {
return &criContainerdService{ return &criContainerdService{
os: ostesting.NewFakeOS(), os: ostesting.NewFakeOS(),
rootDir: testRootDir, rootDir: testRootDir,
containerService: servertesting.NewFakeExecutionClient(), containerService: servertesting.NewFakeExecutionClient(),
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()), sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
sandboxNameIndex: registrar.NewRegistrar(), sandboxNameIndex: registrar.NewRegistrar(),
sandboxIDIndex: truncindex.NewTruncIndex(nil), sandboxIDIndex: truncindex.NewTruncIndex(nil),
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
containerNameIndex: registrar.NewRegistrar(),
} }
} }

View File

@ -51,7 +51,10 @@ type EventClient struct {
// Recv is a test implementation of Recv // Recv is a test implementation of Recv
func (cli *EventClient) Recv() (*container.Event, error) { func (cli *EventClient) Recv() (*container.Event, error) {
event := <-cli.Events event, ok := <-cli.Events
if !ok {
return nil, fmt.Errorf("event channel closed")
}
return event, nil return event, nil
} }
@ -76,6 +79,18 @@ func NewFakeExecutionClient() *FakeExecutionClient {
} }
} }
// Stop the fake execution service. Needed when event is enabled.
func (f *FakeExecutionClient) Stop() {
if f.eventsQueue != nil {
close(f.eventsQueue)
}
f.Lock()
defer f.Unlock()
for _, client := range f.eventClients {
close(client.Events)
}
}
// WithEvents setup events publisher for FakeExecutionClient // WithEvents setup events publisher for FakeExecutionClient
func (f *FakeExecutionClient) WithEvents() *FakeExecutionClient { func (f *FakeExecutionClient) WithEvents() *FakeExecutionClient {
f.eventsQueue = make(chan *container.Event, 1024) f.eventsQueue = make(chan *container.Event, 1024)
@ -154,6 +169,13 @@ func (f *FakeExecutionClient) GetCalledNames() []string {
return names return names
} }
// ClearCalls clear all call detail.
func (f *FakeExecutionClient) ClearCalls() {
f.Lock()
defer f.Unlock()
f.called = []CalledDetail{}
}
// GetCalledDetails get detail of each call. // GetCalledDetails get detail of each call.
func (f *FakeExecutionClient) GetCalledDetails() []CalledDetail { func (f *FakeExecutionClient) GetCalledDetails() []CalledDetail {
f.Lock() f.Lock()