The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7930
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) === Ready for custom volume backups.
From fdea618103eb2c9b00e8dd358871bf52ef683963 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:13:27 +0100 Subject: [PATCH 01/14] lxd/backup/instance/config: Renames InstanceConfig to Config As a precursor to including custom volume support in this struct. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- ...backup_instance_config.go => backup_config.go} | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) rename lxd/backup/{backup_instance_config.go => backup_config.go} (84%) diff --git a/lxd/backup/backup_instance_config.go b/lxd/backup/backup_config.go similarity index 84% rename from lxd/backup/backup_instance_config.go rename to lxd/backup/backup_config.go index 5120087bb5..c0e6d3357d 100644 --- a/lxd/backup/backup_instance_config.go +++ b/lxd/backup/backup_config.go @@ -13,22 +13,22 @@ import ( "github.com/lxc/lxd/shared/api" ) -// InstanceConfig represents the config of an instance that can be stored in a backup.yaml file. -type InstanceConfig struct { +// Config represents the config of a backup that can be stored in a backup.yaml file (or embedded in index.yaml). +type Config struct { Container *api.Instance `yaml:"container"` Snapshots []*api.InstanceSnapshot `yaml:"snapshots"` Pool *api.StoragePool `yaml:"pool"` Volume *api.StorageVolume `yaml:"volume"` } -// ParseInstanceConfigYamlFile decodes the YAML file at path specified into an InstanceConfig. -func ParseInstanceConfigYamlFile(path string) (*InstanceConfig, error) { +// ParseConfigYamlFile decodes the YAML file at path specified into a Config. +func ParseConfigYamlFile(path string) (*Config, error) { data, err := ioutil.ReadFile(path) if err != nil { return nil, err } - backup := InstanceConfig{} + backup := Config{} if err := yaml.Unmarshal(data, &backup); err != nil { return nil, err } @@ -50,8 +50,7 @@ func updateRootDevicePool(devices map[string]map[string]string, poolName string) return false } -// UpdateInstanceConfigStoragePool changes the pool information in the backup.yaml to the pool -// specified in b.Pool. +// UpdateInstanceConfigStoragePool changes the pool information in the backup.yaml to the pool specified in b.Pool. func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info, mountPath string) error { // Load the storage pool. _, pool, err := c.GetStoragePool(b.Pool) @@ -61,7 +60,7 @@ func UpdateInstanceConfigStoragePool(c *db.Cluster, b Info, mountPath string) er f := func(path string) error { // Read in the backup.yaml file. - backup, err := ParseInstanceConfigYamlFile(path) + backup, err := ParseConfigYamlFile(path) if err != nil { return err } From acb0551aad7b544be9055c0a1b2ccce4fbf08f59 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:18:26 +0100 Subject: [PATCH 02/14] lxd/backup/config: Makes Config fields omitempty so custom volume's encoded yaml doesn't contain instance fields Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup/backup_config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lxd/backup/backup_config.go b/lxd/backup/backup_config.go index c0e6d3357d..ddfd8078a0 100644 --- a/lxd/backup/backup_config.go +++ b/lxd/backup/backup_config.go @@ -15,10 +15,10 @@ import ( // Config represents the config of a backup that can be stored in a backup.yaml file (or embedded in index.yaml). type Config struct { - Container *api.Instance `yaml:"container"` - Snapshots []*api.InstanceSnapshot `yaml:"snapshots"` - Pool *api.StoragePool `yaml:"pool"` - Volume *api.StorageVolume `yaml:"volume"` + Container *api.Instance `yaml:"container,omitempty"` + Snapshots []*api.InstanceSnapshot `yaml:"snapshots,omitempty"` + Pool *api.StoragePool `yaml:"pool,omitempty"` + Volume *api.StorageVolume `yaml:"volume,omitempty"` } // ParseConfigYamlFile decodes the YAML file at path specified into a Config. From 52d6705ac9f6189a4a0e6c8f5cde9c84595bf14a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:19:13 +0100 Subject: [PATCH 03/14] lxd/storage/pool/interface: backup.Config usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/pool_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/storage/pool_interface.go b/lxd/storage/pool_interface.go index 82a424f07f..a4de84cc73 100644 --- a/lxd/storage/pool_interface.go +++ b/lxd/storage/pool_interface.go @@ -39,7 +39,7 @@ type Pool interface { DeleteInstance(inst instance.Instance, op *operations.Operation) error UpdateInstance(inst instance.Instance, newDesc string, newConfig map[string]string, op *operations.Operation) error UpdateInstanceBackupFile(inst instance.Instance, op *operations.Operation) error - CheckInstanceBackupFileSnapshots(backupConf *backup.InstanceConfig, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) + CheckInstanceBackupFileSnapshots(backupConf *backup.Config, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) MigrateInstance(inst instance.Instance, conn io.ReadWriteCloser, args *migration.VolumeSourceArgs, op *operations.Operation) error RefreshInstance(inst instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, op *operations.Operation) error From fe5dd1852003af29bed60a9d54d01e72d15801ee Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:19:38 +0100 Subject: [PATCH 04/14] lxd/api/internal: backup.ParseConfigYamlFile usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/api_internal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/api_internal.go b/lxd/api_internal.go index 8df0ac82bc..320f09a955 100644 --- a/lxd/api_internal.go +++ b/lxd/api_internal.go @@ -495,7 +495,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response { // Read in the backup.yaml file. backupYamlPath := filepath.Join(instanceMountPoint, "backup.yaml") - backupConf, err := backup.ParseInstanceConfigYamlFile(backupYamlPath) + backupConf, err := backup.ParseConfigYamlFile(backupYamlPath) if err != nil { return response.SmartError(err) } From e26ca50d0f058a14be21312f7019f531e08bf47c Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:20:06 +0100 Subject: [PATCH 05/14] lxd/storage/backend: backup.Config usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_lxd.go | 4 ++-- lxd/storage/backend_mock.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index c4f34c08d6..d5b523f437 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -3254,7 +3254,7 @@ func (b *lxdBackend) UpdateInstanceBackupFile(inst instance.Instance, op *operat return err } - data, err := yaml.Marshal(&backup.InstanceConfig{ + data, err := yaml.Marshal(&backup.Config{ Container: ci.(*api.Instance), Snapshots: sis, Pool: &b.db, @@ -3302,7 +3302,7 @@ func (b *lxdBackend) UpdateInstanceBackupFile(inst instance.Instance, op *operat // config are removed from the storage device, and any snapshots that exist in the backup config but do not exist // on the storage device are ignored. The remaining set of snapshots that exist on both the storage device and the // backup config are returned. They set can be used to re-create the snapshot database entries when importing. -func (b *lxdBackend) CheckInstanceBackupFileSnapshots(backupConf *backup.InstanceConfig, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) { +func (b *lxdBackend) CheckInstanceBackupFileSnapshots(backupConf *backup.Config, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) { logger := logging.AddContext(b.logger, log.Ctx{"project": projectName, "instance": backupConf.Container.Name, "deleteMissing": deleteMissing}) logger.Debug("CheckInstanceBackupFileSnapshots started") defer logger.Debug("CheckInstanceBackupFileSnapshots finished") diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go index 48d8be7ecd..becf5391bb 100644 --- a/lxd/storage/backend_mock.go +++ b/lxd/storage/backend_mock.go @@ -103,7 +103,7 @@ func (b *mockBackend) UpdateInstanceBackupFile(inst instance.Instance, op *opera return nil } -func (b *mockBackend) CheckInstanceBackupFileSnapshots(backupConf *backup.InstanceConfig, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) { +func (b *mockBackend) CheckInstanceBackupFileSnapshots(backupConf *backup.Config, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) { return nil, nil } From 90a3ec2f66d06cacef316bca93ffe4d5e4fabc65 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:21:53 +0100 Subject: [PATCH 06/14] lxd/backup: Moves Instance interface into own file Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup/backup.go | 7 ------- lxd/backup/backup_instance.go | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 lxd/backup/backup_instance.go diff --git a/lxd/backup/backup.go b/lxd/backup/backup.go index c7226b32d3..6f6867bb05 100644 --- a/lxd/backup/backup.go +++ b/lxd/backup/backup.go @@ -20,13 +20,6 @@ import ( // WorkingDirPrefix is used when temporary working directories are needed. const WorkingDirPrefix = "lxd_backup" -// Instance represents the backup relevant subset of a LXD instance. -// This is used rather than instance.Instance to avoid import loops. -type Instance interface { - Name() string - Project() string -} - // Info represents exported backup information. type Info struct { Project string `json:"-" yaml:"-"` // Project is set during import based on current project. diff --git a/lxd/backup/backup_instance.go b/lxd/backup/backup_instance.go new file mode 100644 index 0000000000..d2d63c7074 --- /dev/null +++ b/lxd/backup/backup_instance.go @@ -0,0 +1,8 @@ +package backup + +// Instance represents the backup relevant subset of a LXD instance. +// This is used rather than instance.Instance to avoid import loops. +type Instance interface { + Name() string + Project() string +} From 9d6ee4b8d012a04391fad2ac8509cb50d4b82a00 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:24:49 +0100 Subject: [PATCH 07/14] lxd/backup: Moves Info struct and GetInfo function into own file Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup/backup.go | 103 ----------------------------------- lxd/backup/backup_info.go | 110 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 103 deletions(-) create mode 100644 lxd/backup/backup_info.go diff --git a/lxd/backup/backup.go b/lxd/backup/backup.go index 6f6867bb05..e586906feb 100644 --- a/lxd/backup/backup.go +++ b/lxd/backup/backup.go @@ -1,16 +1,10 @@ package backup import ( - "context" - "fmt" - "io" "os" "strings" "time" - "github.com/pkg/errors" - "gopkg.in/yaml.v2" - "github.com/lxc/lxd/lxd/project" "github.com/lxc/lxd/lxd/state" "github.com/lxc/lxd/shared" @@ -20,103 +14,6 @@ import ( // WorkingDirPrefix is used when temporary working directories are needed. const WorkingDirPrefix = "lxd_backup" -// Info represents exported backup information. -type Info struct { - Project string `json:"-" yaml:"-"` // Project is set during import based on current project. - Name string `json:"name" yaml:"name"` - 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. - 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"` -} - -// GetInfo extracts backup information from a given ReadSeeker. -func GetInfo(r io.ReadSeeker) (*Info, error) { - result := Info{} - hasIndexFile := false - - // Define some bools used to create points for OptimizedStorage field. - optimizedStorageFalse := false - optimizedHeaderFalse := false - - // Extract - r.Seek(0, 0) - _, _, unpacker, err := shared.DetectCompressionFile(r) - if err != nil { - return nil, err - } - - if unpacker == nil { - return nil, fmt.Errorf("Unsupported backup compression") - } - - tr, cancelFunc, err := shared.CompressedTarReader(context.Background(), r, unpacker) - if err != nil { - return nil, err - } - defer cancelFunc() - - for { - hdr, err := tr.Next() - if err == io.EOF { - break // End of archive - } - if err != nil { - return nil, errors.Wrapf(err, "Error reading backup file info") - } - - if hdr.Name == "backup/index.yaml" { - err = yaml.NewDecoder(tr).Decode(&result) - if err != nil { - return nil, err - } - - hasIndexFile = true - - // Default to container if index doesn't specify instance type. - if result.Type == api.InstanceTypeAny { - result.Type = api.InstanceTypeContainer - } - - // Default to no optimized header if not specified. - if result.OptimizedHeader == nil { - result.OptimizedHeader = &optimizedHeaderFalse - } - - if result.OptimizedStorage != nil { - // No need to continue looking for optimized storage hint using the presence of the - // container.bin file below, as the index.yaml file tells us directly. - cancelFunc() - break - } else { - // Default to non-optimized if not specified and continue reading to see if - // optimized container.bin file present. - result.OptimizedStorage = &optimizedStorageFalse - } - } - - // If the tarball contains a binary dump of the container, then this is an optimized backup. - if hdr.Name == "backup/container.bin" { - optimizedStorageTrue := true - result.OptimizedStorage = &optimizedStorageTrue - - // Stop read loop if index.yaml already parsed. - if hasIndexFile { - cancelFunc() - break - } - } - } - - if !hasIndexFile { - return nil, fmt.Errorf("Backup is missing index.yaml") - } - - return &result, nil -} - // Backup represents a container backup type Backup struct { state *state.State diff --git a/lxd/backup/backup_info.go b/lxd/backup/backup_info.go new file mode 100644 index 0000000000..1c8e47083e --- /dev/null +++ b/lxd/backup/backup_info.go @@ -0,0 +1,110 @@ +package backup + +import ( + "context" + "fmt" + "io" + + "github.com/pkg/errors" + "gopkg.in/yaml.v2" + + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" +) + +// Info represents exported backup information. +type Info struct { + Project string `json:"-" yaml:"-"` // Project is set during import based on current project. + Name string `json:"name" yaml:"name"` + 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. + 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"` +} + +// GetInfo extracts backup information from a given ReadSeeker. +func GetInfo(r io.ReadSeeker) (*Info, error) { + result := Info{} + hasIndexFile := false + + // Define some bools used to create points for OptimizedStorage field. + optimizedStorageFalse := false + optimizedHeaderFalse := false + + // Extract + r.Seek(0, 0) + _, _, unpacker, err := shared.DetectCompressionFile(r) + if err != nil { + return nil, err + } + + if unpacker == nil { + return nil, fmt.Errorf("Unsupported backup compression") + } + + tr, cancelFunc, err := shared.CompressedTarReader(context.Background(), r, unpacker) + if err != nil { + return nil, err + } + defer cancelFunc() + + for { + hdr, err := tr.Next() + if err == io.EOF { + break // End of archive + } + if err != nil { + return nil, errors.Wrapf(err, "Error reading backup file info") + } + + if hdr.Name == "backup/index.yaml" { + err = yaml.NewDecoder(tr).Decode(&result) + if err != nil { + return nil, err + } + + hasIndexFile = true + + // Default to container if index doesn't specify instance type. + if result.Type == api.InstanceTypeAny { + result.Type = api.InstanceTypeContainer + } + + // Default to no optimized header if not specified. + if result.OptimizedHeader == nil { + result.OptimizedHeader = &optimizedHeaderFalse + } + + if result.OptimizedStorage != nil { + // No need to continue looking for optimized storage hint using the presence of the + // container.bin file below, as the index.yaml file tells us directly. + cancelFunc() + break + } else { + // Default to non-optimized if not specified and continue reading to see if + // optimized container.bin file present. + result.OptimizedStorage = &optimizedStorageFalse + } + } + + // If the tarball contains a binary dump of the container, then this is an optimized backup. + if hdr.Name == "backup/container.bin" { + optimizedStorageTrue := true + result.OptimizedStorage = &optimizedStorageTrue + + // Stop read loop if index.yaml already parsed. + if hasIndexFile { + cancelFunc() + break + } + } + } + + if !hasIndexFile { + return nil, fmt.Errorf("Backup is missing index.yaml") + } + + return &result, nil +} From f87c451c0718223ed57511c3e909fe786bbee05e Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:27:31 +0100 Subject: [PATCH 08/14] lxd/backup: Renames backup to backup_common To accomodate more specific implementation types. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup/{backup.go => backup_common.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lxd/backup/{backup.go => backup_common.go} (100%) diff --git a/lxd/backup/backup.go b/lxd/backup/backup_common.go similarity index 100% rename from lxd/backup/backup.go rename to lxd/backup/backup_common.go From 00d0039cbb715cb2125187a8476cc41048c0e4dc Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:44:30 +0100 Subject: [PATCH 09/14] lxd/backup/backup/common: Renames Backup to BackupCommon Removes any-non common backup functionality. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup/backup_common.go | 132 +++--------------------------------- 1 file changed, 9 insertions(+), 123 deletions(-) diff --git a/lxd/backup/backup_common.go b/lxd/backup/backup_common.go index e586906feb..6b298bcdd2 100644 --- a/lxd/backup/backup_common.go +++ b/lxd/backup/backup_common.go @@ -1,156 +1,42 @@ package backup import ( - "os" - "strings" "time" - "github.com/lxc/lxd/lxd/project" "github.com/lxc/lxd/lxd/state" - "github.com/lxc/lxd/shared" - "github.com/lxc/lxd/shared/api" ) // WorkingDirPrefix is used when temporary working directories are needed. const WorkingDirPrefix = "lxd_backup" -// Backup represents a container backup -type Backup struct { - state *state.State - instance Instance - - // Properties +// CommonBackup represents a common backup. +type CommonBackup struct { + state *state.State id int name string creationDate time.Time expiryDate time.Time - instanceOnly bool optimizedStorage bool compressionAlgorithm string } -// New instantiates a new Backup struct. -func New(state *state.State, inst Instance, ID int, name string, creationDate, expiryDate time.Time, instanceOnly, optimizedStorage bool) *Backup { - return &Backup{ - state: state, - instance: inst, - id: ID, - name: name, - creationDate: creationDate, - expiryDate: expiryDate, - instanceOnly: instanceOnly, - optimizedStorage: optimizedStorage, - } +// Name returns the name of the backup. +func (b *CommonBackup) Name() string { + return b.name } // CompressionAlgorithm returns the compression used for the tarball. -func (b *Backup) CompressionAlgorithm() string { +func (b *CommonBackup) CompressionAlgorithm() string { return b.compressionAlgorithm } // SetCompressionAlgorithm sets the tarball compression. -func (b *Backup) SetCompressionAlgorithm(compression string) { +func (b *CommonBackup) SetCompressionAlgorithm(compression string) { b.compressionAlgorithm = compression } -// InstanceOnly returns whether only the instance itself is to be backed up. -func (b *Backup) InstanceOnly() bool { - return b.instanceOnly -} - -// Name returns the name of the backup. -func (b *Backup) Name() string { - return b.name -} - // OptimizedStorage returns whether the backup is to be performed using // optimization supported by the storage driver. -func (b *Backup) OptimizedStorage() bool { +func (b *CommonBackup) OptimizedStorage() bool { return b.optimizedStorage } - -// Rename renames an instance backup. -func (b *Backup) Rename(newName string) error { - oldBackupPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), b.name)) - newBackupPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), newName)) - - // Create the new backup path. - backupsPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), b.instance.Name())) - if !shared.PathExists(backupsPath) { - err := os.MkdirAll(backupsPath, 0700) - if err != nil { - return err - } - } - - // Rename the backup directory. - err := os.Rename(oldBackupPath, newBackupPath) - if err != nil { - return err - } - - // Check if we can remove the instance directory. - empty, _ := shared.PathIsEmpty(backupsPath) - if empty { - err := os.Remove(backupsPath) - if err != nil { - return err - } - } - - // Rename the database record. - err = b.state.Cluster.RenameInstanceBackup(b.name, newName) - if err != nil { - return err - } - - return nil -} - -// Delete removes an instance backup -func (b *Backup) Delete() error { - return DoBackupDelete(b.state, b.instance.Project(), b.name, b.instance.Name()) -} - -// Render returns an InstanceBackup struct of the backup. -func (b *Backup) Render() *api.InstanceBackup { - return &api.InstanceBackup{ - Name: strings.SplitN(b.name, "/", 2)[1], - CreatedAt: b.creationDate, - ExpiresAt: b.expiryDate, - InstanceOnly: b.instanceOnly, - ContainerOnly: b.instanceOnly, - OptimizedStorage: b.optimizedStorage, - } -} - -// DoBackupDelete deletes a backup. -func DoBackupDelete(s *state.State, projectName, backupName, instanceName string) error { - backupPath := shared.VarPath("backups", "instances", project.Instance(projectName, backupName)) - - // Delete the on-disk data. - if shared.PathExists(backupPath) { - err := os.RemoveAll(backupPath) - if err != nil { - return err - } - } - - // Check if we can remove the instance directory. - backupsPath := shared.VarPath("backups", "instances", project.Instance(projectName, instanceName)) - empty, _ := shared.PathIsEmpty(backupsPath) - if empty { - err := os.Remove(backupsPath) - if err != nil { - return err - } - } - - // Remove the database record. - err := s.Cluster.DeleteInstanceBackup(backupName) - if err != nil { - return err - } - - return nil -} From 36a3f9d76401c11c0f5dfca0dfc943487c3965f2 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:45:20 +0100 Subject: [PATCH 10/14] lxd/backup/backup/instance: Adds InstanceBackup using CommonBackup as basis Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup/backup_instance.go | 121 ++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/lxd/backup/backup_instance.go b/lxd/backup/backup_instance.go index d2d63c7074..9d6554bd60 100644 --- a/lxd/backup/backup_instance.go +++ b/lxd/backup/backup_instance.go @@ -1,8 +1,129 @@ package backup +import ( + "os" + "strings" + "time" + + "github.com/lxc/lxd/lxd/project" + "github.com/lxc/lxd/lxd/state" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" +) + // Instance represents the backup relevant subset of a LXD instance. // This is used rather than instance.Instance to avoid import loops. type Instance interface { Name() string Project() string } + +// InstanceBackup represents an instance backup. +type InstanceBackup struct { + CommonBackup + + instance Instance + instanceOnly bool +} + +// NewInstanceBackup instantiates a new InstanceBackup struct. +func NewInstanceBackup(state *state.State, inst Instance, ID int, name string, creationDate time.Time, expiryDate time.Time, instanceOnly bool, optimizedStorage bool) *InstanceBackup { + return &InstanceBackup{ + CommonBackup: CommonBackup{ + state: state, + id: ID, + name: name, + creationDate: creationDate, + expiryDate: expiryDate, + optimizedStorage: optimizedStorage, + }, + instance: inst, + instanceOnly: instanceOnly, + } +} + +// InstanceOnly returns whether only the instance itself is to be backed up. +func (b *InstanceBackup) InstanceOnly() bool { + return b.instanceOnly +} + +// Rename renames an instance backup. +func (b *InstanceBackup) Rename(newName string) error { + oldBackupPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), b.name)) + newBackupPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), newName)) + + // Create the new backup path. + backupsPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), b.instance.Name())) + if !shared.PathExists(backupsPath) { + err := os.MkdirAll(backupsPath, 0700) + if err != nil { + return err + } + } + + // Rename the backup directory. + err := os.Rename(oldBackupPath, newBackupPath) + if err != nil { + return err + } + + // Check if we can remove the instance directory. + empty, _ := shared.PathIsEmpty(backupsPath) + if empty { + err := os.Remove(backupsPath) + if err != nil { + return err + } + } + + // Rename the database record. + err = b.state.Cluster.RenameInstanceBackup(b.name, newName) + if err != nil { + return err + } + + return nil +} + +// Delete removes an instance backup. +func (b *InstanceBackup) Delete() error { + backupPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), b.name)) + + // Delete the on-disk data. + if shared.PathExists(backupPath) { + err := os.RemoveAll(backupPath) + if err != nil { + return err + } + } + + // Check if we can remove the instance directory. + backupsPath := shared.VarPath("backups", "instances", project.Instance(b.instance.Project(), b.instance.Name())) + empty, _ := shared.PathIsEmpty(backupsPath) + if empty { + err := os.Remove(backupsPath) + if err != nil { + return err + } + } + + // Remove the database record. + err := b.state.Cluster.DeleteInstanceBackup(b.name) + if err != nil { + return err + } + + return nil +} + +// Render returns an InstanceBackup struct of the backup. +func (b *InstanceBackup) Render() *api.InstanceBackup { + return &api.InstanceBackup{ + Name: strings.SplitN(b.name, "/", 2)[1], + CreatedAt: b.creationDate, + ExpiresAt: b.expiryDate, + InstanceOnly: b.instanceOnly, + ContainerOnly: b.instanceOnly, + OptimizedStorage: b.optimizedStorage, + } +} From 68e2ecb85f4523449cf25911f6e4a3a47b4bb161 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:46:24 +0100 Subject: [PATCH 11/14] lxd/backup: Changes pruneExpiredContainerBackups to use InstanceBackup.Delete() function Avoids having to expose a separate standalone function. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/backup.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lxd/backup.go b/lxd/backup.go index 3014912073..c7c352e7c2 100644 --- a/lxd/backup.go +++ b/lxd/backup.go @@ -279,12 +279,13 @@ func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error { for _, b := range backups { inst, err := instance.LoadByID(d.State(), b.InstanceID) if err != nil { - return errors.Wrapf(err, "Error deleting instance backup %s", b.Name) + return errors.Wrapf(err, "Error loading instance for deleting backup %q", b.Name) } - err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name()) + instBackup := backup.NewInstanceBackup(d.State(), inst, b.ID, b.Name, b.CreationDate, b.ExpiryDate, b.InstanceOnly, b.OptimizedStorage) + err = instBackup.Delete() if err != nil { - return errors.Wrapf(err, "Error deleting instance backup %s", b.Name) + return errors.Wrapf(err, "Error deleting instance backup %q", b.Name) } } From 17336382dca08e0f80782edb1fe3f2139b42ac98 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:48:23 +0100 Subject: [PATCH 12/14] lxd/instance/instance/utils: backup.InstanceBackup usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/instance_utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go index 7af24f7ee8..589ab7b567 100644 --- a/lxd/instance/instance_utils.go +++ b/lxd/instance/instance_utils.go @@ -661,7 +661,7 @@ func DeviceNextInterfaceHWAddr() (string, error) { } // BackupLoadByName load an instance backup from the database. -func BackupLoadByName(s *state.State, project, name string) (*backup.Backup, error) { +func BackupLoadByName(s *state.State, project, name string) (*backup.InstanceBackup, error) { // Get the backup database record args, err := s.Cluster.GetInstanceBackup(project, name) if err != nil { @@ -674,7 +674,7 @@ func BackupLoadByName(s *state.State, project, name string) (*backup.Backup, err return nil, errors.Wrap(err, "Load instance from database") } - return backup.New(s, instance, args.ID, name, args.CreationDate, args.ExpiryDate, args.InstanceOnly, args.OptimizedStorage), nil + return backup.NewInstanceBackup(s, instance, args.ID, name, args.CreationDate, args.ExpiryDate, args.InstanceOnly, args.OptimizedStorage), nil } // ResolveImage takes an instance source and returns a hash suitable for instance creation or download. From 84e4129c9e9e043db4906afcb837fa58f1751c9e Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:48:37 +0100 Subject: [PATCH 13/14] lxd/instance/instance/interface: backup.InstanceBackup usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/instance_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go index bfe9054a70..e49458bd86 100644 --- a/lxd/instance/instance_interface.go +++ b/lxd/instance/instance_interface.go @@ -59,7 +59,7 @@ type Instance interface { // Snapshots & migration & backups. Restore(source Instance, stateful bool) error Snapshots() ([]Instance, error) - Backups() ([]backup.Backup, error) + Backups() ([]backup.InstanceBackup, error) UpdateBackupFile() error // Config handling. From 76ba76ba0a976994b6e58a2dbc7b66028cb2e964 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 24 Sep 2020 15:49:04 +0100 Subject: [PATCH 14/14] lxd/instance/drivers: backup.InstanceBackup usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_lxc.go | 4 ++-- lxd/instance/drivers/driver_qemu.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go index 70fe25c4e7..49c68c9469 100644 --- a/lxd/instance/drivers/driver_lxc.go +++ b/lxd/instance/drivers/driver_lxc.go @@ -3274,7 +3274,7 @@ func (c *lxc) Snapshots() ([]instance.Instance, error) { } // Backups returns the backups of the instance. -func (c *lxc) Backups() ([]backup.Backup, error) { +func (c *lxc) Backups() ([]backup.InstanceBackup, error) { // Get all the backups backupNames, err := c.state.Cluster.GetInstanceBackups(c.project, c.name) if err != nil { @@ -3282,7 +3282,7 @@ func (c *lxc) Backups() ([]backup.Backup, error) { } // Build the backup list - backups := []backup.Backup{} + backups := []backup.InstanceBackup{} for _, backupName := range backupNames { backup, err := instance.BackupLoadByName(c.state, c.project, backupName) if err != nil { diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index c0ad24b982..8572365fb2 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2505,8 +2505,8 @@ func (vm *qemu) Snapshots() ([]instance.Instance, error) { } // Backups returns a list of backups. -func (vm *qemu) Backups() ([]backup.Backup, error) { - return []backup.Backup{}, nil +func (vm *qemu) Backups() ([]backup.InstanceBackup, error) { + return []backup.InstanceBackup{}, nil } // Rename the instance.
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel