[lxc-devel] [lxd/master] Storage: Pre-VM support backup improvements

tomponline on Github lxc-bot at linuxcontainers.org
Wed Mar 25 12:05:32 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 899 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200325/dd896646/attachment-0001.bin>
-------------- next part --------------
From b9a3fedcb9ea692d129ca935be2a974d9114cd65 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 25 Mar 2020 11:53:28 +0000
Subject: [PATCH 01/16] lxc/storage/drivers/driver/btrfs/volumes:
 CreateVolumeFromBackup to use tar reader for optimized volume restore

Avoids the need to unpack the tarball to a temporary location.

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

diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go b/lxd/storage/drivers/driver_btrfs_volumes.go
index 97d6773305..e1db35fe2d 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -1,6 +1,7 @@
 package drivers
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -119,39 +120,45 @@ func (d *btrfs) CreateVolumeFromBackup(vol Volume, snapshots []string, srcData i
 		// And lastly the main volume.
 		d.DeleteVolume(vol, op)
 	}
-
 	// Only execute the revert function if we have had an error internally.
 	revert.Add(revertHook)
 
-	// Create a temporary directory to unpack the backup into.
-	unpackDir, err := ioutil.TempDir(GetVolumeMountPath(d.name, vol.volType, ""), "backup.")
-	if err != nil {
-		return nil, nil, errors.Wrapf(err, "Failed to create temporary directory under '%s'", GetVolumeMountPath(d.name, vol.volType, ""))
-	}
-	defer os.RemoveAll(unpackDir)
+	// Define function to unpack a volume from a backup tarball file.
+	unpackVolume := func(r io.ReadSeeker, unpacker []string, srcFile string, mountPath string) error {
+		d.Logger().Debug("Unpacking optimized volume", log.Ctx{"source": srcFile, "target": mountPath})
+		tr, cancelFunc, err := shared.CompressedTarReader(context.Background(), r, unpacker)
+		if err != nil {
+			return err
+		}
+		defer cancelFunc()
 
-	err = os.Chmod(unpackDir, 0100)
-	if err != nil {
-		return nil, nil, errors.Wrapf(err, "Failed to chmod '%s'", unpackDir)
-	}
+		for {
+			hdr, err := tr.Next()
+			if err == io.EOF {
+				break // End of archive
+			}
+			if err != nil {
+				return err
+			}
 
-	// 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
-	}
+			if hdr.Name == srcFile {
+				// Extract the backup.
+				err = shared.RunCommandWithFds(tr, nil, "btrfs", "receive", "-e", mountPath)
+				if err != nil {
+					return err
+				}
+
+				cancelFunc()
+				return nil
+			}
+		}
 
-	// Prepare tar arguments.
-	args := append(tarArgs, []string{
-		"-",
-		"--strip-components=1",
-		"-C", unpackDir, "backup",
-	}...)
+		return fmt.Errorf("Could not find %q", srcFile)
+	}
 
-	// Unpack the backup.
+	// Find the compression algorithm used for backup source data.
 	srcData.Seek(0, 0)
-	err = shared.RunCommandWithFds(srcData, nil, "tar", args...)
+	_, _, unpacker, err := shared.DetectCompressionFile(srcData)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -167,39 +174,26 @@ func (d *btrfs) CreateVolumeFromBackup(vol Volume, snapshots []string, srcData i
 	// Restore backups from oldest to newest.
 	snapshotsDir := GetVolumeSnapshotDir(d.name, vol.volType, vol.name)
 	for _, snapName := range snapshots {
-		// Open the backup.
-		feeder, err := os.Open(filepath.Join(unpackDir, "snapshots", fmt.Sprintf("%s.bin", snapName)))
-		if err != nil {
-			return nil, nil, errors.Wrapf(err, "Failed to open '%s'", filepath.Join(unpackDir, "snapshots", fmt.Sprintf("%s.bin", snapName)))
-		}
-		defer feeder.Close()
-
-		// Extract the backup.
-		err = shared.RunCommandWithFds(feeder, nil, "btrfs", "receive", "-e", snapshotsDir)
+		err = unpackVolume(srcData, unpacker, fmt.Sprintf("backup/snapshots/%s.bin", snapName), snapshotsDir)
 		if err != nil {
 			return nil, nil, err
 		}
 	}
 
-	// Open the backup.
-	feeder, err := os.Open(filepath.Join(unpackDir, "container.bin"))
+	// Create a temporary directory to unpack the backup into.
+	unpackDir, err := ioutil.TempDir(GetVolumeMountPath(d.name, vol.volType, ""), "backup.")
 	if err != nil {
-		return nil, nil, errors.Wrapf(err, "Failed to open '%s'", filepath.Join(unpackDir, "container.bin"))
+		return nil, nil, errors.Wrapf(err, "Failed to create temporary directory under '%s'", GetVolumeMountPath(d.name, vol.volType, ""))
 	}
-	defer feeder.Close()
+	defer os.RemoveAll(unpackDir)
 
-	// Extrack the backup.
-	err = shared.RunCommandWithFds(feeder, nil, "btrfs", "receive", "-e", unpackDir)
+	err = unpackVolume(srcData, unpacker, fmt.Sprintf("backup/container.bin"), unpackDir)
 	if err != nil {
 		return nil, nil, err
 	}
-	defer d.deleteSubvolume(filepath.Join(unpackDir, ".backup"), true)
 
 	// Re-create the writable subvolume.
 	err = d.snapshotSubvolume(filepath.Join(unpackDir, ".backup"), vol.MountPath(), false, false)
-	if err != nil {
-		return nil, nil, err
-	}
 
 	revert.Success()
 	return nil, revertHook, nil

From 6fa271e577b9148d0d4e53d17f3e766dc0636b6d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 24 Mar 2020 14:18:43 +0000
Subject: [PATCH 02/16] shared/archive: Adds CompressedTarReader function

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

diff --git a/shared/archive.go b/shared/archive.go
index 2746f60d4c..875874ffdb 100644
--- a/shared/archive.go
+++ b/shared/archive.go
@@ -1,12 +1,16 @@
 package shared
 
 import (
+	"archive/tar"
 	"bytes"
+	"context"
 	"fmt"
 	"io"
 	"os"
+	"os/exec"
 )
 
+// DetectCompression detects compression from a file name.
 func DetectCompression(fname string) ([]string, string, []string, error) {
 	f, err := os.Open(fname)
 	if err != nil {
@@ -54,3 +58,43 @@ func DetectCompressionFile(f io.Reader) ([]string, string, []string, error) {
 		return nil, "", nil, fmt.Errorf("Unsupported compression")
 	}
 }
+
+// CompressedTarReader returns a tar reader from the supplied (optionally compressed) tarball stream.
+// The unpacker arguments are those returned by DetectCompressionFile().
+// The returned cancelFunc should be called when finished with reader to clean up any resources used. This can be
+// done before reading to the end of the tarball if desired.
+func CompressedTarReader(ctx context.Context, r io.ReadSeeker, unpacker []string) (*tar.Reader, context.CancelFunc, error) {
+	ctx, cancelFunc := context.WithCancel(ctx)
+
+	r.Seek(0, 0)
+	var tr *tar.Reader
+
+	if len(unpacker) > 0 {
+		cmd := exec.CommandContext(ctx, unpacker[0], unpacker[1:]...)
+		cmd.Stdin = r
+		stdout, err := cmd.StdoutPipe()
+		if err != nil {
+			return nil, cancelFunc, err
+		}
+
+		err = cmd.Start()
+		if err != nil {
+			stdout.Close()
+			return nil, cancelFunc, err
+		}
+
+		// Wait for context and command to finish in go routine so reader can return.
+		go func() {
+			select {
+			case <-ctx.Done():
+				cmd.Wait()
+			}
+		}()
+
+		tr = tar.NewReader(stdout)
+	} else {
+		tr = tar.NewReader(r)
+	}
+
+	return tr, cancelFunc, nil
+}

From b989bf3be50049972d06b06785c83d71e04cb8f9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 24 Mar 2020 14:19:09 +0000
Subject: [PATCH 03/16] lxd/backup/backup: shared.CompressedTarReader usage

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

diff --git a/lxd/backup/backup.go b/lxd/backup/backup.go
index 813ab82157..47be52f619 100644
--- a/lxd/backup/backup.go
+++ b/lxd/backup/backup.go
@@ -1,15 +1,14 @@
 package backup
 
 import (
-	"archive/tar"
 	"context"
 	"fmt"
 	"io"
 	"os"
-	"os/exec"
 	"strings"
 	"time"
 
+	"github.com/pkg/errors"
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/lxd/project"
@@ -38,7 +37,6 @@ type Info struct {
 
 // GetInfo extracts backup information from a given ReadSeeker.
 func GetInfo(r io.ReadSeeker) (*Info, error) {
-	var tr *tar.Reader
 	result := Info{}
 	hasIndexFile := false
 
@@ -52,35 +50,16 @@ func GetInfo(r io.ReadSeeker) (*Info, error) {
 	if err != nil {
 		return nil, err
 	}
-	r.Seek(0, 0)
 
 	if unpacker == nil {
 		return nil, fmt.Errorf("Unsupported backup compression")
 	}
 
-	ctx, cancelFunc := context.WithCancel(context.Background())
-	defer cancelFunc()
-
-	if len(unpacker) > 0 {
-		cmd := exec.CommandContext(ctx, unpacker[0], unpacker[1:]...)
-		cmd.Stdin = r
-
-		stdout, err := cmd.StdoutPipe()
-		if err != nil {
-			return nil, err
-		}
-		defer stdout.Close()
-
-		err = cmd.Start()
-		if err != nil {
-			return nil, err
-		}
-		defer cmd.Wait()
-
-		tr = tar.NewReader(stdout)
-	} else {
-		tr = tar.NewReader(r)
+	tr, cancelFunc, err := shared.CompressedTarReader(context.Background(), r, unpacker)
+	if err != nil {
+		return nil, err
 	}
+	defer cancelFunc()
 
 	for {
 		hdr, err := tr.Next()
@@ -88,7 +67,7 @@ func GetInfo(r io.ReadSeeker) (*Info, error) {
 			break // End of archive
 		}
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrapf(err, "Error reading backup file info")
 		}
 
 		if hdr.Name == "backup/index.yaml" {

From 8bded34d8488c6d2a81b8a3c547e94bd64bd9952 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 24 Mar 2020 17:53:02 +0000
Subject: [PATCH 04/16] test/suites/static/analysis: Reinstates checks for
 shared/instancewriter

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 test/suites/static_analysis.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index ff95fb4057..8d5814d9f2 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -109,7 +109,7 @@ test_static_analysis() {
       golint -set_exit_status shared/api/...
       golint -set_exit_status shared/cancel/...
       golint -set_exit_status shared/cmd/...
-      golint -set_exit_status shared/containerwriter/...
+      golint -set_exit_status shared/instancewriter/...
       golint -set_exit_status shared/dnsutil/...
       golint -set_exit_status shared/eagain/...
       golint -set_exit_status shared/generate/...

From 1826b5ff51ea133e7ec3f9c907b27e7a13b1b220 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 24 Mar 2020 11:43:50 +0000
Subject: [PATCH 05/16] lxd/instance/post: InstanceID usage

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

diff --git a/lxd/instance_post.go b/lxd/instance_post.go
index b48974c624..791d43d8cd 100644
--- a/lxd/instance_post.go
+++ b/lxd/instance_post.go
@@ -244,7 +244,7 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 	}
 
 	// Check that the name isn't already in use.
-	id, _ := d.cluster.ContainerID(project, req.Name)
+	id, _ := d.cluster.InstanceID(project, req.Name)
 	if id > 0 {
 		return response.Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
 	}
@@ -373,7 +373,7 @@ func containerPostClusteringMigrate(d *Daemon, c instance.Instance, oldName, new
 		}
 
 		// Restore the original value of "volatile.apply_template"
-		id, err := d.cluster.ContainerID(c.Project(), destName)
+		id, err := d.cluster.InstanceID(c.Project(), destName)
 		if err != nil {
 			return errors.Wrap(err, "Failed to get ID of moved instance")
 		}

From 9cd93a2e1851caa19466aeb2ad10064231d6a069 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 24 Mar 2020 11:43:28 +0000
Subject: [PATCH 06/16] lxd/db/containers: Renames ContainerID to InstanceID

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

diff --git a/lxd/db/containers.go b/lxd/db/containers.go
index 47476e0046..dd4fed1e15 100644
--- a/lxd/db/containers.go
+++ b/lxd/db/containers.go
@@ -632,8 +632,8 @@ WHERE instances.id=?
 	return project, name, err
 }
 
-// ContainerID returns the ID of the container with the given name.
-func (c *Cluster) ContainerID(project, name string) (int, error) {
+// InstanceID returns the ID of the instance with the given name.
+func (c *Cluster) InstanceID(project, name string) (int, error) {
 	var id int64
 	err := c.Transaction(func(tx *ClusterTx) error {
 		var err error

From 5090fa9666165b80a053f1e5e923041363754258 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 24 Mar 2020 16:27:01 +0000
Subject: [PATCH 07/16] lxd/instances/post: Logging in createFromBackup

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

diff --git a/lxd/instances_post.go b/lxd/instances_post.go
index 87e6be7235..dc79878a26 100644
--- a/lxd/instances_post.go
+++ b/lxd/instances_post.go
@@ -586,6 +586,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 
 	// Parse the backup information.
 	backupFile.Seek(0, 0)
+	logger.Debug("Reading backup file info")
 	bInfo, err := backup.GetInfo(backupFile)
 	if err != nil {
 		backupFile.Close()
@@ -598,6 +599,16 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 		bInfo.Pool = pool
 	}
 
+	logger.Debug("Backup file info loaded", log.Ctx{
+		"type":      bInfo.Type,
+		"name":      bInfo.Name,
+		"project":   bInfo.Project,
+		"backend":   bInfo.Backend,
+		"pool":      bInfo.Pool,
+		"optimized": *bInfo.OptimizedStorage,
+		"snapshots": bInfo.Snapshots,
+	})
+
 	// Check storage pool exists.
 	_, _, err = d.State().Cluster.StoragePoolGet(bInfo.Pool)
 	if errors.Cause(err) == db.ErrNoSuchObject {

From a5188f301507e7091d14b07e499177a520b91f44 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 23 Mar 2020 17:48:37 +0000
Subject: [PATCH 08/16] lxd/instances/post: Logging message change from
 container to instance

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

diff --git a/lxd/instances_post.go b/lxd/instances_post.go
index dc79878a26..91302d98e3 100644
--- a/lxd/instances_post.go
+++ b/lxd/instances_post.go
@@ -710,7 +710,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 
 func containersPost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
-	logger.Debugf("Responding to container create")
+	logger.Debugf("Responding to instance create")
 
 	// If we're getting binary content, process separately
 	if r.Header.Get("Content-Type") == "application/octet-stream" {

From efb0d45e894e91c6224fe6986fd8f95f26dd8439 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 20 Mar 2020 15:30:33 +0000
Subject: [PATCH 09/16] lxd/instances/post: Switches to revert package in
 createFromBackup

Other minor formatting changes.

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

diff --git a/lxd/instances_post.go b/lxd/instances_post.go
index 91302d98e3..fb832fcdf1 100644
--- a/lxd/instances_post.go
+++ b/lxd/instances_post.go
@@ -26,6 +26,7 @@ import (
 	"github.com/lxc/lxd/lxd/operations"
 	projecthelpers "github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/revert"
 	storagePools "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -536,17 +537,20 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) response.
 }
 
 func createFromBackup(d *Daemon, project string, data io.Reader, pool string) response.Response {
+	revert := revert.New()
+	defer revert.Fail()
+
 	// Create temporary file to store uploaded backup data.
 	backupFile, err := ioutil.TempFile("", "lxd_backup_")
 	if err != nil {
 		return response.InternalError(err)
 	}
 	defer os.Remove(backupFile.Name())
+	revert.Add(func() { backupFile.Close() })
 
 	// Stream uploaded backup data into temporary file.
 	_, err = io.Copy(backupFile, data)
 	if err != nil {
-		backupFile.Close()
 		return response.InternalError(err)
 	}
 
@@ -554,7 +558,6 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 	backupFile.Seek(0, 0)
 	_, algo, decomArgs, err := shared.DetectCompressionFile(backupFile)
 	if err != nil {
-		backupFile.Close()
 		return response.InternalError(err)
 	}
 
@@ -565,7 +568,6 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 		// 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())
@@ -589,7 +591,6 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 	logger.Debug("Reading backup file info")
 	bInfo, err := backup.GetInfo(backupFile)
 	if err != nil {
-		backupFile.Close()
 		return response.BadRequest(err)
 	}
 	bInfo.Project = project
@@ -636,25 +637,19 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 		return response.InternalError(err)
 	}
 
+	// Copy reverter so far so we can use it inside run after this function has finished.
+	runRevert := revert.Clone()
+
 	run := func(op *operations.Operation) error {
 		defer backupFile.Close()
+		defer runRevert.Fail()
 
 		// Dump tarball to storage.
 		postHook, revertHook, err := instanceCreateFromBackup(d.State(), *bInfo, backupFile)
 		if err != nil {
 			return errors.Wrap(err, "Create instance from backup")
 		}
-
-		revert := true
-		defer func() {
-			if !revert {
-				return
-			}
-
-			if revertHook != nil {
-				revertHook()
-			}
-		}()
+		revert.Add(revertHook)
 
 		body, err := json.Marshal(&internalImportPost{
 			Name:  bInfo.Name,
@@ -664,14 +659,16 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 			return errors.Wrap(err, "Marshal internal import request")
 		}
 
+		// Generate internal request to import instance from storage.
 		req := &http.Request{
 			Body: ioutil.NopCloser(bytes.NewReader(body)),
 		}
+
 		req.URL = &url.URL{
 			RawQuery: fmt.Sprintf("project=%s", project),
 		}
-		resp := internalImport(d, req)
 
+		resp := internalImport(d, req)
 		if resp.String() != "success" {
 			return fmt.Errorf("Internal import request: %v", resp.String())
 		}
@@ -690,7 +687,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 			}
 		}
 
-		revert = false
+		runRevert.Success()
 		return nil
 	}
 
@@ -698,13 +695,12 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 	resources["instances"] = []string{bInfo.Name}
 	resources["containers"] = resources["instances"]
 
-	op, err := operations.OperationCreate(d.State(), project, operations.OperationClassTask, db.OperationBackupRestore,
-		resources, nil, run, nil, nil)
+	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)
 	}
 
+	revert.Success()
 	return operations.OperationResponse(op)
 }
 

From 824186f4b5f2a393329037bc5fb3b33907cd11a5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 23 Mar 2020 15:24:05 +0000
Subject: [PATCH 10/16] lxd: Merges instanceCreateFromBackup into
 createFromBackup

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance.go       | 21 ---------------------
 lxd/instances_post.go | 13 +++++++++++--
 2 files changed, 11 insertions(+), 23 deletions(-)

diff --git a/lxd/instance.go b/lxd/instance.go
index eb306f4d62..e66561789c 100644
--- a/lxd/instance.go
+++ b/lxd/instance.go
@@ -3,7 +3,6 @@ package main
 import (
 	"context"
 	"fmt"
-	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -16,7 +15,6 @@ import (
 	cron "gopkg.in/robfig/cron.v2"
 
 	"github.com/flosch/pongo2"
-	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
@@ -72,25 +70,6 @@ func instanceCreateAsEmpty(d *Daemon, args db.InstanceArgs) (instance.Instance,
 	return inst, nil
 }
 
-// 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 necessary 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) {
-	pool, err := storagePools.GetPoolByName(s, info.Pool)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	postHook, revertHook, err := pool.CreateInstanceFromBackup(info, srcData, nil)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return postHook, revertHook, nil
-}
-
 // instanceCreateFromImage creates an instance from a rootfs image.
 func instanceCreateFromImage(d *Daemon, args db.InstanceArgs, hash string, op *operations.Operation) (instance.Instance, error) {
 	s := d.State()
diff --git a/lxd/instances_post.go b/lxd/instances_post.go
index fb832fcdf1..768a82d4a0 100644
--- a/lxd/instances_post.go
+++ b/lxd/instances_post.go
@@ -644,8 +644,17 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) re
 		defer backupFile.Close()
 		defer runRevert.Fail()
 
-		// Dump tarball to storage.
-		postHook, revertHook, err := instanceCreateFromBackup(d.State(), *bInfo, backupFile)
+		pool, err := storagePools.GetPoolByName(d.State(), bInfo.Pool)
+		if err != nil {
+			return err
+		}
+
+		// Dump tarball to storage. Because the backup file is unpacked and restored onto the storage
+		// device before the instance is created in the database it is necessary 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.
+		postHook, revertHook, err := pool.CreateInstanceFromBackup(*bInfo, backupFile, nil)
 		if err != nil {
 			return errors.Wrap(err, "Create instance from backup")
 		}

From 5682095f3ccf80097a08edde7440382f2f3bf4d9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 20 Mar 2020 12:10:10 +0000
Subject: [PATCH 11/16] lxd/storage/drivers/utils: Adds blockDevSizeBytes
 function

For retrieving size of block devices in bytes.

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

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index fdd1bfb29b..b68e2af454 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"path/filepath"
 	"sort"
+	"strconv"
 	"strings"
 	"time"
 
@@ -732,3 +733,18 @@ func ShiftZFSSkipper(dir string, absPath string, fi os.FileInfo) bool {
 
 	return false
 }
+
+// blockDevSizeBytes returns the size of a block device.
+func blockDevSizeBytes(blockDevPath string) (int64, error) {
+	output, err := shared.RunCommand("blockdev", "--getsize64", blockDevPath)
+	if err != nil {
+		return -1, err
+	}
+
+	sizeBytes, err := strconv.ParseInt(strings.TrimSpace(output), 10, 64)
+	if err != nil {
+		return -1, err
+	}
+
+	return sizeBytes, nil
+}

From 4a604e61a2b6fcef4f20eaa6c636dfebd4cdce3e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 20 Mar 2020 12:10:42 +0000
Subject: [PATCH 12/16] lxd/storage/drivers/driver/ceph/volumes: Updates
 SetVolumeQuota to use blockDevSizeBytes

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

diff --git a/lxd/storage/drivers/driver_ceph_volumes.go b/lxd/storage/drivers/driver_ceph_volumes.go
index 9cb8cd49a8..fa967dbcdb 100644
--- a/lxd/storage/drivers/driver_ceph_volumes.go
+++ b/lxd/storage/drivers/driver_ceph_volumes.go
@@ -4,10 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
-	"path/filepath"
-	"strconv"
 	"strings"
 
 	"github.com/pborman/uuid"
@@ -676,26 +673,18 @@ func (d *ceph) SetVolumeQuota(vol Volume, size string, op *operations.Operation)
 		return err
 	}
 
-	// The grow/shrink functions use Mount/Unmount which may cause an
-	// unmap, so make sure to keep a reference.
+	// The grow/shrink functions use Mount/Unmount which may cause an unmap, so make sure to keep a reference.
 	oldKeepDevice := vol.keepDevice
 	vol.keepDevice = true
 	defer func() {
 		vol.keepDevice = oldKeepDevice
 	}()
 
-	RBDSize, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/block/%s/size", filepath.Base(RBDDevPath)))
+	oldSizeBytes, err := blockDevSizeBytes(RBDDevPath)
 	if err != nil {
 		return errors.Wrapf(err, "Error getting current size")
 	}
 
-	RBDSizeBlocks, err := strconv.Atoi(strings.TrimSpace(string(RBDSize)))
-	if err != nil {
-		return errors.Wrapf(err, "Error getting converting current size to integer")
-	}
-
-	oldSizeBytes := int64(RBDSizeBlocks * 512)
-
 	newSizeBytes, err := units.ParseByteSizeString(size)
 	if err != nil {
 		return err

From a232f3d2caf3f2a32dd76d8d98f4540901e04054 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 20 Mar 2020 12:09:04 +0000
Subject: [PATCH 13/16] shared/instancewriter/instance/file/info: Adds FileInfo
 for os.FileInfo implementation

Used to generate 'fake' file info when streaming block devices into a tarball.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/instancewriter/instance_file_info.go | 52 +++++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 shared/instancewriter/instance_file_info.go

diff --git a/shared/instancewriter/instance_file_info.go b/shared/instancewriter/instance_file_info.go
new file mode 100644
index 0000000000..0d0b288a4a
--- /dev/null
+++ b/shared/instancewriter/instance_file_info.go
@@ -0,0 +1,52 @@
+package instancewriter
+
+import (
+	"archive/tar"
+	"os"
+	"time"
+)
+
+// FileInfo static file implementation of os.FileInfo.
+type FileInfo struct {
+	FileName    string
+	FileSize    int64
+	FileMode    os.FileMode
+	FileModTime time.Time
+}
+
+// Name of file.
+func (f *FileInfo) Name() string {
+	return f.FileName
+}
+
+// Size of file.
+func (f *FileInfo) Size() int64 {
+	return f.FileSize
+}
+
+// Mode of file.
+func (f *FileInfo) Mode() os.FileMode {
+	return f.FileMode
+}
+
+// ModTime of file.
+func (f *FileInfo) ModTime() time.Time {
+	return f.FileModTime
+}
+
+// IsDir is file a directory.
+func (f *FileInfo) IsDir() bool {
+	return false
+}
+
+// Sys returns further unix attributes for a file owned by root.
+func (f *FileInfo) Sys() interface{} {
+	return &tar.Header{
+		Uid:        0,
+		Gid:        0,
+		Uname:      "root",
+		Gname:      "root",
+		AccessTime: time.Now(),
+		ChangeTime: time.Now(),
+	}
+}

From 8d12f9e5fb73c33d05c77fae7ea5bee0f0f8200e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 20 Mar 2020 12:08:32 +0000
Subject: [PATCH 14/16] shared/instancewriter/instance/tar/writer: Adds
 WriteFileFromReader function

For streaming files into a tarball from an io.Reader.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/instancewriter/instance_tar_writer.go | 23 ++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/shared/instancewriter/instance_tar_writer.go b/shared/instancewriter/instance_tar_writer.go
index 52a422f9ab..021858fa64 100644
--- a/shared/instancewriter/instance_tar_writer.go
+++ b/shared/instancewriter/instance_tar_writer.go
@@ -103,7 +103,8 @@ func (ctw *InstanceTarWriter) WriteFile(name string, srcPath string, fi os.FileI
 		}
 	}
 
-	if err := ctw.tarWriter.WriteHeader(hdr); err != nil {
+	err = ctw.tarWriter.WriteHeader(hdr)
+	if err != nil {
 		return errors.Wrap(err, "Failed to write tar header")
 	}
 
@@ -114,7 +115,8 @@ func (ctw *InstanceTarWriter) WriteFile(name string, srcPath string, fi os.FileI
 		}
 		defer f.Close()
 
-		if _, err := io.Copy(ctw.tarWriter, f); err != nil {
+		_, err = io.Copy(ctw.tarWriter, f)
+		if err != nil {
 			return errors.Wrapf(err, "Failed to copy file content %q", srcPath)
 		}
 	}
@@ -122,6 +124,23 @@ func (ctw *InstanceTarWriter) WriteFile(name string, srcPath string, fi os.FileI
 	return nil
 }
 
+// WriteFileFromReader streams a file into the tarball using the src reader.
+// A manually generated os.FileInfo should be supplied so that the tar header can be added before streaming starts.
+func (ctw *InstanceTarWriter) WriteFileFromReader(src io.Reader, fi os.FileInfo) error {
+	hdr, err := tar.FileInfoHeader(fi, "")
+	if err != nil {
+		return errors.Wrap(err, "Failed to create tar info header")
+	}
+
+	err = ctw.tarWriter.WriteHeader(hdr)
+	if err != nil {
+		return errors.Wrap(err, "Failed to write tar header")
+	}
+
+	_, err = io.Copy(ctw.tarWriter, src)
+	return err
+}
+
 // Close finishes writing the tarball.
 func (ctw *InstanceTarWriter) Close() error {
 	err := ctw.tarWriter.Close()

From ab2201a7c0c0f977cc50b8b0e68c59169c55d690 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 20 Mar 2020 12:12:28 +0000
Subject: [PATCH 15/16] lxd/backup: Switches index.yaml file generation to use
 WriteFileFromReader in backupCreate

This is to avoid the need for a temporary dir and file.

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

diff --git a/lxd/backup.go b/lxd/backup.go
index b96fb44060..8385bb75d0 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -1,11 +1,10 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
-	"path/filepath"
 	"time"
 
 	"context"
@@ -95,15 +94,6 @@ func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instanc
 
 	target := shared.VarPath("backups", project.Instance(sourceInst.Project(), b.Name()))
 
-	// Create temp dir for storing transient files that will be removed at end.
-	tmpDirPath := fmt.Sprintf("%s_tmp", target)
-	logger.Debug("Creating temporary backup directory", log.Ctx{"path": tmpDirPath})
-	err = os.Mkdir(tmpDirPath, 0700)
-	if err != nil {
-		return err
-	}
-	defer os.RemoveAll(tmpDirPath)
-
 	// Setup the tarball writer.
 	logger.Debug("Opening backup tarball for writing", log.Ctx{"path": target})
 	tarFileWriter, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0600)
@@ -142,9 +132,8 @@ func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instanc
 	}(tarWriterRes)
 
 	// Write index file.
-	indexFile := filepath.Join(tmpDirPath, "index.yaml")
-	logger.Debug("Adding backup index file", log.Ctx{"path": indexFile})
-	err = backupWriteIndex(sourceInst, pool, b.OptimizedStorage(), !b.InstanceOnly(), indexFile, tarWriter)
+	logger.Debug("Adding backup index file")
+	err = backupWriteIndex(sourceInst, pool, b.OptimizedStorage(), !b.InstanceOnly(), tarWriter)
 	if err != nil {
 		return errors.Wrapf(err, "Error writing backup index file")
 	}
@@ -176,7 +165,7 @@ func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instanc
 }
 
 // backupWriteIndex generates an index.yaml file and then writes it to the root of the backup tarball.
-func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, optimized bool, snapshots bool, indexFile string, tarWriter *instancewriter.InstanceTarWriter) error {
+func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, optimized bool, snapshots bool, tarWriter *instancewriter.InstanceTarWriter) error {
 	indexInfo := backup.Info{
 		Name:             sourceInst.Name(),
 		Pool:             pool.Name(),
@@ -203,21 +192,17 @@ func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, opti
 	if err != nil {
 		return err
 	}
+	r := bytes.NewReader(indexData)
 
-	// Write index JSON to file.
-	err = ioutil.WriteFile(indexFile, indexData, 0644)
-	if err != nil {
-		return err
-	}
-	defer os.Remove(indexFile)
-
-	indexFileInfo, err := os.Lstat(indexFile)
-	if err != nil {
-		return err
+	indexFileInfo := instancewriter.FileInfo{
+		FileName:    "backup/index.yaml",
+		FileSize:    int64(len(indexData)),
+		FileMode:    0644,
+		FileModTime: time.Now(),
 	}
 
 	// Write to tarball.
-	err = tarWriter.WriteFile("backup/index.yaml", indexFile, indexFileInfo)
+	err = tarWriter.WriteFileFromReader(r, &indexFileInfo)
 	if err != nil {
 		return err
 	}

From a5993df492627c09ed3bd10aa9381af6423f7843 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 25 Mar 2020 11:59:34 +0000
Subject: [PATCH 16/16] lxd/api/internal: d.cluster.InstanceID usage

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

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 9ca6c1aed4..88e46b1432 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -531,7 +531,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
 	}
 
 	// Check if an entry for the container already exists in the db.
-	_, containerErr := d.cluster.ContainerID(projectName, req.Name)
+	_, containerErr := d.cluster.InstanceID(projectName, req.Name)
 	if containerErr != nil {
 		if containerErr != db.ErrNoSuchObject {
 			return response.SmartError(containerErr)


More information about the lxc-devel mailing list