Files
kata-containers/virtcontainers/store/filesystem_backend.go
Samuel Ortiz 2ecffda170 virtcontainers: store: Add a ItemLock API
The ItemLock API allows for taking shared and exclusive locks on all
items.
For virtcontainers, this is specialized into taking locks on the Lock
item, and will be used for sandbox locking.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-02-06 14:19:18 +01:00

312 lines
6.8 KiB
Go

// Copyright (c) 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package store
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"github.com/kata-containers/runtime/virtcontainers/pkg/uuid"
opentracing "github.com/opentracing/opentracing-go"
"github.com/sirupsen/logrus"
)
const (
// ConfigurationFile is the file name used for every JSON sandbox configuration.
ConfigurationFile string = "config.json"
// StateFile is the file name storing a sandbox state.
StateFile = "state.json"
// NetworkFile is the file name storing a sandbox network.
NetworkFile = "network.json"
// HypervisorFile is the file name storing a hypervisor's state.
HypervisorFile = "hypervisor.json"
// AgentFile is the file name storing an agent's state.
AgentFile = "agent.json"
// ProcessFile is the file name storing a container process.
ProcessFile = "process.json"
// LockFile is the file name locking the usage of a resource.
LockFile = "lock"
// MountsFile is the file name storing a container's mount points.
MountsFile = "mounts.json"
// DevicesFile is the file name storing a container's devices.
DevicesFile = "devices.json"
)
// DirMode is the permission bits used for creating a directory
const DirMode = os.FileMode(0750) | os.ModeDir
// 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"
// VMPathSuffix is the suffix used for guest VMs.
const VMPathSuffix = "vm"
// ConfigStoragePath is the sandbox configuration directory.
// It will contain one config.json file for each created sandbox.
var ConfigStoragePath = filepath.Join("/var/lib", StoragePathSuffix, SandboxPathSuffix)
// 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)
// RunVMStoragePath is the vm directory.
// It will contain all guest vm sockets and shared mountpoints.
var RunVMStoragePath = filepath.Join("/run", StoragePathSuffix, VMPathSuffix)
func itemToFile(item Item) (string, error) {
switch item {
case Configuration:
return ConfigurationFile, nil
case State:
return StateFile, nil
case Network:
return NetworkFile, nil
case Hypervisor:
return HypervisorFile, nil
case Agent:
return AgentFile, nil
case Process:
return ProcessFile, nil
case Lock:
return LockFile, nil
case Mounts:
return MountsFile, nil
case Devices, DeviceIDs:
return DevicesFile, nil
}
return "", fmt.Errorf("Unknown item %s", item)
}
type filesystem struct {
ctx context.Context
path string
rawPath string
lockTokens map[string]*os.File
}
// Logger returns a logrus logger appropriate for logging Store filesystem messages
func (f *filesystem) logger() *logrus.Entry {
return storeLog.WithFields(logrus.Fields{
"subsystem": "store",
"backend": "filesystem",
"path": f.path,
})
}
func (f *filesystem) trace(name string) (opentracing.Span, context.Context) {
if f.ctx == nil {
f.logger().WithField("type", "bug").Error("trace called before context set")
f.ctx = context.Background()
}
span, ctx := opentracing.StartSpanFromContext(f.ctx, name)
span.SetTag("subsystem", "store")
span.SetTag("type", "filesystem")
span.SetTag("path", f.path)
return span, ctx
}
func (f *filesystem) itemToPath(item Item) (string, error) {
fileName, err := itemToFile(item)
if err != nil {
return "", err
}
return filepath.Join(f.path, fileName), nil
}
func (f *filesystem) initialize() error {
_, err := os.Stat(f.path)
if err == nil {
return nil
}
// Our root path does not exist, we need to create the initial layout:
// The root directory, a lock file and a raw files directory.
// Root directory
if err := os.MkdirAll(f.path, DirMode); err != nil {
return err
}
// Raw directory
if err := os.MkdirAll(f.rawPath, DirMode); err != nil {
return err
}
// Lock
lockPath := filepath.Join(f.path, LockFile)
lockFile, err := os.Create(lockPath)
if err != nil {
f.delete()
return err
}
lockFile.Close()
return nil
}
func (f *filesystem) new(ctx context.Context, path string, host string) error {
f.ctx = ctx
f.path = path
f.rawPath = filepath.Join(f.path, "raw")
f.lockTokens = make(map[string]*os.File)
f.logger().Debugf("New filesystem store backend for %s", path)
return f.initialize()
}
func (f *filesystem) delete() error {
return os.RemoveAll(f.path)
}
func (f *filesystem) load(item Item, data interface{}) error {
span, _ := f.trace("load")
defer span.Finish()
span.SetTag("item", item)
filePath, err := f.itemToPath(item)
if err != nil {
return err
}
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
if err := json.Unmarshal(fileData, data); err != nil {
return err
}
return nil
}
func (f *filesystem) store(item Item, data interface{}) error {
span, _ := f.trace("store")
defer span.Finish()
span.SetTag("item", item)
filePath, err := f.itemToPath(item)
if err != nil {
return err
}
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
jsonOut, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("Could not marshall data: %s", err)
}
file.Write(jsonOut)
return nil
}
func (f *filesystem) raw(id string) (string, error) {
span, _ := f.trace("raw")
defer span.Finish()
span.SetTag("id", id)
// We must use the item ID.
if id != "" {
filePath := filepath.Join(f.rawPath, id)
file, err := os.Create(filePath)
if err != nil {
return "", err
}
return filesystemScheme + "://" + file.Name(), nil
}
// Generate a random temporary file.
file, err := ioutil.TempFile(f.rawPath, "raw-")
if err != nil {
return "", err
}
defer file.Close()
return filesystemScheme + "://" + file.Name(), nil
}
func (f *filesystem) lock(item Item, exclusive bool) (string, error) {
itemPath, err := f.itemToPath(item)
if err != nil {
return "", err
}
itemFile, err := os.Open(itemPath)
if err != nil {
return "", err
}
var lockType int
if exclusive {
lockType = syscall.LOCK_EX
} else {
lockType = syscall.LOCK_SH
}
if err := syscall.Flock(int(itemFile.Fd()), lockType); err != nil {
itemFile.Close()
return "", err
}
token := uuid.Generate().String()
f.lockTokens[token] = itemFile
return token, nil
}
func (f *filesystem) unlock(item Item, token string) error {
itemFile := f.lockTokens[token]
if itemFile == nil {
return fmt.Errorf("No lock for token %s", token)
}
if err := syscall.Flock(int(itemFile.Fd()), syscall.LOCK_UN); err != nil {
return err
}
itemFile.Close()
delete(f.lockTokens, token)
return nil
}