containerd/pkg/server/container_stop_test.go
Lantao Liu 7b16a35287 Use new metadata store.
Signed-off-by: Lantao Liu <lantaol@google.com>
2017-07-28 23:35:31 +00:00

333 lines
9.5 KiB
Go

/*
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/task"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"golang.org/x/sys/unix"
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing"
containerstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/container"
imagestore "github.com/kubernetes-incubator/cri-containerd/pkg/store/image"
)
func TestWaitContainerStop(t *testing.T) {
id := "test-id"
for desc, test := range map[string]struct {
status *containerstore.Status
cancel bool
timeout time.Duration
expectErr bool
}{
"should return error if timeout exceeds": {
status: &containerstore.Status{
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
timeout: 2 * stopCheckPollInterval,
expectErr: true,
},
"should return error if context is cancelled": {
status: &containerstore.Status{
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
timeout: time.Hour,
cancel: true,
expectErr: true,
},
"should not return error if container is removed before timeout": {
status: nil,
timeout: time.Hour,
expectErr: false,
},
"should not return error if container is stopped before timeout": {
status: &containerstore.Status{
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
FinishedAt: time.Now().UnixNano(),
},
timeout: time.Hour,
expectErr: false,
},
} {
c := newTestCRIContainerdService()
if test.status != nil {
container, err := containerstore.NewContainer(
containerstore.Metadata{ID: id},
*test.status,
)
assert.NoError(t, err)
assert.NoError(t, c.containerStore.Add(container))
}
ctx := context.Background()
if test.cancel {
cancelledCtx, cancel := context.WithCancel(ctx)
cancel()
ctx = cancelledCtx
}
err := c.waitContainerStop(ctx, id, test.timeout)
assert.Equal(t, test.expectErr, err != nil, desc)
}
}
func TestStopContainer(t *testing.T) {
testID := "test-id"
testPid := uint32(1234)
testImageID := "test-image-id"
testStatus := containerstore.Status{
Pid: testPid,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
}
testImage := imagestore.Image{
ID: testImageID,
Config: &imagespec.ImageConfig{},
}
testContainer := task.Task{
ID: testID,
Pid: testPid,
Status: task.StatusRunning,
}
for desc, test := range map[string]struct {
status *containerstore.Status
containerdContainer *task.Task
stopSignal string
stopErr error
noTimeout bool
expectErr bool
expectCalls []servertesting.CalledDetail
}{
"should return error when container does not exist": {
status: nil,
expectErr: true,
expectCalls: []servertesting.CalledDetail{},
},
"should not return error when container is not running": {
status: &containerstore.Status{CreatedAt: time.Now().UnixNano()},
expectErr: false,
expectCalls: []servertesting.CalledDetail{},
},
"should not return error if containerd task does not exist": {
status: &testStatus,
containerdContainer: &testContainer,
// Since it's hard to inject event during `StopContainer` is running,
// we only test the case that first stop returns error, but container
// status is not updated yet.
// We also leverage this behavior to test that when graceful
// stop doesn't take effect, container should be SIGKILL-ed.
stopErr: servertesting.TaskNotExistError,
expectErr: false,
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGTERM),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGKILL),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "delete",
Argument: &execution.DeleteRequest{ContainerID: testID},
},
},
},
"should not return error if containerd task process already finished": {
status: &testStatus,
containerdContainer: &testContainer,
stopErr: errors.New("os: process already finished"),
expectErr: false,
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGTERM),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGKILL),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "delete",
Argument: &execution.DeleteRequest{ContainerID: testID},
},
},
},
"should return error if graceful stop returns random error": {
status: &testStatus,
containerdContainer: &testContainer,
stopErr: errors.New("random stop error"),
expectErr: true,
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGTERM),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
},
},
"should not return error if containerd task is gracefully stopped": {
status: &testStatus,
containerdContainer: &testContainer,
expectErr: false,
// deleted by the event monitor.
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGTERM),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "delete",
Argument: &execution.DeleteRequest{ContainerID: testID},
},
},
},
"should use stop signal specified in image config if not empty": {
status: &testStatus,
containerdContainer: &testContainer,
stopSignal: "SIGHUP",
expectErr: false,
// deleted by the event monitor.
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGHUP),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "delete",
Argument: &execution.DeleteRequest{ContainerID: testID},
},
},
},
"should directly kill container if timeout is 0": {
status: &testStatus,
containerdContainer: &testContainer,
noTimeout: true,
expectErr: false,
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGKILL),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "delete",
Argument: &execution.DeleteRequest{ContainerID: testID},
},
},
},
// TODO(random-liu): Test "should return error if both failed" after we have
// fake clock for test.
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
fake := servertesting.NewFakeExecutionClient().WithEvents()
defer fake.Stop()
c.taskService = fake
// Inject the container.
if test.status != nil {
cntr, err := containerstore.NewContainer(
containerstore.Metadata{
ID: testID,
ImageRef: testImageID,
},
*test.status,
)
assert.NoError(t, err)
assert.NoError(t, c.containerStore.Add(cntr))
}
// Inject containerd task.
if test.containerdContainer != nil {
fake.SetFakeTasks([]task.Task{*test.containerdContainer})
}
testImage.Config.StopSignal = test.stopSignal
c.imageStore.Add(testImage)
if test.stopErr != nil {
fake.InjectError("kill", test.stopErr)
}
eventClient, err := fake.Events(context.Background(), &execution.EventsRequest{})
assert.NoError(t, err)
// Start a simple test event monitor.
go func(e execution.Tasks_EventsClient) {
for {
if err := c.handleEventStream(e); err != nil { // nolint: vetshadow
return
}
}
}(eventClient)
fake.ClearCalls()
timeout := int64(1)
if test.noTimeout {
timeout = 0
}
// 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: timeout,
})
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.GetCalledDetails())
}
}