[lxc-devel] [lxd/master] Storage backup import

tomponline on Github lxc-bot at linuxcontainers.org
Thu Nov 28 12:26:45 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 346 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191128/f19417f4/attachment-0001.bin>
-------------- next part --------------
From 6d9ad09ee7f3f98b1a8efd6bc290bc816f2c7433 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:25:05 +0000
Subject: [PATCH 01/25] lxd/backup/backup/instance/config: Adds instance config
 backup.yml tools

Moved from main package and cleaned up.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/backup/backup_instance_config.go | 122 +++++++++++++++++++++++++++
 1 file changed, 122 insertions(+)
 create mode 100644 lxd/backup/backup_instance_config.go

diff --git a/lxd/backup/backup_instance_config.go b/lxd/backup/backup_instance_config.go
new file mode 100644
index 0000000000..a871ad0a7e
--- /dev/null
+++ b/lxd/backup/backup_instance_config.go
@@ -0,0 +1,122 @@
+package backup
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"gopkg.in/yaml.v2"
+
+	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/project"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// InstanceConfig represents the config of an instance that can be stored in a backup.yaml file.
+type InstanceConfig struct {
+	Container *api.Instance           `yaml:"container"`
+	Snapshots []*api.InstanceSnapshot `yaml:"snapshots"`
+	Pool      *api.StoragePool        `yaml:"pool"`
+	Volume    *api.StorageVolume      `yaml:"volume"`
+}
+
+// ParseInstanceConfigYamlFile decodes the yaml file at path specified into an InstanceConfig.
+func ParseInstanceConfigYamlFile(path string) (*InstanceConfig, error) {
+	data, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, err
+	}
+
+	backup := InstanceConfig{}
+	if err := yaml.Unmarshal(data, &backup); err != nil {
+		return nil, err
+	}
+
+	return &backup, nil
+}
+
+// updateRootDevicePool updates the root disk device in the supplied list of devices to the pool
+// specified. Returns true if a root disk device has been found and updated otherwise false.
+func updateRootDevicePool(devices map[string]map[string]string, poolName string) bool {
+	if devices != nil {
+		devName, _, err := shared.GetRootDiskDevice(devices)
+		if err == nil {
+			devices[devName]["pool"] = poolName
+			return true
+		}
+	}
+
+	return false
+}
+
+// UpdateInstanceConfigStoragePool changes the pool information in the backup.yaml to the pool
+// specified in b.Pool.
+func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info) error {
+	// Load the storage pool.
+	_, pool, err := c.StoragePoolGet(b.Pool)
+	if err != nil {
+		return err
+	}
+
+	f := func(path string) error {
+		// Read in the backup.yaml file.
+		backup, err := ParseInstanceConfigYamlFile(path)
+		if err != nil {
+			return err
+		}
+
+		rootDiskDeviceFound := false
+
+		// Change the pool in the backup.yaml.
+		backup.Pool = pool
+
+		if updateRootDevicePool(backup.Container.Devices, pool.Name) {
+			rootDiskDeviceFound = true
+		}
+
+		if updateRootDevicePool(backup.Container.ExpandedDevices, pool.Name) {
+			rootDiskDeviceFound = true
+		}
+
+		for _, snapshot := range backup.Snapshots {
+			updateRootDevicePool(snapshot.Devices, pool.Name)
+			updateRootDevicePool(snapshot.ExpandedDevices, pool.Name)
+		}
+
+		if !rootDiskDeviceFound {
+			return fmt.Errorf("No root device could be found")
+		}
+
+		file, err := os.Create(path)
+		if err != nil {
+			return err
+		}
+		defer file.Close()
+
+		data, err := yaml.Marshal(&backup)
+		if err != nil {
+			return err
+		}
+
+		_, err = file.Write(data)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	err = f(shared.VarPath("storage-pools", pool.Name, "containers", project.Prefix(b.Project, b.Name), "backup.yaml"))
+	if err != nil {
+		return err
+	}
+
+	for _, snap := range b.Snapshots {
+		err = f(shared.VarPath("storage-pools", pool.Name, "containers-snapshots", project.Prefix(b.Project, b.Name), snap, "backup.yaml"))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

From 66cbcfe212c3330b4357814dea2ad9accd095699 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:26:08 +0000
Subject: [PATCH 02/25] lxd/api/internal: Removes slurpBackupFile and switches
 to backup.ParseInstanceConfigYamlFile

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/api_internal.go | 20 ++------------------
 1 file changed, 2 insertions(+), 18 deletions(-)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 957b0107b7..38ee0da172 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -4,7 +4,6 @@ import (
 	"database/sql"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -15,8 +14,8 @@ import (
 
 	"github.com/gorilla/mux"
 	"github.com/pkg/errors"
-	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/db/cluster"
 	"github.com/lxc/lxd/lxd/db/node"
@@ -390,21 +389,6 @@ func internalSQLExec(tx *sql.Tx, query string, result *internalSQLResult) error
 	return nil
 }
 
-func slurpBackupFile(path string) (*backupFile, error) {
-	data, err := ioutil.ReadFile(path)
-	if err != nil {
-		return nil, err
-	}
-
-	backup := backupFile{}
-
-	if err := yaml.Unmarshal(data, &backup); err != nil {
-		return nil, err
-	}
-
-	return &backup, nil
-}
-
 type internalImportPost struct {
 	Name  string `json:"name" yaml:"name"`
 	Force bool   `json:"force" yaml:"force"`
@@ -476,7 +460,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
 
 	// Read in the backup.yaml file.
 	backupYamlPath := filepath.Join(containerMntPoint, "backup.yaml")
-	backup, err := slurpBackupFile(backupYamlPath)
+	backup, err := backup.ParseInstanceConfigYamlFile(backupYamlPath)
 	if err != nil {
 		return response.SmartError(err)
 	}

From 036decee073c92bef4ae056d77cc65bd5b510fe9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:26:38 +0000
Subject: [PATCH 03/25] lxd/backup: Removes backupFixStoragePool

Replaced by backup.UpdateInstanceConfigStoragePool

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/backup.go | 94 ---------------------------------------------------
 1 file changed, 94 deletions(-)

diff --git a/lxd/backup.go b/lxd/backup.go
index be3b24511b..15189bffd7 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -101,100 +101,6 @@ func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instanc
 	return nil
 }
 
-// fixBackupStoragePool changes the pool information in the backup.yaml. This
-// is done only if the provided pool doesn't exist. In this case, the pool of
-// the default profile will be used.
-func backupFixStoragePool(c *db.Cluster, b backup.Info, useDefaultPool bool) error {
-	var poolName string
-
-	if useDefaultPool {
-		// Get the default profile
-		_, profile, err := c.ProfileGet("default", "default")
-		if err != nil {
-			return err
-		}
-
-		_, v, err := shared.GetRootDiskDevice(profile.Devices)
-		if err != nil {
-			return err
-		}
-
-		poolName = v["pool"]
-	} else {
-		poolName = b.Pool
-	}
-
-	// Get the default's profile pool
-	_, pool, err := c.StoragePoolGet(poolName)
-	if err != nil {
-		return err
-	}
-
-	f := func(path string) error {
-		// Read in the backup.yaml file.
-		backup, err := slurpBackupFile(path)
-		if err != nil {
-			return err
-		}
-
-		rootDiskDeviceFound := false
-
-		// Change the pool in the backup.yaml
-		backup.Pool = pool
-		if backup.Container.Devices != nil {
-			devName, _, err := shared.GetRootDiskDevice(backup.Container.Devices)
-			if err == nil {
-				backup.Container.Devices[devName]["pool"] = poolName
-				rootDiskDeviceFound = true
-			}
-		}
-
-		if backup.Container.ExpandedDevices != nil {
-			devName, _, err := shared.GetRootDiskDevice(backup.Container.ExpandedDevices)
-			if err == nil {
-				backup.Container.ExpandedDevices[devName]["pool"] = poolName
-				rootDiskDeviceFound = true
-			}
-		}
-
-		if !rootDiskDeviceFound {
-			return fmt.Errorf("No root device could be found")
-		}
-
-		file, err := os.Create(path)
-		if err != nil {
-			return err
-		}
-		defer file.Close()
-
-		data, err := yaml.Marshal(&backup)
-		if err != nil {
-			return err
-		}
-
-		_, err = file.Write(data)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	}
-
-	err = f(shared.VarPath("storage-pools", pool.Name, "containers", project.Prefix(b.Project, b.Name), "backup.yaml"))
-	if err != nil {
-		return err
-	}
-
-	for _, snap := range b.Snapshots {
-		err = f(shared.VarPath("storage-pools", pool.Name, "containers-snapshots", project.Prefix(b.Project, b.Name), snap,
-			"backup.yaml"))
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 func backupCreateTarball(s *state.State, path string, b backup.Backup, c instance.Instance) error {
 	// Create the index
 	pool, err := c.StoragePool()

From 9f74401c5453ab4943570806738cf918496e158d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:28:25 +0000
Subject: [PATCH 04/25] lxd/containers/post: Updates instanceCreateFromBackup
 usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/containers_post.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index eae96c5597..c3440ce4fb 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -657,7 +657,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 
 		// Dump tarball to storage.
 		f.Seek(0, 0)
-		revertFunc, err := instanceCreateFromBackup(d.State(), *bInfo, f, pool != "")
+		revertFunc, err := instanceCreateFromBackup(d.State(), *bInfo, f)
 		if err != nil {
 			return errors.Wrap(err, "Create instance from backup")
 		}

From 2c7d6fbff24fc870bd740b30d4b9829fb69927ab Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 15:43:27 +0000
Subject: [PATCH 05/25] lxd/container: Updates instanceCreateFromBackup
 signature

- Removes need for customBool variable and instead will just always update backup.yaml file to use current storage pool.
- This simplifies the function signature needed later for pool.CreateInstanceFromBackup().

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 27 ++++++++++-----------------
 1 file changed, 10 insertions(+), 17 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 125e5c4b8e..625c57ffd4 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -307,13 +307,10 @@ func instanceCreateAsEmpty(d *Daemon, args db.InstanceArgs) (instance.Instance,
 
 // instanceCreateFromBackup imports a backup file to restore an instance. Returns a revert function
 // that can be called to delete the files created during import if subsequent steps fail.
-func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadSeeker, customPool bool) (func(), error) {
-	var fixBackupFile = false
-	poolName := info.Pool
-
+func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadSeeker) (func(), error) {
 	// Check storage pool from index.yaml exists. If the storage pool in the index.yaml doesn't
-	// exist, try and use the default profile's storage pool.
-	_, _, err := s.Cluster.StoragePoolGet(poolName)
+	// exist, try and use the project's default profile storage pool.
+	_, _, err := s.Cluster.StoragePoolGet(info.Pool)
 	if errors.Cause(err) == db.ErrNoSuchObject {
 		// The backup is in binary format so we cannot alter the backup.yaml.
 		if info.HasBinaryFormat {
@@ -332,10 +329,7 @@ func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadS
 		}
 
 		// Use the default-profile's root pool.
-		poolName = v["pool"]
-
-		// Mark requirement to change the pool information in the backup.yaml file.
-		fixBackupFile = true
+		info.Pool = v["pool"]
 	} else if err != nil {
 		return nil, err
 	}
@@ -385,7 +379,7 @@ func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadS
 		srcData = tarData
 	}
 
-	pool, err := storagePoolInit(s, poolName)
+	pool, err := storagePoolInit(s, info.Pool)
 	if err != nil {
 		return nil, err
 	}
@@ -396,12 +390,11 @@ func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadS
 		return nil, err
 	}
 
-	if fixBackupFile || customPool {
-		// Update pool information in the backup.yaml file.
-		err = backupFixStoragePool(s.Cluster, info, !customPool)
-		if err != nil {
-			return nil, err
-		}
+	// Update pool information in the backup.yaml file.
+	// Requires the volume and snapshots be mounted from pool.ContainerBackupLoad().
+	err = backup.UpdateInstanceConfigStoragePool(s.Cluster, info)
+	if err != nil {
+		return nil, err
 	}
 
 	// Create revert function to remove the files created so far.

From 46151f83a197316b4e89113aae0f6e22491d5812 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 09:07:25 +0000
Subject: [PATCH 06/25] lxd/backup/backup/instance/config:
 UpdateInstanceConfigStoragePool no longer updates snapshots backup.yaml

Several storage drivers do not make backup.yaml available during backup restore (for example BTRFS snapshots are read only, and ZFS needs to be mounted).

As such we should only update the pool name of the main volume's backup.yaml during backup restore.

We regenerate the backup.yaml file of snapshots when they are restored to the main instance instead.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/backup/backup_instance_config.go | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/lxd/backup/backup_instance_config.go b/lxd/backup/backup_instance_config.go
index a871ad0a7e..d15dca1744 100644
--- a/lxd/backup/backup_instance_config.go
+++ b/lxd/backup/backup_instance_config.go
@@ -112,11 +112,5 @@ func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info) error {
 		return err
 	}
 
-	for _, snap := range b.Snapshots {
-		err = f(shared.VarPath("storage-pools", pool.Name, "containers-snapshots", project.Prefix(b.Project, b.Name), snap, "backup.yaml"))
-		if err != nil {
-			return err
-		}
-	}
 	return nil
 }

From 1479cfb87005966497623f79f51d49cb4d4ff673 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 09:10:05 +0000
Subject: [PATCH 07/25] lxd: Comment improvements

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go     | 10 +++++-----
 lxd/container_lxc.go |  3 ++-
 lxd/vm_qemu.go       |  4 ++--
 3 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 625c57ffd4..6a251fe92d 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -736,7 +736,7 @@ func instanceCreateAsSnapshot(s *state.State, args db.InstanceArgs, sourceInstan
 		return nil, fmt.Errorf("Source instance and snapshot instance types do not match")
 	}
 
-	// Deal with state
+	// Deal with state.
 	if args.Stateful {
 		if !sourceInstance.IsRunning() {
 			return nil, fmt.Errorf("Unable to create a stateful snapshot. The instance isn't running")
@@ -781,7 +781,7 @@ func instanceCreateAsSnapshot(s *state.State, args db.InstanceArgs, sourceInstan
 		}
 	}
 
-	// Create the snapshot
+	// Create the snapshot.
 	inst, err := instanceCreateInternal(s, args)
 	if err != nil {
 		return nil, err
@@ -836,13 +836,13 @@ func instanceCreateAsSnapshot(s *state.State, args db.InstanceArgs, sourceInstan
 		return nil, fmt.Errorf("Instance type not supported")
 	}
 
-	// Attempt to update backup.yaml for instance
+	// Attempt to update backup.yaml for instance.
 	err = writeBackupFile(sourceInstance)
 	if err != nil {
 		return nil, err
 	}
 
-	// Once we're done, remove the state directory
+	// Once we're done, remove the state directory.
 	if args.Stateful {
 		os.RemoveAll(sourceInstance.StatePath())
 	}
@@ -1113,7 +1113,7 @@ func instanceConfigureInternal(state *state.State, c instance.Instance) error {
 
 		ct := c.(*containerLXC)
 
-		// handle quota: at this point, storage is guaranteed to be ready
+		// handle quota: at this point, storage is guaranteed to be ready.
 		storage := ct.Storage()
 		if rootDiskDevice["size"] != "" {
 			storageTypeName := storage.GetStorageTypeName()
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index c15289ac17..6f6977ac72 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3328,6 +3328,7 @@ func (c *containerLXC) Restore(sourceContainer instance.Instance, stateful bool)
 			return err
 		}
 
+		// Ensure that storage is mounted for state path checks and for backup.yaml updates.
 		ourStart, err := pool.MountInstance(c, nil)
 		if err != nil {
 			return err
@@ -3396,7 +3397,7 @@ func (c *containerLXC) Restore(sourceContainer instance.Instance, stateful bool)
 			return err
 		}
 
-		// Ensure that storage is mounted for state path checks.
+		// Ensure that storage is mounted for state path checks and for backup.yaml updates.
 		if pool != nil {
 			ourStart, err := pool.MountInstance(c, nil)
 			if err != nil {
diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index e9002f18f9..62df3470f1 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -1660,14 +1660,14 @@ func (vm *vmQemu) Update(args db.InstanceArgs, userRequested bool) error {
 	}
 
 	if shared.StringInSlice("security.secureboot", changedConfig) {
-		// Re-generate the NVRAM
+		// Re-generate the NVRAM.
 		err = vm.setupNvram()
 		if err != nil {
 			return err
 		}
 	}
 
-	// Finally, apply the changes to the database
+	// Finally, apply the changes to the database.
 	err = query.Retry(func() error {
 		tx, err := vm.state.Cluster.Begin()
 		if err != nil {

From a0ef27d1c045631ee7cd9ba8f186fe3e0f8a332d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 09:22:38 +0000
Subject: [PATCH 08/25] lxd/containers/post: Adds storage pool check to
 createFromBackup

This check differentiates between a user specified pool from the CLI vs the pool contained within the backup.yaml.

If the latter pool doesn't exist then the project's default pool is used, however if the former doesn't exist then an error is returned.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/containers_post.go | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index c3440ce4fb..d07066f337 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -652,6 +652,33 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 		bInfo.Pool = pool
 	}
 
+	// Check storage pool exists.
+	_, _, err = d.State().Cluster.StoragePoolGet(bInfo.Pool)
+	if errors.Cause(err) == db.ErrNoSuchObject {
+		// The storage pool doesn't exist. If backup is in binary format (so we cannot alter
+		// the backup.yaml) or the pool has been specified directly from the user restoring
+		// the backup then we cannot proceed so return an error.
+		if bInfo.HasBinaryFormat || pool != "" {
+			return response.InternalError(errors.Wrap(err, "Storage pool not found"))
+		}
+
+		// Otherwise try and restore to the project's default profile pool.
+		_, profile, err := d.State().Cluster.ProfileGet(bInfo.Project, "default")
+		if err != nil {
+			return response.InternalError(errors.Wrap(err, "Failed to get default profile"))
+		}
+
+		_, v, err := shared.GetRootDiskDevice(profile.Devices)
+		if err != nil {
+			return response.InternalError(errors.Wrap(err, "Failed to get root disk device"))
+		}
+
+		// Use the default-profile's root pool.
+		bInfo.Pool = v["pool"]
+	} else if err != nil {
+		return response.InternalError(err)
+	}
+
 	run := func(op *operations.Operation) error {
 		defer f.Close()
 

From e33c299ec5627ff71e6e95a3997f489512ae3897 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 09:23:47 +0000
Subject: [PATCH 09/25] lxd/container: Removes storage pool check from
 instanceCreateFromBackup

So that this can be done earlier on where the user specified pool can be differentiated between that and the pool inside backup.yaml.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 26 --------------------------
 1 file changed, 26 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 6a251fe92d..3cd9f24cfe 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -308,32 +308,6 @@ func instanceCreateAsEmpty(d *Daemon, args db.InstanceArgs) (instance.Instance,
 // instanceCreateFromBackup imports a backup file to restore an instance. Returns a revert function
 // that can be called to delete the files created during import if subsequent steps fail.
 func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadSeeker) (func(), error) {
-	// Check storage pool from index.yaml exists. If the storage pool in the index.yaml doesn't
-	// exist, try and use the project's default profile storage pool.
-	_, _, err := s.Cluster.StoragePoolGet(info.Pool)
-	if errors.Cause(err) == db.ErrNoSuchObject {
-		// The backup is in binary format so we cannot alter the backup.yaml.
-		if info.HasBinaryFormat {
-			return nil, err
-		}
-
-		// Get the default profile.
-		_, profile, err := s.Cluster.ProfileGet(info.Project, "default")
-		if err != nil {
-			return nil, errors.Wrap(err, "Failed to get default profile")
-		}
-
-		_, v, err := shared.GetRootDiskDevice(profile.Devices)
-		if err != nil {
-			return nil, errors.Wrap(err, "Failed to get root disk device")
-		}
-
-		// Use the default-profile's root pool.
-		info.Pool = v["pool"]
-	} else if err != nil {
-		return nil, err
-	}
-
 	// Find the compression algorithm.
 	tarArgs, algo, decomArgs, err := shared.DetectCompressionFile(srcData)
 	if err != nil {

From 0e786b9dbc76a7c3366b65302ea5b8aceebc067f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 16:12:29 +0000
Subject: [PATCH 10/25] lxd/backup/backup: Removes squashfs handling from
 GetInfo

Expects to always be passed an (optionally compressed) tarball now.

Squashfs files should be converted to a tarball before being passed to this function.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/backup/backup.go | 23 +----------------------
 1 file changed, 1 insertion(+), 22 deletions(-)

diff --git a/lxd/backup/backup.go b/lxd/backup/backup.go
index 421517d1f4..8f3fdc1067 100644
--- a/lxd/backup/backup.go
+++ b/lxd/backup/backup.go
@@ -4,7 +4,6 @@ import (
 	"archive/tar"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"strings"
@@ -48,7 +47,7 @@ func GetInfo(r io.ReadSeeker) (*Info, error) {
 
 	// Extract
 	r.Seek(0, 0)
-	_, algo, unpacker, err := shared.DetectCompressionFile(r)
+	_, _, unpacker, err := shared.DetectCompressionFile(r)
 	if err != nil {
 		return nil, err
 	}
@@ -59,26 +58,6 @@ func GetInfo(r io.ReadSeeker) (*Info, error) {
 	}
 
 	if len(unpacker) > 0 {
-		if algo == ".squashfs" {
-			// 'sqfs2tar' tool does not support reading from stdin. So
-			// create a temporary file to write the compressed data and
-			// pass it as program argument
-			tempfile, err := ioutil.TempFile("", "lxd_decompress_")
-			if err != nil {
-				return nil, err
-			}
-			defer tempfile.Close()
-			defer os.Remove(tempfile.Name())
-
-			// Write compressed data
-			_, err = io.Copy(tempfile, r)
-			if err != nil {
-				return nil, err
-			}
-
-			// Prepare to pass the temporary file as program argument
-			unpacker = append(unpacker, tempfile.Name())
-		}
 		cmd := exec.Command(unpacker[0], unpacker[1:]...)
 		cmd.Stdin = r
 

From e9372b8b3eb158d5bc3ec7f7db001ecc89a06c0a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 16:14:08 +0000
Subject: [PATCH 11/25] lxd/container: Removes squashfs handling from
 instanceCreateBackup

Expects to always be passed an (optionally compressed) tarball now.

Squashfs files should be converted to a tarball before being passed to this function.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 41 +----------------------------------------
 1 file changed, 1 insertion(+), 40 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 3cd9f24cfe..0b13dd3eb1 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -309,50 +308,12 @@ func instanceCreateAsEmpty(d *Daemon, args db.InstanceArgs) (instance.Instance,
 // that can be called to delete the files created during import if subsequent steps fail.
 func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadSeeker) (func(), error) {
 	// Find the compression algorithm.
-	tarArgs, algo, decomArgs, err := shared.DetectCompressionFile(srcData)
+	tarArgs, _, _, err := shared.DetectCompressionFile(srcData)
 	if err != nil {
 		return nil, err
 	}
 	srcData.Seek(0, 0)
 
-	if algo == ".squashfs" {
-		// The 'sqfs2tar' tool does not support reading from stdin. So we need to create a
-		// temporary file, write the compressed data to it and pass it as program argument.
-		tempFile, err := ioutil.TempFile("", "lxd_decompress_")
-		if err != nil {
-			return nil, err
-		}
-		defer tempFile.Close()
-		defer os.Remove(tempFile.Name())
-
-		// Copy the compressed data stream to the temporart file.
-		_, err = io.Copy(tempFile, srcData)
-		if err != nil {
-			return nil, err
-		}
-
-		// Pass the temporary file as program argument to the decompression command.
-		decomArgs := append(decomArgs, tempFile.Name())
-
-		// Create another temporary file to write the decompressed data.
-		tarData, err := ioutil.TempFile("", "lxd_decompress_")
-		if err != nil {
-			return nil, err
-		}
-		defer tarData.Close()
-		defer os.Remove(tarData.Name())
-
-		// Decompress to tarData temporary file.
-		err = shared.RunCommandWithFds(nil, tarData, decomArgs[0], decomArgs[1:]...)
-		if err != nil {
-			return nil, err
-		}
-		tarData.Seek(0, 0)
-
-		// Set source data stream to newly created tar file handle.
-		srcData = tarData
-	}
-
 	pool, err := storagePoolInit(s, info.Pool)
 	if err != nil {
 		return nil, err

From 2586514bb72cb79f3397d26dd74e181caf1b007c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 16:14:54 +0000
Subject: [PATCH 12/25] lxd/containers/post: Moves backup restore squashfs
 handling to createFromBackup

This avoids copying the uploaded backup file to multiple temporary files and simplifies the downstream logic.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/containers_post.go | 58 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 47 insertions(+), 11 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index d07066f337..774e12891b 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -625,24 +625,59 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) response.
 }
 
 func createFromBackup(d *Daemon, project string, data io.Reader, pool string) response.Response {
-	// Write the data to a temp file.
-	f, err := ioutil.TempFile("", "lxd_backup_")
+	// Create temporary file to store uploaded backup data.
+	backupFile, err := ioutil.TempFile("", "lxd_backup_")
 	if err != nil {
 		return response.InternalError(err)
 	}
-	defer os.Remove(f.Name())
+	defer os.Remove(backupFile.Name())
 
-	_, err = io.Copy(f, data)
+	// Stream uploaded backup data into temporary file.
+	_, err = io.Copy(backupFile, data)
 	if err != nil {
-		f.Close()
+		backupFile.Close()
 		return response.InternalError(err)
 	}
 
+	// Detect squashfs compression and convert to tarball.
+	backupFile.Seek(0, 0)
+	_, algo, decomArgs, err := shared.DetectCompressionFile(backupFile)
+	if err != nil {
+		backupFile.Close()
+		return response.InternalError(err)
+	}
+
+	if algo == ".squashfs" {
+		// Pass the temporary file as program argument to the decompression command.
+		decomArgs := append(decomArgs, backupFile.Name())
+
+		// Create temporary file to store the decompressed tarball in.
+		tarFile, err := ioutil.TempFile("", "lxd_decompress_")
+		if err != nil {
+			backupFile.Close()
+			return response.InternalError(err)
+		}
+		defer os.Remove(tarFile.Name())
+
+		// Decompress to tarData temporary file.
+		err = shared.RunCommandWithFds(nil, tarFile, decomArgs[0], decomArgs[1:]...)
+		if err != nil {
+			return response.InternalError(err)
+		}
+
+		// We don't need the original squashfs file anymore.
+		backupFile.Close()
+		os.Remove(backupFile.Name())
+
+		// Replace the backup file handle with the handle to the tar file.
+		backupFile = tarFile
+	}
+
 	// Parse the backup information.
-	f.Seek(0, 0)
-	bInfo, err := backup.GetInfo(f)
+	backupFile.Seek(0, 0)
+	bInfo, err := backup.GetInfo(backupFile)
 	if err != nil {
-		f.Close()
+		backupFile.Close()
 		return response.BadRequest(err)
 	}
 	bInfo.Project = project
@@ -680,11 +715,11 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 	}
 
 	run := func(op *operations.Operation) error {
-		defer f.Close()
+		defer backupFile.Close()
 
 		// Dump tarball to storage.
-		f.Seek(0, 0)
-		revertFunc, err := instanceCreateFromBackup(d.State(), *bInfo, f)
+		backupFile.Seek(0, 0)
+		revertFunc, err := instanceCreateFromBackup(d.State(), *bInfo, backupFile)
 		if err != nil {
 			return errors.Wrap(err, "Create instance from backup")
 		}
@@ -739,6 +774,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 	op, err := operations.OperationCreate(d.State(), project, operations.OperationClassTask, db.OperationBackupRestore,
 		resources, nil, run, nil, nil)
 	if err != nil {
+		backupFile.Close()
 		return response.InternalError(err)
 	}
 

From dac07e89724be61d1eab61bc75bcd589d9754a28 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:27:42 +0000
Subject: [PATCH 13/25] lxd/container/lxc: Updates use of backupFile to
 backup.InstanceConfig

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_lxc.go | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6f6977ac72..09120cfca4 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3997,13 +3997,6 @@ func (c *containerLXC) VolatileSet(changes map[string]string) error {
 	return nil
 }
 
-type backupFile struct {
-	Container *api.Instance           `yaml:"container"`
-	Snapshots []*api.InstanceSnapshot `yaml:"snapshots"`
-	Pool      *api.StoragePool        `yaml:"pool"`
-	Volume    *api.StorageVolume      `yaml:"volume"`
-}
-
 func writeBackupFile(c instance.Instance) error {
 	// We only write backup files out for actual containers
 	if c.IsSnapshot() {
@@ -4058,7 +4051,7 @@ func writeBackupFile(c instance.Instance) error {
 		return err
 	}
 
-	data, err := yaml.Marshal(&backupFile{
+	data, err := yaml.Marshal(&backup.InstanceConfig{
 		Container: ci.(*api.Instance),
 		Snapshots: sis,
 		Pool:      pool,

From 1acd579173937a2a052261ad08eabb950427d715 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:19:35 +0000
Subject: [PATCH 14/25] lxd/storage/backend/lxd: Implements
 CreateInstanceFromBackup

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/backend_lxd.go | 93 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 91 insertions(+), 2 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 1a904bc203..bd71ccf57e 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -330,8 +330,97 @@ func (b *lxdBackend) CreateInstance(inst instance.Instance, op *operations.Opera
 	return nil
 }
 
-func (b *lxdBackend) CreateInstanceFromBackup(inst instance.Instance, sourcePath string, op *operations.Operation) error {
-	return ErrNotImplemented
+// CreateInstanceFromBackup restores a backup file onto the storage device. Because the backup file
+// is unpacked and restored onto the storage device before the instance is created in the database
+// it is neccessary to return two functions; a post hook that can be run once the instance has been
+// created in the database to run any storage layer finalisations, and a revert hook that can be
+// run if the instance database load process fails that will remove anything created thus far.
+func (b *lxdBackend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (func(instance.Instance) error, func(), error) {
+	logger := logging.AddContext(b.logger, log.Ctx{"project": srcBackup.Project, "instance": srcBackup.Name, "snapshots": srcBackup.Snapshots, "hasBinaryFormat": srcBackup.HasBinaryFormat})
+	logger.Debug("CreateInstanceFromBackup started")
+	defer logger.Debug("CreateInstanceFromBackup finished")
+
+	// Get the volume name on storage.
+	volStorageName := project.Prefix(srcBackup.Project, srcBackup.Name)
+
+	// Currently there is no concept of instance type in backups, so we assume container.
+	// We don't know the volume's config yet as tarball hasn't been unpacked.
+	// We will apply the config as part of the post hook function returned if driver needs to.
+	vol := b.newVolume(drivers.VolumeTypeContainer, drivers.ContentTypeFS, volStorageName, nil)
+
+	revertFuncs := []func(){}
+	defer func() {
+		for _, revertFunc := range revertFuncs {
+			revertFunc()
+		}
+	}()
+
+	// Unpack the backup into the new storage volume(s).
+	volPostHook, revertHook, err := b.driver.RestoreBackupVolume(vol, srcBackup.Snapshots, srcData, op)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if revertHook != nil {
+		revertFuncs = append(revertFuncs, revertHook)
+	}
+
+	err = b.ensureInstanceSymlink(instancetype.Container, srcBackup.Project, srcBackup.Name, vol.MountPath())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	revertFuncs = append(revertFuncs, func() {
+		b.removeInstanceSymlink(instancetype.Container, srcBackup.Project, srcBackup.Name)
+	})
+
+	if len(srcBackup.Snapshots) > 0 {
+		err = b.ensureInstanceSnapshotSymlink(instancetype.Container, srcBackup.Project, srcBackup.Name)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		revertFuncs = append(revertFuncs, func() {
+			b.removeInstanceSnapshotSymlinkIfUnused(instancetype.Container, srcBackup.Project, srcBackup.Name)
+		})
+	}
+
+	// Update pool information in the backup.yaml file.
+	vol.MountTask(func(mountPath string, op *operations.Operation) error {
+		return backup.UpdateInstanceConfigStoragePool(b.state.Cluster, srcBackup, mountPath)
+	}, op)
+
+	var postHook func(instance.Instance) error
+	if volPostHook != nil {
+		// Create a post hook function that will use the instance (that will be created) to
+		// setup a new volume containing the instance's root disk device's config so that
+		// the driver's post hook function can access that config to perform any post
+		// instance creation setup.
+		postHook = func(inst instance.Instance) error {
+			// Get the root disk device config.
+			_, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
+			if err != nil {
+				return err
+			}
+
+			// Get the volume name on storage.
+			volStorageName := project.Prefix(inst.Project(), inst.Name())
+
+			volType, err := InstanceTypeToVolumeType(inst.Type())
+			if err != nil {
+				return err
+			}
+
+			contentType := InstanceContentType(inst)
+
+			// Initialise new volume containing root disk config supplied in instance.
+			vol := b.newVolume(volType, contentType, volStorageName, rootDiskConf)
+			return volPostHook(vol)
+		}
+	}
+
+	revertFuncs = nil
+	return postHook, revertHook, nil
 }
 
 // CreateInstanceFromCopy copies an instance volume and optionally its snapshots to new volume(s).

From b8132f00b5d907d631a09b196ebada20535d4e11 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:44:01 +0000
Subject: [PATCH 15/25] lxd/storage: Updates CreateInstanceFromBackup signature

Will use an io.ReadSeeker to access backup file source rather than opening file path.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/backend_lxd.go    | 1 +
 lxd/storage/backend_mock.go   | 5 +++--
 lxd/storage/pool_interface.go | 3 ++-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index bd71ccf57e..54bca4617c 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -8,6 +8,7 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go
index cb3a464b71..089ff98676 100644
--- a/lxd/storage/backend_mock.go
+++ b/lxd/storage/backend_mock.go
@@ -3,6 +3,7 @@ package storage
 import (
 	"io"
 
+	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/operations"
@@ -63,8 +64,8 @@ func (b *mockBackend) CreateInstance(inst instance.Instance, op *operations.Oper
 	return nil
 }
 
-func (b *mockBackend) CreateInstanceFromBackup(inst instance.Instance, sourcePath string, op *operations.Operation) error {
-	return nil
+func (b *mockBackend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (func(instance.Instance) error, func(), error) {
+	return nil, nil, nil
 }
 
 func (b *mockBackend) CreateInstanceFromCopy(inst instance.Instance, src instance.Instance, snapshots bool, op *operations.Operation) error {
diff --git a/lxd/storage/pool_interface.go b/lxd/storage/pool_interface.go
index e00cbcc24f..bd7462345f 100644
--- a/lxd/storage/pool_interface.go
+++ b/lxd/storage/pool_interface.go
@@ -3,6 +3,7 @@ package storage
 import (
 	"io"
 
+	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/operations"
@@ -29,7 +30,7 @@ type Pool interface {
 
 	// Instances.
 	CreateInstance(inst instance.Instance, op *operations.Operation) error
-	CreateInstanceFromBackup(inst instance.Instance, sourcePath string, op *operations.Operation) error
+	CreateInstanceFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (func(instance.Instance) error, func(), error)
 	CreateInstanceFromCopy(inst instance.Instance, src instance.Instance, snapshots bool, op *operations.Operation) error
 	CreateInstanceFromImage(inst instance.Instance, fingerprint string, op *operations.Operation) error
 	CreateInstanceFromMigration(inst instance.Instance, conn io.ReadWriteCloser, args migration.VolumeTargetArgs, op *operations.Operation) error

From 521b2d73546117c35f0943fbf93dfe484677be04 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 12:32:52 +0000
Subject: [PATCH 16/25] lxd/container: Updates instanceCreateFromBackup to use
 new storage pkg

- pool.CreateInstanceFromBackup() will do decompression for new storage layer.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 90 +++++++++++++++++++++++++++++++++---------------
 1 file changed, 62 insertions(+), 28 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 0b13dd3eb1..cf201ca6f0 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -304,40 +304,74 @@ func instanceCreateAsEmpty(d *Daemon, args db.InstanceArgs) (instance.Instance,
 	return inst, nil
 }
 
-// instanceCreateFromBackup imports a backup file to restore an instance. Returns a revert function
-// that can be called to delete the files created during import if subsequent steps fail.
-func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadSeeker) (func(), error) {
-	// Find the compression algorithm.
-	tarArgs, _, _, err := shared.DetectCompressionFile(srcData)
-	if err != nil {
-		return nil, err
-	}
-	srcData.Seek(0, 0)
+// instanceCreateFromBackup imports a backup file to restore an instance. Because the backup file
+// is unpacked and restored onto the storage device before the instance is created in the database
+// it is neccessary to return two functions; a post hook that can be run once the instance has been
+// created in the database to run any storage layer finalisations, and a revert hook that can be
+// run if the instance database load process fails that will remove anything created thus far.
+func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadSeeker) (func(instance.Instance) error, func(), error) {
+	// Define hook functions that will be returned to caller.
+	var postHook func(instance.Instance) error
+	var revertHook func()
 
-	pool, err := storagePoolInit(s, info.Pool)
-	if err != nil {
-		return nil, err
-	}
+	// Check if we can load new storage layer for pool driver type.
+	pool, err := storagePools.GetPoolByName(s, info.Pool)
+	if err != storageDrivers.ErrUnknownDriver {
+		if err != nil {
+			return nil, nil, err
+		}
 
-	// Unpack tarball from the source tar stream.
-	err = pool.ContainerBackupLoad(info, srcData, tarArgs)
-	if err != nil {
-		return nil, err
-	}
+		postHook, revertHook, err = pool.CreateInstanceFromBackup(info, srcData, nil)
+		if err != nil {
+			return nil, nil, err
+		}
+	} else { // Fallback to old storage layer.
 
-	// Update pool information in the backup.yaml file.
-	// Requires the volume and snapshots be mounted from pool.ContainerBackupLoad().
-	err = backup.UpdateInstanceConfigStoragePool(s.Cluster, info)
-	if err != nil {
-		return nil, err
-	}
+		// Find the compression algorithm.
+		srcData.Seek(0, 0)
+		tarArgs, _, _, err := shared.DetectCompressionFile(srcData)
+		if err != nil {
+			return nil, nil, err
+		}
 
-	// Create revert function to remove the files created so far.
-	revertFunc := func() {
-		pool.ContainerDelete(&containerLXC{name: info.Name, project: info.Project})
+		pool, err := storagePoolInit(s, info.Pool)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		// Unpack tarball from the source tar stream.
+		srcData.Seek(0, 0)
+		err = pool.ContainerBackupLoad(info, srcData, tarArgs)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		// Update pool information in the backup.yaml file.
+		// Requires the volume and snapshots be mounted from pool.ContainerBackupLoad().
+		err = backup.UpdateInstanceConfigStoragePool(s.Cluster, info)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		// Set revert function to remove the files created so far.
+		revertHook = func() {
+			// Create a temporary container struct (because the container DB record
+			// hasn't been imported yet) for use with storage layer.
+			ctTmp := &containerLXC{name: info.Name, project: info.Project}
+			pool.ContainerDelete(ctTmp)
+		}
+
+		postHook = func(inst instance.Instance) error {
+			_, err = inst.StorageStop()
+			if err != nil {
+				return errors.Wrap(err, "Stop storage pool")
+			}
+
+			return nil
+		}
 	}
 
-	return revertFunc, nil
+	return postHook, revertHook, nil
 }
 
 func containerCreateEmptySnapshot(s *state.State, args db.InstanceArgs) (instance.Instance, error) {

From 3cff3b25635e9fe29155756a646babad6d5f3f74 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 27 Nov 2019 16:55:18 +0000
Subject: [PATCH 17/25] lxd/containers/post: Updates instanceCreateFromBackup
 usage with hooks

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/containers_post.go | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 774e12891b..9d45831ecc 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -718,8 +718,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 		defer backupFile.Close()
 
 		// Dump tarball to storage.
-		backupFile.Seek(0, 0)
-		revertFunc, err := instanceCreateFromBackup(d.State(), *bInfo, backupFile)
+		postHook, revertHook, err := instanceCreateFromBackup(d.State(), *bInfo, backupFile)
 		if err != nil {
 			return errors.Wrap(err, "Create instance from backup")
 		}
@@ -730,7 +729,9 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 				return
 			}
 
-			revertFunc()
+			if revertHook != nil {
+				revertHook()
+			}
 		}()
 
 		body, err := json.Marshal(&internalImportPost{
@@ -758,9 +759,13 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 			return errors.Wrap(err, "Load instance")
 		}
 
-		_, err = c.StorageStop()
-		if err != nil {
-			return errors.Wrap(err, "Stop storage pool")
+		// Run the storage post hook to perform any final actions now that the instance
+		// has been created in the database.
+		if postHook != nil {
+			err = postHook(c)
+			if err != nil {
+				return errors.Wrap(err, "Post hook")
+			}
 		}
 
 		revert = false

From 42b5d89c2bd018024e4344e578a3afaaa3969502 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:17:04 +0000
Subject: [PATCH 18/25] lxd/backup/backup/instance/config: Updates
 UpdateInstanceConfigStoragePool to take mount path

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/backup/backup_instance_config.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/backup/backup_instance_config.go b/lxd/backup/backup_instance_config.go
index d15dca1744..1a60152371 100644
--- a/lxd/backup/backup_instance_config.go
+++ b/lxd/backup/backup_instance_config.go
@@ -4,11 +4,11 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -52,7 +52,7 @@ func updateRootDevicePool(devices map[string]map[string]string, poolName string)
 
 // UpdateInstanceConfigStoragePool changes the pool information in the backup.yaml to the pool
 // specified in b.Pool.
-func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info) error {
+func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info, mountPath string) error {
 	// Load the storage pool.
 	_, pool, err := c.StoragePoolGet(b.Pool)
 	if err != nil {
@@ -107,7 +107,7 @@ func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info) error {
 		return nil
 	}
 
-	err = f(shared.VarPath("storage-pools", pool.Name, "containers", project.Prefix(b.Project, b.Name), "backup.yaml"))
+	err = f(filepath.Join(mountPath, "backup.yaml"))
 	if err != nil {
 		return err
 	}

From f201e53497f4dff07b63bd2f822072772f673fee Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:17:30 +0000
Subject: [PATCH 19/25] lxd/container: Updates
 backup.UpdateInstanceConfigStoragePool usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxd/container.go b/lxd/container.go
index cf201ca6f0..86a5465c9e 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -24,6 +24,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/seccomp"
 	"github.com/lxc/lxd/lxd/state"
 	storagePools "github.com/lxc/lxd/lxd/storage"
@@ -348,7 +349,8 @@ func instanceCreateFromBackup(s *state.State, info backup.Info, srcData io.ReadS
 
 		// Update pool information in the backup.yaml file.
 		// Requires the volume and snapshots be mounted from pool.ContainerBackupLoad().
-		err = backup.UpdateInstanceConfigStoragePool(s.Cluster, info)
+		mountPath := shared.VarPath("storage-pools", info.Pool, "containers", project.Prefix(info.Project, info.Name))
+		err = backup.UpdateInstanceConfigStoragePool(s.Cluster, info, mountPath)
 		if err != nil {
 			return nil, nil, err
 		}

From 75a8e0bea3242e26d6072bcf18ac8ee0ecfeb3c4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:18:57 +0000
Subject: [PATCH 20/25] lxd/storage/backend/lxd: Switches to
 InstanceContentType function

Clarifies that image type is not the same as instance type.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/backend_lxd.go | 44 ++++++++++----------------------------
 1 file changed, 11 insertions(+), 33 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 54bca4617c..2b0462736d 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -297,10 +297,7 @@ func (b *lxdBackend) CreateInstance(inst instance.Instance, op *operations.Opera
 		b.DeleteInstance(inst, op)
 	}()
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	// Find the root device config for instance.
 	_, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
@@ -444,10 +441,7 @@ func (b *lxdBackend) CreateInstanceFromCopy(inst instance.Instance, src instance
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	if b.driver.HasVolume(volType, project.Prefix(inst.Project(), inst.Name())) {
 		return fmt.Errorf("Cannot create volume, already exists on target")
@@ -593,10 +587,7 @@ func (b *lxdBackend) RefreshInstance(inst instance.Instance, src instance.Instan
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	// Get the root disk device config.
 	_, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
@@ -750,10 +741,7 @@ func (b *lxdBackend) CreateInstanceFromImage(inst instance.Instance, fingerprint
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	revert := true
 	defer func() {
@@ -824,10 +812,7 @@ func (b *lxdBackend) CreateInstanceFromMigration(inst instance.Instance, conn io
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	volExists := b.driver.HasVolume(volType, project.Prefix(inst.Project(), inst.Name()))
 	if args.Refresh && !volExists {
@@ -1041,10 +1026,7 @@ func (b *lxdBackend) MigrateInstance(inst instance.Instance, conn io.ReadWriteCl
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	// Get the root disk device config.
 	_, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
@@ -1077,10 +1059,7 @@ func (b *lxdBackend) BackupInstance(inst instance.Instance, targetPath string, o
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	// Get the root disk device config.
 	_, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
@@ -1376,10 +1355,7 @@ func (b *lxdBackend) RestoreInstanceSnapshot(inst instance.Instance, src instanc
 		return err
 	}
 
-	contentType := drivers.ContentTypeFS
-	if inst.Type() == instancetype.VM {
-		contentType = drivers.ContentTypeBlock
-	}
+	contentType := InstanceContentType(inst)
 
 	// Find the root device config for source snapshot instance.
 	_, rootDiskConf, err := shared.GetRootDiskDevice(src.ExpandedDevices().CloneNative())
@@ -1479,7 +1455,9 @@ func (b *lxdBackend) EnsureImage(fingerprint string, op *operations.Operation) e
 	}
 
 	contentType := drivers.ContentTypeFS
-	if api.InstanceType(image.Type) == api.InstanceTypeVM {
+
+	// Image types are not the same as instance types, so compare to instance type constants.
+	if image.Type == "virtual-machine" {
 		contentType = drivers.ContentTypeBlock
 	}
 

From 9253029eb1a419f88b535f4bb3b39eda92c12e5c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:20:36 +0000
Subject: [PATCH 21/25] lxd/storage/utils: Adds InstanceContentType

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/utils.go | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index 25e8e273fd..db4eb0f1ff 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -10,6 +10,7 @@ import (
 	"golang.org/x/sys/unix"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/storage/drivers"
@@ -756,3 +757,13 @@ func ImageUnpack(imageFile, destPath, destBlockFile string, blockBackend, runnin
 
 	return nil
 }
+
+// InstanceContentType returns the instance's content type.
+func InstanceContentType(inst instance.Instance) drivers.ContentType {
+	contentType := drivers.ContentTypeFS
+	if inst.Type() == instancetype.VM {
+		contentType = drivers.ContentTypeBlock
+	}
+
+	return contentType
+}

From f08ff272977c3fd7999eb92512e0e244e3d21d2f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:20:51 +0000
Subject: [PATCH 22/25] lxd/storage/drivers/interface: RestoreBackupVolume
 signature

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/drivers/interface.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go
index 11d8343f05..09082f128f 100644
--- a/lxd/storage/drivers/interface.go
+++ b/lxd/storage/drivers/interface.go
@@ -72,4 +72,5 @@ type Driver interface {
 
 	// Backup.
 	BackupVolume(vol Volume, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error
+	RestoreBackupVolume(vol Volume, snapshots []string, srcData io.ReadSeeker, op *operations.Operation) (func(vol Volume) error, func(), error)
 }

From 9325180f9160b9261939202b0415c3b02d5b4dfe Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:21:12 +0000
Subject: [PATCH 23/25] lxd/storage/drivers/driver/cephfs: RestoreBackupVolume
 placeholder

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/drivers/driver_cephfs.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage/drivers/driver_cephfs.go b/lxd/storage/drivers/driver_cephfs.go
index 98b593cd80..75e589ea68 100644
--- a/lxd/storage/drivers/driver_cephfs.go
+++ b/lxd/storage/drivers/driver_cephfs.go
@@ -991,3 +991,7 @@ func (d *cephfs) getConfig(clusterName string, userName string) ([]string, strin
 func (d *cephfs) BackupVolume(vol Volume, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error {
 	return ErrNotImplemented
 }
+
+func (d *cephfs) RestoreBackupVolume(vol Volume, snapshots []string, srcData io.ReadSeeker, op *operations.Operation) (func(vol Volume) error, func(), error) {
+	return nil, nil, ErrNotImplemented
+}

From f24f760761e9a32742d87858d4fafad839c23b70 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:22:08 +0000
Subject: [PATCH 24/25] lxd/storage/drivers/driver/dir: Moves initial project
 quota setup to own function

Will be used in forthcoming backup restore function.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/drivers/driver_dir.go | 83 +++++++++++++++++++++----------
 1 file changed, 58 insertions(+), 25 deletions(-)

diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go
index 66c85d9ce7..a5eec749a3 100644
--- a/lxd/storage/drivers/driver_dir.go
+++ b/lxd/storage/drivers/driver_dir.go
@@ -173,6 +173,51 @@ func (d *dir) GetVolumeDiskPath(volType VolumeType, volName string) (string, err
 	return filepath.Join(GetVolumeMountPath(d.name, volType, volName), "root.img"), nil
 }
 
+// setupInitialQuota enables quota on a new volume and sets with an initial quota from config.
+// Returns a revert function that can be used to remove the quota if there is a subsequent error.
+func (d *dir) setupInitialQuota(vol Volume) (func(), error) {
+	// Extract specified size from pool or volume config.
+	size := d.config["volume.size"]
+	if vol.config["size"] != "" {
+		size = vol.config["size"]
+	}
+
+	volPath := vol.MountPath()
+
+	// Get the volume ID for the new volume, which is used to set project quota.
+	volID, err := d.getVolID(vol.volType, vol.name)
+	if err != nil {
+		return nil, err
+	}
+
+	// Define a function to revert the quota being setup.
+	revertFunc := func() {
+		d.deleteQuota(volPath, volID)
+	}
+
+	// Initialise the volume's quota using the volume ID.
+	err = d.initQuota(volPath, volID)
+	if err != nil {
+		return nil, err
+	}
+
+	revert := true
+	defer func() {
+		if revert {
+			revertFunc()
+		}
+	}()
+
+	// Set the quota.
+	err = d.setQuota(volPath, volID, size)
+	if err != nil {
+		return nil, err
+	}
+
+	revert = false
+	return revertFunc, nil
+}
+
 // CreateVolume creates an empty volume and can optionally fill it by executing the supplied
 // filler function.
 func (d *dir) CreateVolume(vol Volume, filler func(mountPath, rootBlockPath string) error, op *operations.Operation) error {
@@ -182,12 +227,6 @@ func (d *dir) CreateVolume(vol Volume, filler func(mountPath, rootBlockPath stri
 		return err
 	}
 
-	// Extract specified size from pool or volume config.
-	size := d.config["volume.size"]
-	if vol.config["size"] != "" {
-		size = vol.config["size"]
-	}
-
 	revertPath := true
 	defer func() {
 		if revertPath {
@@ -204,28 +243,17 @@ func (d *dir) CreateVolume(vol Volume, filler func(mountPath, rootBlockPath stri
 			return err
 		}
 	} else {
-		// Get the volume ID for the new volume, which is used to set project quota.
-		volID, err := d.getVolID(vol.volType, vol.name)
-		if err != nil {
-			return err
-		}
-
-		// Initialise the volume's quota using the volume ID.
-		err = d.initQuota(volPath, volID)
+		revertFunc, err := d.setupInitialQuota(vol)
 		if err != nil {
 			return err
 		}
 
-		defer func() {
-			if revertPath {
-				d.deleteQuota(volPath, volID)
-			}
-		}()
-
-		// Set the quota.
-		err = d.setQuota(volPath, volID, size)
-		if err != nil {
-			return err
+		if revertFunc != nil {
+			defer func() {
+				if revertPath {
+					revertFunc()
+				}
+			}()
 		}
 	}
 
@@ -240,7 +268,12 @@ func (d *dir) CreateVolume(vol Volume, filler func(mountPath, rootBlockPath stri
 	// If we are creating a block volume, resize it to the requested size or 10GB.
 	// We expect the filler function to have converted the qcow2 image to raw into the rootBlockPath.
 	if vol.contentType == ContentTypeBlock {
-		blockSize := size
+		// Extract specified size from pool or volume config.
+		blockSize := d.config["volume.size"]
+		if vol.config["size"] != "" {
+			blockSize = vol.config["size"]
+		}
+
 		if blockSize == "" {
 			blockSize = "10GB"
 		}

From 8c95a4a78f1624bc0ae73568351d77ca359566f7 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 Nov 2019 12:22:40 +0000
Subject: [PATCH 25/25] lxd/storage/drivers/driver/dir: Implements
 RestoreBackupVolume

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/drivers/driver_dir.go | 86 +++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go
index a5eec749a3..2a6b83c6b3 100644
--- a/lxd/storage/drivers/driver_dir.go
+++ b/lxd/storage/drivers/driver_dir.go
@@ -978,3 +978,89 @@ func (d *dir) BackupVolume(vol Volume, targetPath string, _, snapshots bool, op
 
 	return nil
 }
+
+// RestoreBackupVolume restores a backup tarball onto the storage device.
+func (d *dir) RestoreBackupVolume(vol Volume, snapshots []string, srcData io.ReadSeeker, op *operations.Operation) (func(vol Volume) error, func(), error) {
+	revert := true
+	revertPaths := []string{}
+
+	// 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 _, revertPath := range revertPaths {
+			os.RemoveAll(revertPath)
+		}
+	}
+
+	// Only execute the revert function if we have had an error internally and revert is true.
+	defer func() {
+		if revert {
+			revertHook()
+		}
+	}()
+
+	volPath := vol.MountPath()
+	err := vol.CreateMountPath()
+	if err != nil {
+		return nil, nil, err
+	}
+	revertPaths = append(revertPaths, volPath)
+
+	// Find the compression algorithm used for backup source data.
+	srcData.Seek(0, 0)
+	tarArgs, _, _, err := shared.DetectCompressionFile(srcData)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Prepare tar extraction arguments.
+	args := append(tarArgs, []string{
+		"-",
+		"--strip-components=2",
+		"--xattrs-include=*",
+		"-C", volPath, "backup/container",
+	}...)
+
+	// Extract instance.
+	srcData.Seek(0, 0)
+	err = shared.RunCommandWithFds(srcData, nil, "tar", args...)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if len(snapshots) > 0 {
+		// Create new snapshots directory.
+		snapshotDir := GetVolumeSnapshotDir(d.name, vol.volType, vol.name)
+		err := os.MkdirAll(snapshotDir, 0711)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		revertPaths = append(revertPaths, snapshotDir)
+
+		// Prepare tar arguments.
+		args := append(tarArgs, []string{
+			"-",
+			"--strip-components=2",
+			"--xattrs-include=*",
+			"-C", snapshotDir, "backup/snapshots",
+		}...)
+
+		// Extract snapshots.
+		srcData.Seek(0, 0)
+		err = shared.RunCommandWithFds(srcData, nil, "tar", args...)
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	// Define a post hook function that can be run once the backup config has been restored.
+	// This will setup the quota using the restored config.
+	postHook := func(vol Volume) error {
+		_, err := d.setupInitialQuota(vol)
+		return err
+	}
+
+	revert = false
+	return postHook, revertHook, nil
+}


More information about the lxc-devel mailing list