The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7360
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) === WIP - just running tests with only backup side changed.
From 1b15e36f20e46a399e28647e2839d1d6da4a3c43 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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 }
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel