The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6649

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From b9f219ba53ea8a516e06a3ac4a095388ab80c691 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Thu, 19 Dec 2019 12:15:58 -0500
Subject: [PATCH 1/6] lxd/storage/drivers: Introduce vfsBackupVolume
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/storage/drivers/driver_common.go      | 62 +++++++++++++++++++++++
 lxd/storage/drivers/driver_dir_volumes.go | 51 +------------------
 2 files changed, 64 insertions(+), 49 deletions(-)

diff --git a/lxd/storage/drivers/driver_common.go 
b/lxd/storage/drivers/driver_common.go
index cd85e90f98..f671f65c2c 100644
--- a/lxd/storage/drivers/driver_common.go
+++ b/lxd/storage/drivers/driver_common.go
@@ -283,3 +283,65 @@ func (d *common) vfsGetVolumeDiskPath(vol Volume) (string, 
error) {
 
        return filepath.Join(vol.MountPath(), "root.img"), nil
 }
+
+// vfsBackupVolume is a generic BackupVolume implementation for VFS-only 
drivers.
+func (d *common) vfsBackupVolume(vol Volume, targetPath string, snapshots 
bool, op *operations.Operation) error {
+       bwlimit := d.config["rsync.bwlimit"]
+
+       // Backups only implemented for containers currently.
+       if vol.volType != VolumeTypeContainer {
+               return ErrNotImplemented
+       }
+       // Handle snapshots.
+       if snapshots {
+               snapshotsPath := filepath.Join(targetPath, "snapshots")
+
+               // List the snapshots.
+               snapshots, err := vol.Snapshots(op)
+               if err != nil {
+                       return err
+               }
+
+               // Create the snapshot path.
+               if len(snapshots) > 0 {
+                       err = os.MkdirAll(snapshotsPath, 0711)
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               for _, snapshot := range snapshots {
+                       _, snapName, _ := 
shared.InstanceGetParentAndSnapshotName(snapshot.Name())
+                       target := filepath.Join(snapshotsPath, snapName)
+
+                       // Copy the snapshot.
+                       err = snapshot.MountTask(func(mountPath string, op 
*operations.Operation) error {
+                               _, err := rsync.LocalCopy(mountPath, target, 
bwlimit, true)
+                               if err != nil {
+                                       return err
+                               }
+
+                               return nil
+                       }, op)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // Copy the parent volume itself.
+       target := filepath.Join(targetPath, "container")
+       err := vol.MountTask(func(mountPath string, op *operations.Operation) 
error {
+               _, err := rsync.LocalCopy(mountPath, target, bwlimit, true)
+               if err != nil {
+                       return err
+               }
+
+               return nil
+       }, op)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
diff --git a/lxd/storage/drivers/driver_dir_volumes.go 
b/lxd/storage/drivers/driver_dir_volumes.go
index bda0866ef1..28790c9209 100644
--- a/lxd/storage/drivers/driver_dir_volumes.go
+++ b/lxd/storage/drivers/driver_dir_volumes.go
@@ -4,7 +4,6 @@ import (
        "fmt"
        "io"
        "os"
-       "path/filepath"
 
        "github.com/lxc/lxd/lxd/migration"
        "github.com/lxc/lxd/lxd/operations"
@@ -293,54 +292,8 @@ func (d *dir) MigrateVolume(vol Volume, conn 
io.ReadWriteCloser, volSrcArgs migr
 
 // BackupVolume copies a volume (and optionally its snapshots) to a specified 
target path.
 // This driver does not support optimized backups.
-func (d *dir) BackupVolume(vol Volume, targetPath string, _, snapshots bool, 
op *operations.Operation) error {
-       bwlimit := d.config["rsync.bwlimit"]
-
-       var parentVolDir string
-
-       // Backups only implemented for containers currently.
-       if vol.volType == VolumeTypeContainer {
-               parentVolDir = "container"
-       } else {
-               return ErrNotImplemented
-       }
-
-       // Handle snapshots.
-       if snapshots {
-               snapshotsPath := filepath.Join(targetPath, "snapshots")
-               snapshots, err := vol.Snapshots(op)
-               if err != nil {
-                       return err
-               }
-
-               // Create the snapshot path.
-               if len(snapshots) > 0 {
-                       err = os.MkdirAll(snapshotsPath, 0711)
-                       if err != nil {
-                               return err
-                       }
-               }
-
-               for _, snap := range snapshots {
-                       _, snapName, _ := 
shared.InstanceGetParentAndSnapshotName(snap.Name())
-                       target := filepath.Join(snapshotsPath, snapName)
-
-                       // Copy the snapshot.
-                       _, err := rsync.LocalCopy(snap.MountPath(), target, 
bwlimit, true)
-                       if err != nil {
-                               return fmt.Errorf("Failed to rsync: %s", err)
-                       }
-               }
-       }
-
-       // Copy the parent volume itself.
-       target := filepath.Join(targetPath, parentVolDir)
-       _, err := rsync.LocalCopy(vol.MountPath(), target, bwlimit, true)
-       if err != nil {
-               return fmt.Errorf("Failed to rsync: %s", err)
-       }
-
-       return nil
+func (d *dir) BackupVolume(vol Volume, targetPath string, optimized bool, 
snapshots bool, op *operations.Operation) error {
+       return d.vfsBackupVolume(vol, targetPath, snapshots, op)
 }
 
 // CreateVolumeSnapshot creates a snapshot of a volume.

From d2b0eb98fd6e34cc88759949fd54312dd07655e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 17 Dec 2019 12:48:20 -0500
Subject: [PATCH 2/6] lxd/storage/utils: Add fsUUID
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/storage/drivers/utils.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index a2230b18ab..b5fc345dfa 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -163,6 +163,10 @@ func TryUnmount(path string, flags int) error {
        return nil
 }
 
+func fsUUID(path string) (string, error) {
+       return shared.RunCommand("blkid", "-s", "UUID", "-o", "value", path)
+}
+
 // GetPoolMountPath returns the mountpoint of the given pool.
 // {LXD_DIR}/storage-pools/<pool>
 func GetPoolMountPath(poolName string) string {

From 73a68159880f3075c3bd760ec1d3655164c2c473 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 17 Dec 2019 12:53:20 -0500
Subject: [PATCH 3/6] lxd/storage/utils: Add tryExists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/storage/drivers/utils.go | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index b5fc345dfa..fb84da724c 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -163,6 +163,19 @@ func TryUnmount(path string, flags int) error {
        return nil
 }
 
+func tryExists(path string) bool {
+       // Attempt 20 checks over 10s
+       for i := 0; i < 20; i++ {
+               if shared.PathExists(path) {
+                       return true
+               }
+
+               time.Sleep(500 * time.Millisecond)
+       }
+
+       return false
+}
+
 func fsUUID(path string) (string, error) {
        return shared.RunCommand("blkid", "-s", "UUID", "-o", "value", path)
 }

From 37caa39572cbb6397abb9a68c6f9378a9af8a5a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 17 Dec 2019 12:54:13 -0500
Subject: [PATCH 4/6] lxd/storage/utils: Add hasFilesystem
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/storage/drivers/utils.go | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index fb84da724c..d65481f259 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -180,6 +180,21 @@ func fsUUID(path string) (string, error) {
        return shared.RunCommand("blkid", "-s", "UUID", "-o", "value", path)
 }
 
+func hasFilesystem(path string, fsType int64) bool {
+       fs := unix.Statfs_t{}
+
+       err := unix.Statfs(path, &fs)
+       if err != nil {
+               return false
+       }
+
+       if int64(fs.Type) != fsType {
+               return false
+       }
+
+       return true
+}
+
 // GetPoolMountPath returns the mountpoint of the given pool.
 // {LXD_DIR}/storage-pools/<pool>
 func GetPoolMountPath(poolName string) string {

From afda79ab633c02c24b70617569ff3248d613b465 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Mon, 18 Nov 2019 15:24:41 +0100
Subject: [PATCH 5/6] lxd/storage/drivers: Add btrfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This adds the btrfs storage driver.

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/storage/drivers/driver_btrfs.go         | 361 ++++++++++
 lxd/storage/drivers/driver_btrfs_utils.go   | 374 ++++++++++
 lxd/storage/drivers/driver_btrfs_volumes.go | 760 ++++++++++++++++++++
 lxd/storage/drivers/load.go                 |   1 +
 4 files changed, 1496 insertions(+)
 create mode 100644 lxd/storage/drivers/driver_btrfs.go
 create mode 100644 lxd/storage/drivers/driver_btrfs_utils.go
 create mode 100644 lxd/storage/drivers/driver_btrfs_volumes.go

diff --git a/lxd/storage/drivers/driver_btrfs.go 
b/lxd/storage/drivers/driver_btrfs.go
new file mode 100644
index 0000000000..cbc1016628
--- /dev/null
+++ b/lxd/storage/drivers/driver_btrfs.go
@@ -0,0 +1,361 @@
+package drivers
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "strings"
+
+       "golang.org/x/sys/unix"
+
+       "github.com/lxc/lxd/lxd/migration"
+       "github.com/lxc/lxd/lxd/operations"
+       "github.com/lxc/lxd/lxd/util"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/api"
+       "github.com/lxc/lxd/shared/logger"
+       "github.com/lxc/lxd/shared/units"
+)
+
+var btrfsVersion string
+var btrfsLoaded bool
+
+type btrfs struct {
+       common
+}
+
+// load is used to run one-time action per-driver rather than per-pool.
+func (d *btrfs) load() error {
+       if btrfsLoaded {
+               return nil
+       }
+
+       // Validate the required binaries.
+       for _, tool := range []string{"btrfs"} {
+               _, err := exec.LookPath(tool)
+               if err != nil {
+                       return fmt.Errorf("Required tool '%s' is missing", tool)
+               }
+       }
+
+       // Detect and record the version.
+       if btrfsVersion == "" {
+               out, err := shared.RunCommand("btrfs", "version")
+               if err != nil {
+                       return err
+               }
+
+               count, err := fmt.Sscanf(strings.SplitN(out, " ", 2)[1], 
"v%s\n", &btrfsVersion)
+               if err != nil || count != 1 {
+                       return fmt.Errorf("The 'btrfs' tool isn't working 
properly")
+               }
+       }
+
+       btrfsLoaded = true
+       return nil
+}
+
+// Info returns info about the driver and its environment.
+func (d *btrfs) Info() Info {
+       return Info{
+               Name:                  "btrfs",
+               Version:               btrfsVersion,
+               OptimizedImages:       true,
+               PreservesInodes:       !d.state.OS.RunningInUserNS,
+               Remote:                false,
+               VolumeTypes:           []VolumeType{VolumeTypeCustom, 
VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
+               BlockBacking:          false,
+               RunningQuotaResize:    true,
+               RunningSnapshotFreeze: false,
+       }
+}
+
+// Create is called during pool creation and is effectively using an empty 
driver struct.
+// WARNING: The Create() function cannot rely on any of the struct attributes 
being set.
+func (d *btrfs) Create() error {
+       // Store the provided source as we are likely to be mangling it.
+       d.config["volatile.initial_source"] = d.config["source"]
+
+       loopPath := filepath.Join(shared.VarPath("disks"), 
fmt.Sprintf("%s.img", d.name))
+       if d.config["source"] == "" || d.config["source"] == loopPath {
+               // Create a loop based pool.
+               d.config["source"] = loopPath
+
+               // Create the loop file itself.
+               size, err := units.ParseByteSizeString(d.config["size"])
+               if err != nil {
+                       return err
+               }
+
+               err = createSparseFile(d.config["source"], size)
+               if err != nil {
+                       return fmt.Errorf("Failed to create the sparse file: 
%v", err)
+               }
+
+               // Format the file.
+               _, err = makeFSType(d.config["source"], "btrfs", 
&mkfsOptions{Label: d.name})
+               if err != nil {
+                       return fmt.Errorf("Failed to format sparse file: %v", 
err)
+               }
+       } else if shared.IsBlockdevPath(d.config["source"]) {
+               // Format the block device.
+               _, err := makeFSType(d.config["source"], "btrfs", 
&mkfsOptions{Label: d.name})
+               if err != nil {
+                       return fmt.Errorf("Failed to format block device: %v", 
err)
+               }
+
+               // Record the UUID as the source.
+               devUUID, err := fsUUID(d.config["source"])
+               if err != nil {
+                       return err
+               }
+
+               // Confirm that the symlink is appearing (give it 10s).
+               if tryExists(fmt.Sprintf("/dev/disk/by-uuid/%s", devUUID)) {
+                       // Override the config to use the UUID.
+                       d.config["source"] = devUUID
+               }
+       } else if d.config["source"] != "" {
+               hostPath := shared.HostPath(d.config["source"])
+               if d.isSubvolume(hostPath) {
+                       // Existing btrfs subvolume.
+                       subvols, err := d.getSubvolumes(hostPath)
+                       if err != nil {
+                               return fmt.Errorf("Could not determine if 
existing btrfs subvolume is empty: %v", err)
+                       }
+
+                       // Check that the provided subvolume is empty.
+                       if len(subvols) > 0 {
+                               return fmt.Errorf("Requested btrfs subvolume 
exists but is not empty")
+                       }
+               } else {
+                       // New btrfs subvolume on existing btrfs filesystem.
+                       cleanSource := filepath.Clean(hostPath)
+                       lxdDir := shared.VarPath()
+
+                       if shared.PathExists(hostPath) && 
!hasFilesystem(hostPath, util.FilesystemSuperMagicBtrfs) {
+                               return fmt.Errorf("Provided path does not 
reside on a btrfs filesystem")
+                       } else if strings.HasPrefix(cleanSource, lxdDir) {
+                               if cleanSource != GetPoolMountPath(d.name) {
+                                       return fmt.Errorf("Only allowed source 
path under %s is %s", shared.VarPath(), GetPoolMountPath(d.name))
+                               } else if 
!hasFilesystem(shared.VarPath("storage-pools"), util.FilesystemSuperMagicBtrfs) 
{
+                                       return fmt.Errorf("Provided path does 
not reside on a btrfs filesystem")
+                               }
+
+                               // Delete the current directory to replace by 
subvolume.
+                               err := os.Remove(cleanSource)
+                               if err != nil {
+                                       return err
+                               }
+                       }
+
+                       // Create the subvolume.
+                       _, err := shared.RunCommand("btrfs", "subvolume", 
"create", hostPath)
+                       if err != nil {
+                               return err
+                       }
+               }
+       } else {
+               return fmt.Errorf("Invalid \"source\" property")
+       }
+
+       return nil
+}
+
+// Delete removes the storage pool from the storage device.
+func (d *btrfs) Delete(op *operations.Operation) error {
+       // If the user completely destroyed it, call it done.
+       if !shared.PathExists(GetPoolMountPath(d.name)) {
+               return nil
+       }
+
+       // Delete potential intermediate btrfs subvolumes.
+       for _, volType := range d.Info().VolumeTypes {
+               for _, dir := range BaseDirectories[volType] {
+                       path := filepath.Join(GetPoolMountPath(d.name), dir)
+                       if !shared.PathExists(path) {
+                               continue
+                       }
+
+                       if !d.isSubvolume(path) {
+                               continue
+                       }
+
+                       err := d.deleteSubvolume(path, true)
+                       if err != nil {
+                               return fmt.Errorf("Could not delete btrfs 
subvolume: %s", path)
+                       }
+               }
+       }
+
+       // On delete, wipe everything in the directory.
+       err := wipeDirectory(GetPoolMountPath(d.name))
+       if err != nil {
+               return err
+       }
+
+       // Unmount the path.
+       _, err = d.Unmount()
+       if err != nil {
+               return err
+       }
+
+       // If the pool path is a subvolume itself, delete it.
+       if d.isSubvolume(GetPoolMountPath(d.name)) {
+               err := d.deleteSubvolume(GetPoolMountPath(d.name), false)
+               if err != nil {
+                       return err
+               }
+
+               // And re-create as an empty directory to make the backend 
happy.
+               err = os.Mkdir(GetPoolMountPath(d.name), 0700)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Delete any loop file we may have used.
+       loopPath := filepath.Join(shared.VarPath("disks"), 
fmt.Sprintf("%s.img", d.name))
+       if shared.PathExists(loopPath) {
+               err = os.Remove(loopPath)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// Validate checks that all provide keys are supported and that no conflicting 
or missing configuration is present.
+func (d *btrfs) Validate(config map[string]string) error {
+       return nil
+}
+
+// Update applies any driver changes required from a configuration change.
+func (d *btrfs) Update(changedConfig map[string]string) error {
+       // We only care about btrfs.mount_options.
+       val, ok := changedConfig["btrfs.mount_options"]
+       if !ok {
+               return nil
+       }
+
+       // Trigger a re-mount.
+       d.config["btrfs.mount_options"] = val
+       mntFlags, mntOptions := resolveMountOptions(d.getMountOptions())
+       mntFlags |= unix.MS_REMOUNT
+
+       err := TryMount("", GetPoolMountPath(d.name), "none", mntFlags, 
mntOptions)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// Mount mounts the storage pool.
+func (d *btrfs) Mount() (bool, error) {
+       // Check if already mounted.
+       if shared.IsMountPoint(GetPoolMountPath(d.name)) {
+               logger.Errorf("here: %v", d.name)
+               return false, nil
+       }
+
+       // Setup mount options.
+       loopPath := filepath.Join(shared.VarPath("disks"), 
fmt.Sprintf("%s.img", d.name))
+       mntSrc := ""
+       mntDst := GetPoolMountPath(d.name)
+       mntFilesystem := "btrfs"
+       if d.config["source"] == loopPath {
+               // Bring up the loop device.
+               loopF, err := PrepareLoopDev(d.config["source"], 
LoFlagsAutoclear)
+               if err != nil {
+                       return false, err
+               }
+               defer loopF.Close()
+
+               mntSrc = loopF.Name()
+       } else if filepath.IsAbs(d.config["source"]) {
+               // Bring up an existing device or path.
+               mntSrc = shared.HostPath(d.config["source"])
+
+               if !shared.IsBlockdevPath(mntSrc) {
+                       mntFilesystem = "none"
+
+                       if !hasFilesystem(mntSrc, 
util.FilesystemSuperMagicBtrfs) {
+                               return false, fmt.Errorf("Source path '%s' 
isn't btrfs", mntSrc)
+                       }
+               }
+       } else {
+               // Mount using UUID.
+               mntSrc = fmt.Sprintf("/dev/disk/by-uuid/%s", d.config["source"])
+       }
+
+       // Get the custom mount flags/options.
+       mntFlags, mntOptions := resolveMountOptions(d.getMountOptions())
+
+       // Handle bind-mounts first.
+       if mntFilesystem == "none" {
+               // Setup the bind-mount itself.
+               err := TryMount(mntSrc, mntDst, mntFilesystem, unix.MS_BIND, "")
+               if err != nil {
+                       return false, err
+               }
+
+               // Now apply the custom options.
+               mntFlags |= unix.MS_REMOUNT
+               err = TryMount("", mntDst, mntFilesystem, mntFlags, mntOptions)
+               if err != nil {
+                       return false, err
+               }
+
+               return true, nil
+       }
+
+       // Handle traditional mounts.
+       err := TryMount(mntSrc, mntDst, mntFilesystem, mntFlags, mntOptions)
+       if err != nil {
+               return false, err
+       }
+
+       return true, nil
+}
+
+// Unmount unmounts the storage pool.
+func (d *btrfs) Unmount() (bool, error) {
+       return forceUnmount(GetPoolMountPath(d.name))
+}
+
+// GetResources returns the pool resource usage information.
+func (d *btrfs) GetResources() (*api.ResourcesStoragePool, error) {
+       return d.vfsGetResources()
+}
+
+// MigrationType returns the type of transfer methods to be used when doing 
migrations between pools in preference order.
+func (d *btrfs) MigrationTypes(contentType ContentType, refresh bool) 
[]migration.Type {
+       if contentType != ContentTypeFS {
+               return nil
+       }
+
+       // When performing a refresh, always use rsync. Using btrfs send/receive
+       // here doesn't make sense since it would need to send everything again
+       // which defeats the purpose of a refresh.
+       if refresh {
+               return []migration.Type{
+                       {
+                               FSType:   migration.MigrationFSType_RSYNC,
+                               Features: []string{"xattrs", "delete", 
"compress", "bidirectional"},
+                       },
+               }
+       }
+
+       return []migration.Type{
+               {
+                       FSType: migration.MigrationFSType_BTRFS,
+               },
+               {
+                       FSType:   migration.MigrationFSType_RSYNC,
+                       Features: []string{"xattrs", "delete", "compress", 
"bidirectional"},
+               },
+       }
+}
diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
new file mode 100644
index 0000000000..5f759dd3c6
--- /dev/null
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -0,0 +1,374 @@
+package drivers
+
+import (
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "sort"
+       "strconv"
+       "strings"
+
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/ioprogress"
+       "github.com/lxc/lxd/shared/logger"
+       "golang.org/x/sys/unix"
+)
+
+// Errors
+var errBtrfsNoQuota = fmt.Errorf("Quotas disabled on filesystem")
+var errBtrfsNoQGroup = fmt.Errorf("Unable to find quota group")
+
+func (d *btrfs) getMountOptions() string {
+       // Allow overriding the default options.
+       if d.config["btrfs.mount_options"] != "" {
+               return d.config["btrfs.mount_options"]
+       }
+
+       return "user_subvol_rm_allowed"
+}
+
+func (d *btrfs) isSubvolume(path string) bool {
+       // Stat the path.
+       fs := unix.Stat_t{}
+       err := unix.Lstat(path, &fs)
+       if err != nil {
+               return false
+       }
+
+       // Check if BTRFS_FIRST_FREE_OBJECTID is the inode number.
+       if fs.Ino != 256 {
+               return false
+       }
+
+       return true
+}
+
+func (d *btrfs) getSubvolumes(path string) ([]string, error) {
+       result := []string{}
+
+       // Make sure the path has a trailing slash.
+       if !strings.HasSuffix(path, "/") {
+               path = path + "/"
+       }
+
+       // Walk through the entire tree looking for subvolumes.
+       err := filepath.Walk(path, func(fpath string, fi os.FileInfo, err 
error) error {
+               if err != nil {
+                       return err
+               }
+
+               // Ignore the base path.
+               if strings.TrimRight(fpath, "/") == strings.TrimRight(path, 
"/") {
+                       return nil
+               }
+
+               // Subvolumes can only be directories.
+               if !fi.IsDir() {
+                       return nil
+               }
+
+               // Check if a subvolume.
+               if d.isSubvolume(fpath) {
+                       result = append(result, strings.TrimPrefix(fpath, path))
+               }
+
+               return nil
+       })
+       if err != nil {
+               return nil, err
+       }
+
+       return result, nil
+}
+
+func (d *btrfs) snapshotSubvolume(path string, dest string, readonly bool, 
recursion bool) error {
+       // Single subvolume deletion.
+       snapshot := func(path string, dest string) error {
+               if readonly && !d.state.OS.RunningInUserNS {
+                       _, err := shared.RunCommand("btrfs", "subvolume", 
"snapshot", "-r", path, dest)
+                       if err != nil {
+                               return err
+                       }
+
+                       return nil
+               }
+
+               _, err := shared.RunCommand("btrfs", "subvolume", "snapshot", 
path, dest)
+               if err != nil {
+                       return err
+               }
+
+               return nil
+       }
+
+       // Now snapshot all subvolumes of the root.
+       if recursion {
+               // Get the subvolumes list.
+               subsubvols, err := d.getSubvolumes(path)
+               if err != nil {
+                       return err
+               }
+               sort.Sort(sort.StringSlice(subsubvols))
+
+               if len(subsubvols) > 0 && readonly {
+                       // Creating subvolumes requires the parent to be 
writable.
+                       readonly = false
+               }
+
+               // First snapshot the root.
+               err = snapshot(path, dest)
+               if err != nil {
+                       return err
+               }
+
+               for _, subsubvol := range subsubvols {
+                       // Clear the target for the subvol to use.
+                       os.Remove(filepath.Join(dest, subsubvol))
+
+                       err := snapshot(filepath.Join(path, subsubvol), 
filepath.Join(dest, subsubvol))
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               return nil
+       }
+
+       // Handle standalone volume.
+       err := snapshot(path, dest)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (d *btrfs) deleteSubvolume(path string, recursion bool) error {
+       // Single subvolume deletion.
+       destroy := func(path string) error {
+               // Attempt (but don't fail on) to delete any qgroup on the 
subvolume.
+               qgroup, _, err := d.getQGroup(path)
+               if err == nil {
+                       shared.RunCommand("btrfs", "qgroup", "destroy", qgroup, 
path)
+               }
+
+               // Attempt to make the subvolume writable.
+               shared.RunCommand("btrfs", "property", "set", path, "ro", 
"false")
+
+               // Delete the subvolume itself.
+               _, err = shared.RunCommand("btrfs", "subvolume", "delete", path)
+
+               return err
+       }
+
+       // Delete subsubvols.
+       if recursion {
+               // Get the subvolumes list.
+               subsubvols, err := d.getSubvolumes(path)
+               if err != nil {
+                       return err
+               }
+               sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))
+
+               for _, subsubvol := range subsubvols {
+                       err := destroy(filepath.Join(path, subsubvol))
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // Delete the subvol itself.
+       err := destroy(path)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (d *btrfs) getQGroup(path string) (string, int64, error) {
+       // Try to get the qgroup details.
+       output, err := shared.RunCommand("btrfs", "qgroup", "show", "-e", "-f", 
path)
+       if err != nil {
+               return "", -1, errBtrfsNoQuota
+       }
+
+       // Parse to extract the qgroup identifier.
+       var qgroup string
+       usage := int64(-1)
+       for _, line := range strings.Split(output, "\n") {
+               if line == "" || strings.HasPrefix(line, "qgroupid") || 
strings.HasPrefix(line, "---") {
+                       continue
+               }
+
+               fields := strings.Fields(line)
+               if len(fields) != 4 {
+                       continue
+               }
+
+               qgroup = fields[0]
+               val, err := strconv.ParseInt(fields[2], 10, 64)
+               if err == nil {
+                       usage = val
+               }
+
+               break
+       }
+
+       if qgroup == "" {
+               return "", -1, errBtrfsNoQGroup
+       }
+
+       return qgroup, usage, nil
+}
+
+func (d *btrfs) sendSubvolume(path string, parent string, conn 
io.ReadWriteCloser, tracker *ioprogress.ProgressTracker) error {
+       // Assemble btrfs send command.
+       args := []string{"send"}
+       if parent != "" {
+               args = append(args, "-p", parent)
+       }
+       args = append(args, path)
+       cmd := exec.Command("btrfs", args...)
+
+       // Prepare stdout/stderr.
+       stdout, err := cmd.StdoutPipe()
+       if err != nil {
+               return err
+       }
+
+       stderr, err := cmd.StderrPipe()
+       if err != nil {
+               return err
+       }
+
+       // Setup progress tracker.
+       stdoutPipe := stdout
+       if tracker != nil {
+               stdoutPipe = &ioprogress.ProgressReader{
+                       ReadCloser: stdout,
+                       Tracker:    tracker,
+               }
+       }
+
+       // Forward any output on stdout.
+       chStdoutPipe := make(chan error, 1)
+       go func() {
+               _, err := io.Copy(conn, stdoutPipe)
+               chStdoutPipe <- err
+               conn.Close()
+       }()
+
+       // Run the command.
+       err = cmd.Start()
+       if err != nil {
+               return err
+       }
+
+       // Read any error.
+       output, err := ioutil.ReadAll(stderr)
+       if err != nil {
+               logger.Errorf("Problem reading btrfs send stderr: %s", err)
+       }
+
+       // Handle errors.
+       errs := []error{}
+       chStdoutPipeErr := <-chStdoutPipe
+
+       err = cmd.Wait()
+       if err != nil {
+               errs = append(errs, err)
+
+               if chStdoutPipeErr != nil {
+                       errs = append(errs, chStdoutPipeErr)
+               }
+       }
+
+       if len(errs) > 0 {
+               return fmt.Errorf("Btrfs send failed: %v (%s)", errs, 
string(output))
+       }
+
+       return nil
+}
+
+func (d *btrfs) receiveSubvolume(path string, targetPath string, conn 
io.ReadWriteCloser, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
+       // Assemble btrfs send command.
+       cmd := exec.Command("btrfs", "receive", "-e", path)
+
+       // Prepare stdin/stderr.
+       stdin, err := cmd.StdinPipe()
+       if err != nil {
+               return err
+       }
+
+       stderr, err := cmd.StderrPipe()
+       if err != nil {
+               return err
+       }
+
+       // Forward input through stdin.
+       chCopyConn := make(chan error, 1)
+       go func() {
+               _, err = io.Copy(stdin, conn)
+               stdin.Close()
+               chCopyConn <- err
+       }()
+
+       // Run the command.
+       err = cmd.Start()
+       if err != nil {
+               return err
+       }
+
+       // Read any error.
+       output, err := ioutil.ReadAll(stderr)
+       if err != nil {
+               logger.Debugf("Problem reading btrfs receive stderr %s", err)
+       }
+
+       // Handle errors.
+       errs := []error{}
+       chCopyConnErr := <-chCopyConn
+
+       err = cmd.Wait()
+       if err != nil {
+               errs = append(errs, err)
+
+               if chCopyConnErr != nil {
+                       errs = append(errs, chCopyConnErr)
+               }
+       }
+
+       if len(errs) > 0 {
+               return fmt.Errorf("Problem with btrfs receive: (%v) %s", errs, 
string(output))
+       }
+
+       // If we receive and target paths match, we're done.
+       if path == targetPath {
+               return nil
+       }
+
+       // Handle older LXD versions.
+       receivedSnapshot := fmt.Sprintf("%s/.migration-send", path)
+       if !shared.PathExists(receivedSnapshot) {
+               receivedSnapshot = fmt.Sprintf("%s/.root", path)
+       }
+
+       // Mark the received subvolume writable.
+       _, err = shared.RunCommand("btrfs", "property", "set", "-ts", 
receivedSnapshot, "ro", "false")
+       if err != nil {
+               return err
+       }
+
+       // And move it to the target path.
+       err = os.Rename(receivedSnapshot, targetPath)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go 
b/lxd/storage/drivers/driver_btrfs_volumes.go
new file mode 100644
index 0000000000..f06beaa4da
--- /dev/null
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -0,0 +1,760 @@
+package drivers
+
+import (
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "strings"
+
+       "github.com/lxc/lxd/lxd/migration"
+       "github.com/lxc/lxd/lxd/operations"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/ioprogress"
+       "github.com/lxc/lxd/shared/units"
+)
+
+// CreateVolume creates an empty volume and can optionally fill it by 
executing the supplied filler function.
+func (d *btrfs) CreateVolume(vol Volume, filler *VolumeFiller, op 
*operations.Operation) error {
+       volPath := vol.MountPath()
+
+       // Create the volume itself.
+       _, err := shared.RunCommand("btrfs", "subvolume", "create", volPath)
+       if err != nil {
+               return err
+       }
+
+       // Setup revert.
+       revertPath := true
+       defer func() {
+               if revertPath {
+                       d.deleteSubvolume(volPath, false)
+               }
+       }()
+
+       // Create sparse loopback file if volume is block.
+       rootBlockPath := ""
+       if vol.contentType == ContentTypeBlock {
+               // We expect the filler to copy the VM image into this path.
+               rootBlockPath, err = d.GetVolumeDiskPath(vol)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Run the volume filler function if supplied.
+       if filler != nil && filler.Fill != nil {
+               err = filler.Fill(volPath, rootBlockPath)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // If we are creating a block volume, resize it to the requested size 
or the default.
+       // We expect the filler function to have converted the qcow2 image to 
raw into the rootBlockPath.
+       if vol.contentType == ContentTypeBlock {
+               err := ensureVolumeBlockFile(vol, rootBlockPath)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Tweak any permissions that need tweaking.
+       err = vol.EnsureMountPath()
+       if err != nil {
+               return err
+       }
+
+       // Attempt to mark image read-only.
+       if vol.volType == VolumeTypeImage {
+               _, err = shared.RunCommand("btrfs", "property", "set", volPath, 
"ro", "true")
+               if err != nil && !d.state.OS.RunningInUserNS {
+                       return err
+               }
+       }
+
+       revertPath = false
+       return nil
+}
+
+// CreateVolumeFromBackup restores a backup tarball onto the storage device.
+func (d *btrfs) CreateVolumeFromBackup(vol Volume, snapshots []string, srcData 
io.ReadSeeker, optimized bool, op *operations.Operation) (func(vol Volume) 
error, func(), error) {
+       // Handle the non-optimized tarballs through the generic unpacker.
+       if !optimized {
+               return genericBackupUnpack(d, vol, snapshots, srcData, op)
+       }
+
+       // Now deal with the binary btrfs backups.
+       revert := true
+
+       // Define a revert function that will be used both to revert if an 
error occurs inside this
+       // function but also return it for use from the calling functions if no 
error internally.
+       revertHook := func() {
+               for _, snapName := range snapshots {
+                       fullSnapshotName := GetSnapshotVolumeName(vol.name, 
snapName)
+                       snapVol := NewVolume(d, d.name, vol.volType, 
vol.contentType, fullSnapshotName, vol.config)
+                       d.DeleteVolumeSnapshot(snapVol, op)
+               }
+
+               // And lastly the main volume.
+               d.DeleteVolume(vol, op)
+       }
+
+       // Only execute the revert function if we have had an error internally 
and revert is true.
+       defer func() {
+               if revert {
+                       revertHook()
+               }
+       }()
+
+       // Create a temporary directory to unpack the backup into.
+       unpackDir, err := ioutil.TempDir(GetVolumeMountPath(d.name, 
vol.volType, ""), vol.name)
+       if err != nil {
+               return nil, nil, err
+       }
+       defer os.RemoveAll(unpackDir)
+
+       err = os.Chmod(unpackDir, 0100)
+       if err != nil {
+               return nil, nil, 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
+       }
+
+       // Prepare tar arguments.
+       args := append(tarArgs, []string{
+               "-",
+               "--strip-components=1",
+               "-C", unpackDir, "backup",
+       }...)
+
+       // Unpack the backup.
+       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.
+               err := createParentSnapshotDirIfMissing(d.name, vol.volType, 
vol.name)
+               if err != nil {
+                       return nil, nil, err
+               }
+       }
+
+       // 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, err
+               }
+               defer feeder.Close()
+
+               // Extract the backup.
+               err = shared.RunCommandWithFds(feeder, nil, "btrfs", "receive", 
"-e", snapshotsDir)
+               if err != nil {
+                       return nil, nil, err
+               }
+       }
+
+       // Open the backup.
+       feeder, err := os.Open(filepath.Join(unpackDir, "container.bin"))
+       if err != nil {
+               return nil, nil, err
+       }
+       defer feeder.Close()
+
+       // Extrack the backup.
+       err = shared.RunCommandWithFds(feeder, nil, "btrfs", "receive", "-e", 
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 = false
+
+       return nil, revertHook, nil
+}
+
+// CreateVolumeFromCopy provides same-pool volume copying functionality.
+func (d *btrfs) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots 
bool, op *operations.Operation) error {
+       // Recursively copy the main volume.
+       err := d.snapshotSubvolume(srcVol.MountPath(), vol.MountPath(), false, 
true)
+       if err != nil {
+               return err
+       }
+
+       // Fixup permissions.
+       err = vol.EnsureMountPath()
+       if err != nil {
+               return err
+       }
+
+       // If we're not copying any snapshots, we're done here.
+       if !copySnapshots || srcVol.IsSnapshot() {
+               return nil
+       }
+
+       // Get the list of snapshots.
+       snapshots, err := d.VolumeSnapshots(srcVol, op)
+       if err != nil {
+               return err
+       }
+
+       // If no snapshots, we're done here.
+       if len(snapshots) == 0 {
+               return nil
+       }
+
+       // Create the parent directory.
+       err = createParentSnapshotDirIfMissing(d.name, vol.volType, vol.name)
+       if err != nil {
+               return err
+       }
+
+       // Copy the snapshots.
+       for _, snapName := range snapshots {
+               srcSnapshot := GetVolumeMountPath(d.name, srcVol.volType, 
GetSnapshotVolumeName(srcVol.name, snapName))
+               dstSnapshot := GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, snapName))
+
+               err = d.snapshotSubvolume(srcSnapshot, dstSnapshot, true, false)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// CreateVolumeFromMigration creates a volume being sent via a migration.
+func (d *btrfs) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, 
volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op 
*operations.Operation) error {
+       if vol.contentType != ContentTypeFS {
+               return fmt.Errorf("Content type not supported")
+       }
+
+       // Handle simple rsync through generic.
+       if volTargetArgs.MigrationType.FSType == 
migration.MigrationFSType_RSYNC {
+               return genericCreateVolumeFromMigration(d, nil, vol, conn, 
volTargetArgs, preFiller, op)
+       } else if volTargetArgs.MigrationType.FSType != 
migration.MigrationFSType_BTRFS {
+               return fmt.Errorf("Migration type not supported")
+       }
+
+       // Handle btrfs send/receive migration.
+       if len(volTargetArgs.Snapshots) > 0 {
+               snapshotsDir := GetVolumeSnapshotDir(d.name, vol.volType, 
vol.name)
+
+               // Create the parent directory.
+               err := createParentSnapshotDirIfMissing(d.name, vol.volType, 
vol.name)
+               if err != nil {
+                       return err
+               }
+
+               // Transfer the snapshots.
+               for _, snapName := range volTargetArgs.Snapshots {
+                       fullSnapshotName := GetSnapshotVolumeName(vol.name, 
snapName)
+                       wrapper := migration.ProgressWriter(op, "fs_progress", 
fullSnapshotName)
+
+                       err = d.receiveSubvolume(snapshotsDir, snapshotsDir, 
conn, wrapper)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // Get instances directory (e.g. 
/var/lib/lxd/storage-pools/btrfs/containers).
+       instancesPath := GetVolumeMountPath(d.name, vol.volType, "")
+
+       // Create a temporary directory which will act as the parent directory 
of the received ro snapshot.
+       tmpVolumesMountPoint, err := ioutil.TempDir(instancesPath, vol.name)
+       if err != nil {
+               return err
+       }
+       defer os.RemoveAll(tmpVolumesMountPoint)
+
+       err = os.Chmod(tmpVolumesMountPoint, 0100)
+       if err != nil {
+               return err
+       }
+
+       wrapper := migration.ProgressWriter(op, "fs_progress", vol.name)
+       err = d.receiveSubvolume(tmpVolumesMountPoint, vol.MountPath(), conn, 
wrapper)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// RefreshVolume provides same-pool volume and specific snapshots syncing 
functionality.
+func (d *btrfs) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots 
[]Volume, op *operations.Operation) error {
+       return genericCopyVolume(d, nil, vol, srcVol, srcSnapshots, op)
+}
+
+// DeleteVolume deletes a volume of the storage device. If any snapshots of 
the volume remain then
+// this function will return an error.
+func (d *btrfs) DeleteVolume(vol Volume, op *operations.Operation) error {
+       // Check that we don't have snapshots.
+       snapshots, err := d.VolumeSnapshots(vol, op)
+       if err != nil {
+               return err
+       }
+
+       if len(snapshots) > 0 {
+               return fmt.Errorf("Cannot remove a volume that has snapshots")
+       }
+
+       // If the volume doesn't exist, then nothing more to do.
+       volPath := GetVolumeMountPath(d.name, vol.volType, vol.name)
+       if !shared.PathExists(volPath) {
+               return nil
+       }
+
+       // Delete the volume (and any subvolumes).
+       err = d.deleteSubvolume(volPath, true)
+       if err != nil {
+               return err
+       }
+
+       // Although the volume snapshot directory should already be removed, 
lets remove it here
+       // to just in case the top-level directory is left.
+       err = deleteParentSnapshotDirIfEmpty(d.name, vol.volType, vol.name)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// HasVolume indicates whether a specific volume exists on the storage pool.
+func (d *btrfs) HasVolume(vol Volume) bool {
+       return d.vfsHasVolume(vol)
+}
+
+// ValidateVolume validates the supplied volume config.
+func (d *btrfs) ValidateVolume(vol Volume, removeUnknownKeys bool) error {
+       return d.validateVolume(vol, nil, removeUnknownKeys)
+}
+
+// UpdateVolume applies config changes to the volume.
+func (d *btrfs) UpdateVolume(vol Volume, changedConfig map[string]string) 
error {
+       if vol.contentType != ContentTypeFS {
+               return fmt.Errorf("Content type not supported")
+       }
+
+       if vol.volType != VolumeTypeCustom {
+               return fmt.Errorf("Volume type not supported")
+       }
+
+       return d.SetVolumeQuota(vol, vol.config["size"], nil)
+}
+
+// GetVolumeUsage returns the disk space used by the volume.
+func (d *btrfs) GetVolumeUsage(vol Volume) (int64, error) {
+       // Attempt to get the qgroup information.
+       _, usage, err := d.getQGroup(vol.MountPath())
+       if err != nil {
+               return -1, err
+       }
+
+       return usage, nil
+}
+
+// SetVolumeQuota sets the quota on the volume.
+func (d *btrfs) SetVolumeQuota(vol Volume, size string, op 
*operations.Operation) error {
+       volPath := vol.MountPath()
+
+       // Convert to bytes.
+       sizeBytes, err := units.ParseByteSizeString(size)
+       if err != nil {
+               return err
+       }
+
+       // Try to locate an existing quota group.
+       qgroup, _, err := d.getQGroup(volPath)
+       if err != nil && !d.state.OS.RunningInUserNS {
+               // If quotas are disabled, attempt to enable them.
+               if err == errBtrfsNoQuota {
+                       path := GetPoolMountPath(d.name)
+
+                       _, err = shared.RunCommand("btrfs", "quota", "enable", 
path)
+                       if err != nil {
+                               return err
+                       }
+
+                       // Try again.
+                       qgroup, _, err = d.getQGroup(volPath)
+               }
+
+               // If there's no qgroup, attempt to create one.
+               if err == errBtrfsNoQGroup {
+                       // Find the volume ID.
+                       var output string
+                       output, err = shared.RunCommand("btrfs", "subvolume", 
"show", volPath)
+                       if err != nil {
+                               return fmt.Errorf("Failed to get subvol 
information: %v", err)
+                       }
+
+                       id := ""
+                       for _, line := range strings.Split(output, "\n") {
+                               line = strings.TrimSpace(line)
+                               if strings.HasPrefix(line, "Subvolume ID:") {
+                                       fields := strings.Split(line, ":")
+                                       id = 
strings.TrimSpace(fields[len(fields)-1])
+                               }
+                       }
+
+                       if id == "" {
+                               return fmt.Errorf("Failed to find subvolume id 
for %s", volPath)
+                       }
+
+                       // Create a qgroup.
+                       _, err = shared.RunCommand("btrfs", "qgroup", "create", 
fmt.Sprintf("0/%s", id), volPath)
+                       if err != nil {
+                               return err
+                       }
+
+                       // Try to get the qgroup again.
+                       qgroup, _, err = d.getQGroup(volPath)
+               }
+
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Modify the limit.
+       if sizeBytes > 0 {
+               // Apply the limit.
+               _, err := shared.RunCommand("btrfs", "qgroup", "limit", "-e", 
fmt.Sprintf("%d", sizeBytes), volPath)
+               if err != nil {
+                       return err
+               }
+       } else if qgroup != "" {
+               // Remove the limit.
+               _, err := shared.RunCommand("btrfs", "qgroup", "destroy", 
qgroup, volPath)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// GetVolumeDiskPath returns the location and file format of a disk volume.
+func (d *btrfs) GetVolumeDiskPath(vol Volume) (string, error) {
+       return d.vfsGetVolumeDiskPath(vol)
+}
+
+// MountVolume simulates mounting a volume. As dir driver doesn't have volumes 
to mount it returns
+// false indicating that there is no need to issue an unmount.
+func (d *btrfs) MountVolume(vol Volume, op *operations.Operation) (bool, 
error) {
+       return true, nil
+}
+
+// UnmountVolume simulates unmounting a volume. As dir driver doesn't have 
volumes to unmount it
+// returns false indicating the volume was already unmounted.
+func (d *btrfs) UnmountVolume(vol Volume, op *operations.Operation) (bool, 
error) {
+       return false, nil
+}
+
+// RenameVolume renames a volume and its snapshots.
+func (d *btrfs) RenameVolume(vol Volume, newVolName string, op 
*operations.Operation) error {
+       return d.vfsRenameVolume(vol, newVolName, op)
+}
+
+// MigrateVolume sends a volume for migration.
+func (d *btrfs) MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs 
migration.VolumeSourceArgs, op *operations.Operation) error {
+       if vol.contentType != ContentTypeFS {
+               return fmt.Errorf("Content type not supported")
+       }
+
+       // Handle simple rsync through generic.
+       if volSrcArgs.MigrationType.FSType == migration.MigrationFSType_RSYNC {
+               return d.vfsMigrateVolume(vol, conn, volSrcArgs, op)
+       } else if volSrcArgs.MigrationType.FSType != 
migration.MigrationFSType_BTRFS {
+               return fmt.Errorf("Migration type not supported")
+       }
+
+       // Handle btrfs send/receive migration.
+       if volSrcArgs.FinalSync {
+               // This is not needed if the migration is performed using btrfs 
send/receive.
+               return nil
+       }
+
+       // Transfer the snapshots first.
+       for i, snapName := range volSrcArgs.Snapshots {
+               snapshot, _ := vol.NewSnapshot(snapName)
+
+               // Locate the parent snapshot.
+               parentSnapshotPath := ""
+               if i > 0 {
+                       parentSnapshotPath = GetVolumeMountPath(d.name, 
vol.volType, GetSnapshotVolumeName(vol.name, volSrcArgs.Snapshots[i-1]))
+               }
+
+               // Setup progress tracking.
+               var wrapper *ioprogress.ProgressTracker
+               if volSrcArgs.TrackProgress {
+                       wrapper = migration.ProgressTracker(op, "fs_progress", 
snapshot.name)
+               }
+
+               // Send snapshot to recipient (ensure local snapshot volume is 
mounted if needed).
+               err := d.sendSubvolume(snapshot.MountPath(), 
parentSnapshotPath, conn, wrapper)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Get instances directory (e.g. 
/var/lib/lxd/storage-pools/btrfs/containers).
+       instancesPath := GetVolumeMountPath(d.name, vol.volType, "")
+
+       // Create a temporary directory which will act as the parent directory 
of the read-only snapshot.
+       tmpVolumesMountPoint, err := ioutil.TempDir(instancesPath, vol.name)
+       if err != nil {
+               return err
+       }
+       defer os.RemoveAll(tmpVolumesMountPoint)
+
+       err = os.Chmod(tmpVolumesMountPoint, 0100)
+       if err != nil {
+               return err
+       }
+
+       // Make read-only snapshot of the subvolume as writable subvolumes 
cannot be sent.
+       migrationSendSnapshot := filepath.Join(tmpVolumesMountPoint, 
".migration-send")
+       err = d.snapshotSubvolume(vol.MountPath(), migrationSendSnapshot, true, 
false)
+       if err != nil {
+               return err
+       }
+       defer d.deleteSubvolume(migrationSendSnapshot, true)
+
+       // Setup progress tracking.
+       var wrapper *ioprogress.ProgressTracker
+       if volSrcArgs.TrackProgress {
+               wrapper = migration.ProgressTracker(op, "fs_progress", vol.name)
+       }
+
+       // Compare to latest snapshot.
+       btrfsParent := ""
+       if len(volSrcArgs.Snapshots) > 0 {
+               btrfsParent = GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, 
volSrcArgs.Snapshots[len(volSrcArgs.Snapshots)-1]))
+       }
+
+       // Send the volume itself.
+       err = d.sendSubvolume(migrationSendSnapshot, btrfsParent, conn, wrapper)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// BackupVolume copies a volume (and optionally its snapshots) to a specified 
target path.
+// This driver does not support optimized backups.
+func (d *btrfs) BackupVolume(vol Volume, targetPath string, optimized bool, 
snapshots bool, op *operations.Operation) error {
+       // Handle the non-optimized tarballs through the generic packer.
+       if !optimized {
+               return d.vfsBackupVolume(vol, targetPath, snapshots, op)
+       }
+
+       // Handle the optimized tarballs.
+       sendToFile := func(path string, parent string, file string) error {
+               // Prepare btrfs send arguments.
+               args := []string{"send"}
+               if parent != "" {
+                       args = append(args, "-p", parent)
+               }
+               args = append(args, path)
+
+               // Create the file.
+               fd, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE, 0644)
+               if err != nil {
+                       return err
+               }
+               defer fd.Close()
+
+               // Write the subvolume to the file.
+               err = shared.RunCommandWithFds(nil, fd, "btrfs", args...)
+               if err != nil {
+                       return err
+               }
+
+               return nil
+       }
+
+       // Handle snapshots.
+       finalParent := ""
+       if snapshots {
+               snapshotsPath := fmt.Sprintf("%s/snapshots", targetPath)
+
+               // Retrieve the snapshots.
+               volSnapshots, err := d.VolumeSnapshots(vol, op)
+               if err != nil {
+                       return err
+               }
+
+               // Create the snapshot path.
+               if len(volSnapshots) > 0 {
+                       err = os.MkdirAll(snapshotsPath, 0711)
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               for i, snap := range volSnapshots {
+                       fullSnapshotName := GetSnapshotVolumeName(vol.name, 
snap)
+
+                       // Figure out parent and current subvolumes.
+                       parent := ""
+                       if i > 0 {
+                               parent = GetVolumeMountPath(d.name, 
vol.volType, GetSnapshotVolumeName(vol.name, volSnapshots[i-1]))
+                       }
+
+                       cur := GetVolumeMountPath(d.name, vol.volType, 
fullSnapshotName)
+
+                       // Make a binary btrfs backup.
+                       target := fmt.Sprintf("%s/%s.bin", snapshotsPath, snap)
+
+                       err := sendToFile(cur, parent, target)
+                       if err != nil {
+                               return err
+                       }
+
+                       finalParent = cur
+               }
+       }
+
+       // Make a temporary copy of the container.
+       sourceVolume := vol.MountPath()
+       containersPath := GetVolumeMountPath(d.name, vol.volType, "")
+
+       tmpContainerMntPoint, err := ioutil.TempDir(containersPath, vol.name)
+       if err != nil {
+               return err
+       }
+       defer os.RemoveAll(tmpContainerMntPoint)
+
+       err = os.Chmod(tmpContainerMntPoint, 0100)
+       if err != nil {
+               return err
+       }
+
+       // Create the read-only snapshot.
+       targetVolume := fmt.Sprintf("%s/.backup", tmpContainerMntPoint)
+       err = d.snapshotSubvolume(sourceVolume, targetVolume, true, true)
+       if err != nil {
+               return err
+       }
+       defer d.deleteSubvolume(targetVolume, true)
+
+       // Dump the container to a file.
+       fsDump := fmt.Sprintf("%s/container.bin", targetPath)
+       err = sendToFile(targetVolume, finalParent, fsDump)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// CreateVolumeSnapshot creates a snapshot of a volume.
+func (d *btrfs) CreateVolumeSnapshot(snapVol Volume, op *operations.Operation) 
error {
+       parentName, _, _ := 
shared.InstanceGetParentAndSnapshotName(snapVol.name)
+       srcPath := GetVolumeMountPath(d.name, snapVol.volType, parentName)
+       snapPath := snapVol.MountPath()
+
+       // Create the parent directory.
+       err := createParentSnapshotDirIfMissing(d.name, snapVol.volType, 
parentName)
+       if err != nil {
+               return err
+       }
+
+       return d.snapshotSubvolume(srcPath, snapPath, true, true)
+}
+
+// DeleteVolumeSnapshot removes a snapshot from the storage device. The 
volName and snapshotName
+// must be bare names and should not be in the format "volume/snapshot".
+func (d *btrfs) DeleteVolumeSnapshot(snapVol Volume, op *operations.Operation) 
error {
+       snapPath := snapVol.MountPath()
+
+       // Delete the snapshot.
+       err := d.deleteSubvolume(snapPath, true)
+       if err != nil {
+               return err
+       }
+
+       // Remove the parent snapshot directory if this is the last snapshot 
being removed.
+       parentName, _, _ := 
shared.InstanceGetParentAndSnapshotName(snapVol.name)
+       err = deleteParentSnapshotDirIfEmpty(d.name, snapVol.volType, 
parentName)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// MountVolumeSnapshot sets up a read-only mount on top of the snapshot to 
avoid accidental modifications.
+func (d *btrfs) MountVolumeSnapshot(snapVol Volume, op *operations.Operation) 
(bool, error) {
+       snapPath := snapVol.MountPath()
+       return mountReadOnly(snapPath, snapPath)
+}
+
+// UnmountVolumeSnapshot removes the read-only mount placed on top of a 
snapshot.
+func (d *btrfs) UnmountVolumeSnapshot(snapVol Volume, op 
*operations.Operation) (bool, error) {
+       snapPath := snapVol.MountPath()
+       return forceUnmount(snapPath)
+}
+
+// VolumeSnapshots returns a list of snapshots for the volume.
+func (d *btrfs) VolumeSnapshots(vol Volume, op *operations.Operation) 
([]string, error) {
+       return d.vfsVolumeSnapshots(vol, op)
+}
+
+// RestoreVolume restores a volume from a snapshot.
+func (d *btrfs) RestoreVolume(vol Volume, snapshotName string, op 
*operations.Operation) error {
+       // Create a backup so we can revert.
+       backupSubvolume := fmt.Sprintf("%s.tmp", vol.MountPath())
+       err := os.Rename(vol.MountPath(), backupSubvolume)
+       if err != nil {
+               return err
+       }
+
+       // Setup revert logic.
+       undoSnapshot := true
+       defer func() {
+               if undoSnapshot {
+                       os.Rename(vol.MountPath(), backupSubvolume)
+               }
+       }()
+
+       // Restore the snapshot.
+       source := GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, snapshotName))
+       err = d.snapshotSubvolume(source, vol.MountPath(), false, true)
+       if err != nil {
+               return err
+       }
+
+       undoSnapshot = false
+
+       // Remove the backup subvolume.
+       return d.deleteSubvolume(backupSubvolume, true)
+}
+
+// RenameVolumeSnapshot renames a volume snapshot.
+func (d *btrfs) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, 
op *operations.Operation) error {
+       return d.vfsRenameVolumeSnapshot(snapVol, newSnapshotName, op)
+}
diff --git a/lxd/storage/drivers/load.go b/lxd/storage/drivers/load.go
index 501b579f34..c9374157af 100644
--- a/lxd/storage/drivers/load.go
+++ b/lxd/storage/drivers/load.go
@@ -8,6 +8,7 @@ import (
 var drivers = map[string]func() driver{
        "dir":    func() driver { return &dir{} },
        "cephfs": func() driver { return &cephfs{} },
+       "btrfs":  func() driver { return &btrfs{} },
 }
 
 // Load returns a Driver for an existing low-level storage pool.

From 93cfb7a8d3c0a841a53a4ffeb28bee41e0712bbc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Thu, 12 Dec 2019 09:38:40 -0500
Subject: [PATCH 6/6] tests: Update exclusion for btrfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 test/includes/storage.sh                     | 15 +++++++++++++++
 test/suites/incremental_copy.sh              |  6 ++++++
 test/suites/storage_local_volume_handling.sh | 15 ---------------
 3 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/test/includes/storage.sh b/test/includes/storage.sh
index 11d78e26bb..c41901f01a 100644
--- a/test/includes/storage.sh
+++ b/test/includes/storage.sh
@@ -128,3 +128,18 @@ umount_loops() {
         done < "${test_dir}/loops"
     fi
 }
+
+storage_compatible() {
+    if [ "${1}" = "cephfs" ] || [ "${1}" = "dir" ] || [ "${1}" = "btrfs" ]; 
then
+        if [ "${2}" = "cephfs" ] || [ "${2}" = "dir" ] || [ "${2}" = "btrfs" 
]; then
+            true
+            return
+        else
+            false
+            return
+        fi
+    fi
+
+    true
+    return
+}
diff --git a/test/suites/incremental_copy.sh b/test/suites/incremental_copy.sh
index 2340d65425..cec3528ad9 100644
--- a/test/suites/incremental_copy.sh
+++ b/test/suites/incremental_copy.sh
@@ -10,6 +10,12 @@ test_incremental_copy() {
 
   # cross-pool copy
   if [ "${lxd_backend}" != 'dir' ]; then
+    # FIXME: Skip copies across old and new backends for now
+    if ! storage_compatible "dir" "${lxd_backend}"; then
+        true
+        return
+    fi
+
     # shellcheck disable=2039
     local source_pool
     source_pool="lxdtest-$(basename "${LXD_DIR}")-dir-pool"
diff --git a/test/suites/storage_local_volume_handling.sh 
b/test/suites/storage_local_volume_handling.sh
index a4745b9a9e..3780900a30 100644
--- a/test/suites/storage_local_volume_handling.sh
+++ b/test/suites/storage_local_volume_handling.sh
@@ -1,18 +1,3 @@
-storage_compatible() {
-    if [ "${1}" = "cephfs" ] || [ "${1}" = "dir" ]; then
-        if [ "${2}" = "cephfs" ] || [ "${2}" = "dir" ]; then
-            true
-            return
-        else
-            false
-            return
-        fi
-    fi
-
-    true
-    return
-}
-
 test_storage_local_volume_handling() {
   ensure_import_testimage
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to