[lxc-devel] [lxd/master] Storage Delete Instance and Delete Instance Snapshot
tomponline on Github
lxc-bot at linuxcontainers.org
Mon Nov 4 20:08:21 UTC 2019
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/20191104/8dce56fc/attachment-0001.bin>
-------------- next part --------------
From e474717c85fd0ad269d35596faf85c79686af081 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 09:09:20 +0000
Subject: [PATCH 1/8] lxd/container/lxc: Links container Delete() to new
storage package
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/container_lxc.go | 199 ++++++++++++++++++++++++++++++-------------
1 file changed, 140 insertions(+), 59 deletions(-)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8dc40f7292..7cef710a01 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3522,7 +3522,11 @@ func (c *containerLXC) Delete() error {
return err
}
- // Check if we're dealing with "lxd import"
+ // Check if we're dealing with "lxd import".
+ // "lxd import" is used for disaster recovery, where you already have a container and
+ // snapshots on disk but no DB entry. As such if something has gone wrong during the
+ // creation of the instance and we are now being asked to delete the instance, we should
+ // not remove the storage volumes themselves as this would cause data loss.
isImport := false
if c.storage != nil {
_, poolName, _ := c.storage.GetContainerPoolInfo()
@@ -3539,93 +3543,170 @@ func (c *containerLXC) Delete() error {
}
}
- // Attempt to initialize storage interface for the container.
- err := c.initStorage()
+ // Get the storage pool name of the instance.
+ poolName, err := c.state.Cluster.ContainerPool(c.Project(), c.Name())
if err != nil {
- logger.Warnf("Failed to init storage: %v", err)
+ return err
}
- if c.IsSnapshot() {
- // Remove the snapshot
- if c.storage != nil && !isImport {
- err := c.storage.ContainerSnapshotDelete(c)
+ // Check if we can load new storage layer for pool driver type.
+ pool, err := storagePools.GetPoolByName(c.state, poolName)
+ if err != storageDrivers.ErrUnknownDriver {
+ if err != nil {
+ return err
+ }
+
+ if c.IsSnapshot() {
+ if !isImport {
+ // Remove snapshot volume and database record.
+ err = pool.DeleteInstanceSnapshot(c, nil)
+ if err != nil {
+ return err
+ }
+ }
+ } else {
+ // Remove all snapshots by initialising each snapshot as an Instance and
+ // calling its Delete function.
+ err := containerDeleteSnapshots(c.state, c.Project(), c.Name())
+ if err != nil {
+ logger.Error("Failed to delete instance snapshots", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err})
+ return err
+ }
+
+ // Remove all backups.
+ backups, err := c.Backups()
+ if err != nil {
+ return err
+ }
+
+ for _, backup := range backups {
+ err = backup.Delete()
+ if err != nil {
+ return err
+ }
+ }
+
+ if !isImport {
+ // Remove the storage volume, snapshot volumes and database records.
+ err = pool.DeleteInstance(c, nil)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Clean things up.
+ c.cleanup()
+
+ // Delete the MAAS entry.
+ err = c.maasDelete()
if err != nil {
- logger.Warn("Failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
+ logger.Error("Failed deleting instance MAAS record", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err})
return err
}
+
+ // Run device removal function for each device.
+ for k, m := range c.expandedDevices {
+ err = c.deviceRemove(k, m)
+ if err != nil && err != device.ErrUnsupportedDevType {
+ return errors.Wrapf(err, "Failed to remove device '%s'", k)
+ }
+ }
}
- } else {
- // Remove all snapshots
- err := containerDeleteSnapshots(c.state, c.Project(), c.Name())
- if err != nil {
- logger.Warn("Failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
+
+ // Remove the database record of the instance or snapshot instance.
+ if err := c.state.Cluster.ContainerRemove(c.Project(), c.Name()); err != nil {
+ logger.Error("Failed deleting instance entry", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err})
return err
}
-
- // Remove all backups
- backups, err := c.Backups()
+ } else {
+ // Attempt to initialize storage interface for the container.
+ err := c.initStorage()
if err != nil {
- return err
+ logger.Warnf("Failed to init storage: %v", err)
}
- for _, backup := range backups {
- err = backup.Delete()
+ if c.IsSnapshot() {
+ // Remove the snapshot
+ if c.storage != nil && !isImport {
+ err := c.storage.ContainerSnapshotDelete(c)
+ if err != nil {
+ logger.Warn("Failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
+ return err
+ }
+ }
+ } else {
+ // Remove all snapshots
+ err := containerDeleteSnapshots(c.state, c.Project(), c.Name())
if err != nil {
+ logger.Warn("Failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
return err
}
- }
- // Clean things up
- c.cleanup()
+ // Remove all backups
+ backups, err := c.Backups()
+ if err != nil {
+ return err
+ }
- // Delete the container from disk
- if c.storage != nil && !isImport {
- _, poolName, _ := c.storage.GetContainerPoolInfo()
- containerMountPoint := storagePools.GetContainerMountPoint(c.Project(), poolName, c.Name())
- if shared.PathExists(c.Path()) ||
- shared.PathExists(containerMountPoint) {
- err := c.storage.ContainerDelete(c)
+ for _, backup := range backups {
+ err = backup.Delete()
if err != nil {
- logger.Error("Failed deleting container storage", log.Ctx{"name": c.Name(), "err": err})
return err
}
}
- }
- // Delete the MAAS entry
- err = c.maasDelete()
- if err != nil {
- logger.Error("Failed deleting container MAAS record", log.Ctx{"name": c.Name(), "err": err})
- return err
- }
+ // Clean things up
+ c.cleanup()
- // Remove devices from container.
- for k, m := range c.expandedDevices {
- err = c.deviceRemove(k, m)
- if err != nil && err != device.ErrUnsupportedDevType {
- return errors.Wrapf(err, "Failed to remove device '%s'", k)
+ // Delete the container from disk
+ if c.storage != nil && !isImport {
+ _, poolName, _ := c.storage.GetContainerPoolInfo()
+ containerMountPoint := storagePools.GetContainerMountPoint(c.Project(), poolName, c.Name())
+ if shared.PathExists(c.Path()) ||
+ shared.PathExists(containerMountPoint) {
+ err := c.storage.ContainerDelete(c)
+ if err != nil {
+ logger.Error("Failed deleting container storage", log.Ctx{"name": c.Name(), "err": err})
+ return err
+ }
+ }
}
- }
- }
- // Remove the database record
- if err := c.state.Cluster.ContainerRemove(c.project, c.Name()); err != nil {
- logger.Error("Failed deleting container entry", log.Ctx{"name": c.Name(), "err": err})
- return err
- }
+ // Delete the MAAS entry
+ err = c.maasDelete()
+ if err != nil {
+ logger.Error("Failed deleting container MAAS record", log.Ctx{"name": c.Name(), "err": err})
+ return err
+ }
- // Remove the database entry for the pool device
- if c.storage != nil {
- // Get the name of the storage pool the container is attached to. This
- // reverse-engineering works because container names are globally
- // unique.
- poolID, _, _ := c.storage.GetContainerPoolInfo()
+ // Remove devices from container.
+ for k, m := range c.expandedDevices {
+ err = c.deviceRemove(k, m)
+ if err != nil && err != device.ErrUnsupportedDevType {
+ return errors.Wrapf(err, "Failed to remove device '%s'", k)
+ }
+ }
+ }
- // Remove volume from storage pool.
- err := c.state.Cluster.StoragePoolVolumeDelete(c.Project(), c.Name(), storagePoolVolumeTypeContainer, poolID)
- if err != nil {
+ // Remove the database record
+ if err := c.state.Cluster.ContainerRemove(c.project, c.Name()); err != nil {
+ logger.Error("Failed deleting container entry", log.Ctx{"name": c.Name(), "err": err})
return err
}
+
+ // Remove the database entry for the pool device
+ if c.storage != nil {
+ // Get the name of the storage pool the container is attached to. This
+ // reverse-engineering works because container names are globally
+ // unique.
+ poolID, _, _ := c.storage.GetContainerPoolInfo()
+
+ // Remove volume from storage pool.
+ err := c.state.Cluster.StoragePoolVolumeDelete(c.Project(), c.Name(), storagePoolVolumeTypeContainer, poolID)
+ if err != nil {
+ return err
+ }
+ }
}
logger.Info("Deleted container", ctxMap)
From 82f593c56aa18c8a251122e5ee013224a92fd4f9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 09:09:47 +0000
Subject: [PATCH 2/8] lxd/container/lxc: Improves error logging in diskState
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/container_lxc.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7cef710a01..3e193d5756 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5992,13 +5992,13 @@ func (c *containerLXC) diskState() map[string]api.InstanceStateDisk {
pool, err := storagePools.GetPoolByName(c.state, dev.Config["pool"])
if err != storageDrivers.ErrUnknownDriver {
if err != nil {
- logger.Errorf("Error loading storage pool for %s: %v", c.Name(), err)
+ logger.Error("Error loading storage pool", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err})
continue
}
usage, err = pool.GetInstanceUsage(c)
if err != nil {
- logger.Errorf("Error getting disk usage for %s: %v", c.Name(), err)
+ logger.Error("Error getting disk usage", log.Ctx{"project": c.Project(), "instance": c.Name(), "err": err})
continue
}
} else {
From e2dfe1cc77e2c7f6c9685c83173409f54e2b7332 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 09:37:04 +0000
Subject: [PATCH 3/8] lxd/storage/backend/lxd: Removes duplicated code from
DeleteCustomVolume
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/backend_lxd.go | 20 ++++----------------
1 file changed, 4 insertions(+), 16 deletions(-)
diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 281497532f..3ff3928184 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -801,22 +801,9 @@ func (b *lxdBackend) DeleteCustomVolume(volName string, op *operations.Operation
return err
}
- // Remove the database entry and volume from the storage device for each snapshot.
+ // Remove each snapshot.
for _, snapshot := range snapshots {
- // Extract just the snapshot name from the snapshot.
- _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snapshot.Name)
-
- // Delete the snapshot volume from the storage device.
- // Must come before Cluster.StoragePoolVolumeDelete otherwise driver won't be able
- // to get volume ID.
- err = b.driver.DeleteVolumeSnapshot(drivers.VolumeTypeCustom, volName, snapName, op)
- if err != nil {
- return err
- }
-
- // Remove the snapshot volume record from the database.
- // Must come after driver.DeleteVolume so that volume ID is still available.
- err = b.state.Cluster.StoragePoolVolumeDelete("default", snapshot.Name, db.StoragePoolVolumeTypeCustom, b.ID())
+ err = b.DeleteCustomVolumeSnapshot(snapshot.Name, op)
if err != nil {
return err
}
@@ -962,12 +949,13 @@ func (b *lxdBackend) DeleteCustomVolumeSnapshot(volName string, op *operations.O
}
// Delete the snapshot from the storage device.
+ // Must come before DB StoragePoolVolumeDelete so that the volume ID is still available.
err := b.driver.DeleteVolumeSnapshot(drivers.VolumeTypeCustom, parentName, snapName, op)
if err != nil {
return err
}
- // Finally, remove the volume record from the database.
+ // Remove the snapshot volume record from the database.
err = b.state.Cluster.StoragePoolVolumeDelete("default", volName, db.StoragePoolVolumeTypeCustom, b.ID())
if err != nil {
return err
From 44a3878704664e3f17d44b8283087d325ebe15d6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 20:04:46 +0000
Subject: [PATCH 4/8] lxd/storage/backend/lxd: Adds symlink management
functions
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/backend_lxd.go | 40 ++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 3ff3928184..005a78b4ae 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -186,6 +186,46 @@ func (b *lxdBackend) createInstanceSymlink(inst Instance, mountPath string) erro
return nil
}
+// removeInstanceSymlink removes a symlink in the instance directory to the instance's mount path.
+func (b *lxdBackend) removeInstanceSymlink(inst Instance) error {
+ symlinkPath := inst.Path()
+ if shared.PathExists(symlinkPath) {
+ err := os.Remove(symlinkPath)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// createInstanceSnapshotSymlink creates a symlink in the snapshot directory to the instance's
+// snapshot path.
+func (b *lxdBackend) createInstanceSnapshotSymlink(inst Instance, mountPath string) error {
+ // Check we can convert the instance to the volume types needed.
+ volType, err := InstanceTypeToVolumeType(inst.Type())
+ if err != nil {
+ return err
+ }
+
+ snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(inst.Project(), inst.Name()))
+ volStorageName := project.Prefix(inst.Project(), inst.Name())
+
+ snapshotTargetPath, err := drivers.GetVolumeSnapshotDir(b.name, volType, volStorageName)
+ if err != nil {
+ return err
+ }
+
+ if !shared.PathExists(snapshotMntPointSymlink) {
+ err := os.Symlink(snapshotTargetPath, snapshotMntPointSymlink)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
// imageFiller returns a function that can be used as a filler function with CreateVolume(). This
// function will unpack the specified image archive into the specified mount path of the volume.
func (b *lxdBackend) imageFiller(fingerprint string, op *operations.Operation) func(mountPath string) error {
From 82a4d3d77f1b6c92a174163277c5d3ecab260b56 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 20:05:07 +0000
Subject: [PATCH 5/8] lxd/storage/backend/lxd: Adds Instance and Instance
Snapshot delete functions
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/backend_lxd.go | 92 +++++++++++++++++++++++++++++++++++++-
1 file changed, 90 insertions(+), 2 deletions(-)
diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 005a78b4ae..9c1f4651e5 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -312,12 +312,62 @@ func (b *lxdBackend) RenameInstance(inst Instance, newName string, op *operation
return ErrNotImplemented
}
+// DeleteInstance removes the Instance's root volume (all snapshots need to be removed first).
func (b *lxdBackend) DeleteInstance(inst Instance, op *operations.Operation) error {
logger := logging.AddContext(b.logger, log.Ctx{"project": inst.Project(), "instance": inst.Name()})
logger.Debug("DeleteInstance started")
defer logger.Debug("DeleteInstance finished")
- return ErrNotImplemented
+ if inst.IsSnapshot() {
+ return fmt.Errorf("Instance must not be a snapshot")
+ }
+
+ // Check we can convert the instance to the volume types needed.
+ volType, err := InstanceTypeToVolumeType(inst.Type())
+ if err != nil {
+ return err
+ }
+
+ volDBType, err := VolumeTypeToDBType(volType)
+ if err != nil {
+ return err
+ }
+
+ // Get any snapshots the instance has in the format <instance name>/<snapshot name>.
+ snapshots, err := b.state.Cluster.ContainerGetSnapshots(inst.Project(), inst.Name())
+ if err != nil {
+ return err
+ }
+
+ // Check all snapshots are already removed.
+ if len(snapshots) > 0 {
+ return fmt.Errorf("Cannot remove an instance volume that has snapshots")
+ }
+
+ // Get the volume name on storage.
+ volStorageName := project.Prefix(inst.Project(), inst.Name())
+
+ // Delete the volume from the storage device. Must come after snapshots are removed.
+ // Must come before DB StoragePoolVolumeDelete so that the volume ID is still available.
+ logger.Debug("Deleting instance volume", log.Ctx{"volName": volStorageName})
+ err = b.driver.DeleteVolume(volType, volStorageName, op)
+ if err != nil {
+ return err
+ }
+
+ // Remove symlink.
+ err = b.removeInstanceSymlink(inst)
+ if err != nil {
+ return err
+ }
+
+ // Remove the volume record from the database.
+ err = b.state.Cluster.StoragePoolVolumeDelete(inst.Project(), inst.Name(), volDBType, b.ID())
+ if err != nil {
+ return err
+ }
+
+ return nil
}
func (b *lxdBackend) MigrateInstance(inst Instance, snapshots bool, args migration.SourceArgs) (migration.StorageSourceDriver, error) {
@@ -369,8 +419,46 @@ func (b *lxdBackend) RenameInstanceSnapshot(inst Instance, newName string, op *o
return ErrNotImplemented
}
+// DeleteInstanceSnapshot removes the snapshot volume for the supplied snapshot instance.
func (b *lxdBackend) DeleteInstanceSnapshot(inst Instance, op *operations.Operation) error {
- return ErrNotImplemented
+ logger := logging.AddContext(b.logger, log.Ctx{"project": inst.Project(), "instance": inst.Name()})
+ logger.Debug("DeleteInstanceSnapshot started")
+ defer logger.Debug("DeleteInstanceSnapshot finished")
+
+ parentName, snapName, isSnap := shared.ContainerGetParentAndSnapshotName(inst.Name())
+ if !inst.IsSnapshot() || !isSnap {
+ return fmt.Errorf("Instance must be a snapshot")
+ }
+
+ // Check we can convert the instance to the volume types needed.
+ volType, err := InstanceTypeToVolumeType(inst.Type())
+ if err != nil {
+ return err
+ }
+
+ volDBType, err := VolumeTypeToDBType(volType)
+ if err != nil {
+ return err
+ }
+
+ // Get the parent volume name on storage.
+ parentStorageName := project.Prefix(inst.Project(), parentName)
+
+ // Delete the snapshot from the storage device.
+ // Must come before DB StoragePoolVolumeDelete so that the volume ID is still available.
+ logger.Debug("Deleting instance snapshot volume", log.Ctx{"volName": parentStorageName, "snapshotName": snapName})
+ err = b.driver.DeleteVolumeSnapshot(volType, parentStorageName, snapName, op)
+ if err != nil {
+ return err
+ }
+
+ // Remove the snapshot volume record from the database.
+ err = b.state.Cluster.StoragePoolVolumeDelete(inst.Project(), drivers.GetSnapshotVolumeName(parentName, snapName), volDBType, b.ID())
+ if err != nil {
+ return err
+ }
+
+ return nil
}
func (b *lxdBackend) RestoreInstanceSnapshot(inst Instance, op *operations.Operation) error {
From e6787daf016b12f517d9ce9896b170eeb6e5c7ca Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 20:06:05 +0000
Subject: [PATCH 6/8] lxd/storage/drivers/driver/dir: Reinstates
DeleteParentSnapshotDirIfEmpty for volume and snapshot deletion
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/driver_dir.go | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go
index 61ed55a992..7336d4fb18 100644
--- a/lxd/storage/drivers/driver_dir.go
+++ b/lxd/storage/drivers/driver_dir.go
@@ -644,12 +644,7 @@ func (d *dir) DeleteVolume(volType VolumeType, volName string, op *operations.Op
// Although the volume snapshot directory should already be removed, lets remove it here
// to just in case the top-level directory is left.
- snapshotDir, err := GetVolumeSnapshotDir(d.name, volType, volName)
- if err != nil {
- return err
- }
-
- err = os.RemoveAll(snapshotDir)
+ err = DeleteParentSnapshotDirIfEmpty(d.name, volType, volName)
if err != nil {
return err
}
@@ -830,6 +825,12 @@ func (d *dir) DeleteVolumeSnapshot(volType VolumeType, volName string, snapshotN
return err
}
+ // Remove the parent snapshot directory if this is the last snapshot being removed.
+ err = DeleteParentSnapshotDirIfEmpty(d.name, volType, volName)
+ if err != nil {
+ return err
+ }
+
return nil
}
From f5a73736968a6f391e053dff512549d204747342 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 20:06:34 +0000
Subject: [PATCH 7/8] lxd/storage/drivers/utils: Updates
DeleteParentSnapshotDirIfEmpty to also remove symlink
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/drivers/utils.go | 39 ++++++++++++++++++++++--------------
1 file changed, 24 insertions(+), 15 deletions(-)
diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index c0bbdafe82..d205498e64 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -5,7 +5,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
- "strings"
"time"
"golang.org/x/sys/unix"
@@ -175,27 +174,37 @@ func GetSnapshotVolumeName(parentName, snapshotName string) string {
}
// DeleteParentSnapshotDirIfEmpty removes the parent snapshot directory if it is empty.
-// It accepts the volume name of a snapshot in the form "volume/snap" and the volume path of the
-// snapshot. It will then remove the snapshots directory above "/snap" if it is empty.
-func DeleteParentSnapshotDirIfEmpty(volName string, volPath string) error {
- _, snapName, isSnap := shared.ContainerGetParentAndSnapshotName(volName)
- if !isSnap {
- return fmt.Errorf("Volume is not a snapshot")
- }
-
- // Extract just the snapshot name from the volume name and then remove that suffix
- // from the volume path. This will get us the parent snapshots directory we need.
- snapshotsPath := strings.TrimSuffix(volPath, snapName)
- isEmpty, err := shared.PathIsEmpty(snapshotsPath)
+// It accepts the pool name, volume type and parent volume name.
+func DeleteParentSnapshotDirIfEmpty(poolName string, volType VolumeType, volName string) error {
+ snapshotsPath, err := GetVolumeSnapshotDir(poolName, volType, volName)
if err != nil {
return err
}
- if isEmpty {
- err := os.Remove(snapshotsPath)
+ // If it exists, try to delete it.
+ if shared.PathExists(snapshotsPath) {
+ isEmpty, err := shared.PathIsEmpty(snapshotsPath)
if err != nil {
return err
}
+
+ if isEmpty {
+ err := os.Remove(snapshotsPath)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ // If it no longer exists (may have just removed it), remove symlink.
+ if !shared.PathExists(snapshotsPath) {
+ snapshotSymlink := shared.VarPath("snapshots", volName)
+ if shared.PathExists(snapshotSymlink) {
+ err := os.Remove(snapshotSymlink)
+ if err != nil {
+ return err
+ }
+ }
}
return nil
From 10b7061332f6d10b9ff1f05481acbd9cd7a30f7c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 4 Nov 2019 20:06:54 +0000
Subject: [PATCH 8/8] lxd/storage/interfaces: Adds IsSnapshot to Instance
interface
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/interfaces.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/storage/interfaces.go b/lxd/storage/interfaces.go
index 592b28b14f..4e787f07bb 100644
--- a/lxd/storage/interfaces.go
+++ b/lxd/storage/interfaces.go
@@ -19,6 +19,7 @@ type Instance interface {
Path() string
IsRunning() bool
+ IsSnapshot() bool
TemplateApply(trigger string) error
}
More information about the lxc-devel
mailing list