[lxc-devel] [lxd/master] Storage: BTRFS backup subvolume support
tomponline on Github
lxc-bot at linuxcontainers.org
Wed May 13 13:42:36 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 356 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200513/88a1fd6e/attachment.bin>
-------------- next part --------------
From 1b15e36f20e46a399e28647e2839d1d6da4a3c43 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:23:56 +0100
Subject: [PATCH 1/9] lxd/device/device/utils/generic: Removes deviceNameEncode
and deviceNameDecode
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/device/device_utils_generic.go | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/lxd/device/device_utils_generic.go b/lxd/device/device_utils_generic.go
index 8828403a68..3b1c051f6a 100644
--- a/lxd/device/device_utils_generic.go
+++ b/lxd/device/device_utils_generic.go
@@ -4,20 +4,6 @@ import (
"strings"
)
-// deviceNameEncode encodes a string to be used as part of a file name in the LXD devices path.
-// The encoding scheme replaces "-" with "--" and then "/" with "-".
-func deviceNameEncode(text string) string {
- return strings.Replace(strings.Replace(text, "-", "--", -1), "/", "-", -1)
-}
-
-// deviceNameDecode decodes a string used in the LXD devices path back to its original form.
-// The decoding scheme converts "-" back to "/" and "--" back to "-".
-func deviceNameDecode(text string) string {
- // This converts "--" to the null character "\0" first, to allow remaining "-" chars to be
- // converted back to "/" before making a final pass to convert "\0" back to original "-".
- return strings.Replace(strings.Replace(strings.Replace(text, "--", "\000", -1), "-", "/", -1), "\000", "-", -1)
-}
-
// deviceJoinPath joins together prefix and text delimited by a "." for device path generation.
func deviceJoinPath(parts ...string) string {
return strings.Join(parts, ".")
From b0f658ca41406ab74b3615f2099baa9f371bd559 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:24:26 +0100
Subject: [PATCH 2/9] lxd/storage/drivers/utils: Adds PathNameEncode and
PathNameDecode
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/utils.go | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index 8093fa1d7a..a2cf4dfa7f 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -753,3 +753,17 @@ func BlockDevSizeBytes(blockDevPath string) (int64, error) {
return int64(res), nil
}
+
+// PathNameEncode encodes a path string to be used as part of a file name.
+// The encoding scheme replaces "-" with "--" and then "/" with "-".
+func PathNameEncode(text string) string {
+ return strings.Replace(strings.Replace(text, "-", "--", -1), "/", "-", -1)
+}
+
+// PathNameDecode decodes a string containing an encoded path back to its original form.
+// The decoding scheme converts "-" back to "/" and "--" back to "-".
+func PathNameDecode(text string) string {
+ // This converts "--" to the null character "\0" first, to allow remaining "-" chars to be
+ // converted back to "/" before making a final pass to convert "\0" back to original "-".
+ return strings.Replace(strings.Replace(strings.Replace(text, "--", "\000", -1), "-", "/", -1), "\000", "-", -1)
+}
From 66c0bd5ee9d93389102862dadf42c570c60f89d0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:27:29 +0100
Subject: [PATCH 3/9] lxd/device/device: PathNameEncode and PathNameDecode
usage
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/device/device_utils_unix.go | 17 +++++++++--------
lxd/device/disk.go | 4 ++--
lxd/device/unix_common.go | 3 ++-
3 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/lxd/device/device_utils_unix.go b/lxd/device/device_utils_unix.go
index da711e6f01..810e77dc99 100644
--- a/lxd/device/device_utils_unix.go
+++ b/lxd/device/device_utils_unix.go
@@ -12,6 +12,7 @@ import (
deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/state"
+ storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/idmap"
"github.com/lxc/lxd/shared/logger"
@@ -190,7 +191,7 @@ func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath stri
destPath := unixDeviceDestPath(m)
relativeDestPath := strings.TrimPrefix(destPath, "/")
- devName := deviceNameEncode(deviceJoinPath(prefix, relativeDestPath))
+ devName := storageDrivers.PathNameEncode(deviceJoinPath(prefix, relativeDestPath))
devPath := filepath.Join(devicesPath, devName)
// Create the new entry.
@@ -253,7 +254,7 @@ func unixDeviceSetup(s *state.State, devicesPath string, typePrefix string, devi
// Convert the requested dest path inside the instance to an encoded relative one.
ourDestPath := unixDeviceDestPath(m)
- ourEncRelDestFile := deviceNameEncode(strings.TrimPrefix(ourDestPath, "/"))
+ ourEncRelDestFile := storageDrivers.PathNameEncode(strings.TrimPrefix(ourDestPath, "/"))
// Load all existing host devices.
dents, err := ioutil.ReadDir(devicesPath)
@@ -359,7 +360,7 @@ func unixDeviceSetupBlockNum(s *state.State, devicesPath string, typePrefix stri
// UnixDeviceExists checks if the unix device already exists in devices path.
func UnixDeviceExists(devicesPath string, prefix string, path string) bool {
relativeDestPath := strings.TrimPrefix(path, "/")
- devName := fmt.Sprintf("%s.%s", deviceNameEncode(prefix), deviceNameEncode(relativeDestPath))
+ devName := fmt.Sprintf("%s.%s", storageDrivers.PathNameEncode(prefix), storageDrivers.PathNameEncode(relativeDestPath))
devPath := filepath.Join(devicesPath, devName)
return shared.PathExists(devPath)
@@ -384,9 +385,9 @@ func unixDeviceRemove(devicesPath string, typePrefix string, deviceName string,
var ourPrefix string
// If a prefix override has been supplied, use that for filtering the devices to remove.
if optPrefix != "" {
- ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, deviceName, optPrefix))
+ ourPrefix = storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName, optPrefix))
} else {
- ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, deviceName))
+ ourPrefix = storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName))
}
ourDevs := []string{}
@@ -448,7 +449,7 @@ func unixDeviceRemove(devicesPath string, typePrefix string, deviceName string,
// Append this device to the mount rules (these will be unmounted).
runConf.Mounts = append(runConf.Mounts, deviceConfig.MountEntryItem{
- TargetPath: deviceNameDecode(ourEncRelDestFile),
+ TargetPath: storageDrivers.PathNameDecode(ourEncRelDestFile),
})
absDevPath := filepath.Join(devicesPath, ourDev)
@@ -474,9 +475,9 @@ func unixDeviceDeleteFiles(s *state.State, devicesPath string, typePrefix string
var ourPrefix string
// If a prefix override has been supplied, use that for filtering the devices to remove.
if optPrefix != "" {
- ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, deviceName, optPrefix))
+ ourPrefix = storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName, optPrefix))
} else {
- ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, deviceName))
+ ourPrefix = storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName))
}
// Load all devices.
diff --git a/lxd/device/disk.go b/lxd/device/disk.go
index a0104be82d..10ab81fde0 100644
--- a/lxd/device/disk.go
+++ b/lxd/device/disk.go
@@ -189,7 +189,7 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error {
// getDevicePath returns the absolute path on the host for this instance and supplied device config.
func (d *disk) getDevicePath(devName string, devConfig deviceConfig.Device) string {
relativeDestPath := strings.TrimPrefix(devConfig["path"], "/")
- devPath := deviceNameEncode(deviceJoinPath("disk", devName, relativeDestPath))
+ devPath := storageDrivers.PathNameEncode(deviceJoinPath("disk", devName, relativeDestPath))
return filepath.Join(d.inst.DevicesPath(), devPath)
}
@@ -1514,7 +1514,7 @@ func (d *disk) getParentBlocks(path string) ([]string, error) {
// generateVMConfigDrive generates an ISO containing the cloud init config for a VM.
// Returns the path to the ISO.
func (d *disk) generateVMConfigDrive() (string, error) {
- scratchDir := filepath.Join(d.inst.DevicesPath(), deviceNameEncode(d.name))
+ scratchDir := filepath.Join(d.inst.DevicesPath(), storageDrivers.PathNameEncode(d.name))
// Check we have the mkisofs tool available.
mkisofsPath, err := exec.LookPath("mkisofs")
diff --git a/lxd/device/unix_common.go b/lxd/device/unix_common.go
index 87de76f724..21e273afe1 100644
--- a/lxd/device/unix_common.go
+++ b/lxd/device/unix_common.go
@@ -8,6 +8,7 @@ import (
deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/instance/instancetype"
+ storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
"github.com/lxc/lxd/shared"
)
@@ -91,7 +92,7 @@ func (d *unixCommon) Register() error {
// Derive the host side path for the instance device file.
ourPrefix := deviceJoinPath("unix", deviceName)
relativeDestPath := strings.TrimPrefix(unixDeviceDestPath(devConfig), "/")
- devName := deviceNameEncode(deviceJoinPath(ourPrefix, relativeDestPath))
+ devName := storageDrivers.PathNameEncode(deviceJoinPath(ourPrefix, relativeDestPath))
devPath := filepath.Join(devicesPath, devName)
runConf := deviceConfig.RunConfig{}
From 36c681e2814af736d0719d258d5a3ca19719b12c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:28:44 +0100
Subject: [PATCH 4/9] lxd/storage/drivers/driver/types: Adds
OptimizedBackupHeader field to Info
Used to indicate if driver will generate a backup header file when generating an optimized backup file.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/driver_types.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/storage/drivers/driver_types.go b/lxd/storage/drivers/driver_types.go
index c7547211ed..700eca1cd6 100644
--- a/lxd/storage/drivers/driver_types.go
+++ b/lxd/storage/drivers/driver_types.go
@@ -8,6 +8,7 @@ type Info struct {
Remote bool // Whether the driver uses a remote backing store.
OptimizedImages bool // Whether driver stores images as separate volume.
OptimizedBackups bool // Whether driver supports optimized volume backups.
+ OptimizedBackupHeader bool // Whether driver generates an optimised backup header file in backup.
PreservesInodes bool // Whether driver preserves inodes when volumes are moved hosts.
BlockBacking bool // Whether driver uses block devices as backing store.
RunningQuotaResize bool // Whether quota resize is supported whilst instance running.
From ca9ad71f6984235fd34ca1e258d77ba3e673e538 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:29:56 +0100
Subject: [PATCH 5/9] lxd/backup/backup: Adds OptimizedHeader field to Info
struct
Used to indicate if an optimized backup file will contain a driver specific optimized header file.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/backup/backup.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lxd/backup/backup.go b/lxd/backup/backup.go
index 3648f2d375..5b9b2b571a 100644
--- a/lxd/backup/backup.go
+++ b/lxd/backup/backup.go
@@ -31,7 +31,8 @@ type Info struct {
Backend string `json:"backend" yaml:"backend"`
Pool string `json:"pool" yaml:"pool"`
Snapshots []string `json:"snapshots,omitempty" yaml:"snapshots,omitempty"`
- OptimizedStorage *bool `json:"optimized,omitempty" yaml:"optimized,omitempty"` // Optional field to handle older optimized backups that don't have this field.
+ OptimizedStorage *bool `json:"optimized,omitempty" yaml:"optimized,omitempty"` // Optional field to handle older optimized backups that don't have this field.
+ OptimizedHeader *bool `json:"optimized_header,omitempty" yaml:"optimized_header,omitempty"` // Optional field to handle older optimized backups that don't have this field.
Type api.InstanceType `json:"type" yaml:"type"`
}
From 44da0769461216ff792e4e5cc86acab1aa4e556c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:30:35 +0100
Subject: [PATCH 6/9] lxd/backup: Updates backupWriteIndex to populate the
OptimizedHeader field
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/backup.go | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/lxd/backup.go b/lxd/backup.go
index a1bef75103..f61c119920 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -167,6 +167,12 @@ 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, tarWriter *instancewriter.InstanceTarWriter) error {
+ // Indicate whether the driver will include a driver-specific optimized header.
+ poolDriverOptimizedHeader := false
+ if optimized {
+ poolDriverOptimizedHeader = pool.Driver().Info().OptimizedBackupHeader
+ }
+
indexInfo := backup.Info{
Name: sourceInst.Name(),
Pool: pool.Name(),
@@ -174,6 +180,7 @@ func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, opti
Backend: pool.Driver().Info().Name,
Type: api.InstanceType(sourceInst.Type().String()),
OptimizedStorage: &optimized,
+ OptimizedHeader: &poolDriverOptimizedHeader,
}
if snapshots {
@@ -188,7 +195,7 @@ func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, opti
}
}
- // Convert to JSON.
+ // Convert to YAML.
indexData, err := yaml.Marshal(&indexInfo)
if err != nil {
return err
From da3f45b2f656bdb41eb30f70c956100bd05a5316 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:31:06 +0100
Subject: [PATCH 7/9] lxd/storage/drivers/driver/btrfs: Sets
OptimizedBackupHeader to true in Info struct response
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/driver_btrfs.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/storage/drivers/driver_btrfs.go b/lxd/storage/drivers/driver_btrfs.go
index e69d0ff18b..2ec544339f 100644
--- a/lxd/storage/drivers/driver_btrfs.go
+++ b/lxd/storage/drivers/driver_btrfs.go
@@ -73,6 +73,7 @@ func (d *btrfs) Info() Info {
Version: btrfsVersion,
OptimizedImages: true,
OptimizedBackups: true,
+ OptimizedBackupHeader: true,
PreservesInodes: !d.state.OS.RunningInUserNS,
Remote: false,
VolumeTypes: []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
From eec84caf71e1c3c86a2a8af5a150203e53905c3e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:31:36 +0100
Subject: [PATCH 8/9] lxd/storage/drivers/driver/btrfs/utils: Adds warning to
BTRFSSubVolume and BTRFSMetaDataHeader about shared usage
Modifying these structs in the future will impact both migration and backup subsystems which may be unwanted.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/driver_btrfs_utils.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lxd/storage/drivers/driver_btrfs_utils.go b/lxd/storage/drivers/driver_btrfs_utils.go
index ef1aa554a0..6f3e795212 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -423,6 +423,7 @@ func (d *btrfs) setSubvolumeReadonlyProperty(path string, readonly bool) error {
}
// BTRFSSubVolume is the structure used to store information about a subvolume.
+// Note: This is used by both migration and backup subsystems so do not modify without considering both!
type BTRFSSubVolume struct {
Path string `json:"path" yaml:"path"` // Path inside the volume where the subvolume belongs (so / is the top of the volume tree).
Snapshot string `json:"snapshot" yaml:"snapshot"` // Snapshot name the subvolume belongs to.
@@ -466,6 +467,7 @@ func (d *btrfs) getSubvolumesMetaData(vol Volume) ([]BTRFSSubVolume, error) {
}
// BTRFSMetaDataHeader is the meta data header about the volumes being sent/stored.
+// Note: This is used by both migration and backup subsystems so do not modify without considering both!
type BTRFSMetaDataHeader struct {
Subvolumes []BTRFSSubVolume `json:"subvolumes" yaml:"subvolumes"` // Sub volumes inside the volume (including the top level ones).
}
From 34a1cd6fc2a206a63a849816e8eb378a40f08253 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 13 May 2020 14:33:16 +0100
Subject: [PATCH 9/9] lxd/storage/drivers/driver/btrfs/volumes: Updates
BackupVolume to add subvolumes to optimized backup file
Adds an optimized_header.yaml file containing subvolume metadata.
Adds each subvolume as a separate .bin file, the name of which encodes the relative path of the subvolume so it can be found from the optimized_header.yaml file data.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/driver_btrfs_volumes.go | 166 ++++++++++++++++----
1 file changed, 133 insertions(+), 33 deletions(-)
diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go b/lxd/storage/drivers/driver_btrfs_volumes.go
index fcd3c8bd15..398b0f24d4 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -1,6 +1,7 @@
package drivers
import (
+ "bytes"
"context"
"encoding/json"
"fmt"
@@ -9,8 +10,10 @@ import (
"os"
"path/filepath"
"strings"
+ "time"
"github.com/pkg/errors"
+ "gopkg.in/yaml.v2"
"github.com/lxc/lxd/lxd/migration"
"github.com/lxc/lxd/lxd/operations"
@@ -910,7 +913,45 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWr
return genericVFSBackupVolume(d, vol, tarWriter, snapshots, op)
}
- // Handle the optimized tarballs.
+ // Optimized backup.
+ var err error
+ var volSnapshots []string
+
+ // Retrieve the snapshots if requested.
+ if snapshots {
+ volSnapshots, err = d.VolumeSnapshots(vol, op)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Generate driver restoration header.
+ optimizedHeader, err := d.restorationHeader(vol, volSnapshots)
+ if err != nil {
+ return err
+ }
+
+ // Convert to YAML.
+ optimizedHeaderYAML, err := yaml.Marshal(&optimizedHeader)
+ if err != nil {
+ return err
+ }
+ r := bytes.NewReader(optimizedHeaderYAML)
+
+ indexFileInfo := instancewriter.FileInfo{
+ FileName: "backup/optimized_header.yaml",
+ FileSize: int64(len(optimizedHeaderYAML)),
+ FileMode: 0644,
+ FileModTime: time.Now(),
+ }
+
+ // Write to tarball.
+ err = tarWriter.WriteFileFromReader(r, &indexFileInfo)
+ if err != nil {
+ return err
+ }
+
+ // sendToFile sends a subvolume to backup file.
sendToFile := func(path string, parent string, fileName string) error {
// Prepare btrfs send arguments.
args := []string{"send"}
@@ -929,7 +970,7 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWr
defer os.Remove(tmpFile.Name())
// Write the subvolume to the file.
- d.logger.Debug("Generating optimized volume file", log.Ctx{"sourcePath": path, "file": tmpFile.Name(), "name": fileName})
+ d.logger.Debug("Generating optimized volume file", log.Ctx{"sourcePath": path, "parent": parent, "file": tmpFile.Name(), "name": fileName})
err = shared.RunCommandWithFds(nil, tmpFile, "btrfs", args...)
if err != nil {
return err
@@ -949,44 +990,104 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWr
return tmpFile.Close()
}
- // Handle snapshots.
- finalParent := ""
- if snapshots {
- // Retrieve the snapshots.
- volSnapshots, err := d.VolumeSnapshots(vol, op)
- if err != nil {
- return err
+ // addVolume adds a volume and its subvolumes to backup file.
+ addVolume := func(v Volume, sourcePrefix string, parentPrefix string, fileNamePrefix string) error {
+ snapName := "" // Default to empty (sending main volume) from migrationHeader.Subvolumes.
+
+ // Detect if we are adding a snapshot by comparing to main volume name.
+ // We can't only use IsSnapshot() as the main vol may itself be a snapshot.
+ if v.IsSnapshot() && v.name != vol.name {
+ _, snapName, _ = shared.InstanceGetParentAndSnapshotName(v.name)
}
- for i, snap := range volSnapshots {
- fullSnapshotName := GetSnapshotVolumeName(vol.name, snap)
+ sentVols := 0
- // Figure out parent and current subvolumes.
- parent := ""
- if i > 0 {
- parent = GetVolumeMountPath(d.name, vol.volType, GetSnapshotVolumeName(vol.name, volSnapshots[i-1]))
+ // Add volume (and any subvolumes if supported) to backup file.
+ for _, subVolume := range optimizedHeader.Subvolumes {
+ if subVolume.Snapshot != snapName {
+ continue // Only add subvolumes related to snapshot name (empty for main vol).
}
- cur := GetVolumeMountPath(d.name, vol.volType, fullSnapshotName)
+ // Detect if parent subvolume exists, and if so use it for differential.
+ parentPath := ""
+ if parentPrefix != "" && btrfsIsSubVolume(filepath.Join(parentPrefix, subVolume.Path)) {
+ parentPath = filepath.Join(parentPrefix, subVolume.Path)
+
+ // Set parent subvolume readonly if needed so we can add the subvolume.
+ if !BTRFSSubVolumeIsRo(parentPath) {
+ err = d.setSubvolumeReadonlyProperty(parentPath, true)
+ if err != nil {
+ return err
+ }
+ defer d.setSubvolumeReadonlyProperty(parentPath, false)
+ }
+ }
- // Make a binary btrfs backup.
- prefix := "snapshots"
- fileName := fmt.Sprintf("%s.bin", snap)
- if vol.volType == VolumeTypeVM {
- prefix = "virtual-machine-snapshots"
- if vol.contentType == ContentTypeFS {
- fileName = fmt.Sprintf("%s-config.bin", snap)
+ // Set subvolume readonly if needed so we can add it.
+ sourcePath := filepath.Join(sourcePrefix, subVolume.Path)
+ if !BTRFSSubVolumeIsRo(sourcePath) {
+ err = d.setSubvolumeReadonlyProperty(sourcePath, true)
+ if err != nil {
+ return err
}
+ defer d.setSubvolumeReadonlyProperty(sourcePath, false)
+ }
+
+ // Default to no subvolume name for root subvolume to maintain backwards compatibility
+ // with earlier optimized dump format. Although restoring this backup file on an earlier
+ // system will not restore the subvolumes stored inside the backup.
+ subVolName := ""
+ if subVolume.Path != string(filepath.Separator) {
+ // Encode the path of the subvolume (without the leading /) into the filename so
+ // that we find the file from the optimized header's Path field on restore.
+ subVolName = fmt.Sprintf("_%s", PathNameEncode(strings.TrimPrefix(subVolume.Path, string(filepath.Separator))))
}
- target := fmt.Sprintf("backup/%s/%s", prefix, fileName)
- err := sendToFile(cur, parent, target)
+ fileName := fmt.Sprintf("%s%s.bin", fileNamePrefix, subVolName)
+ err = sendToFile(sourcePath, parentPath, filepath.Join("backup", fileName))
if err != nil {
- return err
+ return errors.Wrapf(err, "Failed adding volume %v:%s", v.name, subVolume.Path)
+ }
+
+ sentVols++
+ }
+
+ // Ensure we found and sent at least root subvolume of the volume requested.
+ if sentVols < 1 {
+ return fmt.Errorf("No matching subvolume(s) for %q found in subvolumes list", v.name)
+ }
+
+ return nil
+ }
+
+ // Backup snapshots if populated.
+ finalParent := ""
+ for i, snapName := range volSnapshots {
+ snapVol, _ := vol.NewSnapshot(snapName)
+
+ // Locate the parent snapshot.
+ parentSnapshotPath := ""
+ if i > 0 {
+ parentSnapshotPath = GetVolumeMountPath(d.name, vol.volType, GetSnapshotVolumeName(vol.name, volSnapshots[i-1]))
+ }
+
+ // Make a binary btrfs backup.
+ snapDir := "snapshots"
+ fileName := snapName
+ if vol.volType == VolumeTypeVM {
+ snapDir = "virtual-machine-snapshots"
+ if vol.contentType == ContentTypeFS {
+ fileName = fmt.Sprintf("%s-config", snapName)
}
+ }
- finalParent = cur
+ fileNamePrefix := filepath.Join(snapDir, fileName)
+ err := addVolume(snapVol, snapVol.MountPath(), parentSnapshotPath, fileNamePrefix)
+ if err != nil {
+ return err
}
+
+ finalParent = snapVol.MountPath()
}
// Make a temporary copy of the instance.
@@ -1017,18 +1118,17 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWr
return err
}
- // Dump the container to a file.
- fileName := "container.bin"
+ // Dump the instance to a file.
+ fileNamePrefix := "container"
if vol.volType == VolumeTypeVM {
if vol.contentType == ContentTypeFS {
- fileName = "virtual-machine-config.bin"
+ fileNamePrefix = "virtual-machine-config"
} else {
- fileName = "virtual-machine.bin"
+ fileNamePrefix = "virtual-machine"
}
}
- // Dump the container to a file.
- err = sendToFile(targetVolume, finalParent, fmt.Sprintf("backup/%s", fileName))
+ err = addVolume(vol, targetVolume, finalParent, fileNamePrefix)
if err != nil {
return err
}
More information about the lxc-devel
mailing list