[lxc-devel] [lxd/master] VM snapshot handling

stgraber on Github lxc-bot at linuxcontainers.org
Sun Jan 19 14:54:39 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200119/cf6dd0a8/attachment.bin>
-------------- next part --------------
From 1c3e543c4011f3eeab41fbae671b5306f00656b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 19 Jan 2020 14:27:55 +0200
Subject: [PATCH 1/4] lxd/vm: Fix incorrect bootindex

---
 lxd/instance/drivers/vm_qemu.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index 2b71e0049a..4f14990274 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -1494,7 +1494,7 @@ netdev = "lxd_%s"
 mac = "%s"
 bus = "qemu_pcie%d"
 addr = "0x0"
-bootindex = "%d""
+bootindex = "%d"
 `, devName, devName, devTap, 5+nicIndex, 14+nicIndex, 5+nicIndex, 4+nicIndex, devName, devName, devHwaddr, 5+nicIndex, 2+nicIndex))
 
 	return

From 6f10f885ea6b1de916c93a95d861dc81c1eec177 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 19 Jan 2020 16:32:35 +0200
Subject: [PATCH 2/4] lxd/vm: Implement snapshot restore
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/instance/drivers/vm_qemu.go | 113 +++++++++++++++++++++++++++++++-
 1 file changed, 112 insertions(+), 1 deletion(-)

diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index 4f14990274..ee9a218112 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -1611,7 +1611,118 @@ func (vm *qemu) IsPrivileged() bool {
 
 // Restore restores an instance snapshot.
 func (vm *qemu) Restore(source instance.Instance, stateful bool) error {
-	return fmt.Errorf("Restore Not implemented")
+	if stateful {
+		return fmt.Errorf("Stateful snapshots of VMs aren't supported yet")
+	}
+
+	var ctxMap log.Ctx
+
+	// Load the storage driver
+	pool, err := storagePools.GetPoolByInstance(vm.state, vm)
+	if err != nil {
+		return err
+	}
+
+	// Ensure that storage is mounted for backup.yaml updates.
+	ourStart, err := pool.MountInstance(vm, nil)
+	if err != nil {
+		return err
+	}
+	if ourStart {
+		defer pool.UnmountInstance(vm, nil)
+	}
+
+	// Stop the instance.
+	wasRunning := false
+	if vm.IsRunning() {
+		wasRunning = true
+
+		ephemeral := vm.IsEphemeral()
+		if ephemeral {
+			// Unset ephemeral flag.
+			args := db.InstanceArgs{
+				Architecture: vm.Architecture(),
+				Config:       vm.LocalConfig(),
+				Description:  vm.Description(),
+				Devices:      vm.LocalDevices(),
+				Ephemeral:    false,
+				Profiles:     vm.Profiles(),
+				Project:      vm.Project(),
+				Type:         vm.Type(),
+				Snapshot:     vm.IsSnapshot(),
+			}
+
+			err := vm.Update(args, false)
+			if err != nil {
+				return err
+			}
+
+			// On function return, set the flag back on.
+			defer func() {
+				args.Ephemeral = ephemeral
+				vm.Update(args, true)
+			}()
+		}
+
+		// This will unmount the instance storage.
+		err := vm.Stop(false)
+		if err != nil {
+			return err
+		}
+	}
+
+	ctxMap = log.Ctx{
+		"project":   vm.project,
+		"name":      vm.name,
+		"created":   vm.creationDate,
+		"ephemeral": vm.ephemeral,
+		"used":      vm.lastUsedDate,
+		"source":    source.Name()}
+
+	logger.Info("Restoring instance", ctxMap)
+
+	// Restore the rootfs.
+	err = pool.RestoreInstanceSnapshot(vm, source, nil)
+	if err != nil {
+		return err
+	}
+
+	// Restore the configuration.
+	args := db.InstanceArgs{
+		Architecture: source.Architecture(),
+		Config:       source.LocalConfig(),
+		Description:  source.Description(),
+		Devices:      source.LocalDevices(),
+		Ephemeral:    source.IsEphemeral(),
+		Profiles:     source.Profiles(),
+		Project:      source.Project(),
+		Type:         source.Type(),
+		Snapshot:     source.IsSnapshot(),
+	}
+
+	err = vm.Update(args, false)
+	if err != nil {
+		logger.Error("Failed restoring instance configuration", ctxMap)
+		return err
+	}
+
+	// The old backup file may be out of date (e.g. it doesn't have all the current snapshots of
+	// the instance listed); let's write a new one to be safe.
+	err = vm.UpdateBackupFile()
+	if err != nil {
+		return err
+	}
+
+	vm.state.Events.SendLifecycle(vm.project, "virtual-machine-snapshot-restored", fmt.Sprintf("/1.0/virtual-machines/%s", vm.name), map[string]interface{}{"snapshot_name": vm.name})
+
+	// Restart the insance.
+	if wasRunning {
+		logger.Info("Restored instance", ctxMap)
+		return vm.Start(false)
+	}
+
+	logger.Info("Restored instance", ctxMap)
+	return nil
 }
 
 // Snapshots returns a list of snapshots.

From dcbb7d6979512152b0d0fff81d3d13a7e5d2701e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 19 Jan 2020 16:53:06 +0200
Subject: [PATCH 3/4] lxd/instance: Move LoadAllInternal
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go               | 57 ++--------------------------------
 lxd/container_lxc.go           |  2 +-
 lxd/instance/instance_utils.go | 51 ++++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+), 55 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index b8281e8114..e86439c2df 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -30,7 +30,6 @@ import (
 	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
 	"github.com/lxc/lxd/lxd/task"
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	log "github.com/lxc/lxd/shared/log15"
 	"github.com/lxc/lxd/shared/logger"
@@ -963,7 +962,7 @@ func instanceLoadByProject(s *state.State, project string) ([]instance.Instance,
 		return nil, err
 	}
 
-	return instanceLoadAllInternal(cts, s)
+	return instance.LoadAllInternal(s, cts)
 }
 
 // Load all instances across all projects.
@@ -1013,7 +1012,7 @@ func instanceLoadNodeAll(s *state.State, instanceType instancetype.Type) ([]inst
 		return nil, err
 	}
 
-	return instanceLoadAllInternal(insts, s)
+	return instance.LoadAllInternal(s, insts)
 }
 
 // Load all instances of this nodes under the given project.
@@ -1033,57 +1032,7 @@ func instanceLoadNodeProjectAll(s *state.State, project string, instanceType ins
 		return nil, err
 	}
 
-	return instanceLoadAllInternal(cts, s)
-}
-
-func instanceLoadAllInternal(dbInstances []db.Instance, s *state.State) ([]instance.Instance, error) {
-	// Figure out what profiles are in use
-	profiles := map[string]map[string]api.Profile{}
-	for _, instArgs := range dbInstances {
-		projectProfiles, ok := profiles[instArgs.Project]
-		if !ok {
-			projectProfiles = map[string]api.Profile{}
-			profiles[instArgs.Project] = projectProfiles
-		}
-		for _, profile := range instArgs.Profiles {
-			_, ok := projectProfiles[profile]
-			if !ok {
-				projectProfiles[profile] = api.Profile{}
-			}
-		}
-	}
-
-	// Get the profile data
-	for project, projectProfiles := range profiles {
-		for name := range projectProfiles {
-			_, profile, err := s.Cluster.ProfileGet(project, name)
-			if err != nil {
-				return nil, err
-			}
-
-			projectProfiles[name] = *profile
-		}
-	}
-
-	// Load the instances structs
-	instances := []instance.Instance{}
-	for _, dbInstance := range dbInstances {
-		// Figure out the instances's profiles
-		cProfiles := []api.Profile{}
-		for _, name := range dbInstance.Profiles {
-			cProfiles = append(cProfiles, profiles[dbInstance.Project][name])
-		}
-
-		args := db.InstanceToArgs(&dbInstance)
-		inst, err := instance.Load(s, args, cProfiles)
-		if err != nil {
-			return nil, err
-		}
-
-		instances = append(instances, inst)
-	}
-
-	return instances, nil
+	return instance.LoadAllInternal(s, cts)
 }
 
 func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ca6e59c495..df12b17c21 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3168,7 +3168,7 @@ func (c *containerLXC) Snapshots() ([]instance.Instance, error) {
 	}
 
 	// Build the snapshot list
-	containers, err := instanceLoadAllInternal(snaps, c.state)
+	containers, err := instance.LoadAllInternal(c.state, snaps)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go
index 86a0b3724e..34e62a4c06 100644
--- a/lxd/instance/instance_utils.go
+++ b/lxd/instance/instance_utils.go
@@ -465,6 +465,57 @@ func LoadByProjectAndName(s *state.State, project, name string) (Instance, error
 	return inst, nil
 }
 
+// LoadAllInternal loads a list of db insances into a list of instances.
+func LoadAllInternal(s *state.State, dbInstances []db.Instance) ([]Instance, error) {
+	// Figure out what profiles are in use
+	profiles := map[string]map[string]api.Profile{}
+	for _, instArgs := range dbInstances {
+		projectProfiles, ok := profiles[instArgs.Project]
+		if !ok {
+			projectProfiles = map[string]api.Profile{}
+			profiles[instArgs.Project] = projectProfiles
+		}
+		for _, profile := range instArgs.Profiles {
+			_, ok := projectProfiles[profile]
+			if !ok {
+				projectProfiles[profile] = api.Profile{}
+			}
+		}
+	}
+
+	// Get the profile data
+	for project, projectProfiles := range profiles {
+		for name := range projectProfiles {
+			_, profile, err := s.Cluster.ProfileGet(project, name)
+			if err != nil {
+				return nil, err
+			}
+
+			projectProfiles[name] = *profile
+		}
+	}
+
+	// Load the instances structs
+	instances := []Instance{}
+	for _, dbInstance := range dbInstances {
+		// Figure out the instances's profiles
+		cProfiles := []api.Profile{}
+		for _, name := range dbInstance.Profiles {
+			cProfiles = append(cProfiles, profiles[dbInstance.Project][name])
+		}
+
+		args := db.InstanceToArgs(&dbInstance)
+		inst, err := Load(s, args, cProfiles)
+		if err != nil {
+			return nil, err
+		}
+
+		instances = append(instances, inst)
+	}
+
+	return instances, nil
+}
+
 // WriteBackupFile writes instance's config to a file. Deprecated, use inst.UpdateBackupFile().
 func WriteBackupFile(state *state.State, inst Instance) error {
 	// We only write backup files out for actual instances.

From 69e9e755365c12a6ed061912f65ad6aacd3e2a93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 19 Jan 2020 16:53:14 +0200
Subject: [PATCH 4/4] lxd/vm: Implement Snapshots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/instance/drivers/vm_qemu.go | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index ee9a218112..a204477198 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -1727,7 +1727,38 @@ func (vm *qemu) Restore(source instance.Instance, stateful bool) error {
 
 // Snapshots returns a list of snapshots.
 func (vm *qemu) Snapshots() ([]instance.Instance, error) {
-	return []instance.Instance{}, nil
+	var snaps []db.Instance
+
+	if vm.IsSnapshot() {
+		return []instance.Instance{}, nil
+	}
+
+	// Get all the snapshots
+	err := vm.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
+		var err error
+		snaps, err = tx.ContainerGetSnapshotsFull(vm.Project(), vm.name)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// Build the snapshot list
+	snapshots, err := instance.LoadAllInternal(vm.state, snaps)
+	if err != nil {
+		return nil, err
+	}
+
+	instances := make([]instance.Instance, len(snapshots))
+	for k, v := range snapshots {
+		instances[k] = instance.Instance(v)
+	}
+
+	return instances, nil
 }
 
 // Backups returns a list of backups.


More information about the lxc-devel mailing list