[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