[lxc-devel] [lxd/master] Storage Instance Backup Config

tomponline on Github lxc-bot at linuxcontainers.org
Wed Jan 8 12:06:52 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1137 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200108/38632e4c/attachment.bin>
-------------- next part --------------
From ef680d12388f78b31ac6e2ea4647bdaf879265a6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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 {


More information about the lxc-devel mailing list