containerd/pkg/server/container_stop_test.go
Lantao Liu d381cfa831 Kill container directly if timeout is 0.
Signed-off-by: Lantao Liu <lantaol@google.com>
2017-06-13 19:29:02 +00:00

231 lines
6.6 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/container"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
"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"
for desc, test := range map[string]struct {
metadata *metadata.ContainerMetadata
cancel bool
timeout time.Duration
expectErr bool
}{
"should return error if timeout exceeds": {
metadata: &metadata.ContainerMetadata{
ID: id,
CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(),
},
timeout: 2 * stopCheckPollInterval,
expectErr: true,
},
"should return error if context is cancelled": {
metadata: &metadata.ContainerMetadata{
ID: id,
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": {
metadata: nil,
timeout: time.Hour,
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(),
},
timeout: time.Hour,
expectErr: false,
},
} {
c := newTestCRIContainerdService()
if test.metadata != nil {
assert.NoError(t, c.containerStore.Create(*test.metadata))
}
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)
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
noTimeout bool
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 directly kill container if timeout is 0": {
metadata: &testMetadata,
containerdContainer: &testContainer,
noTimeout: true,
expectCalls: []string{"delete", "delete"},
},
"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()
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.GetCalledNames())
}
}