The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6678
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) === Includes https://github.com/lxc/lxd/pull/6675 This PR adds a new function to the storage pool interface: `BackupInstanceConfig()` that will ultimately replace the existing `instance.WriteBackupYaml()` function. The reason for this is that writing the backup config yaml file is inherently dependent on the storage layer and for drivers where the volume needs to be mounted, ensuring that the volume was mounted in all occurrences that the backup yaml file needed writing was becoming error prone to track. Instead this moves the yaml generation and writing into the backendLXD, where it can directly mount the volume if needed. The remaining changes then expose a new function on `Instance` called `BackupConfig()` that then selectively uses the new storage function or the legacy one based on the storage driver available.
From ef680d12388f78b31ac6e2ea4647bdaf879265a6 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 6 Jan 2020 16:20:26 +0000 Subject: [PATCH 01/20] lxd/storage/drivers/driver/dir/volumes: Use SetVolumeQuota from UpdateVolume Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/driver_dir_volumes.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lxd/storage/drivers/driver_dir_volumes.go b/lxd/storage/drivers/driver_dir_volumes.go index 0c0e850539..67df0ffe44 100644 --- a/lxd/storage/drivers/driver_dir_volumes.go +++ b/lxd/storage/drivers/driver_dir_volumes.go @@ -197,13 +197,7 @@ func (d *dir) UpdateVolume(vol Volume, changedConfig map[string]string) error { } if _, changed := changedConfig["size"]; changed { - volID, err := d.getVolID(vol.volType, vol.name) - if err != nil { - return err - } - - // Set the quota if specified in volConfig or pool config. - err = d.setQuota(vol.MountPath(), volID, changedConfig["size"]) + err := d.SetVolumeQuota(vol, changedConfig["size"], nil) if err != nil { return err } From e64114a6bd020619c4649ef93b79cc529111e0f0 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 09:20:51 +0000 Subject: [PATCH 02/20] lxd/storage/backend/lxd: Makes specific lock name for volume EnsureImage action Avoids deadlock with MountTask lock during same process. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_lxd.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index e4219293da..82c102be5c 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -1784,9 +1784,10 @@ func (b *lxdBackend) EnsureImage(fingerprint string, op *operations.Operation) e return nil // Nothing to do for drivers that don't support optimized images volumes. } - // We need to lock this operation to ensure that the image is not being - // created multiple times. - unlock := locking.Lock(b.name, string(drivers.VolumeTypeImage), fingerprint) + // We need to lock this operation to ensure that the image is not being created multiple times. + // Uses a lock name of "EnsureImage_<fingerprint>" to avoid deadlocking with CreateVolume below that also + // establishes a lock on the volume type & name if it needs to mount the volume before filling. + unlock := locking.Lock(b.name, string(drivers.VolumeTypeImage), fmt.Sprintf("EnsureImage_%v", fingerprint)) defer unlock() // There's no need to pass the content type or config. Both are not needed From e0018c7aa106cc3233cd0615ce1284753155ced1 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 09:21:41 +0000 Subject: [PATCH 03/20] lxd/storage/drivers/volume: Adds UnmountTask function Allows a volume to be temporarily unmounted (if mounted) and a function be run before volume then being re-mounted (if previously mounted). Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/volume.go | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lxd/storage/drivers/volume.go b/lxd/storage/drivers/volume.go index b7ec773dff..fcba388445 100644 --- a/lxd/storage/drivers/volume.go +++ b/lxd/storage/drivers/volume.go @@ -183,6 +183,54 @@ func (v Volume) MountTask(task func(mountPath string, op *operations.Operation) return task(v.MountPath(), op) } +// UnmountTask runs the supplied task after unmounting the volume if needed. If the volume was unmounted +// for this then it is mounted when the task finishes. +func (v Volume) UnmountTask(task func(op *operations.Operation) error, op *operations.Operation) error { + isSnap := v.IsSnapshot() + + // If the volume is a snapshot then call the snapshot specific mount/unmount functions as + // these will mount the snapshot read only. + if isSnap { + unlock := locking.Lock(v.pool, string(v.volType), v.name) + + ourUnmount, err := v.driver.UnmountVolumeSnapshot(v, op) + if err != nil { + unlock() + return err + } + + unlock() + + if ourUnmount { + defer func() { + unlock := locking.Lock(v.pool, string(v.volType), v.name) + v.driver.MountVolumeSnapshot(v, op) + unlock() + }() + } + } else { + unlock := locking.Lock(v.pool, string(v.volType), v.name) + + ourUnmount, err := v.driver.UnmountVolume(v, op) + if err != nil { + unlock() + return err + } + + unlock() + + if ourUnmount { + defer func() { + unlock := locking.Lock(v.pool, string(v.volType), v.name) + v.driver.MountVolume(v, op) + unlock() + }() + } + } + + return task(op) +} + // Snapshots returns a list of snapshots for the volume. func (v Volume) Snapshots(op *operations.Operation) ([]Volume, error) { if v.IsSnapshot() { From 431aee92688a5745f34917113b5661689e14bec1 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 09:22:32 +0000 Subject: [PATCH 04/20] lxd/storage/drivers/utils: Adds volume filesystem shrink and grow functions Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/utils.go | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go index 768aab8b69..07a8b32268 100644 --- a/lxd/storage/drivers/utils.go +++ b/lxd/storage/drivers/utils.go @@ -10,6 +10,7 @@ import ( "golang.org/x/sys/unix" + "github.com/lxc/lxd/lxd/operations" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/units" ) @@ -401,3 +402,65 @@ func resolveMountOptions(options string) (uintptr, string) { return mountFlags, strings.Join(tmp, ",") } + +// shrinkFileSystem shrinks a filesystem if it is supported. Ext4 volumes will be unmounted temporarily if needed. +func shrinkFileSystem(fsType string, devPath string, vol Volume, byteSize int64) error { + strSize := fmt.Sprintf("%dK", byteSize/1024) + + switch fsType { + case "": // if not specified, default to ext4. + fallthrough + case "xfs": + return fmt.Errorf(`Shrinking not supported for filesystem type "%s". A dump, mkfs, and restore are required`, fsType) + case "ext4": + return vol.UnmountTask(func(op *operations.Operation) error { + _, err := shared.TryRunCommand("e2fsck", "-f", "-y", devPath) + if err != nil { + return err + } + + _, err = shared.TryRunCommand("resize2fs", devPath, strSize) + if err != nil { + return err + } + + return nil + }, nil) + case "btrfs": + _, err := shared.TryRunCommand("btrfs", "filesystem", "resize", strSize, vol.MountPath()) + if err != nil { + return err + } + default: + return fmt.Errorf(`Shrinking not supported for filesystem type "%s"`, fsType) + } + + return nil +} + +// growFileSystem grows a filesystem if it is supported. The volume will be mounted temporarily if needed. +func growFileSystem(fsType string, devPath string, vol Volume) error { + return vol.MountTask(func(mountPath string, op *operations.Operation) error { + var msg string + var err error + switch fsType { + case "": // if not specified, default to ext4 + fallthrough + case "ext4": + msg, err = shared.TryRunCommand("resize2fs", devPath) + case "xfs": + msg, err = shared.TryRunCommand("xfs_growfs", devPath) + case "btrfs": + msg, err = shared.TryRunCommand("btrfs", "filesystem", "resize", "max", mountPath) + default: + return fmt.Errorf(`Growing not supported for filesystem type "%s"`, fsType) + } + + if err != nil { + errorMsg := fmt.Sprintf(`Could not extend underlying %s filesystem for "%s": %s`, fsType, devPath, msg) + return fmt.Errorf(errorMsg) + } + + return nil + }, nil) +} From 8ee162b99649c873a9131caf2d1b234b867da645 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 15:56:27 +0000 Subject: [PATCH 05/20] lxd/storage/drivers/errors: Adds "not supported" error type Can be used when a driver doesn't support a specific feature. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/errors.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lxd/storage/drivers/errors.go b/lxd/storage/drivers/errors.go index 56d1bdab3c..e9fb157bc6 100644 --- a/lxd/storage/drivers/errors.go +++ b/lxd/storage/drivers/errors.go @@ -9,3 +9,6 @@ var ErrNotImplemented = fmt.Errorf("Not implemented") // ErrUnknownDriver is the "Unknown driver" error var ErrUnknownDriver = fmt.Errorf("Unknown driver") + +// ErrNotSupported is the "Not supported" error +var ErrNotSupported = fmt.Errorf("Not supported") From f5763456414eccfb8488374f38d99cff3773cbaf Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 15:55:52 +0000 Subject: [PATCH 06/20] lxd/container/lxc: Detects storage drivers that dont support volume usage stats Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container_lxc.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index 7eede32fcd..e1058c6595 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -5811,7 +5811,9 @@ func (c *containerLXC) diskState() map[string]api.InstanceStateDisk { usage, err = pool.GetInstanceUsage(c) if err != nil { - logger.Error("Error getting disk usage", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err}) + if err != storageDrivers.ErrNotSupported { + logger.Error("Error getting disk usage", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err}) + } continue } } else { From 567ec03f0a49fb5ae47e22aa0548923e254d3ff1 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 16:37:49 +0000 Subject: [PATCH 07/20] lxd/storage/drivers/generic: Improves genericBackupUnpack - Mounts volumes so that this can work with non-dir based drivers. - Returns a valid post hook function that should be used to unmount the primary volume after DB records created. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/generic.go | 85 +++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/lxd/storage/drivers/generic.go b/lxd/storage/drivers/generic.go index 058456e5df..f1bac2babf 100644 --- a/lxd/storage/drivers/generic.go +++ b/lxd/storage/drivers/generic.go @@ -183,26 +183,13 @@ func genericCreateVolumeFromMigration(d Driver, initVolume func(vol Volume) (fun } // genericBackupUnpack unpacks a non-optimized backup tarball through a storage driver. +// Returns a post hook function that should be called once the database entries for the restored backup have been +// created and a revert function that can be used to undo the actions this function performs should something +// subsequently fail. func genericBackupUnpack(d Driver, vol Volume, snapshots []string, srcData io.ReadSeeker, op *operations.Operation) (func(vol Volume) error, func(), error) { revert := revert.New() defer revert.Fail() - // Define a revert function that will be used both to revert if an error occurs inside this - // function but also return it for use from the calling functions if no error internally. - revertHook := func() { - for _, snapName := range snapshots { - fullSnapshotName := GetSnapshotVolumeName(vol.name, snapName) - snapVol := NewVolume(d, d.Name(), vol.volType, vol.contentType, fullSnapshotName, vol.config, vol.poolConfig) - d.DeleteVolumeSnapshot(snapVol, op) - } - - // And lastly the main volume. - d.DeleteVolume(vol, op) - } - - // Only execute the revert function if we have had an error internally. - revert.Add(revertHook) - // Find the compression algorithm used for backup source data. srcData.Seek(0, 0) tarArgs, _, _, err := shared.DetectCompressionFile(srcData) @@ -210,11 +197,16 @@ func genericBackupUnpack(d Driver, vol Volume, snapshots []string, srcData io.Re return nil, nil, err } - // Create the main volume. + if d.HasVolume(vol) { + return nil, nil, fmt.Errorf("Cannot restore volume, already exists on target") + } + + // Create new empty volume. err = d.CreateVolume(vol, nil, nil) if err != nil { return nil, nil, err } + revert.Add(func() { d.DeleteVolume(vol, op) }) if len(snapshots) > 0 { // Create new snapshots directory. @@ -225,28 +217,56 @@ func genericBackupUnpack(d Driver, vol Volume, snapshots []string, srcData io.Re } for _, snapName := range snapshots { - // Prepare tar arguments. - args := append(tarArgs, []string{ - "-", - "--recursive-unlink", - "--xattrs-include=*", - "--strip-components=3", - "-C", vol.MountPath(), fmt.Sprintf("backup/snapshots/%s", snapName), - }...) - - // Extract snapshots. - srcData.Seek(0, 0) - err = shared.RunCommandWithFds(srcData, nil, "tar", args...) + err = vol.MountTask(func(mountPath string, op *operations.Operation) error { + // Prepare tar arguments. + args := append(tarArgs, []string{ + "-", + "--recursive-unlink", + "--xattrs-include=*", + "--strip-components=3", + "-C", mountPath, fmt.Sprintf("backup/snapshots/%s", snapName), + }...) + + // Extract snapshot. + srcData.Seek(0, 0) + err = shared.RunCommandWithFds(srcData, nil, "tar", args...) + if err != nil { + return err + } + + return nil + }, op) + if err != nil { + return nil, nil, err + } + + snapVol, err := vol.NewSnapshot(snapName) if err != nil { return nil, nil, err } - fullSnapshotName := GetSnapshotVolumeName(vol.name, snapName) - snapVol := NewVolume(d, d.Name(), vol.volType, vol.contentType, fullSnapshotName, vol.config, vol.poolConfig) err = d.CreateVolumeSnapshot(snapVol, op) if err != nil { return nil, nil, err } + revert.Add(func() { d.DeleteVolumeSnapshot(snapVol, op) }) + } + + // Mount main volume and leave mounted (as is needed during backup.yaml generation during latter parts of + // the backup restoration process). + ourMount, err := d.MountVolume(vol, op) + if err != nil { + return nil, nil, err + } + + // Create a post hook function that will be called at the end of the backup restore process to unmount + // the volume if needed. + postHook := func(vol Volume) error { + if ourMount { + d.UnmountVolume(vol, op) + } + + return nil } // Prepare tar extraction arguments. @@ -265,6 +285,7 @@ func genericBackupUnpack(d Driver, vol Volume, snapshots []string, srcData io.Re return nil, nil, err } + revertExternal := revert.Clone() // Clone before calling revert.Success() so we can return the Fail func. revert.Success() - return nil, revertHook, nil + return postHook, revertExternal.Fail, nil } From f9e793cb1ab0d89ff59dba87a0a3311fb6ee335c Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 17:47:06 +0000 Subject: [PATCH 08/20] lxd/revert: Adds Clone function to revert Can be used to copy a set of revert steps before calling Success() so that the Fail() function on the cloned revert can be returned as an external revert function. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/revert/revert.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lxd/revert/revert.go b/lxd/revert/revert.go index df4a3b2578..dbe65b7edf 100644 --- a/lxd/revert/revert.go +++ b/lxd/revert/revert.go @@ -31,3 +31,17 @@ func (r *Reverter) Fail() { func (r *Reverter) Success() { r.revertFuncs = nil } + +// Clone returns a copy of the reverter with the current set of revert functions added. +// This can be used if you want to return a reverting function to an external caller but do not want to actually +// execute the previously deferred reverter.Fail() function. +func (r *Reverter) Clone() *Reverter { + rNew := New() + rNew.revertFuncs = make([]func(), 0, len(r.revertFuncs)) + + for _, f := range r.revertFuncs { + rNew.revertFuncs = append(rNew.revertFuncs, f) + } + + return rNew +} From c9fce06103b7319ca1f168e73983876b74075c95 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 7 Jan 2020 17:44:42 +0000 Subject: [PATCH 09/20] lxd/storage/drivers/utils: Comments on wipeDirectory Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/utils.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go index 07a8b32268..91c5e939e4 100644 --- a/lxd/storage/drivers/utils.go +++ b/lxd/storage/drivers/utils.go @@ -15,8 +15,9 @@ import ( "github.com/lxc/lxd/shared/units" ) +// wipeDirectory empties the contents of a directory, but leaves it in place. func wipeDirectory(path string) error { - // List all entries + // List all entries. entries, err := ioutil.ReadDir(path) if err != nil { if os.IsNotExist(err) { @@ -24,7 +25,7 @@ func wipeDirectory(path string) error { } } - // Individually wipe all entries + // Individually wipe all entries. for _, entry := range entries { entryPath := filepath.Join(path, entry.Name()) err := os.RemoveAll(entryPath) From 6b5fd5bd48712904ced95699e9b4c3acda805923 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 10:18:05 +0000 Subject: [PATCH 10/20] lxd/containers/post: Improves comment in createFromBackup Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/containers_post.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/containers_post.go b/lxd/containers_post.go index 1ba3baef48..f45d796c4f 100644 --- a/lxd/containers_post.go +++ b/lxd/containers_post.go @@ -724,8 +724,8 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re return errors.Wrap(err, "Load instance") } - // Run the storage post hook to perform any final actions now that the instance - // has been created in the database. + // Run the storage post hook to perform any final actions now that the instance has been created + // in the database (this normally includes unmounting volumes that were mounted). if postHook != nil { err = postHook(c) if err != nil { From 7361ab9944f6f3e4af4c27a9649f3a73aedb70ca Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:39:12 +0000 Subject: [PATCH 11/20] lxd/storage/pool/interface: Adds BackupInstanceConfig Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/pool_interface.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lxd/storage/pool_interface.go b/lxd/storage/pool_interface.go index 6e4dd65a62..506a381352 100644 --- a/lxd/storage/pool_interface.go +++ b/lxd/storage/pool_interface.go @@ -34,6 +34,7 @@ type Pool interface { RenameInstance(inst instance.Instance, newName string, op *operations.Operation) error DeleteInstance(inst instance.Instance, op *operations.Operation) error UpdateInstance(inst instance.Instance, newDesc string, newConfig map[string]string, op *operations.Operation) error + BackupInstanceConfig(inst instance.Instance, op *operations.Operation) error MigrateInstance(inst instance.Instance, conn io.ReadWriteCloser, args migration.VolumeSourceArgs, op *operations.Operation) error RefreshInstance(inst instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, op *operations.Operation) error From 29bbe2f304ee505af3572f1b5e835cf784e251a6 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:39:35 +0000 Subject: [PATCH 12/20] lxd/storage/backend/mock: Adds BackupInstanceConfig Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_mock.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go index 43a9becec2..dac18c53a6 100644 --- a/lxd/storage/backend_mock.go +++ b/lxd/storage/backend_mock.go @@ -92,6 +92,10 @@ func (b *mockBackend) UpdateInstance(inst instance.Instance, newDesc string, new return nil } +func (b *mockBackend) BackupInstanceConfig(inst instance.Instance, op *operations.Operation) error { + return nil +} + func (b *mockBackend) MigrateInstance(inst instance.Instance, conn io.ReadWriteCloser, args migration.VolumeSourceArgs, op *operations.Operation) error { return nil } From 0df01f841ec56b7b7680118abb7b613da10875b2 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:40:37 +0000 Subject: [PATCH 13/20] lxd/storage/backend/lxd: Adds error checking to MountTask in CreateInstanceFromBackup Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_lxd.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 82c102be5c..aa2e4fe1f8 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -470,9 +470,12 @@ func (b *lxdBackend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io. } // Update pool information in the backup.yaml file. - vol.MountTask(func(mountPath string, op *operations.Operation) error { + err = vol.MountTask(func(mountPath string, op *operations.Operation) error { return backup.UpdateInstanceConfigStoragePool(b.state.Cluster, srcBackup, mountPath) }, op) + if err != nil { + return nil, nil, err + } var postHook func(instance.Instance) error if volPostHook != nil { From 99baeafc55454e497d61ca15c45e55d412736668 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:41:00 +0000 Subject: [PATCH 14/20] lxd/storage/backend/lxd: Implements BackupInstanceConfig Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_lxd.go | 99 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index aa2e4fe1f8..b27d4a82e4 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -8,6 +8,9 @@ import ( "regexp" "strings" + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" + "github.com/lxc/lxd/lxd/backup" "github.com/lxc/lxd/lxd/db" "github.com/lxc/lxd/lxd/instance" @@ -2641,3 +2644,99 @@ func (b *lxdBackend) createStorageStructure(path string) error { return nil } + +// BackupInstanceConfig writes the instance's config to the backup.yaml file on the storage device. +func (b *lxdBackend) BackupInstanceConfig(inst instance.Instance, op *operations.Operation) error { + logger := logging.AddContext(b.logger, log.Ctx{"project": inst.Project(), "instance": inst.Name()}) + logger.Debug("BackupInstanceConfig started") + defer logger.Debug("BackupInstanceConfig finished") + + // We only write backup files out for actual instances. + if inst.IsSnapshot() { + return nil + } + + // Immediately return if the instance directory doesn't exist yet. + if !shared.PathExists(inst.Path()) { + return os.ErrNotExist + } + + // Generate the YAML. + ci, _, err := inst.Render() + if err != nil { + return errors.Wrap(err, "Failed to render instance metadata") + } + + snapshots, err := inst.Snapshots() + if err != nil { + return errors.Wrap(err, "Failed to get snapshots") + } + + var sis []*api.InstanceSnapshot + + for _, s := range snapshots { + si, _, err := s.Render() + if err != nil { + return err + } + + sis = append(sis, si.(*api.InstanceSnapshot)) + } + + volType, err := InstanceTypeToVolumeType(inst.Type()) + if err != nil { + return err + } + + volDBType, err := VolumeTypeToDBType(volType) + if err != nil { + return err + } + + contentType := InstanceContentType(inst) + + _, volume, err := b.state.Cluster.StoragePoolNodeVolumeGetTypeByProject(inst.Project(), inst.Name(), volDBType, b.ID()) + if err != nil { + return err + } + + data, err := yaml.Marshal(&backup.InstanceConfig{ + Container: ci.(*api.Instance), + Snapshots: sis, + Pool: &b.db, + Volume: volume, + }) + if err != nil { + return err + } + + // Get the volume name on storage. + volStorageName := project.Prefix(inst.Project(), inst.Name()) + + // We don't need to use the volume's config for mounting so set to nil. + vol := b.newVolume(volType, contentType, volStorageName, nil) + + // Update pool information in the backup.yaml file. + err = vol.MountTask(func(mountPath string, op *operations.Operation) error { + // Write the YAML + f, err := os.Create(filepath.Join(inst.Path(), "backup.yaml")) + if err != nil { + return err + } + defer f.Close() + + err = f.Chmod(0400) + if err != nil { + return err + } + + err = shared.WriteAll(f, data) + if err != nil { + return err + } + + return nil + }, op) + + return err +} From e10028d0109cb6aebbb7af197501a5038192868a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:41:36 +0000 Subject: [PATCH 15/20] lxd/instance/instance/interface: Adds BackupConfig Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/instance_interface.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go index 3951db18c3..b7fb17b5e3 100644 --- a/lxd/instance/instance_interface.go +++ b/lxd/instance/instance_interface.go @@ -29,6 +29,7 @@ type Instance interface { Restore(source Instance, stateful bool) error Snapshots() ([]Instance, error) Backups() ([]backup.Backup, error) + BackupConfig() error // Config handling Rename(newName string) error From af11af13c0a94c0eec4adbb3856977411f9da520 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:41:58 +0000 Subject: [PATCH 16/20] lxd/instance/qemu/vm/qemu: Implements BackupConfig Only uses new storage package. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/qemu/vm_qemu.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lxd/instance/qemu/vm_qemu.go b/lxd/instance/qemu/vm_qemu.go index b1c0536e72..dd666f61b4 100644 --- a/lxd/instance/qemu/vm_qemu.go +++ b/lxd/instance/qemu/vm_qemu.go @@ -1861,7 +1861,7 @@ func (vm *Qemu) Update(args db.InstanceArgs, userRequested bool) error { return errors.Wrap(err, "Failed to update database") } - err = instance.WriteBackupFile(vm.state, vm) + err = vm.BackupConfig() if err != nil && !os.IsNotExist(err) { return errors.Wrap(err, "Failed to write backup file") } @@ -3296,3 +3296,13 @@ func (vm *Qemu) maasUpdate(oldDevices map[string]map[string]string) error { return vm.state.MAAS.CreateContainer(project.Prefix(vm.project, vm.name), interfaces) } + +// BackupConfig writes the instance's backup.yaml file to storage. +func (vm *Qemu) BackupConfig() error { + pool, err := vm.getStoragePool() + if err != nil { + return err + } + + return pool.BackupInstanceConfig(vm, nil) +} From de857d3af6b08b2aa06e2aa73bebec419422ad33 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:42:30 +0000 Subject: [PATCH 17/20] lxd/container/lxc: Implements BackupConfig Uses new storage package where possible, otherwise fallsback to old function. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container_lxc.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index e1058c6595..d978999b2a 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -7032,3 +7032,19 @@ func (rw *lxcCgroupReadWriter) Set(version cgroup.Backend, controller string, ke return rw.cc.SetCgroupItem(key, value) } + +// BackupConfig writes the instance's backup.yaml file to storage. +func (c *containerLXC) BackupConfig() error { + // Check if we can load new storage layer for pool driver type. + pool, err := c.getStoragePool() + if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented { + if err != nil { + return err + } + + return pool.BackupInstanceConfig(c, nil) + } + + // Fallback to legacy backup function for old storage drivers. + return instance.WriteBackupFile(c.state, c) +} From 4f4dc63fb29522f4a3154ff027f82019e2263920 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:43:02 +0000 Subject: [PATCH 18/20] lxd/container: Switches to inst.BackupConfig() Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/container.go b/lxd/container.go index 8aed96c556..1ad46af469 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -730,7 +730,7 @@ func instanceCreateAsSnapshot(s *state.State, args db.InstanceArgs, sourceInstan } // Attempt to update backup.yaml for instance. - err = instance.WriteBackupFile(s, sourceInstance) + err = sourceInstance.BackupConfig() if err != nil { return nil, err } @@ -1035,7 +1035,7 @@ func instanceConfigureInternal(state *state.State, c instance.Instance) error { return fmt.Errorf("Instance type not supported") } - err = instance.WriteBackupFile(state, c) + err = c.BackupConfig() if err != nil { return err } From 239f741f02c8b8fc4f6264a78aa9f1369fa4d02b Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:43:19 +0000 Subject: [PATCH 19/20] lxd/container/lxc: Switches to inst.BackupConfig() Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container_lxc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index d978999b2a..7a83f1d99d 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -2225,7 +2225,7 @@ func (c *containerLXC) startCommon() (string, []func() error, error) { } // Update the backup.yaml file - err = instance.WriteBackupFile(c.state, c) + err = c.BackupConfig() if err != nil { if ourStart { c.unmount() @@ -3331,7 +3331,7 @@ func (c *containerLXC) Restore(sourceContainer instance.Instance, stateful bool) // The old backup file may be out of date (e.g. it doesn't have all the current snapshots of // the container listed); let's write a new one to be safe. - err = instance.WriteBackupFile(c.state, c) + err = c.BackupConfig() if err != nil { return err } @@ -4511,7 +4511,7 @@ func (c *containerLXC) Update(args db.InstanceArgs, userRequested bool) error { // Only update the backup file if it already exists (indicating the instance is mounted). if shared.PathExists(filepath.Join(c.Path(), "backup.yaml")) { - err := instance.WriteBackupFile(c.state, c) + err := c.BackupConfig() if err != nil && !os.IsNotExist(err) { return errors.Wrap(err, "Failed to write backup file") } From 22e53c105df917bf4fe2712f47c297663d93a842 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 8 Jan 2020 11:43:45 +0000 Subject: [PATCH 20/20] lxd/instance/instance/utils: Removes warning and makes critical error when yaml file creation fails Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/instance_utils.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go index 9d25e4a853..d510e2c412 100644 --- a/lxd/instance/instance_utils.go +++ b/lxd/instance/instance_utils.go @@ -451,7 +451,7 @@ func LoadByProjectAndName(s *state.State, project, name string) (Instance, error return inst, nil } -// WriteBackupFile writes instance's config to a file. +// WriteBackupFile writes instance's config to a file. Deprecated, use inst.BackupConfig(). func WriteBackupFile(state *state.State, inst Instance) error { // We only write backup files out for actual instances. if inst.IsSnapshot() { @@ -515,12 +515,6 @@ func WriteBackupFile(state *state.State, inst Instance) error { return err } - // Ensure the container is currently mounted. - if !shared.PathExists(inst.RootfsPath()) { - logger.Debug("Unable to update backup.yaml at this time", log.Ctx{"name": inst.Name(), "project": inst.Project()}) - return nil - } - // Write the YAML f, err := os.Create(filepath.Join(inst.Path(), "backup.yaml")) if err != nil {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel