Checkpoint container status onto disk.

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu
2017-08-29 04:42:39 +00:00
parent 39854b292a
commit 130aa5ac0d
19 changed files with 1019 additions and 63 deletions

View File

@@ -17,16 +17,19 @@ limitations under the License.
package container
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/docker/docker/pkg/ioutils"
"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
// statusVersion is current version of container status.
const statusVersion = "v1" // nolint
// versionedStatus is the internal used versioned container status.
// nolint
@@ -60,19 +63,42 @@ type Status struct {
}
// State returns current state of the container based on the container status.
func (c Status) State() runtime.ContainerState {
if c.FinishedAt != 0 {
func (s Status) State() runtime.ContainerState {
if s.FinishedAt != 0 {
return runtime.ContainerState_CONTAINER_EXITED
}
if c.StartedAt != 0 {
if s.StartedAt != 0 {
return runtime.ContainerState_CONTAINER_RUNNING
}
if c.CreatedAt != 0 {
if s.CreatedAt != 0 {
return runtime.ContainerState_CONTAINER_CREATED
}
return runtime.ContainerState_CONTAINER_UNKNOWN
}
// encode encodes Status into bytes in json format.
func (s *Status) encode() ([]byte, error) {
return json.Marshal(&versionedStatus{
Version: statusVersion,
Status: *s,
})
}
// decode decodes Status from bytes.
func (s *Status) decode(data []byte) error {
versioned := &versionedStatus{}
if err := json.Unmarshal(data, versioned); err != nil {
return err
}
// Handle old version after upgrade.
switch versioned.Version {
case statusVersion:
*s = versioned.Status
return nil
}
return fmt.Errorf("unsupported version")
}
// 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)
@@ -99,48 +125,73 @@ type StatusStorage interface {
// 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.
func StoreStatus(root, id string, status Status) (StatusStorage, error) {
data, err := status.encode()
if err != nil {
return nil, fmt.Errorf("failed to encode status: %v", err)
}
path := filepath.Join(root, "status")
if err := ioutils.AtomicWriteFile(path, data, 0600); err != nil {
return nil, fmt.Errorf("failed to checkpoint status to %q: %v", path, err)
}
return &statusStorage{
path: path,
status: status,
}, nil
}
// LoadStatus loads container status from checkpoint.
func LoadStatus(id string) (StatusStorage, error) {
// TODO(random-liu): Load container status from disk.
return nil, nil
// LoadStatus loads container status from checkpoint. There shouldn't be threads
// writing to the file during loading.
func LoadStatus(root, id string) (Status, error) {
path := filepath.Join(root, "status")
data, err := ioutil.ReadFile(path)
if err != nil {
return Status{}, fmt.Errorf("failed to read status from %q: %v", path, err)
}
var status Status
if err := status.decode(data); err != nil {
return Status{}, fmt.Errorf("failed to decode status %q: %v", data, err)
}
return status, nil
}
type statusStorage struct {
sync.RWMutex
path string
status Status
}
// Get a copy of container status.
func (m *statusStorage) Get() Status {
m.RLock()
defer m.RUnlock()
return m.status
func (s *statusStorage) Get() Status {
s.RLock()
defer s.RUnlock()
return s.status
}
// Update the container status.
func (m *statusStorage) Update(u UpdateFunc) error {
m.Lock()
defer m.Unlock()
newStatus, err := u(m.status)
func (s *statusStorage) Update(u UpdateFunc) error {
s.Lock()
defer s.Unlock()
newStatus, err := u(s.status)
if err != nil {
return err
}
// TODO(random-liu) *Update* existing status on disk atomically,
// return error if checkpoint failed.
m.status = newStatus
data, err := newStatus.encode()
if err != nil {
return fmt.Errorf("failed to encode status: %v", err)
}
if err := ioutils.AtomicWriteFile(s.path, data, 0600); err != nil {
return fmt.Errorf("failed to checkpoint status to %q: %v", s.path, err)
}
s.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
func (s *statusStorage) Delete() error {
temp := filepath.Dir(s.path) + ".del-" + filepath.Base(s.path)
if err := os.Rename(s.path, temp); err != nil && !os.IsNotExist(err) {
return err
}
return os.RemoveAll(temp)
}