diff --git a/virtcontainers/store/filesystem.go b/virtcontainers/store/filesystem.go deleted file mode 100644 index 56e9aa2fa1..0000000000 --- a/virtcontainers/store/filesystem.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2019 Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 -// - -package store - -import ( - "context" - - opentracing "github.com/opentracing/opentracing-go" - "github.com/sirupsen/logrus" -) - -type filesystem struct { - ctx context.Context - - path string -} - -// 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) new(ctx context.Context, path string, host string) error { - f.ctx = ctx - f.path = path - - f.logger().Infof("New filesystem store backend for %s", path) - - return nil -} - -func (f *filesystem) load(item Item, data interface{}) error { - span, _ := f.trace("load") - defer span.Finish() - - span.SetTag("item", item) - - return nil -} - -func (f *filesystem) store(item Item, data interface{}) error { - span, _ := f.trace("store") - defer span.Finish() - - span.SetTag("item", item) - - return nil -} diff --git a/virtcontainers/store/filesystem_backend.go b/virtcontainers/store/filesystem_backend.go new file mode 100644 index 0000000000..4e5fb2c8df --- /dev/null +++ b/virtcontainers/store/filesystem_backend.go @@ -0,0 +1,227 @@ +// Copyright (c) 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package store + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + 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 +} + +// 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 and a lock file + + // Root directory + if err := os.MkdirAll(f.path, 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.logger().Debugf("New filesystem store backend for %s", path) + + return f.initialize() +} + +func (f *filesystem) delete() { + 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 +} diff --git a/virtcontainers/store/filesystem_backend_test.go b/virtcontainers/store/filesystem_backend_test.go new file mode 100644 index 0000000000..460a61bd83 --- /dev/null +++ b/virtcontainers/store/filesystem_backend_test.go @@ -0,0 +1,66 @@ +// Copyright (c) 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package store + +import ( + "context" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +type TestNoopStructure struct { + Field1 string + Field2 string +} + +var rootPath = "/tmp/root1/" +var expectedFilesystemData = "{\"Field1\":\"value1\",\"Field2\":\"value2\"}" + +func TestStoreFilesystemStore(t *testing.T) { + f := filesystem{} + + err := f.new(context.Background(), rootPath, "") + defer f.delete() + assert.Nil(t, err) + + data := TestNoopStructure{ + Field1: "value1", + Field2: "value2", + } + + err = f.store(State, data) + assert.Nil(t, err) + + filesystemData, err := ioutil.ReadFile(filepath.Join(rootPath, StateFile)) + assert.Nil(t, err) + assert.Equal(t, string(filesystemData), expectedFilesystemData) +} + +func TestStoreFilesystemLoad(t *testing.T) { + f := filesystem{} + + err := f.new(context.Background(), rootPath, "") + defer f.delete() + assert.Nil(t, err) + + data := TestNoopStructure{ + Field1: "value1", + Field2: "value2", + } + + // Store test data + err = f.store(State, data) + assert.Nil(t, err) + + // Load and compare + newData := TestNoopStructure{} + err = f.load(State, &newData) + assert.Nil(t, err) + assert.Equal(t, newData, data) +} diff --git a/virtcontainers/store/manager_test.go b/virtcontainers/store/manager_test.go index 61a3aa0797..066e586256 100644 --- a/virtcontainers/store/manager_test.go +++ b/virtcontainers/store/manager_test.go @@ -12,14 +12,14 @@ import ( "github.com/stretchr/testify/assert" ) -var storeRoot = "file:///root1/" +var storeRoot = "file:///tmp/root1/" func TestNewStore(t *testing.T) { s, err := New(context.Background(), storeRoot) assert.Nil(t, err) assert.Equal(t, s.scheme, "file") assert.Equal(t, s.host, "") - assert.Equal(t, s.path, "/root1/") + assert.Equal(t, s.path, "/tmp/root1/") } func TestManagerAddStore(t *testing.T) {