diff --git a/virtcontainers/store/manager_test.go b/virtcontainers/store/manager_test.go index f8668a0e2f..0a15ffc6fb 100644 --- a/virtcontainers/store/manager_test.go +++ b/virtcontainers/store/manager_test.go @@ -7,11 +7,22 @@ package store import ( "context" + "io/ioutil" + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" ) +const testSandboxID = "7f49d00d-1995-4156-8c79-5f5ab24ce138" + +var sandboxDirConfig = "" +var sandboxFileConfig = "" +var sandboxDirState = "" +var sandboxDirLock = "" +var sandboxFileState = "" +var sandboxFileLock = "" var storeRoot = "file:///tmp/root1/" func TestNewStore(t *testing.T) { @@ -92,3 +103,28 @@ func TestManagerFindStore(t *testing.T) { newStore = stores.findStore(storeRoot + "foobar") assert.Nil(t, newStore, "findStore should not have found a new store") } + +// TestMain is the common main function used by ALL the test functions +// for the store. +func TestMain(m *testing.M) { + testDir, err := ioutil.TempDir("", "store-tmp-") + if err != nil { + panic(err) + } + + // allow the tests to run without affecting the host system. + ConfigStoragePath = filepath.Join(testDir, StoragePathSuffix, "config") + RunStoragePath = filepath.Join(testDir, StoragePathSuffix, "run") + + // set now that ConfigStoragePath has been overridden. + sandboxDirConfig = filepath.Join(ConfigStoragePath, testSandboxID) + sandboxFileConfig = filepath.Join(ConfigStoragePath, testSandboxID, ConfigurationFile) + sandboxDirState = filepath.Join(RunStoragePath, testSandboxID) + sandboxDirLock = filepath.Join(RunStoragePath, testSandboxID) + sandboxFileState = filepath.Join(RunStoragePath, testSandboxID, StateFile) + sandboxFileLock = filepath.Join(RunStoragePath, testSandboxID, LockFile) + + ret := m.Run() + + os.Exit(ret) +} diff --git a/virtcontainers/store/vc.go b/virtcontainers/store/vc.go new file mode 100644 index 0000000000..fb43e6fd23 --- /dev/null +++ b/virtcontainers/store/vc.go @@ -0,0 +1,282 @@ +// Copyright (c) 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package store + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + + "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" +) + +// VCStore is a virtcontainers specific Store. +// Virtcontainers typically needs a configuration Store for +// storing permanent items across reboots. +// It also needs a state Store for storing states and other run-time +// related items. Those should not survive a reboot. +// +// VCStore simply dispatches items into the right Store. +type VCStore struct { + config, state *Store +} + +func (s *VCStore) itemToStore(item Item) *Store { + switch item { + case Configuration: + return s.config + case State, Network, Hypervisor, Agent, Process, Lock, Mounts, Devices, DeviceIDs: + return s.state + } + + return s.state +} + +// NewVCStore creates a virtcontainers specific Store. +func NewVCStore(ctx context.Context, configRoot, stateRoot string) (*VCStore, error) { + config, err := New(ctx, configRoot) + if err != nil { + return nil, err + } + + state, err := New(ctx, stateRoot) + if err != nil { + return nil, err + } + + return &VCStore{ + config: config, + state: state, + }, nil +} + +// NewVCSandboxStore creates a virtcontainers sandbox Store, with filesystem backend. +func NewVCSandboxStore(ctx context.Context, sandboxID string) (*VCStore, error) { + if sandboxID == "" { + return nil, fmt.Errorf("sandbox ID can not be empty") + } + + return NewVCStore(ctx, + SandboxConfigurationRoot(sandboxID), + SandboxRuntimeRoot(sandboxID), + ) +} + +// NewVCContainerStore creates a virtcontainers container Store, with filesystem backend. +func NewVCContainerStore(ctx context.Context, sandboxID, containerID string) (*VCStore, error) { + if sandboxID == "" { + return nil, fmt.Errorf("sandbox ID can not be empty") + } + + if containerID == "" { + return nil, fmt.Errorf("container ID can not be empty") + } + + return NewVCStore(ctx, + ContainerConfigurationRoot(sandboxID, containerID), + ContainerRuntimeRoot(sandboxID, containerID), + ) +} + +// Store stores a virtcontainers item into the right Store. +func (s *VCStore) Store(item Item, data interface{}) error { + return s.itemToStore(item).Store(item, data) +} + +// Load loads a virtcontainers item from the right Store. +func (s *VCStore) Load(item Item, data interface{}) error { + return s.itemToStore(item).Load(item, data) +} + +// Delete deletes all artifacts created by a VCStore. +// Both config and state Stores are also removed from the manager. +func (s *VCStore) Delete() error { + if err := s.config.Delete(); err != nil { + return err + } + + if err := s.state.Delete(); err != nil { + return err + } + + return nil +} + +// LoadState loads an returns a virtcontainer state +func (s *VCStore) LoadState() (types.State, error) { + var state types.State + + if err := s.state.Load(State, &state); err != nil { + return types.State{}, err + } + + return state, nil +} + +// TypedDevice is used as an intermediate representation for marshalling +// and unmarshalling Device implementations. +type TypedDevice struct { + Type string + + // Data is assigned the Device object. + // This being declared as RawMessage prevents it from being marshalled/unmarshalled. + // We do that explicitly depending on Type. + Data json.RawMessage +} + +// StoreDevices stores a virtcontainers devices slice. +// The Device slice is first marshalled into a TypedDevice +// one to include the type of the Device objects. +func (s *VCStore) StoreDevices(devices []api.Device) error { + var typedDevices []TypedDevice + + for _, d := range devices { + tempJSON, _ := json.Marshal(d) + typedDevice := TypedDevice{ + Type: string(d.DeviceType()), + Data: tempJSON, + } + typedDevices = append(typedDevices, typedDevice) + } + + return s.state.Store(Devices, typedDevices) +} + +// LoadDevices loads an returns a virtcontainer devices slice. +// We need a custom unmarshalling routine for translating TypedDevices +// into api.Devices based on their type. +func (s *VCStore) LoadDevices() ([]api.Device, error) { + var typedDevices []TypedDevice + var devices []api.Device + + if err := s.state.Load(Devices, &typedDevices); err != nil { + return []api.Device{}, err + } + + for _, d := range typedDevices { + 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 []api.Device{}, err + } + devices = append(devices, &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 []api.Device{}, err + } + devices = append(devices, &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 []api.Device{}, err + } + devices = append(devices, &device) + default: + return []api.Device{}, fmt.Errorf("Unknown device type, could not unmarshal") + } + } + + return devices, nil +} + +// Utilities for virtcontainers + +// SandboxConfigurationRoot returns a virtcontainers sandbox configuration root URL. +// This will hold across host reboot persistent data about a sandbox configuration. +// It should look like file:///var/lib/vc/sbs// +func SandboxConfigurationRoot(id string) string { + return filesystemScheme + "://" + filepath.Join(ConfigStoragePath, id) +} + +// SandboxConfigurationRootPath returns a virtcontainers sandbox configuration root path. +func SandboxConfigurationRootPath(id string) string { + return filepath.Join(ConfigStoragePath, id) +} + +// SandboxConfigurationItemPath returns a virtcontainers sandbox configuration item path. +func SandboxConfigurationItemPath(id string, item Item) (string, error) { + if id == "" { + return "", fmt.Errorf("Empty sandbox ID") + } + + itemFile, err := itemToFile(item) + if err != nil { + return "", err + } + + return filepath.Join(ConfigStoragePath, id, itemFile), nil +} + +// SandboxRuntimeRoot returns a virtcontainers sandbox runtime root URL. +// This will hold data related to a sandbox run-time state that will not +// be persistent across host reboots. +// It should look like file:///run/vc/sbs// +func SandboxRuntimeRoot(id string) string { + return filesystemScheme + "://" + filepath.Join(RunStoragePath, id) +} + +// SandboxRuntimeRootPath returns a virtcontainers sandbox runtime root path. +func SandboxRuntimeRootPath(id string) string { + return filepath.Join(RunStoragePath, id) +} + +// SandboxRuntimeItemPath returns a virtcontainers sandbox runtime item path. +func SandboxRuntimeItemPath(id string, item Item) (string, error) { + if id == "" { + return "", fmt.Errorf("Empty sandbox ID") + } + + itemFile, err := itemToFile(item) + if err != nil { + return "", err + } + + return filepath.Join(RunStoragePath, id, itemFile), nil +} + +// ContainerConfigurationRoot returns a virtcontainers container configuration root URL. +// This will hold across host reboot persistent data about a container configuration. +// It should look like file:///var/lib/vc/sbs// +func ContainerConfigurationRoot(sandboxID, containerID string) string { + return filesystemScheme + "://" + filepath.Join(ConfigStoragePath, sandboxID, containerID) +} + +// ContainerConfigurationRootPath returns a virtcontainers container configuration root path. +func ContainerConfigurationRootPath(sandboxID, containerID string) string { + return filepath.Join(ConfigStoragePath, sandboxID, containerID) +} + +// ContainerRuntimeRoot returns a virtcontainers container runtime root URL. +// This will hold data related to a container run-time state that will not +// be persistent across host reboots. +// It should look like file:///run/vc/sbs/// +func ContainerRuntimeRoot(sandboxID, containerID string) string { + return filesystemScheme + "://" + filepath.Join(RunStoragePath, sandboxID, containerID) +} + +// ContainerRuntimeRootPath returns a virtcontainers container runtime root path. +func ContainerRuntimeRootPath(sandboxID, containerID string) string { + return filepath.Join(RunStoragePath, sandboxID, containerID) +} + +// VCSandboxStoreExists returns true if a sandbox store already exists. +func VCSandboxStoreExists(ctx context.Context, sandboxID string) bool { + s := stores.findStore(SandboxConfigurationRoot(sandboxID)) + if s != nil { + return true + } + + return false +} diff --git a/virtcontainers/store/vc_test.go b/virtcontainers/store/vc_test.go new file mode 100644 index 0000000000..63449249bd --- /dev/null +++ b/virtcontainers/store/vc_test.go @@ -0,0 +1,118 @@ +// Copyright (c) 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package store + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStoreVCRoots(t *testing.T) { + rootURL := filesystemScheme + "://" + ConfigStoragePath + sandboxID := "sandbox" + containerID := "container" + sConfigRoot := rootURL + "/" + sandboxID + cConfigRoot := rootURL + "/" + sandboxID + "/" + containerID + + assert.Equal(t, SandboxConfigurationRoot(sandboxID), sConfigRoot) + assert.Equal(t, ContainerConfigurationRoot(sandboxID, containerID), cConfigRoot) +} + +func testStoreVCSandboxDir(t *testing.T, item Item, expected string) error { + var dir string + if item == Configuration { + dir = SandboxConfigurationRootPath(testSandboxID) + } else { + dir = SandboxRuntimeRootPath(testSandboxID) + } + + if dir != expected { + return fmt.Errorf("Unexpected sandbox directory %s vs %s", dir, expected) + } + + return nil +} + +func testStoreVCSandboxFile(t *testing.T, item Item, expected string) error { + var file string + var err error + + if item == Configuration { + file, err = SandboxConfigurationItemPath(testSandboxID, item) + } else { + file, err = SandboxRuntimeItemPath(testSandboxID, item) + } + + if err != nil { + return err + } + + if file != expected { + return fmt.Errorf("Unexpected sandbox file %s vs %s", file, expected) + } + + return nil +} + +func TestStoreVCSandboxDirConfig(t *testing.T) { + err := testStoreVCSandboxDir(t, Configuration, sandboxDirConfig) + assert.Nil(t, err) +} + +func TestStoreVCSandboxDirState(t *testing.T) { + err := testStoreVCSandboxDir(t, State, sandboxDirState) + assert.Nil(t, err) +} + +func TestStoreVCSandboxDirLock(t *testing.T) { + err := testStoreVCSandboxDir(t, Lock, sandboxDirLock) + assert.Nil(t, err) +} + +func TestStoreVCSandboxFileConfig(t *testing.T) { + err := testStoreVCSandboxFile(t, Configuration, sandboxFileConfig) + assert.Nil(t, err) +} + +func TestStoreVCSandboxFileState(t *testing.T) { + err := testStoreVCSandboxFile(t, State, sandboxFileState) + assert.Nil(t, err) +} + +func TestStoreVCSandboxFileLock(t *testing.T) { + err := testStoreVCSandboxFile(t, Lock, sandboxFileLock) + assert.Nil(t, err) +} + +func TestStoreVCSandboxFileNegative(t *testing.T) { + _, err := SandboxConfigurationItemPath("", State) + assert.NotNil(t, err) + + _, err = SandboxRuntimeItemPath("", State) + assert.NotNil(t, err) +} + +func TestStoreVCNewVCSandboxStore(t *testing.T) { + _, err := NewVCSandboxStore(context.Background(), testSandboxID) + assert.Nil(t, err) + + _, err = NewVCSandboxStore(context.Background(), "") + assert.NotNil(t, err) +} + +func TestStoreVCNewVCContainerStore(t *testing.T) { + _, err := NewVCContainerStore(context.Background(), testSandboxID, "foobar") + assert.Nil(t, err) + + _, err = NewVCContainerStore(context.Background(), "", "foobar") + assert.NotNil(t, err) + + _, err = NewVCContainerStore(context.Background(), "", "foobar") + assert.NotNil(t, err) +}