Add unit test.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
6ac71e5862
commit
322b6ef333
157
pkg/server/container_create_test.go
Normal file
157
pkg/server/container_create_test.go
Normal 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")
|
||||
}
|
||||
}
|
229
pkg/server/container_list_test.go
Normal file
229
pkg/server/container_list_test.go
Normal 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)
|
||||
}
|
||||
}
|
175
pkg/server/container_remove_test.go
Normal file
175
pkg/server/container_remove_test.go
Normal 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")
|
||||
}
|
||||
}
|
402
pkg/server/container_start_test.go
Normal file
402
pkg/server/container_start_test.go
Normal 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)
|
||||
}
|
||||
}
|
173
pkg/server/container_status_test.go
Normal file
173
pkg/server/container_status_test.go
Normal 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())
|
||||
}
|
||||
}
|
199
pkg/server/container_stop_test.go
Normal file
199
pkg/server/container_stop_test.go
Normal 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())
|
||||
}
|
||||
}
|
@ -28,6 +28,10 @@ import (
|
||||
|
||||
// startEventMonitor starts an event monitor which monitors and handles all
|
||||
// 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 {
|
||||
events, err := c.containerService.Events(context.Background(), &execution.EventsRequest{})
|
||||
if err != nil {
|
||||
@ -35,25 +39,33 @@ func (c *criContainerdService) startEventMonitor() error {
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
c.handleEvent(events)
|
||||
c.handleEventStream(events)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleEvent receives an event from contaienrd and handles the event.
|
||||
func (c *criContainerdService) handleEvent(events execution.ContainerService_EventsClient) {
|
||||
// handleEventStream receives an event from containerd and handles the event.
|
||||
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()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to receive event: %v", err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
// If containerd-shim exits unexpectedly, there will be no corresponding event.
|
||||
// However, containerd could not retrieve container state in that case, so it's
|
||||
// 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:
|
||||
meta, err := c.containerStore.Get(e.ID)
|
||||
if err != nil {
|
||||
@ -61,7 +73,7 @@ func (c *criContainerdService) handleEvent(events execution.ContainerService_Eve
|
||||
return
|
||||
}
|
||||
if e.Pid != meta.Pid {
|
||||
// Not init process dies, ignore the event.
|
||||
// Non-init process died, ignore the event.
|
||||
return
|
||||
}
|
||||
// Delete the container from containerd.
|
||||
@ -90,5 +102,4 @@ func (c *criContainerdService) handleEvent(events execution.ContainerService_Eve
|
||||
case container.Event_OOM:
|
||||
// TODO(random-liu): [P1] Handle OOM event.
|
||||
}
|
||||
return
|
||||
}
|
||||
|
154
pkg/server/events_test.go
Normal file
154
pkg/server/events_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -17,13 +17,106 @@ limitations under the License.
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"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) {
|
||||
c := newTestCRIContainerdService()
|
||||
testID := "abcdefg"
|
||||
|
@ -39,8 +39,9 @@ import (
|
||||
|
||||
type nopReadWriteCloser struct{}
|
||||
|
||||
func (nopReadWriteCloser) Read(p []byte) (n int, err error) { return len(p), nil }
|
||||
func (nopReadWriteCloser) Write(p []byte) (n int, err error) { return len(p), nil }
|
||||
// Return error directly to avoid read/write.
|
||||
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 }
|
||||
|
||||
const testRootDir = "/test/rootfs"
|
||||
@ -54,6 +55,8 @@ func newTestCRIContainerdService() *criContainerdService {
|
||||
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
||||
sandboxNameIndex: registrar.NewRegistrar(),
|
||||
sandboxIDIndex: truncindex.NewTruncIndex(nil),
|
||||
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
|
||||
containerNameIndex: registrar.NewRegistrar(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,10 @@ type EventClient struct {
|
||||
|
||||
// Recv is a test implementation of Recv
|
||||
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
|
||||
}
|
||||
|
||||
@ -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
|
||||
func (f *FakeExecutionClient) WithEvents() *FakeExecutionClient {
|
||||
f.eventsQueue = make(chan *container.Event, 1024)
|
||||
@ -154,6 +169,13 @@ func (f *FakeExecutionClient) GetCalledNames() []string {
|
||||
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.
|
||||
func (f *FakeExecutionClient) GetCalledDetails() []CalledDetail {
|
||||
f.Lock()
|
||||
|
Loading…
Reference in New Issue
Block a user