[lxc-devel] [lxd/master] Storage package

tomponline on Github lxc-bot at linuxcontainers.org
Mon Oct 7 15:08:38 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191007/4751d9b0/attachment-0001.bin>
-------------- next part --------------
From 2d1839e328d4785bbf0940312328ffcdc1513ad2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 4 Oct 2019 19:02:38 -0400
Subject: [PATCH 1/5] lxd/storage: Add new interfaces
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/migration/interfaces.go      |  12 ++
 lxd/storage/backend_lxd.go       | 201 +++++++++++++++++++++++++++++++
 lxd/storage/backend_mock.go      | 192 +++++++++++++++++++++++++++++
 lxd/storage/drivers/driver.go    |  45 +++++++
 lxd/storage/drivers/errors.go    |  14 +++
 lxd/storage/drivers/interface.go |  50 ++++++++
 lxd/storage/errors.go            |  11 ++
 lxd/storage/interfaces.go        |  95 +++++++++++++++
 lxd/storage/load.go              |  85 +++++++++++++
 9 files changed, 705 insertions(+)
 create mode 100644 lxd/migration/interfaces.go
 create mode 100644 lxd/storage/backend_lxd.go
 create mode 100644 lxd/storage/backend_mock.go
 create mode 100644 lxd/storage/drivers/driver.go
 create mode 100644 lxd/storage/drivers/errors.go
 create mode 100644 lxd/storage/drivers/interface.go
 create mode 100644 lxd/storage/errors.go
 create mode 100644 lxd/storage/interfaces.go
 create mode 100644 lxd/storage/load.go

diff --git a/lxd/migration/interfaces.go b/lxd/migration/interfaces.go
new file mode 100644
index 0000000000..7264e9ef91
--- /dev/null
+++ b/lxd/migration/interfaces.go
@@ -0,0 +1,12 @@
+package migration
+
+// FIXME: empty stubs until we're ready to move the migration code over
+
+type SourceArgs struct {
+}
+
+type SinkArgs struct {
+}
+
+type StorageSourceDriver interface {
+}
diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
new file mode 100644
index 0000000000..730be6615c
--- /dev/null
+++ b/lxd/storage/backend_lxd.go
@@ -0,0 +1,201 @@
+package storage
+
+import (
+	"os"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/storage/drivers"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+type lxdBackend struct {
+	driver drivers.Driver
+	id     int64
+	name   string
+	state  *state.State
+}
+
+func (b *lxdBackend) DaemonState() *state.State {
+	return b.state
+}
+
+func (b *lxdBackend) ID() int64 {
+	return b.id
+}
+
+func (b *lxdBackend) Name() string {
+	return b.name
+}
+
+func (b *lxdBackend) Driver() drivers.Driver {
+	return b.driver
+}
+
+func (b *lxdBackend) create(dbPool api.StoragePool, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) GetResources() (*api.ResourcesStoragePool, error) {
+	return nil, ErrNotImplemented
+}
+
+func (b *lxdBackend) Delete(op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) Mount() (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) Unmount() (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateInstance(i Instance, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateInstanceFromBackup(i Instance, sourcePath string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateInstanceFromCopy(i Instance, src Instance, snapshots bool, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateInstanceFromImage(i Instance, fingerprint string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateInstanceFromMigration(i Instance, conn *websocket.Conn, args migration.SinkArgs, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) RenameInstance(i Instance, newName string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) DeleteInstance(i Instance, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) MigrateInstance(i Instance, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error) {
+	return nil, ErrNotImplemented
+}
+
+func (b *lxdBackend) RefreshInstance(i Instance, src Instance, snapshots bool, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) BackupInstance(i Instance, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) GetInstanceUsage(i Instance) (uint64, error) {
+	return 0, ErrNotImplemented
+}
+
+func (b *lxdBackend) SetInstanceQuota(i Instance, quota uint64) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) MountInstance(i Instance) (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) UnmountInstance(i Instance) (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) GetInstanceDisk(i Instance) (string, string, error) {
+	return "", "", ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateInstanceSnapshot(i Instance, name string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) RenameInstanceSnapshot(i Instance, newName string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) DeleteInstanceSnapshot(i Instance, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) RestoreInstanceSnapshot(i Instance, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) MountInstanceSnapshot(i Instance) (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) UnmountInstanceSnapshot(i Instance) (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateImage(img api.Image, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) DeleteImage(img api.Image, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateCustomVolume(vol api.StorageVolume, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateCustomVolumeFromCopy(vol api.StorageVolume, src api.StorageVolume, snapshots bool, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateCustomVolumeFromMigration(vol api.StorageVolume, conn *websocket.Conn, args migration.SinkArgs, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) RenameCustomVolume(vol api.StorageVolume, newName string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) DeleteCustomVolume(vol api.StorageVolume, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) MigrateCustomVolume(vol api.StorageVolume, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error) {
+	return nil, ErrNotImplemented
+}
+
+func (b *lxdBackend) GetCustomVolumeUsage(vol api.StorageVolume) (uint64, error) {
+	return 0, ErrNotImplemented
+}
+
+func (b *lxdBackend) SetCustomVolumeQuota(vol api.StorageVolume, quota uint64) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) MountCustomVolume(vol api.StorageVolume) (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) UnmountCustomVolume(vol api.StorageVolume) (bool, error) {
+	return true, ErrNotImplemented
+}
+
+func (b *lxdBackend) CreateCustomVolumeSnapshot(vol api.StorageVolume, name string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) RenameCustomVolumeSnapshot(vol api.StorageVolume, newName string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (b *lxdBackend) DeleteCustomVolumeSnapshot(vol api.StorageVolume, op *operations.Operation) error {
+	return ErrNotImplemented
+}
diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go
new file mode 100644
index 0000000000..e6082782b7
--- /dev/null
+++ b/lxd/storage/backend_mock.go
@@ -0,0 +1,192 @@
+package storage
+
+import (
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/storage/drivers"
+	"github.com/lxc/lxd/shared/api"
+)
+
+type mockBackend struct {
+	name  string
+	state *state.State
+}
+
+func (b *mockBackend) DaemonState() *state.State {
+	return b.state
+}
+
+func (b *mockBackend) ID() int64 {
+	return -1
+}
+
+func (b *mockBackend) Name() string {
+	return b.name
+}
+
+func (b *mockBackend) Driver() drivers.Driver {
+	return nil
+}
+
+func (b *mockBackend) GetResources() (*api.ResourcesStoragePool, error) {
+	return nil, nil
+}
+
+func (b *mockBackend) Delete(op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) Mount() (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) Unmount() (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) CreateInstance(i Instance, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateInstanceFromBackup(i Instance, sourcePath string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateInstanceFromCopy(i Instance, src Instance, snapshots bool, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateInstanceFromImage(i Instance, fingerprint string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateInstanceFromMigration(i Instance, conn *websocket.Conn, args migration.SinkArgs, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) RenameInstance(i Instance, newName string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) DeleteInstance(i Instance, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) MigrateInstance(i Instance, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error) {
+	return nil, nil
+}
+
+func (b *mockBackend) RefreshInstance(i Instance, src Instance, snapshots bool, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) BackupInstance(i Instance, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) GetInstanceUsage(i Instance) (uint64, error) {
+	return 0, nil
+}
+
+func (b *mockBackend) SetInstanceQuota(i Instance, quota uint64) error {
+	return nil
+}
+
+func (b *mockBackend) MountInstance(i Instance) (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) UnmountInstance(i Instance) (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) GetInstanceDisk(i Instance) (string, string, error) {
+	return "", "", nil
+}
+
+func (b *mockBackend) CreateInstanceSnapshot(i Instance, name string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) RenameInstanceSnapshot(i Instance, newName string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) DeleteInstanceSnapshot(i Instance, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) RestoreInstanceSnapshot(i Instance, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) MountInstanceSnapshot(i Instance) (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) UnmountInstanceSnapshot(i Instance) (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) CreateImage(img api.Image, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) DeleteImage(img api.Image, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateCustomVolume(vol api.StorageVolume, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateCustomVolumeFromCopy(vol api.StorageVolume, src api.StorageVolume, snapshots bool, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) CreateCustomVolumeFromMigration(vol api.StorageVolume, conn *websocket.Conn, args migration.SinkArgs, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) RenameCustomVolume(vol api.StorageVolume, newName string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) DeleteCustomVolume(vol api.StorageVolume, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) MigrateCustomVolume(vol api.StorageVolume, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error) {
+	return nil, nil
+}
+
+func (b *mockBackend) GetCustomVolumeUsage(vol api.StorageVolume) (uint64, error) {
+	return 0, nil
+}
+
+func (b *mockBackend) SetCustomVolumeQuota(vol api.StorageVolume, quota uint64) error {
+	return nil
+}
+
+func (b *mockBackend) MountCustomVolume(vol api.StorageVolume) (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) UnmountCustomVolume(vol api.StorageVolume) (bool, error) {
+	return true, nil
+}
+
+func (b *mockBackend) CreateCustomVolumeSnapshot(vol api.StorageVolume, name string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) RenameCustomVolumeSnapshot(vol api.StorageVolume, newName string, op *operations.Operation) error {
+	return nil
+}
+
+func (b *mockBackend) DeleteCustomVolumeSnapshot(vol api.StorageVolume, op *operations.Operation) error {
+	return nil
+}
diff --git a/lxd/storage/drivers/driver.go b/lxd/storage/drivers/driver.go
new file mode 100644
index 0000000000..ff55d6e250
--- /dev/null
+++ b/lxd/storage/drivers/driver.go
@@ -0,0 +1,45 @@
+package drivers
+
+import (
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+var drivers = map[string]func(name string, path string, config map[string]string) driver{}
+
+// Create performs the initial validation and alteration of the configuration and creates the low-level storage pool, returning a Driver.
+func Create(dbPool *api.StoragePool) (Driver, error) {
+	if dbPool == nil {
+		return nil, ErrNilValue
+	}
+
+	// Locate the driver
+	_, ok := drivers[dbPool.Driver]
+	if !ok {
+		return nil, ErrUnknownDriver
+	}
+
+	path := shared.VarPath("storage-pools", dbPool.Name)
+	d := drivers[dbPool.Driver](dbPool.Name, path, dbPool.Config)
+
+	// Create the low level pool
+	err := d.create(dbPool)
+	if err != nil {
+		return nil, err
+	}
+
+	return d, nil
+}
+
+// Load returns a Driver for an existing low-level storage pool.
+func Load(driverName string, name string, path string, config map[string]string) (Driver, error) {
+	// Locate the driver
+	_, ok := drivers[driverName]
+	if !ok {
+		return nil, ErrUnknownDriver
+	}
+
+	d := drivers[driverName](name, path, config)
+
+	return d, nil
+}
diff --git a/lxd/storage/drivers/errors.go b/lxd/storage/drivers/errors.go
new file mode 100644
index 0000000000..7cca047d15
--- /dev/null
+++ b/lxd/storage/drivers/errors.go
@@ -0,0 +1,14 @@
+package drivers
+
+import (
+	"fmt"
+)
+
+// ErrNilValue is the "Nil value provided" error
+var ErrNilValue = fmt.Errorf("Nil value provided")
+
+// ErrNotImplemented is the "Not implemented" error
+var ErrNotImplemented = fmt.Errorf("Not implemented")
+
+// ErrUnknownDriver is the "Unknown driver" error
+var ErrUnknownDriver = fmt.Errorf("Unknown driver")
diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go
new file mode 100644
index 0000000000..105be9cc9f
--- /dev/null
+++ b/lxd/storage/drivers/interface.go
@@ -0,0 +1,50 @@
+package drivers
+
+import (
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// VolumeType represents a storage volume type.
+type VolumeType string
+
+// VolumeTypeCustom represents a custom storage volume.
+const VolumeTypeCustom = VolumeType("custom")
+
+// VolumeTypeContainer represents a container storage volume.
+const VolumeTypeContainer = VolumeType("containers")
+
+// VolumeTypeImage represents an image storage volume.
+const VolumeTypeImage = VolumeType("images")
+
+// VolumeTypeVM represents a virtual-machine storage volume.
+const VolumeTypeVM = VolumeType("virtual-machines")
+
+// driver is the extended internal interface
+type driver interface {
+	Driver
+
+	create(dbPool *api.StoragePool) error
+}
+
+// Driver repreents a low-level storage driver.
+type Driver interface {
+	// Internal
+	Name() string
+	Version() string
+
+	// Pool
+	Delete(op *operations.Operation) error
+	Mount() (bool, error)
+	Unmount() (bool, error)
+	GetResources() (*api.ResourcesStoragePool, error)
+
+	// Volumes
+	DeleteVolume(volType VolumeType, name string, op *operations.Operation) error
+	RenameVolume(volType VolumeType, name string, newName string, op *operations.Operation) error
+
+	// Migration
+	MigrationType() migration.MigrationFSType
+	PreservesInodes() bool
+}
diff --git a/lxd/storage/errors.go b/lxd/storage/errors.go
new file mode 100644
index 0000000000..efbe5c6c75
--- /dev/null
+++ b/lxd/storage/errors.go
@@ -0,0 +1,11 @@
+package storage
+
+import (
+	"fmt"
+)
+
+// ErrNilValue is the "Nil value provided" error
+var ErrNilValue = fmt.Errorf("Nil value provided")
+
+// ErrNotImplemented is the "Not implemented" error
+var ErrNotImplemented = fmt.Errorf("Not implemented")
diff --git a/lxd/storage/interfaces.go b/lxd/storage/interfaces.go
new file mode 100644
index 0000000000..3246bb034e
--- /dev/null
+++ b/lxd/storage/interfaces.go
@@ -0,0 +1,95 @@
+package storage
+
+import (
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/storage/drivers"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Instance represents the storage relevant subset of a LXD instance
+type Instance interface {
+	Name() string
+	Project() string
+	Type() instancetype.Type
+	Path() string
+
+	IsRunning() bool
+	Snapshots() ([]Instance, error)
+	TemplateApply(trigger string) error
+}
+
+// Pool represents a LXD storage pool
+type Pool interface {
+	// Internal
+	DaemonState() *state.State
+
+	// Pool
+	ID() int64
+	Name() string
+	Driver() drivers.Driver
+
+	GetResources() (*api.ResourcesStoragePool, error)
+	Delete(op *operations.Operation) error
+
+	Mount() (bool, error)
+	Unmount() (bool, error)
+
+	// Instances
+	CreateInstance(i Instance, op *operations.Operation) error
+	CreateInstanceFromBackup(i Instance, sourcePath string, op *operations.Operation) error
+	CreateInstanceFromCopy(i Instance, src Instance, snapshots bool, op *operations.Operation) error
+	CreateInstanceFromImage(i Instance, fingerprint string, op *operations.Operation) error
+	CreateInstanceFromMigration(i Instance, conn *websocket.Conn, args migration.SinkArgs, op *operations.Operation) error
+	RenameInstance(i Instance, newName string, op *operations.Operation) error
+	DeleteInstance(i Instance, op *operations.Operation) error
+
+	MigrateInstance(i Instance, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error)
+	RefreshInstance(i Instance, src Instance, snapshots bool, op *operations.Operation) error
+	BackupInstance(i Instance, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error
+
+	GetInstanceUsage(i Instance) (uint64, error)
+	SetInstanceQuota(i Instance, quota uint64) error
+
+	MountInstance(i Instance) (bool, error)
+	UnmountInstance(i Instance) (bool, error)
+	GetInstanceDisk(i Instance) (string, string, error)
+
+	// Instance snapshots
+	CreateInstanceSnapshot(i Instance, name string, op *operations.Operation) error
+	RenameInstanceSnapshot(i Instance, newName string, op *operations.Operation) error
+	DeleteInstanceSnapshot(i Instance, op *operations.Operation) error
+
+	RestoreInstanceSnapshot(i Instance, op *operations.Operation) error
+
+	MountInstanceSnapshot(i Instance) (bool, error)
+	UnmountInstanceSnapshot(i Instance) (bool, error)
+
+	// Images
+	CreateImage(img api.Image, op *operations.Operation) error
+	DeleteImage(img api.Image, op *operations.Operation) error
+
+	// Custom volumes
+	CreateCustomVolume(vol api.StorageVolume, op *operations.Operation) error
+	CreateCustomVolumeFromCopy(vol api.StorageVolume, src api.StorageVolume, snapshots bool, op *operations.Operation) error
+	CreateCustomVolumeFromMigration(vol api.StorageVolume, conn *websocket.Conn, args migration.SinkArgs, op *operations.Operation) error
+	RenameCustomVolume(vol api.StorageVolume, newName string, op *operations.Operation) error
+	DeleteCustomVolume(vol api.StorageVolume, op *operations.Operation) error
+
+	MigrateCustomVolume(vol api.StorageVolume, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error)
+
+	GetCustomVolumeUsage(vol api.StorageVolume) (uint64, error)
+	SetCustomVolumeQuota(vol api.StorageVolume, quota uint64) error
+
+	MountCustomVolume(vol api.StorageVolume) (bool, error)
+	UnmountCustomVolume(vol api.StorageVolume) (bool, error)
+
+	// Custom volume snapshots
+	CreateCustomVolumeSnapshot(vol api.StorageVolume, name string, op *operations.Operation) error
+	RenameCustomVolumeSnapshot(vol api.StorageVolume, newName string, op *operations.Operation) error
+	DeleteCustomVolumeSnapshot(vol api.StorageVolume, op *operations.Operation) error
+}
diff --git a/lxd/storage/load.go b/lxd/storage/load.go
new file mode 100644
index 0000000000..e88131c585
--- /dev/null
+++ b/lxd/storage/load.go
@@ -0,0 +1,85 @@
+package storage
+
+import (
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/storage/drivers"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// MockBackend controls whether to run the storage logic in mock mode.
+var MockBackend = false
+
+// CreatePool creates a new storage pool on disk and returns a Pool interface.
+func CreatePool(state *state.State, id int64, dbPool *api.StoragePool, op *operations.Operation) (Pool, error) {
+	// Sanity checks
+	if dbPool == nil {
+		return nil, ErrNilValue
+	}
+
+	// Ensure a config map exists
+	if dbPool.Config == nil {
+		dbPool.Config = map[string]string{}
+	}
+
+	// Handle mock requests
+	if MockBackend {
+		pool := mockBackend{}
+		pool.name = dbPool.Name
+		pool.state = state
+		return &pool, nil
+	}
+
+	// Setup the pool struct
+	pool := lxdBackend{}
+	pool.id = id
+	pool.name = dbPool.Name
+	pool.state = state
+
+	// Create the pool itself (also responsible for setting driver)
+	err := pool.create(dbPool, op)
+	if err != nil {
+		return nil, err
+	}
+
+	return &pool, nil
+}
+
+// GetPoolByName retrieves the pool from the database by its name and returns a Pool interface.
+func GetPoolByName(state *state.State, name string) (Pool, error) {
+	// Handle mock requests
+	if MockBackend {
+		pool := mockBackend{}
+		pool.name = name
+		pool.state = state
+		return &pool, nil
+	}
+
+	// Load the database record
+	id, dbPool, err := state.Cluster.StoragePoolGet(name)
+	if err != nil {
+		return nil, err
+	}
+
+	// Ensure a config map exists
+	if dbPool.Config == nil {
+		dbPool.Config = map[string]string{}
+	}
+
+	// Load the storage driver
+	path := shared.VarPath("storage-pools", name)
+	driver, err := drivers.Load(dbPool.Driver, dbPool.Name, path, dbPool.Config)
+	if err != nil {
+		return nil, err
+	}
+
+	// Setup the pool struct
+	pool := lxdBackend{}
+	pool.driver = driver
+	pool.id = id
+	pool.name = dbPool.Name
+	pool.state = state
+
+	return &pool, nil
+}

From ecd05b420108f8a68047a8c7d30ffd4eab27d733 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 6 Oct 2019 22:44:13 -0400
Subject: [PATCH 2/5] lxd/storage/drivers: Add common functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage/drivers/utils.go | 143 +++++++++++++++++++++++++++++++++++
 1 file changed, 143 insertions(+)
 create mode 100644 lxd/storage/drivers/utils.go

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
new file mode 100644
index 0000000000..cd54b45d49
--- /dev/null
+++ b/lxd/storage/drivers/utils.go
@@ -0,0 +1,143 @@
+package drivers
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"time"
+
+	"golang.org/x/sys/unix"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+func wipeDirectory(path string) error {
+	// List all entries
+	entries, err := ioutil.ReadDir(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil
+		}
+	}
+
+	// Individually wipe all entries
+	for _, entry := range entries {
+		entryPath := filepath.Join(path, entry.Name())
+		err := os.RemoveAll(entryPath)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func forceUnmount(path string) (bool, error) {
+	unmounted := false
+
+	for {
+		// Check if already unmounted
+		if !shared.IsMountPoint(path) {
+			return unmounted, nil
+		}
+
+		// Try a clean unmount first
+		err := unix.Unmount(path, 0)
+		if err != nil {
+			// Fallback to lazy unmounting
+			err = unix.Unmount(path, unix.MNT_DETACH)
+			if err != nil {
+				return false, err
+			}
+		}
+
+		unmounted = true
+	}
+
+	return unmounted, nil
+}
+
+func sameMount(srcPath string, dstPath string) bool {
+	// Get the source vfs path information
+	var srcFsStat unix.Statfs_t
+	err := unix.Statfs(srcPath, &srcFsStat)
+	if err != nil {
+		return false
+	}
+
+	// Get the destination vfs path information
+	var dstFsStat unix.Statfs_t
+	err = unix.Statfs(dstPath, &dstFsStat)
+	if err != nil {
+		return false
+	}
+
+	// Compare statfs
+	if srcFsStat.Type != dstFsStat.Type || srcFsStat.Fsid != dstFsStat.Fsid {
+		return false
+	}
+
+	// Get the source path information
+	var srcStat unix.Stat_t
+	err = unix.Stat(srcPath, &srcStat)
+	if err != nil {
+		return false
+	}
+
+	// Get the destination path information
+	var dstStat unix.Stat_t
+	err = unix.Stat(dstPath, &dstStat)
+	if err != nil {
+		return false
+	}
+
+	// Compare inode
+	if srcStat.Ino != dstStat.Ino {
+		return false
+	}
+
+	return true
+}
+
+func tryMount(src string, dst string, fs string, flags uintptr, options string) error {
+	var err error
+
+	// Attempt 20 mounts over 10s
+	for i := 0; i < 20; i++ {
+		err = unix.Mount(src, dst, fs, flags, options)
+		if err == nil {
+			break
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func vfsResources(path string) (*api.ResourcesStoragePool, error) {
+	// Get the VFS information
+	st, err := shared.Statvfs(path)
+	if err != nil {
+		return nil, err
+	}
+
+	// Fill in the struct
+	res := api.ResourcesStoragePool{}
+	res.Space.Total = st.Blocks * uint64(st.Bsize)
+	res.Space.Used = (st.Blocks - st.Bfree) * uint64(st.Bsize)
+
+	// Some filesystems don't report inodes since they allocate them
+	// dynamically e.g. btrfs.
+	if st.Files > 0 {
+		res.Inodes.Total = st.Files
+		res.Inodes.Used = st.Files - st.Ffree
+	}
+
+	return &res, nil
+}

From 14663cb5b98264bd5131a6e4a5ad1d37d382f28b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 6 Oct 2019 20:42:09 -0400
Subject: [PATCH 3/5] lxd/storage/drivers: Add dir backend
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage/drivers/driver.go     |   6 +-
 lxd/storage/drivers/driver_dir.go | 119 ++++++++++++++++++++++++++++++
 2 files changed, 124 insertions(+), 1 deletion(-)
 create mode 100644 lxd/storage/drivers/driver_dir.go

diff --git a/lxd/storage/drivers/driver.go b/lxd/storage/drivers/driver.go
index ff55d6e250..f9360c127d 100644
--- a/lxd/storage/drivers/driver.go
+++ b/lxd/storage/drivers/driver.go
@@ -5,7 +5,11 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-var drivers = map[string]func(name string, path string, config map[string]string) driver{}
+var drivers = map[string]func(name string, path string, config map[string]string) driver{
+	"dir": func(name string, path string, config map[string]string) driver {
+		return &dir{name: name, path: path, config: config}
+	},
+}
 
 // Create performs the initial validation and alteration of the configuration and creates the low-level storage pool, returning a Driver.
 func Create(dbPool *api.StoragePool) (Driver, error) {
diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go
new file mode 100644
index 0000000000..368b6daea8
--- /dev/null
+++ b/lxd/storage/drivers/driver_dir.go
@@ -0,0 +1,119 @@
+package drivers
+
+import (
+	"fmt"
+
+	"golang.org/x/sys/unix"
+
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+type dir struct {
+	name   string
+	path   string
+	config map[string]string
+}
+
+// Functions used by the loader
+func (d *dir) create(dbPool *api.StoragePool) error {
+	// WARNING: The create() function cannot rely on any of the struct attributes being set
+
+	// Set default source if missing
+	if dbPool.Config["source"] == "" {
+		dbPool.Config["source"] = shared.VarPath("storage-pools", dbPool.Name)
+	}
+
+	if !shared.PathExists(dbPool.Config["source"]) {
+		return fmt.Errorf("Source path '%s' doesn't exist", dbPool.Config["source"])
+	}
+
+	// Check that the path is currently empty
+	isEmpty, err := shared.PathIsEmpty(dbPool.Config["source"])
+	if err != nil {
+		return err
+	}
+
+	if !isEmpty {
+		return fmt.Errorf("Source path '%s' isn't empty", dbPool.Config["source"])
+	}
+
+	return nil
+}
+
+func (d *dir) Name() string {
+	return "dir"
+}
+
+func (d *dir) Version() string {
+	return "1"
+}
+
+func (d *dir) Delete(op *operations.Operation) error {
+	// On delete, wipe everything in the directory
+	err := wipeDirectory(d.path)
+	if err != nil {
+		return err
+	}
+
+	// Unmount the path
+	_, err = d.Unmount()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (d *dir) Mount() (bool, error) {
+	// Check if we're dealing with an external mount
+	if d.config["source"] == d.path {
+		return false, nil
+	}
+
+	// Check if already mounted
+	if sameMount(d.config["source"], d.path) {
+		return false, nil
+	}
+
+	// Setup the bind-mount
+	err := tryMount(d.config["source"], d.path, "none", unix.MS_BIND, "")
+	if err != nil {
+		return false, err
+	}
+
+	return true, nil
+}
+
+func (d *dir) Unmount() (bool, error) {
+	// Check if we're dealing with an external mount
+	if d.config["source"] == d.path {
+		return false, nil
+	}
+
+	// Unmount until nothing is left mounted
+	return forceUnmount(d.path)
+}
+
+func (d *dir) GetResources() (*api.ResourcesStoragePool, error) {
+	// Use the generic VFS resources
+	return vfsResources(d.path)
+}
+
+func (d *dir) DeleteVolume(volType VolumeType, name string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (d *dir) RenameVolume(volType VolumeType, name string, newName string, op *operations.Operation) error {
+	return ErrNotImplemented
+}
+
+func (d *dir) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_RSYNC
+}
+
+func (d *dir) PreservesInodes() bool {
+	return false
+}

From 71838908e00e46fc04ba4e3667af8dcfb3d61267 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 6 Oct 2019 20:56:17 -0400
Subject: [PATCH 4/5] lxd/storage: Add common helpers to utils
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage/utils.go | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index d23d4f4171..e04c4fe345 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -3,6 +3,7 @@ package storage
 import (
 	"fmt"
 	"os"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -14,6 +15,27 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
+var baseDirectories = []string{
+	"containers",
+	"containers-snapshots",
+	"custom",
+	"custom-snapshots",
+	"images",
+	"virtual-machines",
+	"virtual-machines-snapshots",
+}
+
+func createStorageStructure(path string) error {
+	for _, name := range baseDirectories {
+		err := os.MkdirAll(filepath.Join(path, name), 0711)
+		if err != nil && !os.IsExist(err) {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // MkfsOptions represents options for filesystem creation.
 type MkfsOptions struct {
 	Label string

From 4ffb702c5ec72092e9c554bfeb81ccefd9708f29 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 6 Oct 2019 21:04:14 -0400
Subject: [PATCH 5/5] lxd/storage: Implement lxd backend
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage/backend_lxd.go | 66 ++++++++++++++++++++++++++++++++++----
 1 file changed, 60 insertions(+), 6 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 730be6615c..27d73f3301 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -36,24 +36,78 @@ func (b *lxdBackend) Driver() drivers.Driver {
 	return b.driver
 }
 
-func (b *lxdBackend) create(dbPool api.StoragePool, op *operations.Operation) error {
-	return ErrNotImplemented
+func (b *lxdBackend) create(dbPool *api.StoragePool, op *operations.Operation) error {
+	revertPath := true
+
+	// Create the storage path
+	path := shared.VarPath("storage-pools", b.name)
+	err := os.MkdirAll(path, 0711)
+	if err != nil && !os.IsExist(err) {
+		return err
+	}
+	defer func() {
+		if !revertPath {
+			return
+		}
+
+		os.RemoveAll(path)
+	}()
+
+	// Create the low-level storage pool
+	driver, err := drivers.Create(dbPool)
+	if err != nil {
+		return err
+	}
+
+	// Mount the storage pool
+	ourMount, err := driver.Mount()
+	if err != nil {
+		return err
+	}
+	if ourMount {
+		defer driver.Unmount()
+	}
+
+	// Create the directory structure
+	err = createStorageStructure(path)
+	if err != nil {
+		return err
+	}
+
+	// Set the driver
+	b.driver = driver
+	revertPath = false
+
+	return nil
 }
 
 func (b *lxdBackend) GetResources() (*api.ResourcesStoragePool, error) {
-	return nil, ErrNotImplemented
+	return b.driver.GetResources()
 }
 
 func (b *lxdBackend) Delete(op *operations.Operation) error {
-	return ErrNotImplemented
+	// Delete the low-level storage
+	err := b.driver.Delete(op)
+	if err != nil {
+		return err
+	}
+
+	// Delete the mountpoint
+	path := shared.VarPath("storage-pools", b.name)
+	err = os.Remove(path)
+	if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 func (b *lxdBackend) Mount() (bool, error) {
-	return true, ErrNotImplemented
+	return b.driver.Mount()
 }
 
 func (b *lxdBackend) Unmount() (bool, error) {
-	return true, ErrNotImplemented
+	return b.driver.Unmount()
 }
 
 func (b *lxdBackend) CreateInstance(i Instance, op *operations.Operation) error {


More information about the lxc-devel mailing list