Add new metadata store.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
e5d69aa537
commit
a393f3a084
113
pkg/store/container/container.go
Normal file
113
pkg/store/container/container.go
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 container
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
|
||||
// Container contains all resources associated with the container. All methods to
|
||||
// mutate the internal state are thread-safe.
|
||||
type Container struct {
|
||||
// Metadata is the metadata of the container, it is **immutable** after created.
|
||||
Metadata
|
||||
// Status stores the status of the container.
|
||||
Status StatusStorage
|
||||
// TODO(random-liu): Add containerd container client.
|
||||
// TODO(random-liu): Add stop channel to get rid of stop poll waiting.
|
||||
}
|
||||
|
||||
// NewContainer creates an internally used container type.
|
||||
func NewContainer(metadata Metadata, status Status) (Container, error) {
|
||||
s, err := StoreStatus(metadata.ID, status)
|
||||
if err != nil {
|
||||
return Container{}, err
|
||||
}
|
||||
return Container{
|
||||
Metadata: metadata,
|
||||
Status: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete deletes checkpoint for the container.
|
||||
func (c *Container) Delete() error {
|
||||
return c.Status.Delete()
|
||||
}
|
||||
|
||||
// LoadContainer loads the internal used container type.
|
||||
func LoadContainer() (Container, error) {
|
||||
return Container{}, nil
|
||||
}
|
||||
|
||||
// Store stores all Containers.
|
||||
type Store struct {
|
||||
lock sync.RWMutex
|
||||
containers map[string]Container
|
||||
// TODO(random-liu): Add trunc index.
|
||||
}
|
||||
|
||||
// LoadStore loads containers from runtime.
|
||||
// TODO(random-liu): Implement LoadStore.
|
||||
func LoadStore() *Store { return nil }
|
||||
|
||||
// NewStore creates a container store.
|
||||
func NewStore() *Store {
|
||||
return &Store{containers: make(map[string]Container)}
|
||||
}
|
||||
|
||||
// Add a container into the store. Returns store.ErrAlreadyExist if the
|
||||
// container already exists.
|
||||
func (s *Store) Add(c Container) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if _, ok := s.containers[c.ID]; ok {
|
||||
return store.ErrAlreadyExist
|
||||
}
|
||||
s.containers[c.ID] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the container with specified id. Returns store.ErrNotExist
|
||||
// if the container doesn't exist.
|
||||
func (s *Store) Get(id string) (Container, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
if c, ok := s.containers[id]; ok {
|
||||
return c, nil
|
||||
}
|
||||
return Container{}, store.ErrNotExist
|
||||
}
|
||||
|
||||
// List lists all containers.
|
||||
func (s *Store) List() []Container {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
var containers []Container
|
||||
for _, c := range s.containers {
|
||||
containers = append(containers, c)
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
||||
// Delete deletes the container from store with specified id.
|
||||
func (s *Store) Delete(id string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
delete(s.containers, id)
|
||||
}
|
138
pkg/store/container/container_test.go
Normal file
138
pkg/store/container/container_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
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 container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
|
||||
func TestContainerStore(t *testing.T) {
|
||||
ids := []string{"1", "2", "3"}
|
||||
metadatas := map[string]Metadata{
|
||||
"1": {
|
||||
ID: "1",
|
||||
Name: "Container-1",
|
||||
SandboxID: "Sandbox-1",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "TestPod-1",
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-1",
|
||||
},
|
||||
"2": {
|
||||
ID: "2",
|
||||
Name: "Container-2",
|
||||
SandboxID: "Sandbox-2",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "TestPod-2",
|
||||
Attempt: 2,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-2",
|
||||
},
|
||||
"3": {
|
||||
ID: "3",
|
||||
Name: "Container-3",
|
||||
SandboxID: "Sandbox-3",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "TestPod-3",
|
||||
Attempt: 3,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-3",
|
||||
},
|
||||
}
|
||||
statuses := map[string]Status{
|
||||
"1": {
|
||||
Pid: 1,
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
FinishedAt: time.Now().UnixNano(),
|
||||
ExitCode: 1,
|
||||
Reason: "TestReason-1",
|
||||
Message: "TestMessage-1",
|
||||
},
|
||||
"2": {
|
||||
Pid: 2,
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
FinishedAt: time.Now().UnixNano(),
|
||||
ExitCode: 2,
|
||||
Reason: "TestReason-2",
|
||||
Message: "TestMessage-2",
|
||||
},
|
||||
"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)
|
||||
containers := map[string]Container{}
|
||||
for _, id := range ids {
|
||||
container, err := NewContainer(metadatas[id], statuses[id])
|
||||
assert.NoError(err)
|
||||
containers[id] = container
|
||||
}
|
||||
|
||||
s := NewStore()
|
||||
|
||||
t.Logf("should be able to add container")
|
||||
for _, c := range containers {
|
||||
assert.NoError(s.Add(c))
|
||||
}
|
||||
|
||||
t.Logf("should be able to get container")
|
||||
for id, c := range containers {
|
||||
got, err := s.Get(id)
|
||||
assert.NoError(err)
|
||||
assert.Equal(c, got)
|
||||
}
|
||||
|
||||
t.Logf("should be able to list containers")
|
||||
cs := s.List()
|
||||
assert.Len(cs, 3)
|
||||
|
||||
testID := "2"
|
||||
t.Logf("add should return already exists error for duplicated container")
|
||||
assert.Equal(store.ErrAlreadyExist, s.Add(containers[testID]))
|
||||
|
||||
t.Logf("should be able to delete container")
|
||||
s.Delete(testID)
|
||||
cs = s.List()
|
||||
assert.Len(cs, 2)
|
||||
|
||||
t.Logf("get should return not exist error after deletion")
|
||||
c, err := s.Get(testID)
|
||||
assert.Equal(Container{}, c)
|
||||
assert.Equal(store.ErrNotExist, err)
|
||||
}
|
76
pkg/store/container/metadata.go
Normal file
76
pkg/store/container/metadata.go
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// NOTE(random-liu):
|
||||
// 1) Metadata is immutable after created.
|
||||
// 2) Metadata is checkpointed as containerd container label.
|
||||
|
||||
// metadataVersion is current version of container metadata.
|
||||
const metadataVersion = "v1" // nolint
|
||||
|
||||
// versionedMetadata is the internal versioned container metadata.
|
||||
// nolint
|
||||
type versionedMetadata struct {
|
||||
// Version indicates the version of the versioned container metadata.
|
||||
Version string
|
||||
Metadata
|
||||
}
|
||||
|
||||
// Metadata is the unversioned container metadata.
|
||||
type Metadata 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
|
||||
}
|
||||
|
||||
// Encode encodes Metadata into bytes in json format.
|
||||
func (c *Metadata) Encode() ([]byte, error) {
|
||||
return json.Marshal(&versionedMetadata{
|
||||
Version: metadataVersion,
|
||||
Metadata: *c,
|
||||
})
|
||||
}
|
||||
|
||||
// Decode decodes Metadata from bytes.
|
||||
func (c *Metadata) Decode(data []byte) error {
|
||||
versioned := &versionedMetadata{}
|
||||
if err := json.Unmarshal(data, versioned); err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle old version after upgrade.
|
||||
switch versioned.Version {
|
||||
case metadataVersion:
|
||||
*c = versioned.Metadata
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported version")
|
||||
}
|
53
pkg/store/container/metadata_test.go
Normal file
53
pkg/store/container/metadata_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func TestMetadataEncodeDecode(t *testing.T) {
|
||||
meta := &Metadata{
|
||||
ID: "test-id",
|
||||
Name: "test-name",
|
||||
SandboxID: "test-sandbox-id",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "test-name",
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
ImageRef: "test-image-ref",
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
data, err := meta.Encode()
|
||||
assert.NoError(err)
|
||||
newMeta := &Metadata{}
|
||||
assert.NoError(newMeta.Decode(data))
|
||||
assert.Equal(meta, newMeta)
|
||||
|
||||
unsupported, err := json.Marshal(&versionedMetadata{
|
||||
Version: "random-test-version",
|
||||
Metadata: *meta,
|
||||
})
|
||||
assert.NoError(err)
|
||||
assert.Error(newMeta.Decode(unsupported))
|
||||
}
|
146
pkg/store/container/status.go
Normal file
146
pkg/store/container/status.go
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 container
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// TODO(random-liu): Handle versioning.
|
||||
// TODO(random-liu): Add checkpoint support.
|
||||
|
||||
// version is current version of container status.
|
||||
const version = "v1" // nolint
|
||||
|
||||
// versionedStatus is the internal used versioned container status.
|
||||
// nolint
|
||||
type versionedStatus struct {
|
||||
// Version indicates the version of the versioned container status.
|
||||
Version string
|
||||
Status
|
||||
}
|
||||
|
||||
// Status is the status of a container.
|
||||
type Status struct {
|
||||
// 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.
|
||||
// This field doesn't need to be checkpointed.
|
||||
// TODO(random-liu): Reset this field to false during state recoverry.
|
||||
Removing bool
|
||||
}
|
||||
|
||||
// State returns current state of the container based on the container status.
|
||||
func (c Status) 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
|
||||
}
|
||||
|
||||
// UpdateFunc is function used to update the container status. If there
|
||||
// is an error, the update will be rolled back.
|
||||
type UpdateFunc func(Status) (Status, error)
|
||||
|
||||
// StatusStorage manages the container status with a storage backend.
|
||||
type StatusStorage interface {
|
||||
// Get a container status.
|
||||
Get() Status
|
||||
// Update the container status. Note that the update MUST be applied
|
||||
// in one transaction.
|
||||
// TODO(random-liu): Distinguish `UpdateSync` and `Update`, only
|
||||
// `UpdateSync` should sync data onto disk, so that disk operation
|
||||
// for non-critical status change could be avoided.
|
||||
Update(UpdateFunc) error
|
||||
// Delete the container status.
|
||||
// Note:
|
||||
// * Delete should be idempotent.
|
||||
// * The status must be deleted in one trasaction.
|
||||
Delete() error
|
||||
}
|
||||
|
||||
// TODO(random-liu): Add factory function and configure checkpoint path.
|
||||
|
||||
// StoreStatus creates the storage containing the passed in container status with the
|
||||
// specified id.
|
||||
// The status MUST be created in one transaction.
|
||||
func StoreStatus(id string, status Status) (StatusStorage, error) {
|
||||
return &statusStorage{status: status}, nil
|
||||
// TODO(random-liu): Create the data on disk atomically.
|
||||
}
|
||||
|
||||
// LoadStatus loads container status from checkpoint.
|
||||
func LoadStatus(id string) (StatusStorage, error) {
|
||||
// TODO(random-liu): Load container status from disk.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type statusStorage struct {
|
||||
sync.RWMutex
|
||||
status Status
|
||||
}
|
||||
|
||||
// Get a copy of container status.
|
||||
func (m *statusStorage) Get() Status {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.status
|
||||
}
|
||||
|
||||
// Update the container status.
|
||||
func (m *statusStorage) Update(u UpdateFunc) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
newStatus, err := u(m.status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(random-liu) *Update* existing status on disk atomically,
|
||||
// return error if checkpoint failed.
|
||||
m.status = newStatus
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the container status from disk atomically.
|
||||
func (m *statusStorage) Delete() error {
|
||||
// TODO(random-liu): Rename the data on the disk, returns error
|
||||
// if fails. No lock is needed because file rename is atomic.
|
||||
// TODO(random-liu): Cleanup temporary files generated, do not
|
||||
// return error.
|
||||
return nil
|
||||
}
|
101
pkg/store/container/status_test.go
Normal file
101
pkg/store/container/status_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
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 container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func TestContainerState(t *testing.T) {
|
||||
for c, test := range map[string]struct {
|
||||
status Status
|
||||
state runtime.ContainerState
|
||||
}{
|
||||
"unknown state": {
|
||||
status: Status{},
|
||||
state: runtime.ContainerState_CONTAINER_UNKNOWN,
|
||||
},
|
||||
"created state": {
|
||||
status: Status{
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
},
|
||||
state: runtime.ContainerState_CONTAINER_CREATED,
|
||||
},
|
||||
"running state": {
|
||||
status: Status{
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
},
|
||||
state: runtime.ContainerState_CONTAINER_RUNNING,
|
||||
},
|
||||
"exited state": {
|
||||
status: Status{
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
FinishedAt: time.Now().UnixNano(),
|
||||
},
|
||||
state: runtime.ContainerState_CONTAINER_EXITED,
|
||||
},
|
||||
} {
|
||||
t.Logf("TestCase %q", c)
|
||||
assertlib.Equal(t, test.state, test.status.State())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
testID := "test-id"
|
||||
testStatus := Status{
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
}
|
||||
updateStatus := Status{
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
}
|
||||
updateErr := errors.New("update error")
|
||||
assert := assertlib.New(t)
|
||||
|
||||
t.Logf("simple store and get")
|
||||
s, err := StoreStatus(testID, testStatus)
|
||||
assert.NoError(err)
|
||||
old := s.Get()
|
||||
assert.Equal(testStatus, old)
|
||||
|
||||
t.Logf("failed update should not take effect")
|
||||
err = s.Update(func(o Status) (Status, error) {
|
||||
o = updateStatus
|
||||
return o, updateErr
|
||||
})
|
||||
assert.Equal(updateErr, err)
|
||||
assert.Equal(testStatus, s.Get())
|
||||
|
||||
t.Logf("successful update should take effect")
|
||||
err = s.Update(func(o Status) (Status, error) {
|
||||
o = updateStatus
|
||||
return o, nil
|
||||
})
|
||||
assert.NoError(err)
|
||||
assert.Equal(updateStatus, s.Get())
|
||||
|
||||
t.Logf("successful update should not affect existing snapshot")
|
||||
assert.Equal(testStatus, old)
|
||||
|
||||
// TODO(random-liu): Test Load and Delete after disc checkpoint is added.
|
||||
}
|
27
pkg/store/errors.go
Normal file
27
pkg/store/errors.go
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 store
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrAlreadyExist is the error returned when data added in the store
|
||||
// already exists.
|
||||
ErrAlreadyExist = errors.New("already exists")
|
||||
// ErrNotExist is the error returned when data is not in the store.
|
||||
ErrNotExist = errors.New("does not exist")
|
||||
)
|
120
pkg/store/image/image.go
Normal file
120
pkg/store/image/image.go
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 image
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
|
||||
// Image contains all resources associated with the image. All fields
|
||||
// MUST not be mutated directly after created.
|
||||
type Image struct {
|
||||
// Id of the image. Normally the digest of image config.
|
||||
ID string
|
||||
// Other names by which this image is known.
|
||||
RepoTags []string
|
||||
// Digests by which this image is known.
|
||||
RepoDigests []string
|
||||
// ChainID is the chainID of the image.
|
||||
ChainID string
|
||||
// Size is the compressed size of the image.
|
||||
Size int64
|
||||
// Config is the oci image config of the image.
|
||||
Config *imagespec.ImageConfig
|
||||
// TODO(random-liu): Add containerd image client.
|
||||
}
|
||||
|
||||
// Store stores all images.
|
||||
type Store struct {
|
||||
lock sync.RWMutex
|
||||
images map[string]Image
|
||||
// TODO(random-liu): Add trunc index.
|
||||
}
|
||||
|
||||
// LoadStore loads images from runtime.
|
||||
// TODO(random-liu): Implement LoadStore.
|
||||
func LoadStore() *Store { return nil }
|
||||
|
||||
// NewStore creates an image store.
|
||||
func NewStore() *Store {
|
||||
return &Store{images: make(map[string]Image)}
|
||||
}
|
||||
|
||||
// Add an image into the store.
|
||||
func (s *Store) Add(img Image) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
i, ok := s.images[img.ID]
|
||||
if !ok {
|
||||
// If the image doesn't exist, add it.
|
||||
s.images[img.ID] = img
|
||||
return
|
||||
}
|
||||
// Or else, merge the repo tags/digests.
|
||||
i.RepoTags = mergeStringSlices(i.RepoTags, img.RepoTags)
|
||||
i.RepoDigests = mergeStringSlices(i.RepoDigests, img.RepoDigests)
|
||||
s.images[img.ID] = i
|
||||
}
|
||||
|
||||
// Get returns the image with specified id. Returns store.ErrNotExist if the
|
||||
// image doesn't exist.
|
||||
func (s *Store) Get(id string) (Image, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
if i, ok := s.images[id]; ok {
|
||||
return i, nil
|
||||
}
|
||||
return Image{}, store.ErrNotExist
|
||||
}
|
||||
|
||||
// List lists all images.
|
||||
func (s *Store) List() []Image {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
var images []Image
|
||||
for _, sb := range s.images {
|
||||
images = append(images, sb)
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
// Delete deletes the image with specified id.
|
||||
func (s *Store) Delete(id string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
delete(s.images, id)
|
||||
}
|
||||
|
||||
// mergeStringSlices merges 2 string slices into one and remove duplicated elements.
|
||||
func mergeStringSlices(a []string, b []string) []string {
|
||||
set := map[string]struct{}{}
|
||||
for _, s := range a {
|
||||
set[s] = struct{}{}
|
||||
}
|
||||
for _, s := range b {
|
||||
set[s] = struct{}{}
|
||||
}
|
||||
var ss []string
|
||||
for s := range set {
|
||||
ss = append(ss, s)
|
||||
}
|
||||
return ss
|
||||
}
|
106
pkg/store/image/image_test.go
Normal file
106
pkg/store/image/image_test.go
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 image
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
|
||||
func TestImageStore(t *testing.T) {
|
||||
images := map[string]Image{
|
||||
"1": {
|
||||
ID: "1",
|
||||
ChainID: "test-chain-id-1",
|
||||
RepoTags: []string{"tag-1"},
|
||||
RepoDigests: []string{"digest-1"},
|
||||
Size: 10,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
"2": {
|
||||
ID: "2",
|
||||
ChainID: "test-chain-id-2",
|
||||
RepoTags: []string{"tag-2"},
|
||||
RepoDigests: []string{"digest-2"},
|
||||
Size: 20,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
"3": {
|
||||
ID: "3",
|
||||
RepoTags: []string{"tag-3"},
|
||||
RepoDigests: []string{"digest-3"},
|
||||
ChainID: "test-chain-id-3",
|
||||
Size: 30,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
|
||||
s := NewStore()
|
||||
|
||||
t.Logf("should be able to add image")
|
||||
for _, img := range images {
|
||||
s.Add(img)
|
||||
}
|
||||
|
||||
t.Logf("should be able to get image")
|
||||
for id, img := range images {
|
||||
got, err := s.Get(id)
|
||||
assert.NoError(err)
|
||||
assert.Equal(img, got)
|
||||
}
|
||||
|
||||
t.Logf("should be able to list images")
|
||||
imgs := s.List()
|
||||
assert.Len(imgs, 3)
|
||||
|
||||
testID := "2"
|
||||
t.Logf("should be able to add new repo tags/digests")
|
||||
newImg := images[testID]
|
||||
newImg.RepoTags = []string{"tag-new"}
|
||||
newImg.RepoDigests = []string{"digest-new"}
|
||||
s.Add(newImg)
|
||||
got, err := s.Get(testID)
|
||||
assert.NoError(err)
|
||||
assert.Len(got.RepoTags, 2)
|
||||
assert.Contains(got.RepoTags, "tag-2", "tag-new")
|
||||
assert.Len(got.RepoDigests, 2)
|
||||
assert.Contains(got.RepoDigests, "digest-2", "digest-new")
|
||||
|
||||
t.Logf("should not be able to add duplicated repo tags/digests")
|
||||
s.Add(newImg)
|
||||
got, err = s.Get(testID)
|
||||
assert.NoError(err)
|
||||
assert.Len(got.RepoTags, 2)
|
||||
assert.Contains(got.RepoTags, "tag-2", "tag-new")
|
||||
assert.Len(got.RepoDigests, 2)
|
||||
assert.Contains(got.RepoDigests, "digest-2", "digest-new")
|
||||
|
||||
t.Logf("should be able to delete image")
|
||||
s.Delete(testID)
|
||||
imgs = s.List()
|
||||
assert.Len(imgs, 2)
|
||||
|
||||
t.Logf("get should return nil after deletion")
|
||||
img, err := s.Get(testID)
|
||||
assert.Equal(Image{}, img)
|
||||
assert.Equal(store.ErrNotExist, err)
|
||||
}
|
79
pkg/store/sandbox/metadata.go
Normal file
79
pkg/store/sandbox/metadata.go
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 sandbox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// NOTE(random-liu):
|
||||
// 1) Metadata is immutable after created.
|
||||
// 2) Metadata is checkpointed as containerd container label.
|
||||
|
||||
// metadataVersion is current version of sandbox metadata.
|
||||
const metadataVersion = "v1" // nolint
|
||||
|
||||
// versionedMetadata is the internal versioned sandbox metadata.
|
||||
// nolint
|
||||
type versionedMetadata struct {
|
||||
// Version indicates the version of the versioned sandbox metadata.
|
||||
Version string
|
||||
Metadata
|
||||
}
|
||||
|
||||
// Metadata is the unversioned sandbox metadata.
|
||||
type Metadata struct {
|
||||
// ID is the sandbox id.
|
||||
ID string
|
||||
// Name is the sandbox name.
|
||||
Name string
|
||||
// Config is the CRI sandbox config.
|
||||
Config *runtime.PodSandboxConfig
|
||||
// CreatedAt is the created timestamp.
|
||||
// TODO(random-liu): Use containerd container CreatedAt (containerd#933)
|
||||
CreatedAt int64
|
||||
// Pid is the process id of the sandbox.
|
||||
Pid uint32
|
||||
// NetNS is the network namespace used by the sandbox.
|
||||
NetNS string
|
||||
}
|
||||
|
||||
// Encode encodes Metadata into bytes in json format.
|
||||
func (c *Metadata) Encode() ([]byte, error) {
|
||||
return json.Marshal(&versionedMetadata{
|
||||
Version: metadataVersion,
|
||||
Metadata: *c,
|
||||
})
|
||||
}
|
||||
|
||||
// Decode decodes Metadata from bytes.
|
||||
func (c *Metadata) Decode(data []byte) error {
|
||||
versioned := &versionedMetadata{}
|
||||
if err := json.Unmarshal(data, versioned); err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle old version after upgrade.
|
||||
switch versioned.Version {
|
||||
case metadataVersion:
|
||||
*c = versioned.Metadata
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported version")
|
||||
}
|
55
pkg/store/sandbox/metadata_test.go
Normal file
55
pkg/store/sandbox/metadata_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
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 sandbox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func TestMetadataEncodeDecode(t *testing.T) {
|
||||
meta := &Metadata{
|
||||
ID: "test-id",
|
||||
Name: "test-name",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "test-name",
|
||||
Uid: "test-uid",
|
||||
Namespace: "test-namespace",
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
data, err := meta.Encode()
|
||||
assert.NoError(err)
|
||||
newMeta := &Metadata{}
|
||||
assert.NoError(newMeta.Decode(data))
|
||||
assert.Equal(meta, newMeta)
|
||||
|
||||
unsupported, err := json.Marshal(&versionedMetadata{
|
||||
Version: "random-test-version",
|
||||
Metadata: *meta,
|
||||
})
|
||||
assert.NoError(err)
|
||||
assert.Error(newMeta.Decode(unsupported))
|
||||
}
|
88
pkg/store/sandbox/sandbox.go
Normal file
88
pkg/store/sandbox/sandbox.go
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 sandbox
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
|
||||
// Sandbox contains all resources associated with the sandbox. All methods to
|
||||
// mutate the internal state are thread safe.
|
||||
type Sandbox struct {
|
||||
// Metadata is the metadata of the sandbox, it is immutable after created.
|
||||
Metadata
|
||||
// TODO(random-liu): Add containerd container client.
|
||||
// TODO(random-liu): Add cni network namespace client.
|
||||
}
|
||||
|
||||
// Store stores all sandboxes.
|
||||
type Store struct {
|
||||
lock sync.RWMutex
|
||||
sandboxes map[string]Sandbox
|
||||
// TODO(random-liu): Add trunc index.
|
||||
}
|
||||
|
||||
// LoadStore loads sandboxes from runtime.
|
||||
// TODO(random-liu): Implement LoadStore.
|
||||
func LoadStore() *Store { return nil }
|
||||
|
||||
// NewStore creates a sandbox store.
|
||||
func NewStore() *Store {
|
||||
return &Store{sandboxes: make(map[string]Sandbox)}
|
||||
}
|
||||
|
||||
// Add a sandbox into the store.
|
||||
func (s *Store) Add(sb Sandbox) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if _, ok := s.sandboxes[sb.ID]; ok {
|
||||
return store.ErrAlreadyExist
|
||||
}
|
||||
s.sandboxes[sb.ID] = sb
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the sandbox with specified id. Returns nil
|
||||
// if the sandbox doesn't exist.
|
||||
func (s *Store) Get(id string) (Sandbox, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
if sb, ok := s.sandboxes[id]; ok {
|
||||
return sb, nil
|
||||
}
|
||||
return Sandbox{}, store.ErrNotExist
|
||||
}
|
||||
|
||||
// List lists all sandboxes.
|
||||
func (s *Store) List() []Sandbox {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
var sandboxes []Sandbox
|
||||
for _, sb := range s.sandboxes {
|
||||
sandboxes = append(sandboxes, sb)
|
||||
}
|
||||
return sandboxes
|
||||
}
|
||||
|
||||
// Delete deletes the sandbox with specified id.
|
||||
func (s *Store) Delete(id string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
delete(s.sandboxes, id)
|
||||
}
|
115
pkg/store/sandbox/sandbox_test.go
Normal file
115
pkg/store/sandbox/sandbox_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 sandbox
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assertlib "github.com/stretchr/testify/assert"
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
|
||||
func TestSandboxStore(t *testing.T) {
|
||||
ids := []string{"1", "2", "3"}
|
||||
metadatas := map[string]Metadata{
|
||||
"1": {
|
||||
ID: "1",
|
||||
Name: "Sandbox-1",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "TestPod-1",
|
||||
Uid: "TestUid-1",
|
||||
Namespace: "TestNamespace-1",
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
Pid: 1001,
|
||||
NetNS: "TestNetNS-1",
|
||||
},
|
||||
"2": {
|
||||
ID: "2",
|
||||
Name: "Sandbox-2",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "TestPod-2",
|
||||
Uid: "TestUid-2",
|
||||
Namespace: "TestNamespace-2",
|
||||
Attempt: 2,
|
||||
},
|
||||
},
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
Pid: 1002,
|
||||
NetNS: "TestNetNS-2",
|
||||
},
|
||||
"3": {
|
||||
ID: "3",
|
||||
Name: "Sandbox-3",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "TestPod-3",
|
||||
Uid: "TestUid-3",
|
||||
Namespace: "TestNamespace-3",
|
||||
Attempt: 3,
|
||||
},
|
||||
},
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
Pid: 1003,
|
||||
NetNS: "TestNetNS-3",
|
||||
},
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
sandboxes := map[string]Sandbox{}
|
||||
for _, id := range ids {
|
||||
sandboxes[id] = Sandbox{metadatas[id]}
|
||||
}
|
||||
|
||||
s := NewStore()
|
||||
|
||||
t.Logf("should be able to add sandbox")
|
||||
for _, sb := range sandboxes {
|
||||
assert.NoError(s.Add(sb))
|
||||
}
|
||||
|
||||
t.Logf("should be able to get sandbox")
|
||||
for id, sb := range sandboxes {
|
||||
got, err := s.Get(id)
|
||||
assert.NoError(err)
|
||||
assert.Equal(sb, got)
|
||||
}
|
||||
|
||||
t.Logf("should be able to list sandboxes")
|
||||
sbs := s.List()
|
||||
assert.Len(sbs, 3)
|
||||
|
||||
testID := "2"
|
||||
t.Logf("add should return already exists error for duplicated sandbox")
|
||||
assert.Equal(store.ErrAlreadyExist, s.Add(sandboxes[testID]))
|
||||
|
||||
t.Logf("should be able to delete sandbox")
|
||||
s.Delete(testID)
|
||||
sbs = s.List()
|
||||
assert.Len(sbs, 2)
|
||||
|
||||
t.Logf("get should return not exist error after deletion")
|
||||
sb, err := s.Get(testID)
|
||||
assert.Equal(Sandbox{}, sb)
|
||||
assert.Equal(store.ErrNotExist, err)
|
||||
}
|
Loading…
Reference in New Issue
Block a user