Files
kata-containers/virtcontainers/filesystem_resource_storage.go
Samuel Ortiz b05dbe3886 runtime: Convert to the new internal types package
We can now remove all the sandbox shared types and convert the rest of
the code to using the new internal types package.

This commit includes virtcontainers, cli and containerd-shim changes in
one atomic change in order to not break bisect'ibility.

Fixes: #1095

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-01-08 14:43:33 +01:00

874 lines
23 KiB
Go

// Copyright (c) 2016 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
opentracing "github.com/opentracing/opentracing-go"
"github.com/sirupsen/logrus"
"github.com/kata-containers/runtime/virtcontainers/device/api"
"github.com/kata-containers/runtime/virtcontainers/device/config"
"github.com/kata-containers/runtime/virtcontainers/device/drivers"
"github.com/kata-containers/runtime/virtcontainers/types"
)
// sandboxResource is an int representing a sandbox resource type.
//
// Note that some are specific to the sandbox itself and others can apply to
// sandboxes and containers.
type sandboxResource int
const (
// configFileType represents a configuration file type
configFileType sandboxResource = iota
// stateFileType represents a state file type
stateFileType
// networkFileType represents a network file type (sandbox only)
networkFileType
// hypervisorFileType represents a hypervisor file type (sandbox only)
hypervisorFileType
// agentFileType represents an agent file type (sandbox only)
agentFileType
// processFileType represents a process file type
processFileType
// lockFileType represents a lock file type (sandbox only)
lockFileType
// mountsFileType represents a mount file type
mountsFileType
// devicesFileType represents a device file type
devicesFileType
// devicesIDFileType saves reference IDs to file, e.g. device IDs
devicesIDFileType
)
// configFile is the file name used for every JSON sandbox configuration.
const configFile = "config.json"
// stateFile is the file name storing a sandbox state.
const stateFile = "state.json"
// networkFile is the file name storing a sandbox network.
const networkFile = "network.json"
// hypervisorFile is the file name storing a hypervisor's state.
const hypervisorFile = "hypervisor.json"
// agentFile is the file name storing an agent's state.
const agentFile = "agent.json"
// processFile is the file name storing a container process.
const processFile = "process.json"
// lockFile is the file name locking the usage of a sandbox.
const lockFileName = "lock"
const mountsFile = "mounts.json"
// devicesFile is the file name storing a container's devices.
const 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)
// filesystem is a resourceStorage interface implementation for a local filesystem.
type filesystem struct {
ctx context.Context
}
// Logger returns a logrus logger appropriate for logging filesystem messages
func (fs *filesystem) Logger() *logrus.Entry {
return virtLog.WithFields(logrus.Fields{
"subsystem": "storage",
"type": "filesystem",
})
}
func (fs *filesystem) trace(name string) (opentracing.Span, context.Context) {
if fs.ctx == nil {
fs.Logger().WithField("type", "bug").Error("trace called before context set")
fs.ctx = context.Background()
}
span, ctx := opentracing.StartSpanFromContext(fs.ctx, name)
span.SetTag("subsystem", "storage")
span.SetTag("type", "filesystem")
return span, ctx
}
func (fs *filesystem) createAllResources(ctx context.Context, sandbox *Sandbox) (err error) {
fs.ctx = ctx
span, _ := fs.trace("createAllResources")
defer span.Finish()
for _, resource := range []sandboxResource{stateFileType, configFileType} {
_, path, _ := fs.sandboxURI(sandbox.id, resource)
err = os.MkdirAll(path, dirMode)
if err != nil {
return err
}
}
for _, container := range sandbox.containers {
for _, resource := range []sandboxResource{stateFileType, configFileType} {
_, path, _ := fs.containerURI(sandbox.id, container.id, resource)
err = os.MkdirAll(path, dirMode)
if err != nil {
fs.deleteSandboxResources(sandbox.id, nil)
return err
}
}
}
sandboxlockFile, _, err := fs.sandboxURI(sandbox.id, lockFileType)
if err != nil {
fs.deleteSandboxResources(sandbox.id, nil)
return err
}
_, err = os.Stat(sandboxlockFile)
if err != nil {
lockFile, err := os.Create(sandboxlockFile)
if err != nil {
fs.deleteSandboxResources(sandbox.id, nil)
return err
}
lockFile.Close()
}
return nil
}
func (fs *filesystem) storeFile(file string, data interface{}) error {
if file == "" {
return errNeedFile
}
f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
jsonOut, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("Could not marshall data: %s", err)
}
f.Write(jsonOut)
return nil
}
// createSandboxTempFile is used to create a temporary file under sandbox runtime storage path.
func (fs *filesystem) createSandboxTempFile(sandboxID string) (string, error) {
if sandboxID == "" {
return "", errNeedSandboxID
}
dirPath := filepath.Join(runStoragePath, sandboxID, "tempDir")
err := os.MkdirAll(dirPath, dirMode)
if err != nil {
return "", err
}
f, err := ioutil.TempFile(dirPath, "tmp-")
if err != nil {
return "", err
}
return f.Name(), nil
}
// storeDeviceIDFile is used to marshal and store device IDs to disk.
func (fs *filesystem) storeDeviceIDFile(file string, data interface{}) error {
if file == "" {
return errNeedFile
}
f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
devices, ok := data.([]ContainerDevice)
if !ok {
return fmt.Errorf("Incorrect data type received, Expected []string")
}
jsonOut, err := json.Marshal(devices)
if err != nil {
return fmt.Errorf("Could not marshal devices: %s", err)
}
if _, err := f.Write(jsonOut); err != nil {
return err
}
return nil
}
// storeDeviceFile is used to provide custom marshalling for Device objects.
// Device is first marshalled into TypedDevice to include the type
// of the Device object.
func (fs *filesystem) storeDeviceFile(file string, data interface{}) error {
if file == "" {
return errNeedFile
}
f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
devices, ok := data.([]api.Device)
if !ok {
return fmt.Errorf("Incorrect data type received, Expected []Device")
}
var typedDevices []TypedDevice
for _, d := range devices {
tempJSON, _ := json.Marshal(d)
typedDevice := TypedDevice{
Type: string(d.DeviceType()),
Data: tempJSON,
}
typedDevices = append(typedDevices, typedDevice)
}
jsonOut, err := json.Marshal(typedDevices)
if err != nil {
return fmt.Errorf("Could not marshal devices: %s", err)
}
if _, err := f.Write(jsonOut); err != nil {
return err
}
return nil
}
func (fs *filesystem) fetchFile(file string, resource sandboxResource, data interface{}) error {
if file == "" {
return errNeedFile
}
fileData, err := ioutil.ReadFile(file)
if err != nil {
return err
}
switch resource {
case devicesFileType:
devices, ok := data.(*[]api.Device)
if !ok {
return fmt.Errorf("Could not cast %v into *[]Device type", data)
}
return fs.fetchDeviceFile(fileData, devices)
}
return json.Unmarshal(fileData, data)
}
// fetchDeviceFile is used for custom unmarshalling of device interface objects.
func (fs *filesystem) fetchDeviceFile(fileData []byte, devices *[]api.Device) error {
var typedDevices []TypedDevice
if err := json.Unmarshal(fileData, &typedDevices); err != nil {
return err
}
var tempDevices []api.Device
for _, d := range typedDevices {
l := fs.Logger().WithField("device-type", d.Type)
l.Info("Device type found")
switch d.Type {
case string(config.DeviceVFIO):
// TODO: remove dependency of drivers package
var device drivers.VFIODevice
if err := json.Unmarshal(d.Data, &device); err != nil {
return err
}
tempDevices = append(tempDevices, &device)
l.Infof("VFIO device unmarshalled [%v]", device)
case string(config.DeviceBlock):
// TODO: remove dependency of drivers package
var device drivers.BlockDevice
if err := json.Unmarshal(d.Data, &device); err != nil {
return err
}
tempDevices = append(tempDevices, &device)
l.Infof("Block Device unmarshalled [%v]", device)
case string(config.DeviceGeneric):
// TODO: remove dependency of drivers package
var device drivers.GenericDevice
if err := json.Unmarshal(d.Data, &device); err != nil {
return err
}
tempDevices = append(tempDevices, &device)
l.Infof("Generic device unmarshalled [%v]", device)
default:
return fmt.Errorf("Unknown device type, could not unmarshal")
}
}
*devices = tempDevices
return nil
}
// resourceNeedsContainerID determines if the specified
// sandboxResource needs a containerID. Since some sandboxResources can
// be used for both sandboxes and containers, it is necessary to specify
// whether the resource is being used in a sandbox-specific context using
// the sandboxSpecific parameter.
func resourceNeedsContainerID(sandboxSpecific bool, resource sandboxResource) bool {
switch resource {
case lockFileType, networkFileType, hypervisorFileType, agentFileType, devicesFileType:
// sandbox-specific resources
return false
default:
return !sandboxSpecific
}
}
func resourceName(resource sandboxResource) string {
switch resource {
case agentFileType:
return "agentFileType"
case configFileType:
return "configFileType"
case devicesFileType:
return "devicesFileType"
case devicesIDFileType:
return "devicesIDFileType"
case hypervisorFileType:
return "hypervisorFileType"
case lockFileType:
return "lockFileType"
case mountsFileType:
return "mountsFileType"
case networkFileType:
return "networkFileType"
case processFileType:
return "processFileType"
case stateFileType:
return "stateFileType"
default:
return ""
}
}
func resourceDir(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource) (string, error) {
if sandboxID == "" {
return "", errNeedSandboxID
}
if resourceNeedsContainerID(sandboxSpecific, resource) == true && containerID == "" {
return "", errNeedContainerID
}
var path string
switch resource {
case configFileType:
path = configStoragePath
break
case stateFileType, networkFileType, processFileType, lockFileType, mountsFileType, devicesFileType, devicesIDFileType, hypervisorFileType, agentFileType:
path = runStoragePath
break
default:
return "", errInvalidResource
}
dirPath := filepath.Join(path, sandboxID, containerID)
return dirPath, nil
}
// If sandboxSpecific is true, the resource is being applied for an empty
// sandbox (meaning containerID may be blank).
// Note that this function defers determining if containerID can be
// blank to resourceDIR()
func (fs *filesystem) resourceURI(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource) (string, string, error) {
if sandboxID == "" {
return "", "", errNeedSandboxID
}
var filename string
dirPath, err := resourceDir(sandboxSpecific, sandboxID, containerID, resource)
if err != nil {
return "", "", err
}
switch resource {
case configFileType:
filename = configFile
break
case stateFileType:
filename = stateFile
case networkFileType:
filename = networkFile
case hypervisorFileType:
filename = hypervisorFile
case agentFileType:
filename = agentFile
case processFileType:
filename = processFile
case lockFileType:
filename = lockFileName
break
case mountsFileType:
filename = mountsFile
break
case devicesFileType:
filename = devicesFile
break
case devicesIDFileType:
filename = devicesFile
break
default:
return "", "", errInvalidResource
}
filePath := filepath.Join(dirPath, filename)
return filePath, dirPath, nil
}
func (fs *filesystem) containerURI(sandboxID, containerID string, resource sandboxResource) (string, string, error) {
if sandboxID == "" {
return "", "", errNeedSandboxID
}
if containerID == "" {
return "", "", errNeedContainerID
}
return fs.resourceURI(false, sandboxID, containerID, resource)
}
func (fs *filesystem) sandboxURI(sandboxID string, resource sandboxResource) (string, string, error) {
return fs.resourceURI(true, sandboxID, "", resource)
}
// commonResourceChecks performs basic checks common to both setting and
// getting a sandboxResource.
func (fs *filesystem) commonResourceChecks(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource) error {
if sandboxID == "" {
return errNeedSandboxID
}
if resourceNeedsContainerID(sandboxSpecific, resource) == true && containerID == "" {
return errNeedContainerID
}
switch resource {
case configFileType:
case stateFileType:
case networkFileType:
case hypervisorFileType:
case agentFileType:
case processFileType:
case mountsFileType:
case devicesFileType:
case devicesIDFileType:
default:
return errInvalidResource
}
return nil
}
func (fs *filesystem) storeSandboxAndContainerConfigResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != configFileType {
return errInvalidResource
}
configFile, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, configFileType)
if err != nil {
return err
}
return fs.storeFile(configFile, file)
}
func (fs *filesystem) storeStateResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != stateFileType {
return errInvalidResource
}
stateFile, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, stateFileType)
if err != nil {
return err
}
return fs.storeFile(stateFile, file)
}
func (fs *filesystem) storeNetworkResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != networkFileType {
return errInvalidResource
}
// sandbox only resource
networkFile, _, err := fs.resourceURI(true, sandboxID, containerID, networkFileType)
if err != nil {
return err
}
return fs.storeFile(networkFile, file)
}
func (fs *filesystem) storeProcessResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != processFileType {
return errInvalidResource
}
processFile, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, processFileType)
if err != nil {
return err
}
return fs.storeFile(processFile, file)
}
func (fs *filesystem) storeMountResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != mountsFileType {
return errInvalidResource
}
mountsFile, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, mountsFileType)
if err != nil {
return err
}
return fs.storeFile(mountsFile, file)
}
func (fs *filesystem) storeDeviceResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != devicesFileType {
return errInvalidResource
}
devicesFile, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, devicesFileType)
if err != nil {
return err
}
return fs.storeDeviceFile(devicesFile, file)
}
func (fs *filesystem) storeDevicesIDResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, file interface{}) error {
if resource != devicesIDFileType {
return errInvalidResource
}
devicesFile, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, resource)
if err != nil {
return err
}
return fs.storeDeviceIDFile(devicesFile, file)
}
func (fs *filesystem) storeResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, data interface{}) error {
if err := fs.commonResourceChecks(sandboxSpecific, sandboxID, containerID, resource); err != nil {
return err
}
switch file := data.(type) {
case SandboxConfig, ContainerConfig:
return fs.storeSandboxAndContainerConfigResource(sandboxSpecific, sandboxID, containerID, resource, file)
case types.State:
return fs.storeStateResource(sandboxSpecific, sandboxID, containerID, resource, file)
case NetworkNamespace:
return fs.storeNetworkResource(sandboxSpecific, sandboxID, containerID, resource, file)
case Process:
return fs.storeProcessResource(sandboxSpecific, sandboxID, containerID, resource, file)
case []Mount:
return fs.storeMountResource(sandboxSpecific, sandboxID, containerID, resource, file)
case []api.Device:
return fs.storeDeviceResource(sandboxSpecific, sandboxID, containerID, resource, file)
case []ContainerDevice:
return fs.storeDevicesIDResource(sandboxSpecific, sandboxID, containerID, resource, file)
default:
return fmt.Errorf("Invalid resource data type")
}
}
func (fs *filesystem) fetchResource(sandboxSpecific bool, sandboxID, containerID string, resource sandboxResource, data interface{}) error {
var span opentracing.Span
if fs.ctx != nil {
span, _ = fs.trace("fetchResource")
defer span.Finish()
span.SetTag("sandbox", sandboxID)
span.SetTag("container", containerID)
span.SetTag("resource", resourceName(resource))
}
if err := fs.commonResourceChecks(sandboxSpecific, sandboxID, containerID, resource); err != nil {
return err
}
path, _, err := fs.resourceURI(sandboxSpecific, sandboxID, containerID, resource)
if err != nil {
return err
}
return fs.fetchFile(path, resource, data)
}
func (fs *filesystem) storeSandboxResource(sandboxID string, resource sandboxResource, data interface{}) error {
return fs.storeResource(true, sandboxID, "", resource, data)
}
func (fs *filesystem) fetchSandboxConfig(sandboxID string) (SandboxConfig, error) {
var sandboxConfig SandboxConfig
if err := fs.fetchResource(true, sandboxID, "", configFileType, &sandboxConfig); err != nil {
return SandboxConfig{}, err
}
return sandboxConfig, nil
}
func (fs *filesystem) fetchSandboxState(sandboxID string) (types.State, error) {
var state types.State
if err := fs.fetchResource(true, sandboxID, "", stateFileType, &state); err != nil {
return types.State{}, err
}
return state, nil
}
func (fs *filesystem) fetchSandboxNetwork(sandboxID string) (NetworkNamespace, error) {
var networkNS NetworkNamespace
if err := fs.fetchResource(true, sandboxID, "", networkFileType, &networkNS); err != nil {
return NetworkNamespace{}, err
}
return networkNS, nil
}
func (fs *filesystem) fetchSandboxDevices(sandboxID string) ([]api.Device, error) {
var devices []api.Device
if err := fs.fetchResource(true, sandboxID, "", devicesFileType, &devices); err != nil {
return []api.Device{}, err
}
return devices, nil
}
func (fs *filesystem) storeSandboxDevices(sandboxID string, devices []api.Device) error {
return fs.storeSandboxResource(sandboxID, devicesFileType, devices)
}
func (fs *filesystem) fetchHypervisorState(sandboxID string, state interface{}) error {
return fs.fetchResource(true, sandboxID, "", hypervisorFileType, state)
}
func (fs *filesystem) fetchAgentState(sandboxID string, state interface{}) error {
return fs.fetchResource(true, sandboxID, "", agentFileType, state)
}
func (fs *filesystem) storeSandboxNetwork(sandboxID string, networkNS NetworkNamespace) error {
return fs.storeSandboxResource(sandboxID, networkFileType, networkNS)
}
func (fs *filesystem) storeHypervisorState(sandboxID string, state interface{}) error {
hypervisorFile, _, err := fs.resourceURI(true, sandboxID, "", hypervisorFileType)
if err != nil {
return err
}
return fs.storeFile(hypervisorFile, state)
}
func (fs *filesystem) storeAgentState(sandboxID string, state interface{}) error {
span, _ := fs.trace("storeAgentState")
defer span.Finish()
agentFile, _, err := fs.resourceURI(true, sandboxID, "", agentFileType)
if err != nil {
return err
}
return fs.storeFile(agentFile, state)
}
func (fs *filesystem) deleteSandboxResources(sandboxID string, resources []sandboxResource) error {
if resources == nil {
resources = []sandboxResource{configFileType, stateFileType}
}
for _, resource := range resources {
_, dir, err := fs.sandboxURI(sandboxID, resource)
if err != nil {
return err
}
err = os.RemoveAll(dir)
if err != nil {
return err
}
}
return nil
}
func (fs *filesystem) storeContainerResource(sandboxID, containerID string, resource sandboxResource, data interface{}) error {
if sandboxID == "" {
return errNeedSandboxID
}
if containerID == "" {
return errNeedContainerID
}
return fs.storeResource(false, sandboxID, containerID, resource, data)
}
func (fs *filesystem) fetchContainerConfig(sandboxID, containerID string) (ContainerConfig, error) {
var config ContainerConfig
if err := fs.fetchResource(false, sandboxID, containerID, configFileType, &config); err != nil {
return ContainerConfig{}, err
}
return config, nil
}
func (fs *filesystem) fetchContainerState(sandboxID, containerID string) (types.State, error) {
var state types.State
if err := fs.fetchResource(false, sandboxID, containerID, stateFileType, &state); err != nil {
return types.State{}, err
}
return state, nil
}
func (fs *filesystem) fetchContainerProcess(sandboxID, containerID string) (Process, error) {
var process Process
if err := fs.fetchResource(false, sandboxID, containerID, processFileType, &process); err != nil {
return Process{}, err
}
return process, nil
}
func (fs *filesystem) storeContainerProcess(sandboxID, containerID string, process Process) error {
return fs.storeContainerResource(sandboxID, containerID, processFileType, process)
}
func (fs *filesystem) fetchContainerMounts(sandboxID, containerID string) ([]Mount, error) {
var mounts []Mount
if err := fs.fetchResource(false, sandboxID, containerID, mountsFileType, &mounts); err != nil {
return []Mount{}, err
}
return mounts, nil
}
func (fs *filesystem) fetchContainerDevices(sandboxID, containerID string) ([]ContainerDevice, error) {
var devices []ContainerDevice
if err := fs.fetchResource(false, sandboxID, containerID, devicesIDFileType, &devices); err != nil {
return nil, err
}
return devices, nil
}
func (fs *filesystem) storeContainerMounts(sandboxID, containerID string, mounts []Mount) error {
return fs.storeContainerResource(sandboxID, containerID, mountsFileType, mounts)
}
func (fs *filesystem) storeContainerDevices(sandboxID, containerID string, devices []ContainerDevice) error {
return fs.storeContainerResource(sandboxID, containerID, devicesIDFileType, devices)
}
func (fs *filesystem) deleteContainerResources(sandboxID, containerID string, resources []sandboxResource) error {
if resources == nil {
resources = []sandboxResource{configFileType, stateFileType}
}
for _, resource := range resources {
_, dir, err := fs.sandboxURI(sandboxID, resource)
if err != nil {
return err
}
containerDir := filepath.Join(dir, containerID, "/")
err = os.RemoveAll(containerDir)
if err != nil {
return err
}
}
return nil
}