diff --git a/pkg/metadata/container.go b/pkg/metadata/container.go new file mode 100644 index 000000000..3e1cf77e4 --- /dev/null +++ b/pkg/metadata/container.go @@ -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) +} diff --git a/pkg/metadata/container_test.go b/pkg/metadata/container_test.go new file mode 100644 index 000000000..f022f0ee9 --- /dev/null +++ b/pkg/metadata/container_test.go @@ -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) +}