The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6285
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 2d1839e328d4785bbf0940312328ffcdc1513ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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 {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel