Merge pull request #22 from Random-Liu/add-container-implementation
Add container implementation
This commit is contained in:
commit
10e3afbb23
31
Godeps/Godeps.json
generated
31
Godeps/Godeps.json
generated
@ -18,8 +18,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/blang/semver",
|
"ImportPath": "github.com/blang/semver",
|
||||||
"Comment": "v3.0.1",
|
"Comment": "v3.1.0",
|
||||||
"Rev": "31b736133b98f26d5e078ec9eb591666edfd091f"
|
"Rev": "aea32c919a18e5ef4537bbd283ff29594b1b0165"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/boltdb/bolt",
|
"ImportPath": "github.com/boltdb/bolt",
|
||||||
@ -153,12 +153,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/distribution/digestset",
|
"ImportPath": "github.com/docker/distribution/digestset",
|
||||||
"Comment": "v2.6.0-rc.1-130-gb38e5838",
|
"Comment": "v2.6.0-rc.1-130-gb38e583",
|
||||||
"Rev": "b38e5838b7b2f2ad48e06ec4b500011976080621"
|
"Rev": "b38e5838b7b2f2ad48e06ec4b500011976080621"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/distribution/reference",
|
"ImportPath": "github.com/docker/distribution/reference",
|
||||||
"Comment": "v2.6.0-rc.1-130-gb38e5838",
|
"Comment": "v2.6.0-rc.1-130-gb38e583",
|
||||||
"Rev": "b38e5838b7b2f2ad48e06ec4b500011976080621"
|
"Rev": "b38e5838b7b2f2ad48e06ec4b500011976080621"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -178,27 +178,27 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gogo/protobuf/gogoproto",
|
"ImportPath": "github.com/gogo/protobuf/gogoproto",
|
||||||
"Comment": "v0.3-150-gd2e1ade2",
|
"Comment": "v0.3-150-gd2e1ade",
|
||||||
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gogo/protobuf/proto",
|
"ImportPath": "github.com/gogo/protobuf/proto",
|
||||||
"Comment": "v0.3-150-gd2e1ade2",
|
"Comment": "v0.3-150-gd2e1ade",
|
||||||
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor",
|
"ImportPath": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor",
|
||||||
"Comment": "v0.3-150-gd2e1ade2",
|
"Comment": "v0.3-150-gd2e1ade",
|
||||||
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gogo/protobuf/sortkeys",
|
"ImportPath": "github.com/gogo/protobuf/sortkeys",
|
||||||
"Comment": "v0.3-150-gd2e1ade2",
|
"Comment": "v0.3-150-gd2e1ade",
|
||||||
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gogo/protobuf/types",
|
"ImportPath": "github.com/gogo/protobuf/types",
|
||||||
"Comment": "v0.3-150-gd2e1ade2",
|
"Comment": "v0.3-150-gd2e1ade",
|
||||||
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
"Rev": "d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -238,7 +238,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/opencontainers/runc/libcontainer/system",
|
"ImportPath": "github.com/opencontainers/runc/libcontainer/system",
|
||||||
"Comment": "v1.0.0-rc3-21-g50401b5b",
|
"Comment": "v1.0.0-rc3-21-g50401b5",
|
||||||
"Rev": "50401b5b4c2e01e4f1372b73a021742deeaf4e2d"
|
"Rev": "50401b5b4c2e01e4f1372b73a021742deeaf4e2d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -248,15 +248,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/opencontainers/runtime-tools/generate",
|
"ImportPath": "github.com/opencontainers/runtime-tools/generate",
|
||||||
"Rev": "8addcc695096a0fc61010af8766952546bba7cd0"
|
"Rev": "68c195c3f2fa04a9a298b839eb2d94f31141271a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/opencontainers/runtime-tools/generate/seccomp",
|
"ImportPath": "github.com/opencontainers/runtime-tools/generate/seccomp",
|
||||||
"Rev": "8addcc695096a0fc61010af8766952546bba7cd0"
|
"Rev": "68c195c3f2fa04a9a298b839eb2d94f31141271a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/opencontainers/runtime-tools/validate",
|
"ImportPath": "github.com/opencontainers/runtime-tools/validate",
|
||||||
"Rev": "8addcc695096a0fc61010af8766952546bba7cd0"
|
"Rev": "68c195c3f2fa04a9a298b839eb2d94f31141271a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/pkg/errors",
|
"ImportPath": "github.com/pkg/errors",
|
||||||
@ -265,6 +265,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||||
|
"Comment": "v1.0.0",
|
||||||
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -399,12 +400,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime",
|
"ImportPath": "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime",
|
||||||
"Comment": "v1.7.0-alpha.1-493-g32e927f4d8",
|
"Comment": "v1.7.0-alpha.1-493-g32e927f",
|
||||||
"Rev": "32e927f4d86cfe7d3a7ad3c231fc445fb01463f5"
|
"Rev": "32e927f4d86cfe7d3a7ad3c231fc445fb01463f5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/kubernetes/pkg/util/interrupt",
|
"ImportPath": "k8s.io/kubernetes/pkg/util/interrupt",
|
||||||
"Comment": "v1.7.0-alpha.1-493-g32e927f4d8",
|
"Comment": "v1.7.0-alpha.1-493-g32e927f",
|
||||||
"Rev": "32e927f4d86cfe7d3a7ad3c231fc445fb01463f5"
|
"Rev": "32e927f4d86cfe7d3a7ad3c231fc445fb01463f5"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
183
pkg/metadata/container.go
Normal file
183
pkg/metadata/container.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
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 metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata/store"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The code is very similar with sandbox.go, but there is no template support
|
||||||
|
// in golang, we have to have similar files for different types.
|
||||||
|
// TODO(random-liu): Figure out a way to simplify this.
|
||||||
|
// TODO(random-liu): Handle versioning with the same mechanism with container.go
|
||||||
|
|
||||||
|
// containerMetadataVersion is current version of container metadata.
|
||||||
|
const containerMetadataVersion = "v1" // nolint
|
||||||
|
|
||||||
|
// versionedContainerMetadata is the internal versioned container metadata.
|
||||||
|
// nolint
|
||||||
|
type versionedContainerMetadata struct {
|
||||||
|
// Version indicates the version of the versioned container metadata.
|
||||||
|
Version string
|
||||||
|
ContainerMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerMetadata is the unversioned container metadata.
|
||||||
|
type ContainerMetadata struct {
|
||||||
|
// ID is the container id.
|
||||||
|
ID string
|
||||||
|
// Name is the container name.
|
||||||
|
Name string
|
||||||
|
// SandboxID is the sandbox id the container belongs to.
|
||||||
|
SandboxID string
|
||||||
|
// Config is the CRI container config.
|
||||||
|
Config *runtime.ContainerConfig
|
||||||
|
// ImageRef is the reference of image used by the container.
|
||||||
|
ImageRef string
|
||||||
|
// Pid is the init process id of the container.
|
||||||
|
Pid uint32
|
||||||
|
// CreatedAt is the created timestamp.
|
||||||
|
CreatedAt int64
|
||||||
|
// StartedAt is the started timestamp.
|
||||||
|
StartedAt int64
|
||||||
|
// FinishedAt is the finished timestamp.
|
||||||
|
FinishedAt int64
|
||||||
|
// ExitCode is the container exit code.
|
||||||
|
ExitCode int32
|
||||||
|
// CamelCase string explaining why container is in its current state.
|
||||||
|
Reason string
|
||||||
|
// Human-readable message indicating details about why container is in its
|
||||||
|
// current state.
|
||||||
|
Message string
|
||||||
|
// Removing indicates that the container is in removing state.
|
||||||
|
// In fact, this field doesn't need to be checkpointed.
|
||||||
|
// TODO(random-liu): Skip this during serialization when we put object
|
||||||
|
// into the store directly.
|
||||||
|
// TODO(random-liu): Reset this field to false during state recoverry.
|
||||||
|
Removing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// State returns current state of the container based on the metadata.
|
||||||
|
func (c *ContainerMetadata) State() runtime.ContainerState {
|
||||||
|
if c.FinishedAt != 0 {
|
||||||
|
return runtime.ContainerState_CONTAINER_EXITED
|
||||||
|
}
|
||||||
|
if c.StartedAt != 0 {
|
||||||
|
return runtime.ContainerState_CONTAINER_RUNNING
|
||||||
|
}
|
||||||
|
if c.CreatedAt != 0 {
|
||||||
|
return runtime.ContainerState_CONTAINER_CREATED
|
||||||
|
}
|
||||||
|
return runtime.ContainerState_CONTAINER_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerUpdateFunc is the function used to update ContainerMetadata.
|
||||||
|
type ContainerUpdateFunc func(ContainerMetadata) (ContainerMetadata, error)
|
||||||
|
|
||||||
|
// ContainerToStoreUpdateFunc generates a metadata store UpdateFunc from ContainerUpdateFunc.
|
||||||
|
func ContainerToStoreUpdateFunc(u ContainerUpdateFunc) store.UpdateFunc {
|
||||||
|
return func(data []byte) ([]byte, error) {
|
||||||
|
meta := &ContainerMetadata{}
|
||||||
|
if err := json.Unmarshal(data, meta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newMeta, err := u(*meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(newMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerStore is the store for metadata of all containers.
|
||||||
|
type ContainerStore interface {
|
||||||
|
// Create creates a container from ContainerMetadata in the store.
|
||||||
|
Create(ContainerMetadata) error
|
||||||
|
// Get gets a specified container.
|
||||||
|
Get(string) (*ContainerMetadata, error)
|
||||||
|
// Update updates a specified container.
|
||||||
|
Update(string, ContainerUpdateFunc) error
|
||||||
|
// List lists all containers.
|
||||||
|
List() ([]*ContainerMetadata, error)
|
||||||
|
// Delete deletes the container from the store.
|
||||||
|
Delete(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// containerStore is an implmentation of ContainerStore.
|
||||||
|
type containerStore struct {
|
||||||
|
store store.MetadataStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerStore creates a ContainerStore from a basic MetadataStore.
|
||||||
|
func NewContainerStore(store store.MetadataStore) ContainerStore {
|
||||||
|
return &containerStore{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a container from ContainerMetadata in the store.
|
||||||
|
func (c *containerStore) Create(metadata ContainerMetadata) error {
|
||||||
|
data, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.store.Create(metadata.ID, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets a specified container.
|
||||||
|
func (c *containerStore) Get(containerID string) (*ContainerMetadata, error) {
|
||||||
|
data, err := c.store.Get(containerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
container := &ContainerMetadata{}
|
||||||
|
if err := json.Unmarshal(data, container); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a specified container. The function is running in a
|
||||||
|
// transaction. Update will not be applied when the update function
|
||||||
|
// returns error.
|
||||||
|
func (c *containerStore) Update(containerID string, u ContainerUpdateFunc) error {
|
||||||
|
return c.store.Update(containerID, ContainerToStoreUpdateFunc(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all containers.
|
||||||
|
func (c *containerStore) List() ([]*ContainerMetadata, error) {
|
||||||
|
allData, err := c.store.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var containers []*ContainerMetadata
|
||||||
|
for _, data := range allData {
|
||||||
|
container := &ContainerMetadata{}
|
||||||
|
if err := json.Unmarshal(data, container); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
containers = append(containers, container)
|
||||||
|
}
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the Container from the store.
|
||||||
|
func (c *containerStore) Delete(containerID string) error {
|
||||||
|
return c.store.Delete(containerID)
|
||||||
|
}
|
180
pkg/metadata/container_test.go
Normal file
180
pkg/metadata/container_test.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authorc.
|
||||||
|
|
||||||
|
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 metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assertlib "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata/store"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContainerState(t *testing.T) {
|
||||||
|
for c, test := range map[string]struct {
|
||||||
|
metadata *ContainerMetadata
|
||||||
|
state runtime.ContainerState
|
||||||
|
}{
|
||||||
|
"unknown state": {
|
||||||
|
metadata: &ContainerMetadata{
|
||||||
|
ID: "1",
|
||||||
|
Name: "Container-1",
|
||||||
|
},
|
||||||
|
state: runtime.ContainerState_CONTAINER_UNKNOWN,
|
||||||
|
},
|
||||||
|
"created state": {
|
||||||
|
metadata: &ContainerMetadata{
|
||||||
|
ID: "2",
|
||||||
|
Name: "Container-2",
|
||||||
|
CreatedAt: time.Now().UnixNano(),
|
||||||
|
},
|
||||||
|
state: runtime.ContainerState_CONTAINER_CREATED,
|
||||||
|
},
|
||||||
|
"running state": {
|
||||||
|
metadata: &ContainerMetadata{
|
||||||
|
ID: "3",
|
||||||
|
Name: "Container-3",
|
||||||
|
CreatedAt: time.Now().UnixNano(),
|
||||||
|
StartedAt: time.Now().UnixNano(),
|
||||||
|
},
|
||||||
|
state: runtime.ContainerState_CONTAINER_RUNNING,
|
||||||
|
},
|
||||||
|
"exited state": {
|
||||||
|
metadata: &ContainerMetadata{
|
||||||
|
ID: "3",
|
||||||
|
Name: "Container-3",
|
||||||
|
CreatedAt: time.Now().UnixNano(),
|
||||||
|
FinishedAt: time.Now().UnixNano(),
|
||||||
|
},
|
||||||
|
state: runtime.ContainerState_CONTAINER_EXITED,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("TestCase %q", c)
|
||||||
|
assertlib.Equal(t, test.state, test.metadata.State())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerStore(t *testing.T) {
|
||||||
|
containers := map[string]*ContainerMetadata{
|
||||||
|
"1": {
|
||||||
|
ID: "1",
|
||||||
|
Name: "Container-1",
|
||||||
|
SandboxID: "Sandbox-1",
|
||||||
|
Config: &runtime.ContainerConfig{
|
||||||
|
Metadata: &runtime.ContainerMetadata{
|
||||||
|
Name: "TestPod-1",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ImageRef: "TestImage-1",
|
||||||
|
Pid: 1,
|
||||||
|
CreatedAt: time.Now().UnixNano(),
|
||||||
|
StartedAt: time.Now().UnixNano(),
|
||||||
|
FinishedAt: time.Now().UnixNano(),
|
||||||
|
ExitCode: 1,
|
||||||
|
Reason: "TestReason-1",
|
||||||
|
Message: "TestMessage-1",
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
ID: "2",
|
||||||
|
Name: "Container-2",
|
||||||
|
SandboxID: "Sandbox-2",
|
||||||
|
Config: &runtime.ContainerConfig{
|
||||||
|
Metadata: &runtime.ContainerMetadata{
|
||||||
|
Name: "TestPod-2",
|
||||||
|
Attempt: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ImageRef: "TestImage-2",
|
||||||
|
Pid: 2,
|
||||||
|
CreatedAt: time.Now().UnixNano(),
|
||||||
|
StartedAt: time.Now().UnixNano(),
|
||||||
|
FinishedAt: time.Now().UnixNano(),
|
||||||
|
ExitCode: 2,
|
||||||
|
Reason: "TestReason-2",
|
||||||
|
Message: "TestMessage-2",
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
ID: "3",
|
||||||
|
Name: "Container-3",
|
||||||
|
SandboxID: "Sandbox-3",
|
||||||
|
Config: &runtime.ContainerConfig{
|
||||||
|
Metadata: &runtime.ContainerMetadata{
|
||||||
|
Name: "TestPod-3",
|
||||||
|
Attempt: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ImageRef: "TestImage-3",
|
||||||
|
Pid: 3,
|
||||||
|
CreatedAt: time.Now().UnixNano(),
|
||||||
|
StartedAt: time.Now().UnixNano(),
|
||||||
|
FinishedAt: time.Now().UnixNano(),
|
||||||
|
ExitCode: 3,
|
||||||
|
Reason: "TestReason-3",
|
||||||
|
Message: "TestMessage-3",
|
||||||
|
Removing: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert := assertlib.New(t)
|
||||||
|
|
||||||
|
c := NewContainerStore(store.NewMetadataStore())
|
||||||
|
|
||||||
|
t.Logf("should be able to create container metadata")
|
||||||
|
for _, meta := range containers {
|
||||||
|
assert.NoError(c.Create(*meta))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("should be able to get container metadata")
|
||||||
|
for id, expectMeta := range containers {
|
||||||
|
meta, err := c.Get(id)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(expectMeta, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("should be able to list container metadata")
|
||||||
|
cntrs, err := c.List()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Len(cntrs, 3)
|
||||||
|
|
||||||
|
t.Logf("should be able to update container metadata")
|
||||||
|
testID := "2"
|
||||||
|
newCreatedAt := time.Now().UnixNano()
|
||||||
|
expectMeta := *containers[testID]
|
||||||
|
expectMeta.CreatedAt = newCreatedAt
|
||||||
|
err = c.Update(testID, func(o ContainerMetadata) (ContainerMetadata, error) {
|
||||||
|
o.CreatedAt = newCreatedAt
|
||||||
|
return o, nil
|
||||||
|
})
|
||||||
|
assert.NoError(err)
|
||||||
|
newMeta, err := c.Get(testID)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(&expectMeta, newMeta)
|
||||||
|
|
||||||
|
t.Logf("should be able to delete container metadata")
|
||||||
|
assert.NoError(c.Delete(testID))
|
||||||
|
cntrs, err = c.List()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Len(cntrs, 2)
|
||||||
|
|
||||||
|
t.Logf("get should return nil without error after deletion")
|
||||||
|
meta, err := c.Get(testID)
|
||||||
|
assert.Error(store.ErrNotExist, err)
|
||||||
|
assert.True(meta == nil)
|
||||||
|
}
|
@ -17,14 +17,84 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateContainer creates a new container in the given PodSandbox.
|
// CreateContainer creates a new container in the given PodSandbox.
|
||||||
func (c *criContainerdService) CreateContainer(ctx context.Context, r *runtime.CreateContainerRequest) (*runtime.CreateContainerResponse, error) {
|
func (c *criContainerdService) CreateContainer(ctx context.Context, r *runtime.CreateContainerRequest) (retRes *runtime.CreateContainerResponse, retErr error) {
|
||||||
return nil, errors.New("not implemented")
|
glog.V(2).Infof("CreateContainer within sandbox %q with container config %+v and sandbox config %+v",
|
||||||
|
r.GetPodSandboxId(), r.GetConfig(), r.GetSandboxConfig())
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
glog.V(2).Infof("CreateContainer returns container id %q", retRes.GetContainerId())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
config := r.GetConfig()
|
||||||
|
sandboxConfig := r.GetSandboxConfig()
|
||||||
|
sandbox, err := c.getSandbox(r.GetPodSandboxId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find sandbox id %q: %v", r.GetPodSandboxId(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique id and name for the container and reserve the name.
|
||||||
|
// Reserve the container name to avoid concurrent `CreateContainer` request creating
|
||||||
|
// the same container.
|
||||||
|
id := generateID()
|
||||||
|
name := makeContainerName(config.GetMetadata(), sandboxConfig.GetMetadata())
|
||||||
|
if err := c.containerNameIndex.Reserve(name, id); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to reserve container name %q: %v", name, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Release the name if the function returns with an error.
|
||||||
|
if retErr != nil {
|
||||||
|
c.containerNameIndex.ReleaseByName(name)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create initial container metadata.
|
||||||
|
meta := metadata.ContainerMetadata{
|
||||||
|
ID: id,
|
||||||
|
Name: name,
|
||||||
|
SandboxID: sandbox.ID,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(random-liu): [P0] Prepare container rootfs.
|
||||||
|
|
||||||
|
// TODO(random-liu): [P0] Set ImageRef in ContainerMetadata with image id.
|
||||||
|
|
||||||
|
// Create container root directory.
|
||||||
|
containerRootDir := getContainerRootDir(c.rootDir, id)
|
||||||
|
if err := c.os.MkdirAll(containerRootDir, 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create container root directory %q: %v",
|
||||||
|
containerRootDir, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
// Cleanup the container root directory.
|
||||||
|
if err := c.os.RemoveAll(containerRootDir); err != nil {
|
||||||
|
glog.Errorf("Failed to remove container root directory %q: %v",
|
||||||
|
containerRootDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Update container CreatedAt.
|
||||||
|
meta.CreatedAt = time.Now().UnixNano()
|
||||||
|
// Add container into container store.
|
||||||
|
if err := c.containerStore.Create(meta); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add container metadata %+v into store: %v",
|
||||||
|
meta, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &runtime.CreateContainerResponse{ContainerId: id}, nil
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,87 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListContainers lists all containers matching the filter.
|
// ListContainers lists all containers matching the filter.
|
||||||
func (c *criContainerdService) ListContainers(ctx context.Context, r *runtime.ListContainersRequest) (*runtime.ListContainersResponse, error) {
|
func (c *criContainerdService) ListContainers(ctx context.Context, r *runtime.ListContainersRequest) (retRes *runtime.ListContainersResponse, retErr error) {
|
||||||
return nil, errors.New("not implemented")
|
glog.V(4).Infof("ListContainers with filter %+v", r.GetFilter())
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
glog.V(4).Infof("ListContainers returns containers %+v", retRes.GetContainers())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// List all container metadata from store.
|
||||||
|
metas, err := c.containerStore.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list metadata from container store: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var containers []*runtime.Container
|
||||||
|
for _, meta := range metas {
|
||||||
|
containers = append(containers, toCRIContainer(meta))
|
||||||
|
}
|
||||||
|
|
||||||
|
containers = c.filterCRIContainers(containers, r.GetFilter())
|
||||||
|
return &runtime.ListContainersResponse{Containers: containers}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toCRIContainer converts container metadata into CRI container.
|
||||||
|
func toCRIContainer(meta *metadata.ContainerMetadata) *runtime.Container {
|
||||||
|
return &runtime.Container{
|
||||||
|
Id: meta.ID,
|
||||||
|
PodSandboxId: meta.SandboxID,
|
||||||
|
Metadata: meta.Config.GetMetadata(),
|
||||||
|
Image: meta.Config.GetImage(),
|
||||||
|
ImageRef: meta.ImageRef,
|
||||||
|
State: meta.State(),
|
||||||
|
CreatedAt: meta.CreatedAt,
|
||||||
|
Labels: meta.Config.GetLabels(),
|
||||||
|
Annotations: meta.Config.GetAnnotations(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterCRIContainers filters CRIContainers.
|
||||||
|
func (c *criContainerdService) filterCRIContainers(containers []*runtime.Container, filter *runtime.ContainerFilter) []*runtime.Container {
|
||||||
|
if filter == nil {
|
||||||
|
return containers
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := []*runtime.Container{}
|
||||||
|
for _, cntr := range containers {
|
||||||
|
if filter.GetId() != "" && filter.GetId() != cntr.Id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filter.GetPodSandboxId() != "" && filter.GetPodSandboxId() != cntr.PodSandboxId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filter.GetState() != nil && filter.GetState().GetState() != cntr.State {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filter.GetLabelSelector() != nil {
|
||||||
|
match := true
|
||||||
|
for k, v := range filter.GetLabelSelector() {
|
||||||
|
got, ok := cntr.Labels[k]
|
||||||
|
if !ok || got != v {
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filtered = append(filtered, cntr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,96 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveContainer removes the container.
|
// RemoveContainer removes the container.
|
||||||
func (c *criContainerdService) RemoveContainer(ctx context.Context, r *runtime.RemoveContainerRequest) (*runtime.RemoveContainerResponse, error) {
|
func (c *criContainerdService) RemoveContainer(ctx context.Context, r *runtime.RemoveContainerRequest) (retRes *runtime.RemoveContainerResponse, retErr error) {
|
||||||
return nil, errors.New("not implemented")
|
glog.V(2).Infof("RemoveContainer for %q", r.GetContainerId())
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
glog.V(2).Infof("RemoveContainer %q returns successfully", r.GetContainerId())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
id := r.GetContainerId()
|
||||||
|
|
||||||
|
// Set removing state to prevent other start/remove operations against this container
|
||||||
|
// while it's being removed.
|
||||||
|
if err := c.setContainerRemoving(id); err != nil {
|
||||||
|
if !metadata.IsNotExistError(err) {
|
||||||
|
return nil, fmt.Errorf("failed to set removing state for container %q: %v",
|
||||||
|
id, err)
|
||||||
|
}
|
||||||
|
// Do not return error if container metadata doesn't exist.
|
||||||
|
glog.V(5).Infof("RemoveContainer called for container %q that does not exist", id)
|
||||||
|
return &runtime.RemoveContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
// Cleanup all index after successfully remove the container.
|
||||||
|
c.containerNameIndex.ReleaseByKey(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Reset removing if remove failed.
|
||||||
|
if err := c.resetContainerRemoving(id); err != nil {
|
||||||
|
// TODO(random-liu): Deal with update failure. Actually Removing doesn't need to
|
||||||
|
// be checkpointed, we only need it to have the same lifecycle with container metadata.
|
||||||
|
glog.Errorf("failed to reset removing state for container %q: %v",
|
||||||
|
id, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// NOTE(random-liu): Docker set container to "Dead" state when start removing the
|
||||||
|
// container so as to avoid start/restart the container again. However, for current
|
||||||
|
// kubelet implementation, we'll never start a container once we decide to remove it,
|
||||||
|
// so we don't need the "Dead" state for now.
|
||||||
|
|
||||||
|
// TODO(random-liu): [P0] Cleanup container rootfs.
|
||||||
|
|
||||||
|
// Cleanup container root directory.
|
||||||
|
containerRootDir := getContainerRootDir(c.rootDir, id)
|
||||||
|
if err := c.os.RemoveAll(containerRootDir); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to remove container root directory %q: %v",
|
||||||
|
containerRootDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete container metadata.
|
||||||
|
if err := c.containerStore.Delete(id); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to delete container metadata for %q: %v", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &runtime.RemoveContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setContainerRemoving sets the container into removing state. In removing state, the
|
||||||
|
// container will not be started or removed again.
|
||||||
|
func (c *criContainerdService) setContainerRemoving(id string) error {
|
||||||
|
return c.containerStore.Update(id, func(meta metadata.ContainerMetadata) (metadata.ContainerMetadata, error) {
|
||||||
|
// Do not remove container if it's still running.
|
||||||
|
if meta.State() == runtime.ContainerState_CONTAINER_RUNNING {
|
||||||
|
return meta, fmt.Errorf("container %q is still running", id)
|
||||||
|
}
|
||||||
|
if meta.Removing {
|
||||||
|
return meta, fmt.Errorf("container is already in removing state")
|
||||||
|
}
|
||||||
|
meta.Removing = true
|
||||||
|
return meta, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetContainerRemoving resets the container removing state on remove failure. So
|
||||||
|
// that we could remove the container again.
|
||||||
|
func (c *criContainerdService) resetContainerRemoving(id string) error {
|
||||||
|
return c.containerStore.Update(id, func(meta metadata.ContainerMetadata) (metadata.ContainerMetadata, error) {
|
||||||
|
meta.Removing = false
|
||||||
|
return meta, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,328 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
prototypes "github.com/gogo/protobuf/types"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/opencontainers/runtime-tools/generate"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
|
"github.com/containerd/containerd/api/types/container"
|
||||||
|
"github.com/containerd/containerd/api/types/mount"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartContainer starts the container.
|
// StartContainer starts the container.
|
||||||
func (c *criContainerdService) StartContainer(ctx context.Context, r *runtime.StartContainerRequest) (*runtime.StartContainerResponse, error) {
|
func (c *criContainerdService) StartContainer(ctx context.Context, r *runtime.StartContainerRequest) (retRes *runtime.StartContainerResponse, retErr error) {
|
||||||
return nil, errors.New("not implemented")
|
glog.V(2).Infof("StartContainer for %q", r.GetContainerId())
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
glog.V(2).Infof("StartContainer %q returns successfully", r.GetContainerId())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
container, err := c.containerStore.Get(r.GetContainerId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("an error occurred when try to find container %q: %v", r.GetContainerId(), err)
|
||||||
|
}
|
||||||
|
id := container.ID
|
||||||
|
|
||||||
|
var startErr error
|
||||||
|
// start container in one transaction to avoid race with event monitor.
|
||||||
|
if err := c.containerStore.Update(id, func(meta metadata.ContainerMetadata) (metadata.ContainerMetadata, error) {
|
||||||
|
// Always apply metadata change no matter startContainer fails or not. Because startContainer
|
||||||
|
// may change container state no matter it fails or succeeds.
|
||||||
|
startErr = c.startContainer(ctx, id, &meta)
|
||||||
|
return meta, nil
|
||||||
|
}); startErr != nil {
|
||||||
|
return nil, startErr
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update container %q metadata: %v", id, err)
|
||||||
|
}
|
||||||
|
return &runtime.StartContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// startContainer actually starts the container. The function needs to be run in one transaction. Any updates
|
||||||
|
// to the metadata passed in will be applied to container store no matter the function returns error or not.
|
||||||
|
func (c *criContainerdService) startContainer(ctx context.Context, id string, meta *metadata.ContainerMetadata) (retErr error) {
|
||||||
|
config := meta.Config
|
||||||
|
// Return error if container is not in created state.
|
||||||
|
if meta.State() != runtime.ContainerState_CONTAINER_CREATED {
|
||||||
|
return fmt.Errorf("container %q is in %s state", id, criContainerStateToString(meta.State()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not start the container when there is a removal in progress.
|
||||||
|
if meta.Removing {
|
||||||
|
return fmt.Errorf("container %q is in removing state", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
// Set container to exited if fail to start.
|
||||||
|
meta.Pid = 0
|
||||||
|
meta.FinishedAt = time.Now().UnixNano()
|
||||||
|
meta.ExitCode = errorStartExitCode
|
||||||
|
meta.Reason = errorStartReason
|
||||||
|
meta.Message = retErr.Error()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get sandbox config from sandbox store.
|
||||||
|
sandboxMeta, err := c.getSandbox(meta.SandboxID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sandbox %q not found: %v", meta.SandboxID, err)
|
||||||
|
}
|
||||||
|
sandboxConfig := sandboxMeta.Config
|
||||||
|
sandboxID := meta.SandboxID
|
||||||
|
// Make sure sandbox is running.
|
||||||
|
sandboxInfo, err := c.containerService.Info(ctx, &execution.InfoRequest{ID: sandboxID})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get sandbox container %q info: %v", sandboxID, err)
|
||||||
|
}
|
||||||
|
// This is only a best effort check, sandbox may still exit after this. If sandbox fails
|
||||||
|
// before starting the container, the start will fail.
|
||||||
|
if sandboxInfo.Status != container.Status_RUNNING {
|
||||||
|
return fmt.Errorf("sandbox container %q is not running", sandboxID)
|
||||||
|
}
|
||||||
|
sandboxPid := sandboxInfo.Pid
|
||||||
|
glog.V(2).Infof("Sandbox container %q is running with pid %d", sandboxID, sandboxPid)
|
||||||
|
|
||||||
|
// Generate containerd container create options.
|
||||||
|
// TODO(random-liu): [P0] Create container rootfs with image ref.
|
||||||
|
// TODO(random-liu): [P0] Apply default image config.
|
||||||
|
// Use fixed rootfs path for now.
|
||||||
|
const rootPath = "/"
|
||||||
|
|
||||||
|
spec, err := c.generateContainerSpec(id, sandboxPid, config, sandboxConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate container %q spec: %v", id, err)
|
||||||
|
}
|
||||||
|
rawSpec, err := json.Marshal(spec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal oci spec %+v: %v", spec, err)
|
||||||
|
}
|
||||||
|
glog.V(4).Infof("Container spec: %+v", spec)
|
||||||
|
|
||||||
|
containerRootDir := getContainerRootDir(c.rootDir, id)
|
||||||
|
stdin, stdout, stderr := getStreamingPipes(containerRootDir)
|
||||||
|
// Set stdin to empty if Stdin == false.
|
||||||
|
if !config.GetStdin() {
|
||||||
|
stdin = ""
|
||||||
|
}
|
||||||
|
stdinPipe, stdoutPipe, stderrPipe, err := c.prepareStreamingPipes(ctx, stdin, stdout, stderr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to prepare streaming pipes: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
if stdinPipe != nil {
|
||||||
|
stdinPipe.Close()
|
||||||
|
}
|
||||||
|
stdoutPipe.Close()
|
||||||
|
stderrPipe.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Redirect the stream to std for now.
|
||||||
|
// TODO(random-liu): [P1] Support container logging.
|
||||||
|
// TODO(random-liu): [P1] Support StdinOnce after container logging is added.
|
||||||
|
if stdinPipe != nil {
|
||||||
|
go func(w io.WriteCloser) {
|
||||||
|
io.Copy(w, os.Stdin) // nolint: errcheck
|
||||||
|
w.Close()
|
||||||
|
}(stdinPipe)
|
||||||
|
}
|
||||||
|
go func(r io.ReadCloser) {
|
||||||
|
io.Copy(os.Stdout, r) // nolint: errcheck
|
||||||
|
r.Close()
|
||||||
|
}(stdoutPipe)
|
||||||
|
// Only redirect stderr when there is no tty.
|
||||||
|
if !config.GetTty() {
|
||||||
|
go func(r io.ReadCloser) {
|
||||||
|
io.Copy(os.Stderr, r) // nolint: errcheck
|
||||||
|
r.Close()
|
||||||
|
}(stderrPipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create containerd container.
|
||||||
|
createOpts := &execution.CreateRequest{
|
||||||
|
ID: id,
|
||||||
|
Spec: &prototypes.Any{
|
||||||
|
TypeUrl: runtimespec.Version,
|
||||||
|
Value: rawSpec,
|
||||||
|
},
|
||||||
|
// TODO(random-liu): [P0] Get rootfs mount from containerd.
|
||||||
|
Rootfs: []*mount.Mount{
|
||||||
|
{
|
||||||
|
Type: "bind",
|
||||||
|
Source: rootPath,
|
||||||
|
Options: []string{
|
||||||
|
"rw",
|
||||||
|
"rbind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Runtime: defaultRuntime,
|
||||||
|
Stdin: stdin,
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: stderr,
|
||||||
|
Terminal: config.GetTty(),
|
||||||
|
}
|
||||||
|
glog.V(2).Infof("Create containerd container (id=%q, name=%q) with options %+v.",
|
||||||
|
id, meta.Name, createOpts)
|
||||||
|
createResp, err := c.containerService.Create(ctx, createOpts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create containerd container: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
// Cleanup the containerd container if an error is returned.
|
||||||
|
if _, err := c.containerService.Delete(ctx, &execution.DeleteRequest{ID: id}); err != nil {
|
||||||
|
glog.Errorf("Failed to delete containerd container %q: %v", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Start containerd container.
|
||||||
|
if _, err := c.containerService.Start(ctx, &execution.StartRequest{ID: id}); err != nil {
|
||||||
|
return fmt.Errorf("failed to start containerd container %q: %v", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update container start timestamp.
|
||||||
|
meta.Pid = createResp.Pid
|
||||||
|
meta.StartedAt = time.Now().UnixNano()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint32, config *runtime.ContainerConfig, sandboxConfig *runtime.PodSandboxConfig) (*runtimespec.Spec, error) {
|
||||||
|
// Creates a spec Generator with the default spec.
|
||||||
|
// TODO(random-liu): [P2] Move container runtime spec generation into a helper function.
|
||||||
|
g := generate.New()
|
||||||
|
|
||||||
|
// Set the relative path to the rootfs of the container from containerd's
|
||||||
|
// pre-defined directory.
|
||||||
|
g.SetRootPath(relativeRootfsPath)
|
||||||
|
|
||||||
|
if len(config.GetCommand()) != 0 || len(config.GetArgs()) != 0 {
|
||||||
|
g.SetProcessArgs(append(config.GetCommand(), config.GetArgs()...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.GetWorkingDir() != "" {
|
||||||
|
g.SetProcessCwd(config.GetWorkingDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range config.GetEnvs() {
|
||||||
|
g.AddProcessEnv(e.GetKey(), e.GetValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
addOCIBindMounts(&g, config.GetMounts())
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Set device mapping.
|
||||||
|
// Ref https://github.com/moby/moby/blob/master/oci/devices_linux.go.
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Handle container logging, decorate and redirect to file.
|
||||||
|
|
||||||
|
setOCILinuxResource(&g, config.GetLinux().GetResources())
|
||||||
|
|
||||||
|
if sandboxConfig.GetLinux().GetCgroupParent() != "" {
|
||||||
|
cgroupsPath := getCgroupsPath(sandboxConfig.GetLinux().GetCgroupParent(), id)
|
||||||
|
g.SetLinuxCgroupsPath(cgroupsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.SetProcessTerminal(config.GetTty())
|
||||||
|
|
||||||
|
securityContext := config.GetLinux().GetSecurityContext()
|
||||||
|
|
||||||
|
if err := setOCICapabilities(&g, securityContext.GetCapabilities()); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set capabilities %+v: %v",
|
||||||
|
securityContext.GetCapabilities(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(random-liu): [P0] Handle privileged.
|
||||||
|
|
||||||
|
// Set namespaces, share namespace with sandbox container.
|
||||||
|
setOCINamespaces(&g, securityContext.GetNamespaceOptions(), sandboxPid)
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Set selinux options.
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Set user/username.
|
||||||
|
|
||||||
|
supplementalGroups := securityContext.GetSupplementalGroups()
|
||||||
|
for _, group := range supplementalGroups {
|
||||||
|
g.AddProcessAdditionalGid(uint32(group))
|
||||||
|
}
|
||||||
|
|
||||||
|
g.SetRootReadonly(securityContext.GetReadonlyRootfs())
|
||||||
|
|
||||||
|
// TODO(random-liu): [P2] Add apparmor and seccomp.
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Bind mount sandbox /dev/shm.
|
||||||
|
|
||||||
|
// TODO(random-liu): [P0] Bind mount sandbox resolv.conf.
|
||||||
|
|
||||||
|
return g.Spec(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addOCIBindMounts adds bind mounts.
|
||||||
|
func addOCIBindMounts(g *generate.Generator, mounts []*runtime.Mount) {
|
||||||
|
for _, mount := range mounts {
|
||||||
|
dst := mount.GetContainerPath()
|
||||||
|
src := mount.GetHostPath()
|
||||||
|
options := []string{"rw"}
|
||||||
|
if mount.GetReadonly() {
|
||||||
|
options = []string{"ro"}
|
||||||
|
}
|
||||||
|
// TODO(random-liu): [P1] Apply selinux label
|
||||||
|
g.AddBindMount(src, dst, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOCILinuxResource set container resource limit.
|
||||||
|
func setOCILinuxResource(g *generate.Generator, resources *runtime.LinuxContainerResources) {
|
||||||
|
if resources == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.SetLinuxResourcesCPUPeriod(uint64(resources.GetCpuPeriod()))
|
||||||
|
g.SetLinuxResourcesCPUQuota(resources.GetCpuQuota())
|
||||||
|
g.SetLinuxResourcesCPUShares(uint64(resources.GetCpuShares()))
|
||||||
|
g.SetLinuxResourcesMemoryLimit(uint64(resources.GetMemoryLimitInBytes()))
|
||||||
|
g.SetLinuxResourcesOOMScoreAdj(int(resources.GetOomScoreAdj()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOCICapabilities adds/drops process capabilities.
|
||||||
|
func setOCICapabilities(g *generate.Generator, capabilities *runtime.Capability) error {
|
||||||
|
if capabilities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range capabilities.GetAddCapabilities() {
|
||||||
|
if err := g.AddProcessCapability(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range capabilities.GetDropCapabilities() {
|
||||||
|
if err := g.DropProcessCapability(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOCINamespaces sets namespaces.
|
||||||
|
func setOCINamespaces(g *generate.Generator, namespaces *runtime.NamespaceOption, sandboxPid uint32) {
|
||||||
|
g.AddOrReplaceLinuxNamespace(string(runtimespec.NetworkNamespace), getNetworkNamespace(sandboxPid)) // nolint: errcheck
|
||||||
|
g.AddOrReplaceLinuxNamespace(string(runtimespec.IPCNamespace), getIPCNamespace(sandboxPid)) // nolint: errcheck
|
||||||
|
g.AddOrReplaceLinuxNamespace(string(runtimespec.UTSNamespace), getUTSNamespace(sandboxPid)) // nolint: errcheck
|
||||||
|
g.AddOrReplaceLinuxNamespace(string(runtimespec.PIDNamespace), getPIDNamespace(sandboxPid)) // nolint: errcheck
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,60 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerStatus inspects the container and returns the status.
|
// ContainerStatus inspects the container and returns the status.
|
||||||
func (c *criContainerdService) ContainerStatus(ctx context.Context, r *runtime.ContainerStatusRequest) (*runtime.ContainerStatusResponse, error) {
|
func (c *criContainerdService) ContainerStatus(ctx context.Context, r *runtime.ContainerStatusRequest) (retRes *runtime.ContainerStatusResponse, retErr error) {
|
||||||
return nil, errors.New("not implemented")
|
glog.V(4).Infof("ContainerStatus for container %q", r.GetContainerId())
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
glog.V(4).Infof("ContainerStatus for %q returns status %+v", r.GetContainerId(), retRes.GetStatus())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
meta, err := c.containerStore.Get(r.GetContainerId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("an error occurred when try to find container %q: %v", r.GetContainerId(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &runtime.ContainerStatusResponse{
|
||||||
|
Status: toCRIContainerStatus(meta),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toCRIContainerStatus converts container metadata to CRI container status.
|
||||||
|
func toCRIContainerStatus(meta *metadata.ContainerMetadata) *runtime.ContainerStatus {
|
||||||
|
state := meta.State()
|
||||||
|
reason := meta.Reason
|
||||||
|
if state == runtime.ContainerState_CONTAINER_EXITED && reason == "" {
|
||||||
|
if meta.ExitCode == 0 {
|
||||||
|
reason = completeExitReason
|
||||||
|
} else {
|
||||||
|
reason = errorExitReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &runtime.ContainerStatus{
|
||||||
|
Id: meta.ID,
|
||||||
|
Metadata: meta.Config.GetMetadata(),
|
||||||
|
State: state,
|
||||||
|
CreatedAt: meta.CreatedAt,
|
||||||
|
StartedAt: meta.StartedAt,
|
||||||
|
FinishedAt: meta.FinishedAt,
|
||||||
|
ExitCode: meta.ExitCode,
|
||||||
|
Image: meta.Config.GetImage(),
|
||||||
|
ImageRef: meta.ImageRef,
|
||||||
|
Reason: reason,
|
||||||
|
Message: meta.Message,
|
||||||
|
Labels: meta.Config.GetLabels(),
|
||||||
|
Annotations: meta.Config.GetAnnotations(),
|
||||||
|
Mounts: meta.Config.GetMounts(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,115 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// stopCheckPollInterval is the the interval to check whether a container
|
||||||
|
// is stopped successfully.
|
||||||
|
stopCheckPollInterval = 100 * time.Millisecond
|
||||||
|
|
||||||
|
// killContainerTimeout is the timeout that we wait for the container to
|
||||||
|
// be SIGKILLed.
|
||||||
|
killContainerTimeout = 2 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
// StopContainer stops a running container with a grace period (i.e., timeout).
|
// StopContainer stops a running container with a grace period (i.e., timeout).
|
||||||
func (c *criContainerdService) StopContainer(ctx context.Context, r *runtime.StopContainerRequest) (*runtime.StopContainerResponse, error) {
|
func (c *criContainerdService) StopContainer(ctx context.Context, r *runtime.StopContainerRequest) (retRes *runtime.StopContainerResponse, retErr error) {
|
||||||
return nil, errors.New("not implemented")
|
glog.V(2).Infof("StopContainer for %q with timeout %d (s)", r.GetContainerId(), r.GetTimeout())
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
glog.V(2).Infof("StopContainer %q returns successfully", r.GetContainerId())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get container config from container store.
|
||||||
|
meta, err := c.containerStore.Get(r.GetContainerId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("an error occurred when try to find container %q: %v", r.GetContainerId(), err)
|
||||||
|
}
|
||||||
|
id := r.GetContainerId()
|
||||||
|
|
||||||
|
// Return without error if container is not running. This makes sure that
|
||||||
|
// stop only takes real action after the container is started.
|
||||||
|
if meta.State() != runtime.ContainerState_CONTAINER_RUNNING {
|
||||||
|
glog.V(2).Infof("Container to stop %q is not running, current state %q",
|
||||||
|
id, criContainerStateToString(meta.State()))
|
||||||
|
return &runtime.StopContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Get stop signal from image config.
|
||||||
|
stopSignal := unix.SIGTERM
|
||||||
|
glog.V(2).Infof("Stop container %q with signal %v", id, stopSignal)
|
||||||
|
_, err = c.containerService.Kill(ctx, &execution.KillRequest{ID: id, Signal: uint32(stopSignal)})
|
||||||
|
if err != nil {
|
||||||
|
if isContainerdContainerNotExistError(err) {
|
||||||
|
return &runtime.StopContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to stop container %q: %v", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.waitContainerStop(id, time.Duration(r.GetTimeout())*time.Second)
|
||||||
|
if err == nil {
|
||||||
|
return &runtime.StopContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
glog.Errorf("Stop container %q timed out: %v", id, err)
|
||||||
|
|
||||||
|
glog.V(2).Infof("Delete container from containerd %q", id)
|
||||||
|
_, err = c.containerService.Delete(ctx, &execution.DeleteRequest{ID: id})
|
||||||
|
if err != nil {
|
||||||
|
if isContainerdContainerNotExistError(err) {
|
||||||
|
return &runtime.StopContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to delete container %q: %v", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait forever until container stop is observed by event monitor.
|
||||||
|
if err := c.waitContainerStop(id, killContainerTimeout); err != nil {
|
||||||
|
return nil, fmt.Errorf("error occurs during waiting for container %q to stop: %v",
|
||||||
|
id, err)
|
||||||
|
}
|
||||||
|
return &runtime.StopContainerResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitContainerStop polls container state until timeout exceeds or container is stopped.
|
||||||
|
func (c *criContainerdService) waitContainerStop(id string, timeout time.Duration) error {
|
||||||
|
ticker := time.NewTicker(stopCheckPollInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
timeoutTimer := time.NewTimer(timeout)
|
||||||
|
defer timeoutTimer.Stop()
|
||||||
|
for {
|
||||||
|
// Poll once before waiting for stopCheckPollInterval.
|
||||||
|
meta, err := c.containerStore.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
if !metadata.IsNotExistError(err) {
|
||||||
|
return fmt.Errorf("failed to get container %q metadata: %v", id, err)
|
||||||
|
}
|
||||||
|
// Do not return error here because container was removed means
|
||||||
|
// it is already stopped.
|
||||||
|
glog.Warningf("Container %q was removed during stopping", id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// TODO(random-liu): Use channel with event handler instead of polling.
|
||||||
|
if meta.State() == runtime.ContainerState_CONTAINER_EXITED {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-timeoutTimer.C:
|
||||||
|
return fmt.Errorf("wait container %q stop timeout", id)
|
||||||
|
case <-ticker.C:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
105
pkg/server/events.go
Normal file
105
pkg/server/events.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
|
"github.com/containerd/containerd/api/types/container"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
c.handleEventStream(events)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 containerd-shim exit.
|
||||||
|
case container.Event_EXIT:
|
||||||
|
meta, err := c.containerStore.Get(e.ID)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to get container %q metadata: %v", e.ID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.Pid != meta.Pid {
|
||||||
|
// Non-init process died, ignore the event.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Delete the container from containerd.
|
||||||
|
_, err = c.containerService.Delete(context.Background(), &execution.DeleteRequest{ID: e.ID})
|
||||||
|
if err != nil && !isContainerdContainerNotExistError(err) {
|
||||||
|
// TODO(random-liu): [P0] Enqueue the event and retry.
|
||||||
|
glog.Errorf("Failed to delete container %q: %v", e.ID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.containerStore.Update(e.ID, func(meta metadata.ContainerMetadata) (metadata.ContainerMetadata, error) {
|
||||||
|
// If FinishedAt has been set (e.g. with start failure), keep as
|
||||||
|
// it is.
|
||||||
|
if meta.FinishedAt != 0 {
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
meta.Pid = 0
|
||||||
|
meta.FinishedAt = e.ExitedAt.UnixNano()
|
||||||
|
meta.ExitCode = int32(e.ExitStatus)
|
||||||
|
return meta, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to update container %q state: %v", e.ID, err)
|
||||||
|
// TODO(random-liu): [P0] Enqueue the event and retry.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case container.Event_OOM:
|
||||||
|
// TODO(random-liu): [P1] Handle OOM event.
|
||||||
|
}
|
||||||
|
}
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -18,11 +18,14 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/truncindex"
|
"github.com/docker/docker/pkg/truncindex"
|
||||||
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
@ -32,6 +35,18 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// errorStartReason is the exit reason when fails to start container.
|
||||||
|
errorStartReason = "StartError"
|
||||||
|
// errorStartExitCode is the exit code when fails to start container.
|
||||||
|
// 128 is the same with Docker's behavior.
|
||||||
|
errorStartExitCode = 128
|
||||||
|
// completeExitReason is the exit reason when container exits with code 0.
|
||||||
|
completeExitReason = "Completed"
|
||||||
|
// errorExitReason is the exit reason when container exits with code non-zero.
|
||||||
|
errorExitReason = "Error"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// relativeRootfsPath is the rootfs path relative to bundle path.
|
// relativeRootfsPath is the rootfs path relative to bundle path.
|
||||||
relativeRootfsPath = "rootfs"
|
relativeRootfsPath = "rootfs"
|
||||||
@ -42,6 +57,8 @@ const (
|
|||||||
// directory of the sandbox, all files created for the sandbox will be
|
// directory of the sandbox, all files created for the sandbox will be
|
||||||
// placed under this directory.
|
// placed under this directory.
|
||||||
sandboxesDir = "sandboxes"
|
sandboxesDir = "sandboxes"
|
||||||
|
// containersDir contains all container root.
|
||||||
|
containersDir = "containers"
|
||||||
// stdinNamedPipe is the name of stdin named pipe.
|
// stdinNamedPipe is the name of stdin named pipe.
|
||||||
stdinNamedPipe = "stdin"
|
stdinNamedPipe = "stdin"
|
||||||
// stdoutNamedPipe is the name of stdout named pipe.
|
// stdoutNamedPipe is the name of stdout named pipe.
|
||||||
@ -52,6 +69,12 @@ const (
|
|||||||
nameDelimiter = "_"
|
nameDelimiter = "_"
|
||||||
// netNSFormat is the format of network namespace of a process.
|
// netNSFormat is the format of network namespace of a process.
|
||||||
netNSFormat = "/proc/%v/ns/net"
|
netNSFormat = "/proc/%v/ns/net"
|
||||||
|
// ipcNSFormat is the format of ipc namespace of a process.
|
||||||
|
ipcNSFormat = "/proc/%v/ns/ipc"
|
||||||
|
// utsNSFormat is the format of uts namespace of a process.
|
||||||
|
utsNSFormat = "/proc/%v/ns/uts"
|
||||||
|
// pidNSFormat is the format of pid namespace of a process.
|
||||||
|
pidNSFormat = "/proc/%v/ns/pid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateID generates a random unique id.
|
// generateID generates a random unique id.
|
||||||
@ -70,6 +93,19 @@ func makeSandboxName(s *runtime.PodSandboxMetadata) string {
|
|||||||
}, nameDelimiter)
|
}, nameDelimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeContainerName generates container name from sandbox and container metadata.
|
||||||
|
// The name generated is unique as long as the sandbox container combination is
|
||||||
|
// unique.
|
||||||
|
func makeContainerName(c *runtime.ContainerMetadata, s *runtime.PodSandboxMetadata) string {
|
||||||
|
return strings.Join([]string{
|
||||||
|
c.Name, // 0
|
||||||
|
s.Name, // 1: sandbox name
|
||||||
|
s.Namespace, // 2: sandbox namespace
|
||||||
|
s.Uid, // 3: sandbox uid
|
||||||
|
fmt.Sprintf("%d", c.Attempt), // 4
|
||||||
|
}, nameDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
// getCgroupsPath generates container cgroups path.
|
// getCgroupsPath generates container cgroups path.
|
||||||
func getCgroupsPath(cgroupsParent string, id string) string {
|
func getCgroupsPath(cgroupsParent string, id string) string {
|
||||||
// TODO(random-liu): [P0] Handle systemd.
|
// TODO(random-liu): [P0] Handle systemd.
|
||||||
@ -82,6 +118,11 @@ func getSandboxRootDir(rootDir, id string) string {
|
|||||||
return filepath.Join(rootDir, sandboxesDir, id)
|
return filepath.Join(rootDir, sandboxesDir, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getContainerRootDir returns the root directory for managing container files.
|
||||||
|
func getContainerRootDir(rootDir, id string) string {
|
||||||
|
return filepath.Join(rootDir, containersDir, id)
|
||||||
|
}
|
||||||
|
|
||||||
// getStreamingPipes returns the stdin/stdout/stderr pipes path in the root.
|
// getStreamingPipes returns the stdin/stdout/stderr pipes path in the root.
|
||||||
func getStreamingPipes(rootDir string) (string, string, string) {
|
func getStreamingPipes(rootDir string) (string, string, string) {
|
||||||
stdin := filepath.Join(rootDir, stdinNamedPipe)
|
stdin := filepath.Join(rootDir, stdinNamedPipe)
|
||||||
@ -90,11 +131,57 @@ func getStreamingPipes(rootDir string) (string, string, string) {
|
|||||||
return stdin, stdout, stderr
|
return stdin, stdout, stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepareStreamingPipes prepares stream named pipe for container. returns nil
|
||||||
|
// streaming handler if corresponding stream path is empty.
|
||||||
|
func (c *criContainerdService) prepareStreamingPipes(ctx context.Context, stdin, stdout, stderr string) (
|
||||||
|
i io.WriteCloser, o io.ReadCloser, e io.ReadCloser, retErr error) {
|
||||||
|
pipes := map[string]io.ReadWriteCloser{}
|
||||||
|
for t, stream := range map[string]struct {
|
||||||
|
path string
|
||||||
|
flag int
|
||||||
|
}{
|
||||||
|
"stdin": {stdin, syscall.O_WRONLY | syscall.O_CREAT | syscall.O_NONBLOCK},
|
||||||
|
"stdout": {stdout, syscall.O_RDONLY | syscall.O_CREAT | syscall.O_NONBLOCK},
|
||||||
|
"stderr": {stderr, syscall.O_RDONLY | syscall.O_CREAT | syscall.O_NONBLOCK},
|
||||||
|
} {
|
||||||
|
if stream.path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, err := c.os.OpenFifo(ctx, stream.path, stream.flag, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("failed to open named pipe %q: %v",
|
||||||
|
stream.path, err)
|
||||||
|
}
|
||||||
|
defer func(cl io.Closer) {
|
||||||
|
if retErr != nil {
|
||||||
|
cl.Close()
|
||||||
|
}
|
||||||
|
}(s)
|
||||||
|
pipes[t] = s
|
||||||
|
}
|
||||||
|
return pipes["stdin"], pipes["stdout"], pipes["stderr"], nil
|
||||||
|
}
|
||||||
|
|
||||||
// getNetworkNamespace returns the network namespace of a process.
|
// getNetworkNamespace returns the network namespace of a process.
|
||||||
func getNetworkNamespace(pid uint32) string {
|
func getNetworkNamespace(pid uint32) string {
|
||||||
return fmt.Sprintf(netNSFormat, pid)
|
return fmt.Sprintf(netNSFormat, pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getIPCNamespace returns the ipc namespace of a process.
|
||||||
|
func getIPCNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(ipcNSFormat, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUTSNamespace returns the uts namespace of a process.
|
||||||
|
func getUTSNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(utsNSFormat, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPIDNamespace returns the pid namespace of a process.
|
||||||
|
func getPIDNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(pidNSFormat, pid)
|
||||||
|
}
|
||||||
|
|
||||||
// isContainerdContainerNotExistError checks whether a grpc error is containerd
|
// isContainerdContainerNotExistError checks whether a grpc error is containerd
|
||||||
// ErrContainerNotExist error.
|
// ErrContainerNotExist error.
|
||||||
// TODO(random-liu): Containerd should expose error better through api.
|
// TODO(random-liu): Containerd should expose error better through api.
|
||||||
@ -124,3 +211,8 @@ func (c *criContainerdService) getSandbox(id string) (*metadata.SandboxMetadata,
|
|||||||
}
|
}
|
||||||
return c.sandboxStore.Get(id)
|
return c.sandboxStore.Get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// criContainerStateToString formats CRI container state to string.
|
||||||
|
func criContainerStateToString(state runtime.ContainerState) string {
|
||||||
|
return runtime.ContainerState_name[int32(state)]
|
||||||
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -117,7 +117,8 @@ func (c *criContainerdService) filterCRISandboxes(sandboxes []*runtime.PodSandbo
|
|||||||
if filter.GetLabelSelector() != nil {
|
if filter.GetLabelSelector() != nil {
|
||||||
match := true
|
match := true
|
||||||
for k, v := range filter.GetLabelSelector() {
|
for k, v := range filter.GetLabelSelector() {
|
||||||
if s.Labels[k] != v {
|
got, ok := s.Labels[k]
|
||||||
|
if !ok || got != v {
|
||||||
match = false
|
match = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
|
|||||||
glog.V(2).Infof("RemovePodSandbox for sandbox %q", r.GetPodSandboxId())
|
glog.V(2).Infof("RemovePodSandbox for sandbox %q", r.GetPodSandboxId())
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
glog.V(2).Info("RemovePodSandbox returns successfully")
|
glog.V(2).Info("RemovePodSandbox %q returns successfully", r.GetPodSandboxId())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -56,6 +56,7 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
|
|||||||
// TODO(random-liu): [P2] Remove all containers in the sandbox.
|
// TODO(random-liu): [P2] Remove all containers in the sandbox.
|
||||||
|
|
||||||
// Return error if sandbox container is not fully stopped.
|
// Return error if sandbox container is not fully stopped.
|
||||||
|
// TODO(random-liu): [P0] Make sure network is torn down, may need to introduce a state.
|
||||||
_, err = c.containerService.Info(ctx, &execution.InfoRequest{ID: id})
|
_, err = c.containerService.Info(ctx, &execution.InfoRequest{ID: id})
|
||||||
if err != nil && !isContainerdContainerNotExistError(err) {
|
if err != nil && !isContainerdContainerNotExistError(err) {
|
||||||
return nil, fmt.Errorf("failed to get sandbox container info for %q: %v", id, err)
|
return nil, fmt.Errorf("failed to get sandbox container info for %q: %v", id, err)
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
prototypes "github.com/gogo/protobuf/types"
|
prototypes "github.com/gogo/protobuf/types"
|
||||||
@ -109,14 +108,14 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
|
|||||||
// TODO(random-liu): [P1] Moving following logging related logic into util functions.
|
// TODO(random-liu): [P1] Moving following logging related logic into util functions.
|
||||||
// Discard sandbox container output because we don't care about it.
|
// Discard sandbox container output because we don't care about it.
|
||||||
_, stdout, stderr := getStreamingPipes(sandboxRootDir)
|
_, stdout, stderr := getStreamingPipes(sandboxRootDir)
|
||||||
for _, p := range []string{stdout, stderr} {
|
_, stdoutPipe, stderrPipe, err := c.prepareStreamingPipes(ctx, "", stdout, stderr)
|
||||||
f, err := c.os.OpenFifo(ctx, p, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open named pipe %q: %v", p, err)
|
return nil, fmt.Errorf("failed to prepare streaming pipes: %v", err)
|
||||||
}
|
}
|
||||||
defer func(c io.Closer) {
|
for _, f := range []io.ReadCloser{stdoutPipe, stderrPipe} {
|
||||||
|
defer func(cl io.Closer) {
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
c.Close()
|
cl.Close()
|
||||||
}
|
}
|
||||||
}(f)
|
}(f)
|
||||||
go func(r io.ReadCloser) {
|
go func(r io.ReadCloser) {
|
||||||
|
@ -35,7 +35,7 @@ func (c *criContainerdService) PodSandboxStatus(ctx context.Context, r *runtime.
|
|||||||
glog.V(4).Infof("PodSandboxStatus for sandbox %q", r.GetPodSandboxId())
|
glog.V(4).Infof("PodSandboxStatus for sandbox %q", r.GetPodSandboxId())
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
glog.V(4).Infof("PodSandboxStatus returns status %+v", retRes.GetStatus())
|
glog.V(4).Infof("PodSandboxStatus for %q returns status %+v", r.GetPodSandboxId(), retRes.GetStatus())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
|
|||||||
glog.V(2).Infof("StopPodSandbox for sandbox %q", r.GetPodSandboxId())
|
glog.V(2).Infof("StopPodSandbox for sandbox %q", r.GetPodSandboxId())
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
glog.V(2).Info("StopPodSandbox returns successfully")
|
glog.V(2).Info("StopPodSandbox %q returns successfully", r.GetPodSandboxId())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ import (
|
|||||||
|
|
||||||
// CRIContainerdService is the interface implement CRI remote service server.
|
// CRIContainerdService is the interface implement CRI remote service server.
|
||||||
type CRIContainerdService interface {
|
type CRIContainerdService interface {
|
||||||
|
Start() error
|
||||||
runtime.RuntimeServiceServer
|
runtime.RuntimeServiceServer
|
||||||
runtime.ImageServiceServer
|
runtime.ImageServiceServer
|
||||||
}
|
}
|
||||||
@ -74,6 +75,11 @@ type criContainerdService struct {
|
|||||||
// id "abcdefg" is added, we could use "abcd" to identify the same thing
|
// id "abcdefg" is added, we could use "abcd" to identify the same thing
|
||||||
// as long as there is no ambiguity.
|
// as long as there is no ambiguity.
|
||||||
sandboxIDIndex *truncindex.TruncIndex
|
sandboxIDIndex *truncindex.TruncIndex
|
||||||
|
// containerStore stores all container metadata.
|
||||||
|
containerStore metadata.ContainerStore
|
||||||
|
// containerNameIndex stores all container names and make sure each
|
||||||
|
// name is unique.
|
||||||
|
containerNameIndex *registrar.Registrar
|
||||||
// containerService is containerd container service client.
|
// containerService is containerd container service client.
|
||||||
containerService execution.ContainerServiceClient
|
containerService execution.ContainerServiceClient
|
||||||
// contentIngester is the containerd service to ingest content into
|
// contentIngester is the containerd service to ingest content into
|
||||||
@ -98,10 +104,14 @@ func NewCRIContainerdService(conn *grpc.ClientConn, rootDir string) CRIContainer
|
|||||||
os: osinterface.RealOS{},
|
os: osinterface.RealOS{},
|
||||||
rootDir: rootDir,
|
rootDir: rootDir,
|
||||||
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()),
|
||||||
|
containerStore: metadata.NewContainerStore(store.NewMetadataStore()),
|
||||||
imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()),
|
imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()),
|
||||||
// TODO(random-liu): Register sandbox id/name for recovered sandbox.
|
// TODO(random-liu): Register sandbox/container id/name for recovered sandbox/container.
|
||||||
|
// TODO(random-liu): Use the same name and id index for both container and sandbox.
|
||||||
sandboxNameIndex: registrar.NewRegistrar(),
|
sandboxNameIndex: registrar.NewRegistrar(),
|
||||||
sandboxIDIndex: truncindex.NewTruncIndex(nil),
|
sandboxIDIndex: truncindex.NewTruncIndex(nil),
|
||||||
|
// TODO(random-liu): Add container id index.
|
||||||
|
containerNameIndex: registrar.NewRegistrar(),
|
||||||
containerService: execution.NewContainerServiceClient(conn),
|
containerService: execution.NewContainerServiceClient(conn),
|
||||||
imageStoreService: imagesservice.NewStoreFromClient(imagesapi.NewImagesClient(conn)),
|
imageStoreService: imagesservice.NewStoreFromClient(imagesapi.NewImagesClient(conn)),
|
||||||
contentIngester: contentservice.NewIngesterFromClient(contentapi.NewContentClient(conn)),
|
contentIngester: contentservice.NewIngesterFromClient(contentapi.NewContentClient(conn)),
|
||||||
@ -109,3 +119,7 @@ func NewCRIContainerdService(conn *grpc.ClientConn, rootDir string) CRIContainer
|
|||||||
rootfsUnpacker: rootfsservice.NewUnpackerFromClient(rootfsapi.NewRootFSClient(conn)),
|
rootfsUnpacker: rootfsservice.NewUnpackerFromClient(rootfsapi.NewRootFSClient(conn)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *criContainerdService) Start() error {
|
||||||
|
return c.startEventMonitor()
|
||||||
|
}
|
||||||
|
@ -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"
|
||||||
@ -54,6 +55,8 @@ func newTestCRIContainerdService() *criContainerdService {
|
|||||||
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
@ -151,6 +166,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()
|
||||||
|
77
vendor/github.com/blang/semver/README.md
generated
vendored
77
vendor/github.com/blang/semver/README.md
generated
vendored
@ -40,10 +40,52 @@ Features
|
|||||||
- Comparator-like comparisons
|
- Comparator-like comparisons
|
||||||
- Compare Helper Methods
|
- Compare Helper Methods
|
||||||
- InPlace manipulation
|
- InPlace manipulation
|
||||||
|
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
|
||||||
- Sortable (implements sort.Interface)
|
- Sortable (implements sort.Interface)
|
||||||
- database/sql compatible (sql.Scanner/Valuer)
|
- database/sql compatible (sql.Scanner/Valuer)
|
||||||
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
||||||
|
|
||||||
|
Ranges
|
||||||
|
------
|
||||||
|
|
||||||
|
A `Range` is a set of conditions which specify which versions satisfy the range.
|
||||||
|
|
||||||
|
A condition is composed of an operator and a version. The supported operators are:
|
||||||
|
|
||||||
|
- `<1.0.0` Less than `1.0.0`
|
||||||
|
- `<=1.0.0` Less than or equal to `1.0.0`
|
||||||
|
- `>1.0.0` Greater than `1.0.0`
|
||||||
|
- `>=1.0.0` Greater than or equal to `1.0.0`
|
||||||
|
- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
|
||||||
|
- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
|
||||||
|
|
||||||
|
A `Range` can link multiple `Ranges` separated by space:
|
||||||
|
|
||||||
|
Ranges can be linked by logical AND:
|
||||||
|
|
||||||
|
- `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0`
|
||||||
|
- `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2`
|
||||||
|
|
||||||
|
Ranges can also be linked by logical OR:
|
||||||
|
|
||||||
|
- `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x`
|
||||||
|
|
||||||
|
AND has a higher precedence than OR. It's not possible to use brackets.
|
||||||
|
|
||||||
|
Ranges can be combined by both AND and OR
|
||||||
|
|
||||||
|
- `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
||||||
|
|
||||||
|
Range usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
v, err := semver.Parse("1.2.3")
|
||||||
|
range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0")
|
||||||
|
if range(v) {
|
||||||
|
//valid
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-----
|
-----
|
||||||
@ -103,23 +145,30 @@ if err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Benchmarks
|
Benchmarks
|
||||||
-----
|
-----
|
||||||
|
|
||||||
BenchmarkParseSimple 5000000 328 ns/op 49 B/op 1 allocs/op
|
BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op
|
||||||
BenchmarkParseComplex 1000000 2105 ns/op 263 B/op 7 allocs/op
|
BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op
|
||||||
BenchmarkParseAverage 1000000 1301 ns/op 168 B/op 4 allocs/op
|
BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op
|
||||||
BenchmarkStringSimple 10000000 130 ns/op 5 B/op 1 allocs/op
|
BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op
|
||||||
BenchmarkStringLarger 5000000 280 ns/op 32 B/op 2 allocs/op
|
BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkStringComplex 3000000 512 ns/op 80 B/op 3 allocs/op
|
BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op
|
||||||
BenchmarkStringAverage 5000000 387 ns/op 47 B/op 2 allocs/op
|
BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op
|
||||||
BenchmarkValidateSimple 500000000 7.92 ns/op 0 B/op 0 allocs/op
|
BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkValidateComplex 2000000 923 ns/op 0 B/op 0 allocs/op
|
BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkValidateAverage 5000000 452 ns/op 0 B/op 0 allocs/op
|
BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkCompareSimple 100000000 11.2 ns/op 0 B/op 0 allocs/op
|
BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkCompareComplex 50000000 40.9 ns/op 0 B/op 0 allocs/op
|
BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkCompareAverage 50000000 43.8 ns/op 0 B/op 0 allocs/op
|
BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkSort 5000000 436 ns/op 259 B/op 2 allocs/op
|
BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op
|
||||||
|
BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op
|
||||||
|
BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op
|
||||||
|
BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op
|
||||||
|
BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
See benchmark cases at [semver_test.go](semver_test.go)
|
See benchmark cases at [semver_test.go](semver_test.go)
|
||||||
|
|
||||||
|
224
vendor/github.com/blang/semver/range.go
generated
vendored
Normal file
224
vendor/github.com/blang/semver/range.go
generated
vendored
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type comparator func(Version, Version) bool
|
||||||
|
|
||||||
|
var (
|
||||||
|
compEQ comparator = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) == 0
|
||||||
|
}
|
||||||
|
compNE = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) != 0
|
||||||
|
}
|
||||||
|
compGT = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) == 1
|
||||||
|
}
|
||||||
|
compGE = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) >= 0
|
||||||
|
}
|
||||||
|
compLT = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) == -1
|
||||||
|
}
|
||||||
|
compLE = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) <= 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type versionRange struct {
|
||||||
|
v Version
|
||||||
|
c comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
// rangeFunc creates a Range from the given versionRange.
|
||||||
|
func (vr *versionRange) rangeFunc() Range {
|
||||||
|
return Range(func(v Version) bool {
|
||||||
|
return vr.c(v, vr.v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range represents a range of versions.
|
||||||
|
// A Range can be used to check if a Version satisfies it:
|
||||||
|
//
|
||||||
|
// range, err := semver.ParseRange(">1.0.0 <2.0.0")
|
||||||
|
// range(semver.MustParse("1.1.1") // returns true
|
||||||
|
type Range func(Version) bool
|
||||||
|
|
||||||
|
// OR combines the existing Range with another Range using logical OR.
|
||||||
|
func (rf Range) OR(f Range) Range {
|
||||||
|
return Range(func(v Version) bool {
|
||||||
|
return rf(v) || f(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND combines the existing Range with another Range using logical AND.
|
||||||
|
func (rf Range) AND(f Range) Range {
|
||||||
|
return Range(func(v Version) bool {
|
||||||
|
return rf(v) && f(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRange parses a range and returns a Range.
|
||||||
|
// If the range could not be parsed an error is returned.
|
||||||
|
//
|
||||||
|
// Valid ranges are:
|
||||||
|
// - "<1.0.0"
|
||||||
|
// - "<=1.0.0"
|
||||||
|
// - ">1.0.0"
|
||||||
|
// - ">=1.0.0"
|
||||||
|
// - "1.0.0", "=1.0.0", "==1.0.0"
|
||||||
|
// - "!1.0.0", "!=1.0.0"
|
||||||
|
//
|
||||||
|
// A Range can consist of multiple ranges separated by space:
|
||||||
|
// Ranges can be linked by logical AND:
|
||||||
|
// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
|
||||||
|
// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
|
||||||
|
//
|
||||||
|
// Ranges can also be linked by logical OR:
|
||||||
|
// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
|
||||||
|
//
|
||||||
|
// AND has a higher precedence than OR. It's not possible to use brackets.
|
||||||
|
//
|
||||||
|
// Ranges can be combined by both AND and OR
|
||||||
|
//
|
||||||
|
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
||||||
|
func ParseRange(s string) (Range, error) {
|
||||||
|
parts := splitAndTrim(s)
|
||||||
|
orParts, err := splitORParts(parts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var orFn Range
|
||||||
|
for _, p := range orParts {
|
||||||
|
var andFn Range
|
||||||
|
for _, ap := range p {
|
||||||
|
opStr, vStr, err := splitComparatorVersion(ap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vr, err := buildVersionRange(opStr, vStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
|
||||||
|
}
|
||||||
|
rf := vr.rangeFunc()
|
||||||
|
|
||||||
|
// Set function
|
||||||
|
if andFn == nil {
|
||||||
|
andFn = rf
|
||||||
|
} else { // Combine with existing function
|
||||||
|
andFn = andFn.AND(rf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if orFn == nil {
|
||||||
|
orFn = andFn
|
||||||
|
} else {
|
||||||
|
orFn = orFn.OR(andFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return orFn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitORParts splits the already cleaned parts by '||'.
|
||||||
|
// Checks for invalid positions of the operator and returns an
|
||||||
|
// error if found.
|
||||||
|
func splitORParts(parts []string) ([][]string, error) {
|
||||||
|
var ORparts [][]string
|
||||||
|
last := 0
|
||||||
|
for i, p := range parts {
|
||||||
|
if p == "||" {
|
||||||
|
if i == 0 {
|
||||||
|
return nil, fmt.Errorf("First element in range is '||'")
|
||||||
|
}
|
||||||
|
ORparts = append(ORparts, parts[last:i])
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last == len(parts) {
|
||||||
|
return nil, fmt.Errorf("Last element in range is '||'")
|
||||||
|
}
|
||||||
|
ORparts = append(ORparts, parts[last:])
|
||||||
|
return ORparts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildVersionRange takes a slice of 2: operator and version
|
||||||
|
// and builds a versionRange, otherwise an error.
|
||||||
|
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
|
||||||
|
c := parseComparator(opStr)
|
||||||
|
if c == nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
|
||||||
|
}
|
||||||
|
v, err := Parse(vStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &versionRange{
|
||||||
|
v: v,
|
||||||
|
c: c,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitAndTrim splits a range string by spaces and cleans leading and trailing spaces
|
||||||
|
func splitAndTrim(s string) (result []string) {
|
||||||
|
last := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == ' ' {
|
||||||
|
if last < i-1 {
|
||||||
|
result = append(result, s[last:i])
|
||||||
|
}
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last < len(s)-1 {
|
||||||
|
result = append(result, s[last:])
|
||||||
|
}
|
||||||
|
// parts := strings.Split(s, " ")
|
||||||
|
// for _, x := range parts {
|
||||||
|
// if s := strings.TrimSpace(x); len(s) != 0 {
|
||||||
|
// result = append(result, s)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitComparatorVersion splits the comparator from the version.
|
||||||
|
// Spaces between the comparator and the version are not allowed.
|
||||||
|
// Input must be free of leading or trailing spaces.
|
||||||
|
func splitComparatorVersion(s string) (string, string, error) {
|
||||||
|
i := strings.IndexFunc(s, unicode.IsDigit)
|
||||||
|
if i == -1 {
|
||||||
|
return "", "", fmt.Errorf("Could not get version from string: %q", s)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(s[0:i]), s[i:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseComparator(s string) comparator {
|
||||||
|
switch s {
|
||||||
|
case "==":
|
||||||
|
fallthrough
|
||||||
|
case "":
|
||||||
|
fallthrough
|
||||||
|
case "=":
|
||||||
|
return compEQ
|
||||||
|
case ">":
|
||||||
|
return compGT
|
||||||
|
case ">=":
|
||||||
|
return compGE
|
||||||
|
case "<":
|
||||||
|
return compLT
|
||||||
|
case "<=":
|
||||||
|
return compLE
|
||||||
|
case "!":
|
||||||
|
fallthrough
|
||||||
|
case "!=":
|
||||||
|
return compNE
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
6
vendor/github.com/opencontainers/runtime-tools/generate/generate.go
generated
vendored
6
vendor/github.com/opencontainers/runtime-tools/generate/generate.go
generated
vendored
@ -912,35 +912,30 @@ func (g *Generator) DropProcessCapability(c string) error {
|
|||||||
for i, cap := range g.spec.Process.Capabilities.Bounding {
|
for i, cap := range g.spec.Process.Capabilities.Bounding {
|
||||||
if strings.ToUpper(cap) == cp {
|
if strings.ToUpper(cap) == cp {
|
||||||
g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding[:i], g.spec.Process.Capabilities.Bounding[i+1:]...)
|
g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding[:i], g.spec.Process.Capabilities.Bounding[i+1:]...)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, cap := range g.spec.Process.Capabilities.Effective {
|
for i, cap := range g.spec.Process.Capabilities.Effective {
|
||||||
if strings.ToUpper(cap) == cp {
|
if strings.ToUpper(cap) == cp {
|
||||||
g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective[:i], g.spec.Process.Capabilities.Effective[i+1:]...)
|
g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective[:i], g.spec.Process.Capabilities.Effective[i+1:]...)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, cap := range g.spec.Process.Capabilities.Inheritable {
|
for i, cap := range g.spec.Process.Capabilities.Inheritable {
|
||||||
if strings.ToUpper(cap) == cp {
|
if strings.ToUpper(cap) == cp {
|
||||||
g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable[:i], g.spec.Process.Capabilities.Inheritable[i+1:]...)
|
g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable[:i], g.spec.Process.Capabilities.Inheritable[i+1:]...)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, cap := range g.spec.Process.Capabilities.Permitted {
|
for i, cap := range g.spec.Process.Capabilities.Permitted {
|
||||||
if strings.ToUpper(cap) == cp {
|
if strings.ToUpper(cap) == cp {
|
||||||
g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted[:i], g.spec.Process.Capabilities.Permitted[i+1:]...)
|
g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted[:i], g.spec.Process.Capabilities.Permitted[i+1:]...)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, cap := range g.spec.Process.Capabilities.Ambient {
|
for i, cap := range g.spec.Process.Capabilities.Ambient {
|
||||||
if strings.ToUpper(cap) == cp {
|
if strings.ToUpper(cap) == cp {
|
||||||
g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient[:i], g.spec.Process.Capabilities.Ambient[i+1:]...)
|
g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient[:i], g.spec.Process.Capabilities.Ambient[i+1:]...)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1046,6 +1041,7 @@ func (g *Generator) RemoveDevice(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearLinuxDevices clears g.spec.Linux.Devices
|
||||||
func (g *Generator) ClearLinuxDevices() {
|
func (g *Generator) ClearLinuxDevices() {
|
||||||
if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Devices == nil {
|
if g.spec == nil || g.spec.Linux == nil || g.spec.Linux.Devices == nil {
|
||||||
return
|
return
|
||||||
|
6
vendor/github.com/opencontainers/runtime-tools/validate/validate.go
generated
vendored
6
vendor/github.com/opencontainers/runtime-tools/validate/validate.go
generated
vendored
@ -259,7 +259,7 @@ func (v *Validator) CheckProcess() (msgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs = append(msgs, v.CheckCapablities()...)
|
msgs = append(msgs, v.CheckCapabilities()...)
|
||||||
msgs = append(msgs, v.CheckRlimits()...)
|
msgs = append(msgs, v.CheckRlimits()...)
|
||||||
|
|
||||||
if v.spec.Platform.OS == "linux" {
|
if v.spec.Platform.OS == "linux" {
|
||||||
@ -276,7 +276,8 @@ func (v *Validator) CheckProcess() (msgs []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) CheckCapablities() (msgs []string) {
|
// CheckCapabilities checks v.spec.Process.Capabilities
|
||||||
|
func (v *Validator) CheckCapabilities() (msgs []string) {
|
||||||
process := v.spec.Process
|
process := v.spec.Process
|
||||||
if v.spec.Platform.OS == "linux" {
|
if v.spec.Platform.OS == "linux" {
|
||||||
var caps []string
|
var caps []string
|
||||||
@ -309,6 +310,7 @@ func (v *Validator) CheckCapablities() (msgs []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckRlimits checks v.spec.Process.Rlimits
|
||||||
func (v *Validator) CheckRlimits() (msgs []string) {
|
func (v *Validator) CheckRlimits() (msgs []string) {
|
||||||
process := v.spec.Process
|
process := v.spec.Process
|
||||||
for index, rlimit := range process.Rlimits {
|
for index, rlimit := range process.Rlimits {
|
||||||
|
Loading…
Reference in New Issue
Block a user