Files
kata-containers/virtcontainers/persist/fs/fs.go
Wei Zhang 341a988e06 persist: simplify persist api
Fixes #803

Simplify new store API to make the code easier to understand and use.

Signed-off-by: Wei Zhang <zhangwei555@huawei.com>
2019-04-30 11:54:42 +08:00

262 lines
5.8 KiB
Go

// Copyright (c) 2016 Intel Corporation
// Copyright (c) 2019 Huawei Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package fs
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
persistapi "github.com/kata-containers/runtime/virtcontainers/persist/api"
"github.com/sirupsen/logrus"
)
// persistFile is the file name for JSON sandbox/container configuration
const persistFile = "persist.json"
// dirMode is the permission bits used for creating a directory
const dirMode = os.FileMode(0700)
// fileMode is the permission bits used for creating a file
const fileMode = os.FileMode(0640)
// storagePathSuffix is the suffix used for all storage paths
//
// Note: this very brief path represents "virtcontainers". It is as
// terse as possible to minimise path length.
const storagePathSuffix = "vc"
// sandboxPathSuffix is the suffix used for sandbox storage
const sandboxPathSuffix = "sbs"
// runStoragePath is the sandbox runtime directory.
// It will contain one state.json and one lock file for each created sandbox.
var runStoragePath = filepath.Join("/run", storagePathSuffix, sandboxPathSuffix)
// FS storage driver implementation
type FS struct {
sandboxState *persistapi.SandboxState
containerState map[string]persistapi.ContainerState
lockFile *os.File
}
var fsLog = logrus.WithField("source", "virtcontainers/persist/fs")
// Logger returns a logrus logger appropriate for logging Store messages
func (fs *FS) Logger() *logrus.Entry {
return fsLog.WithFields(logrus.Fields{
"subsystem": "persist",
})
}
// Name returns driver name
func Name() string {
return "fs"
}
// Init FS persist driver and return abstract PersistDriver
func Init() (persistapi.PersistDriver, error) {
return &FS{
sandboxState: &persistapi.SandboxState{},
containerState: make(map[string]persistapi.ContainerState),
}, nil
}
func (fs *FS) sandboxDir() (string, error) {
id := fs.sandboxState.SandboxContainer
if id == "" {
return "", fmt.Errorf("sandbox container id required")
}
return filepath.Join(runStoragePath, id), nil
}
// ToDisk sandboxState and containerState to disk
func (fs *FS) ToDisk(ss persistapi.SandboxState, cs map[string]persistapi.ContainerState) (retErr error) {
fs.sandboxState = &ss
fs.containerState = cs
sandboxDir, err := fs.sandboxDir()
if err != nil {
return err
}
if err := os.MkdirAll(sandboxDir, dirMode); err != nil {
return err
}
if err := fs.lock(); err != nil {
return err
}
defer fs.unlock()
// if error happened, destroy all dirs
defer func() {
if retErr != nil {
if err := fs.Destroy(); err != nil {
fs.Logger().WithError(err).Errorf("failed to destroy dirs")
}
}
}()
// persist sandbox configuration data
sandboxFile := filepath.Join(sandboxDir, persistFile)
f, err := os.OpenFile(sandboxFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
if err != nil {
return err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(fs.sandboxState); err != nil {
return err
}
// persist container configuration data
for cid, cstate := range fs.containerState {
cdir := filepath.Join(sandboxDir, cid)
if err := os.MkdirAll(cdir, dirMode); err != nil {
return err
}
cfile := filepath.Join(cdir, persistFile)
cf, err := os.OpenFile(cfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
if err != nil {
return err
}
if err := json.NewEncoder(cf).Encode(cstate); err != nil {
return err
}
cf.Close()
}
return nil
}
// FromDisk restores state for sandbox with name sid
func (fs *FS) FromDisk(sid string) (persistapi.SandboxState, map[string]persistapi.ContainerState, error) {
ss := persistapi.SandboxState{}
if sid == "" {
return ss, nil, fmt.Errorf("restore requires sandbox id")
}
fs.sandboxState.SandboxContainer = sid
sandboxDir, err := fs.sandboxDir()
if err != nil {
return ss, nil, err
}
if err := fs.lock(); err != nil {
return ss, nil, err
}
defer fs.unlock()
// get sandbox configuration from persist data
sandboxFile := filepath.Join(sandboxDir, persistFile)
f, err := os.OpenFile(sandboxFile, os.O_RDONLY, fileMode)
if err != nil {
return ss, nil, err
}
defer f.Close()
if err := json.NewDecoder(f).Decode(fs.sandboxState); err != nil {
return ss, nil, err
}
// walk sandbox dir and find container
files, err := ioutil.ReadDir(sandboxDir)
if err != nil {
return ss, nil, err
}
for _, file := range files {
if !file.IsDir() {
continue
}
cid := file.Name()
cfile := filepath.Join(sandboxDir, cid, persistFile)
cf, err := os.OpenFile(cfile, os.O_RDONLY, fileMode)
if err != nil {
// if persist.json doesn't exist, ignore and go to next
if os.IsNotExist(err) {
continue
}
return ss, nil, err
}
var cstate persistapi.ContainerState
if err := json.NewDecoder(cf).Decode(&cstate); err != nil {
return ss, nil, err
}
cf.Close()
fs.containerState[cid] = cstate
}
return *fs.sandboxState, fs.containerState, nil
}
// Destroy removes everything from disk
func (fs *FS) Destroy() error {
sandboxDir, err := fs.sandboxDir()
if err != nil {
return err
}
if err := os.RemoveAll(sandboxDir); err != nil {
return err
}
return nil
}
func (fs *FS) lock() error {
sandboxDir, err := fs.sandboxDir()
if err != nil {
return err
}
f, err := os.Open(sandboxDir)
if err != nil {
return err
}
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
f.Close()
return err
}
fs.lockFile = f
return nil
}
func (fs *FS) unlock() error {
if fs.lockFile == nil {
return nil
}
lockFile := fs.lockFile
defer lockFile.Close()
fs.lockFile = nil
if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN); err != nil {
return err
}
return nil
}
// TestSetRunStoragePath set runStoragePath to path
// this function is only used for testing purpose
func TestSetRunStoragePath(path string) {
runStoragePath = path
}