The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6396

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) ===

From e474717c85fd0ad269d35596faf85c79686af081 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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
 }
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to