[lxc-devel] [lxd/master] [RFC] storage: custom storage volume snapshot API

brauner on Github lxc-bot at linuxcontainers.org
Mon Aug 13 14:17:19 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 426 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180813/dc99a7c0/attachment.bin>
-------------- next part --------------
From a1d466e2cb27925fbc0ad1b4aebffd50b7e18227 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 24 Jul 2018 11:24:29 +0200
Subject: [PATCH 01/62] db: add StorageVolumeKind to indicate snapshots

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/db/storage_volumes.go | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lxd/db/storage_volumes.go b/lxd/db/storage_volumes.go
index 6ba972105..e187d80cd 100644
--- a/lxd/db/storage_volumes.go
+++ b/lxd/db/storage_volumes.go
@@ -8,6 +8,19 @@ import (
 	"github.com/lxc/lxd/lxd/db/query"
 )
 
+// StorageVolumeType encodes the type of storage volume (either regular or snapshot).
+type StorageVolumeKind int
+
+// Numerical codes for storage volume types.
+const (
+	// storageVolumeKindDBInternal is only used internally to indicate that
+	// all storage volumes are to be retrieved irrespective of their kind.
+	storageVolumeKindDBInternal StorageVolumeKind = -1
+	StorageVolumeKindValid      StorageVolumeKind = 0
+	StorageVolumeKindRegular    StorageVolumeKind = 0
+	StorageVolumeKindSnapshot   StorageVolumeKind = 1
+)
+
 // StorageVolumeNodeAddresses returns the addresses of all nodes on which the
 // volume with the given name if defined.
 //

From a545190ae58e96f2934ca698c37741fd8af2f1dd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 24 Jul 2018 11:25:05 +0200
Subject: [PATCH 02/62] db: updateFromV9()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/db/cluster/update.go | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 5d09b8280..80dc124bf 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -30,15 +30,25 @@ func SchemaDotGo() error {
 var SchemaVersion = len(updates)
 
 var updates = map[int]schema.Update{
-	1: updateFromV0,
-	2: updateFromV1,
-	3: updateFromV2,
-	4: updateFromV3,
-	5: updateFromV4,
-	6: updateFromV5,
-	7: updateFromV6,
-	8: updateFromV7,
-	9: updateFromV8,
+	1:  updateFromV0,
+	2:  updateFromV1,
+	3:  updateFromV2,
+	4:  updateFromV3,
+	5:  updateFromV4,
+	6:  updateFromV5,
+	7:  updateFromV6,
+	8:  updateFromV7,
+	9:  updateFromV8,
+	10: updateFromV9,
+}
+
+func updateFromV9(tx *sql.Tx) error {
+	stmt := `
+ALTER TABLE storage_volumes ADD COLUMN kind INTEGER NOT NULL DEFAULT 0;
+UPDATE storage_volumes SET kind = 0;
+`
+	_, err := tx.Exec(stmt)
+	return err
 }
 
 // The lvm.thinpool_name and lvm.vg_name config keys are node-specific and need

From b9669a07a28a068bf7d9967ac5f122b68ae3630f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 15:37:57 +0200
Subject: [PATCH 03/62] patches: patchStorageApiRenameContainerSnapshotsDir()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/patches.go | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/lxd/patches.go b/lxd/patches.go
index 2297c3a36..51d90f112 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -58,6 +58,7 @@ var patches = []patch{
 	{name: "storage_api_permissions", run: patchStorageApiPermissions},
 	{name: "container_config_regen", run: patchContainerConfigRegen},
 	{name: "lvm_node_specific_config_keys", run: patchLvmNodeSpecificConfigKeys},
+	{name: "storage_api_rename_container_snapshots_dir", run: patchStorageApiRenameContainerSnapshotsDir},
 }
 
 type patch struct {
@@ -2940,6 +2941,36 @@ func patchStorageApiPermissions(name string, d *Daemon) error {
 	return nil
 }
 
+func patchStorageApiRenameContainerSnapshotsDir(name string, d *Daemon) error {
+	storagePoolsPath := shared.VarPath("storage-pools")
+	storagePoolsDir, err := os.Open(storagePoolsPath)
+	if err != nil {
+		return err
+	}
+
+	// Get a list of all storage pools.
+	storagePoolNames, err := storagePoolsDir.Readdirnames(-1)
+	storagePoolsDir.Close()
+	if err != nil {
+		return err
+	}
+
+	for _, poolName := range storagePoolNames {
+		containerSnapshotDirOld := shared.VarPath("storage-pools", poolName, "snapshots")
+		containerSnapshotDirNew := shared.VarPath("storage-pools", poolName, "containers-snapshots")
+		err := shared.FileMove(containerSnapshotDirOld, containerSnapshotDirNew)
+		if err != nil {
+			if os.IsNotExist(err) {
+				continue
+			}
+
+			return err
+		}
+	}
+
+	return nil
+}
+
 // Patches end here
 
 // Here are a couple of legacy patches that were originally in

From 7cf52e78f57e01877f6fbd276d5f128308cb2b76 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 15:55:02 +0200
Subject: [PATCH 04/62] storage: adapt getContainerMountPoint()

getContainerMountPoint() needs to change from using "snapshots" as directory
name to "containers-snapshots".

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index 8982f63d7..51810a8e7 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -585,7 +585,7 @@ func getContainerMountPoint(poolName string, containerName string) string {
 
 // ${LXD_DIR}/storage-pools/<pool>/snapshots/<snapshot_name>
 func getSnapshotMountPoint(poolName string, snapshotName string) string {
-	return shared.VarPath("storage-pools", poolName, "snapshots", snapshotName)
+	return shared.VarPath("storage-pools", poolName, "containers-snapshots", snapshotName)
 }
 
 // ${LXD_DIR}/storage-pools/<pool>/images/<fingerprint>

From b09ff6e44eb93d7a49992aaff6493d72e86bf612 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 16:31:39 +0200
Subject: [PATCH 05/62] btrfs: adapt getSnapshotSubvolumePath()

getSnapshotSubvolumePath() needs to change from using "snapshots" as directory
name to "containers-snapshots".

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index a78ce9616..dfc405dd9 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -52,7 +52,7 @@ func (s *storageBtrfs) getContainerSubvolumePath(poolName string) string {
 
 // ${LXD_DIR}/storage-pools/<pool>/snapshots
 func getSnapshotSubvolumePath(poolName string, containerName string) string {
-	return shared.VarPath("storage-pools", poolName, "snapshots", containerName)
+	return shared.VarPath("storage-pools", poolName, "containers-snapshots", containerName)
 }
 
 // ${LXD_DIR}/storage-pools/<pool>/images

From 0682680a15b3eababb23f3c065672558b41f7651 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 16:00:40 +0200
Subject: [PATCH 06/62] storage: add getStoragePoolVolumeSnapshotMountPoint()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage.go       | 5 +++++
 lxd/storage_btrfs.go | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index 51810a8e7..22ad36053 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -603,6 +603,11 @@ func getBackupMountPoint(poolName string, backupName string) string {
 	return shared.VarPath("storage-pools", poolName, "backups", backupName)
 }
 
+// ${LXD_DIR}/storage-pools/<pool>/custom-snapshots/<custom volume name>/<snapshot name>
+func getStoragePoolVolumeSnapshotMountPoint(poolName string, snapshotName string) string {
+	return shared.VarPath("storage-pools", poolName, "custom-snapshots", snapshotName)
+}
+
 func createContainerMountpoint(mountPoint string, mountPointSymlink string, privileged bool) error {
 	var mode os.FileMode
 	if privileged {
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index dfc405dd9..a06faf832 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -50,7 +50,7 @@ func (s *storageBtrfs) getContainerSubvolumePath(poolName string) string {
 	return shared.VarPath("storage-pools", poolName, "containers")
 }
 
-// ${LXD_DIR}/storage-pools/<pool>/snapshots
+// ${LXD_DIR}/storage-pools/<pool>/containers-snapshots
 func getSnapshotSubvolumePath(poolName string, containerName string) string {
 	return shared.VarPath("storage-pools", poolName, "containers-snapshots", containerName)
 }

From 8c0500ae99bf4625a1e111d49ba9c64345f6889c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 16:26:24 +0200
Subject: [PATCH 07/62] btrfs: create new "custom-snapshots" subvolume

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index a06faf832..176b37cc5 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -263,6 +263,12 @@ func (s *storageBtrfs) StoragePoolCreate() error {
 		return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
 	}
 
+	dummyDir = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, "")
+	err = btrfsSubVolumeCreate(dummyDir)
+	if err != nil {
+		return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
+	}
+
 	err = s.StoragePoolCheck()
 	if err != nil {
 		return err

From 93b848fe5206f859408f1f1b80e7c1cbeec0a9d8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 16:34:05 +0200
Subject: [PATCH 08/62] btrfs: add getCustomSnapshotSubvolumePath()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 176b37cc5..bb77259d1 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -65,6 +65,11 @@ func (s *storageBtrfs) getCustomSubvolumePath(poolName string) string {
 	return shared.VarPath("storage-pools", poolName, "custom")
 }
 
+// ${LXD_DIR}/storage-pools/<pool>/custom-snapshots
+func (s *storageBtrfs) getCustomSnapshotSubvolumePath(poolName string) string {
+	return shared.VarPath("storage-pools", poolName, "custom-snapshots")
+}
+
 func (s *storageBtrfs) StorageCoreInit() error {
 	s.sType = storageTypeBtrfs
 	typeName, err := storageTypeToString(s.sType)

From 5512fc229789926af9f7f868b20d0a606edebda0 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 16:26:44 +0200
Subject: [PATCH 09/62] zfs: create new "custom-snapshots" dataset

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_zfs.go | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 4fe9ae689..ebad938f4 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -334,6 +334,23 @@ func (s *storageZfs) zfsPoolCreate() error {
 		logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(snapshotsDirMode), 8), err)
 	}
 
+	dataset = fmt.Sprintf("%s/custom-snapshots", poolName)
+	msg, err = zfsPoolVolumeCreate(dataset, "mountpoint=none")
+	if err != nil {
+		logger.Errorf("Failed to create snapshots dataset: %s", msg)
+		return err
+	}
+
+	fixperms = shared.VarPath("storage-pools", s.pool.Name, "custom-snapshots")
+	err = os.MkdirAll(fixperms, snapshotsDirMode)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+	err = os.Chmod(fixperms, snapshotsDirMode)
+	if err != nil {
+		logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(snapshotsDirMode), 8), err)
+	}
+
 	return nil
 }
 

From 327adb14d276360de2f8948cc49797225a00fa29 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 13 Jul 2018 14:08:32 +0200
Subject: [PATCH 10/62] storage: add new custom volume snapshot API entpoints

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 39 +++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 lxd/storage_volumes_snapshot.go

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
new file mode 100644
index 000000000..bf22cd952
--- /dev/null
+++ b/lxd/storage_volumes_snapshot.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+)
+
+var storagePoolVolumeSnapshotsTypeCmd = Command{
+	name: "storage-pools/{pool}/volumes/{type}/{name}/snapshots",
+	post: storagePoolVolumeSnapshotsTypePost,
+	get:  storagePoolVolumeSnapshotsTypeGet,
+}
+
+var storagePoolVolumeSnapshotTypeCmd = Command{
+	name:   "storage-pools/{pool}/volumes/{type}/{name}/snapshots/{snapshotName}",
+	post:   storagePoolVolumeSnapshotTypePost,
+	get:    storagePoolVolumeSnapshotTypeGet,
+	delete: storagePoolVolumeSnapshotTypeDelete,
+}
+
+func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
+	return NotImplemented(fmt.Errorf("Creating storage pool volume snapshots is not implemented"))
+}
+
+func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
+	return NotImplemented(fmt.Errorf("Retrieving storage pool volume snapshots is not implemented"))
+}
+
+func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
+	return NotImplemented(fmt.Errorf("Updating storage pool volume snapshots is not implemented"))
+}
+
+func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
+	return NotImplemented(fmt.Errorf("Retrieving a storage pool volume snapshot is not implemented"))
+}
+
+func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
+	return NotImplemented(fmt.Errorf("Deleting storage pool volume snapshots is not implemented"))
+}

From 89263d4d24131a2ef858247516866378e1b6d0ed Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 13 Jul 2018 14:18:52 +0200
Subject: [PATCH 11/62] api: add StorageVolumeSnapshot struct

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/api/storage_pool_volume_snapshot.go | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 shared/api/storage_pool_volume_snapshot.go

diff --git a/shared/api/storage_pool_volume_snapshot.go b/shared/api/storage_pool_volume_snapshot.go
new file mode 100644
index 000000000..06f61e9e2
--- /dev/null
+++ b/shared/api/storage_pool_volume_snapshot.go
@@ -0,0 +1,12 @@
+package api
+
+import ()
+
+// StorageVolumeSnapshot represents a LXD storage volume snapshot
+//
+// API extension: storage_api_volume_snapshots
+type StorageVolumeSnapshot struct {
+	Name        string            `json:"name" yaml:"name"`
+	Config      map[string]string `json:"config" yaml:"config"`
+	Description string            `json:"description" yaml:"description"`
+}

From 6da06d769be08b2f6cfeb41c4e680308aca03d8e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 13 Jul 2018 14:19:11 +0200
Subject: [PATCH 12/62] api: add StorageVolumeSnapshotPost struct

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/api/storage_pool_volume_snapshot.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/shared/api/storage_pool_volume_snapshot.go b/shared/api/storage_pool_volume_snapshot.go
index 06f61e9e2..f366a8500 100644
--- a/shared/api/storage_pool_volume_snapshot.go
+++ b/shared/api/storage_pool_volume_snapshot.go
@@ -2,6 +2,13 @@ package api
 
 import ()
 
+// StorageVolumeSnapshotPost represents the fields required to rename/move a LXD storage volume snapshot
+//
+// API extension: storage_api_volume_snapshots
+type StorageVolumeSnapshotPost struct {
+	Name string `json:"name" yaml:"name"`
+}
+
 // StorageVolumeSnapshot represents a LXD storage volume snapshot
 //
 // API extension: storage_api_volume_snapshots

From b636aabafabacce177cbbe56f4e98f44c5591f6a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 13 Jul 2018 14:19:28 +0200
Subject: [PATCH 13/62] api: add StorageVolumeSnapshotsPost struct

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/api/storage_pool_volume_snapshot.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/shared/api/storage_pool_volume_snapshot.go b/shared/api/storage_pool_volume_snapshot.go
index f366a8500..0a557e441 100644
--- a/shared/api/storage_pool_volume_snapshot.go
+++ b/shared/api/storage_pool_volume_snapshot.go
@@ -2,6 +2,13 @@ package api
 
 import ()
 
+// StorageVolumeSnapshotsPost represents the fields available for a new LXD storage volume snapshot
+//
+// API extension: storage_api_volume_snapshots
+type StorageVolumeSnapshotsPost struct {
+	Name string `json:"name" yaml:"name"`
+}
+
 // StorageVolumeSnapshotPost represents the fields required to rename/move a LXD storage volume snapshot
 //
 // API extension: storage_api_volume_snapshots

From c204285cc93b281da6292967ead5ad05b85f1413 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 13 Jul 2018 14:45:21 +0200
Subject: [PATCH 14/62] client: add CreateStoragePoolVolumeSnapshot()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go          |  3 +++
 client/lxd_storage_volumes.go | 23 +++++++++++++++++++++++
 lxd/api_1.0.go                |  2 ++
 3 files changed, 28 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 5995055fc..a73a092f6 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -198,6 +198,9 @@ type ContainerServer interface {
 	MoveStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeMoveArgs) (op RemoteOperation, err error)
 	MigrateStoragePoolVolume(pool string, volume api.StorageVolumePost) (op Operation, err error)
 
+	// Storage volume snapshot functions ("storage_api_volume_snapshots" API extension)
+	CreateStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshot api.StorageVolumeSnapshotsPost) (op Operation, err error)
+
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
 	UpdateCluster(cluster api.ClusterPut, ETag string) (op Operation, err error)
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index 4d7a91af5..51262a08b 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -85,6 +85,29 @@ func (r *ProtocolLXD) CreateStoragePoolVolume(pool string, volume api.StorageVol
 	return nil
 }
 
+// CreateStoragePoolVolumeSnapshot defines a new storage volume
+func (r *ProtocolLXD) CreateStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshot api.StorageVolumeSnapshotsPost) (Operation, error) {
+	if !r.HasExtension("storage_api_volume_snapshots") {
+		return nil, fmt.Errorf("The server is missing the required \"storage_api_volume_snapshots\" API extension")
+	}
+
+	// Send the request
+	path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots",
+		url.QueryEscape(pool),
+		url.QueryEscape(volumeType),
+		url.QueryEscape(volumeName))
+	fmt.Println(path)
+	if r.clusterTarget != "" {
+		path += fmt.Sprintf("?target=%s", r.clusterTarget)
+	}
+	op, _, err := r.queryOperation("POST", path, snapshot, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
 // MigrateStoragePoolVolume requests that LXD prepares for a storage volume migration
 func (r *ProtocolLXD) MigrateStoragePoolVolume(pool string, volume api.StorageVolumePost) (Operation, error) {
 	if !r.HasExtension("storage_api_remote_volume_handling") {
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index ca0f06247..8d302ce02 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -72,6 +72,8 @@ var api10 = []Command{
 	storagePoolVolumesCmd,
 	storagePoolVolumesTypeCmd,
 	storagePoolVolumeTypeCmd,
+	storagePoolVolumeSnapshotsTypeCmd,
+	storagePoolVolumeSnapshotTypeCmd,
 	serverResourceCmd,
 	clusterCmd,
 	clusterNodesCmd,

From 753beabf075ecb2cced50d0fd8fbbfb482f5f3aa Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 19 Jul 2018 13:27:31 +0200
Subject: [PATCH 15/62] lxc: add snapshot subcommand to lxc storage

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/storage_volume.go | 77 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/lxc/storage_volume.go b/lxc/storage_volume.go
index 872be04b2..98eeee48d 100644
--- a/lxc/storage_volume.go
+++ b/lxc/storage_volume.go
@@ -92,6 +92,10 @@ Unless specified through a prefix, all volume operations affect "custom" (user c
 	storageVolumeShowCmd := cmdStorageVolumeShow{global: c.global, storage: c.storage, storageVolume: c}
 	cmd.AddCommand(storageVolumeShowCmd.Command())
 
+	// Snapshot
+	storageVolumeSnapshotCmd := cmdStorageVolumeSnapshot{global: c.global, storage: c.storage, storageVolume: c}
+	cmd.AddCommand(storageVolumeSnapshotCmd.Command())
+
 	// Unset
 	storageVolumeUnsetCmd := cmdStorageVolumeUnset{global: c.global, storage: c.storage, storageVolume: c, storageVolumeSet: &storageVolumeSetCmd}
 	cmd.AddCommand(storageVolumeUnsetCmd.Command())
@@ -1243,3 +1247,76 @@ func (c *cmdStorageVolumeUnset) Run(cmd *cobra.Command, args []string) error {
 	args = append(args, "")
 	return c.storageVolumeSet.Run(cmd, args)
 }
+
+// Snapshot
+type cmdStorageVolumeSnapshot struct {
+	global        *cmdGlobal
+	storage       *cmdStorage
+	storageVolume *cmdStorageVolume
+
+	flagMode string
+}
+
+func (c *cmdStorageVolumeSnapshot) Command() *cobra.Command {
+	cmd := &cobra.Command{}
+	cmd.Use = i18n.G("snapshot [<remote>:]<pool> <volume> [<snapshot name>]")
+	cmd.Short = i18n.G("Snapshot storage volumes")
+	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
+		`Snapshot storage volumes`))
+
+	cmd.RunE = c.Run
+
+	return cmd
+}
+
+func (c *cmdStorageVolumeSnapshot) Run(cmd *cobra.Command, args []string) error {
+	// Sanity checks
+	exit, err := c.global.CheckArgs(cmd, args, 2, 3)
+	if exit {
+		return err
+	}
+
+	// Parse remote
+	resources, err := c.global.ParseServers(args[0])
+	if err != nil {
+		return err
+	}
+
+	resource := resources[0]
+	if resource.name == "" {
+		return fmt.Errorf(i18n.G("Missing pool name"))
+	}
+
+	client := resource.server
+
+	// Parse the input
+	volName, volType := c.storageVolume.parseVolume("custom", args[1])
+	if volType != "custom" {
+		return fmt.Errorf(i18n.G("Only \"custom\" volumes can be snapshotted"))
+	}
+
+	// Check if the requested storage volume actually exists
+	_, _, err = resource.server.GetStoragePoolVolume(resource.name, volType, volName)
+	if err != nil {
+		return err
+	}
+
+	var snapname string
+	if len(args) < 3 {
+		snapname = ""
+	} else {
+		snapname = args[2]
+	}
+
+	req := api.StorageVolumeSnapshotsPost{
+		Name: snapname,
+	}
+
+	op, err := client.CreateStoragePoolVolumeSnapshot(resource.name, volType, volName, req)
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+
+}

From 42f71e8ec395721c0e66a842663bd88f80610fa3 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 19 Jul 2018 13:33:10 +0200
Subject: [PATCH 16/62] api: add "storage_api_volume_snapshots"

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/version/api.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared/version/api.go b/shared/version/api.go
index 1d11520ed..85eb4349a 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -120,6 +120,7 @@ var APIExtensions = []string{
 	"proxy_nat",
 	"network_nat_order",
 	"container_full",
+	"storage_api_volume_snapshots",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 061973ec52ce2f229dcc8a9033d0b727deb87a7e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 19 Jul 2018 13:54:27 +0200
Subject: [PATCH 17/62] storage: remove wildcard from old API endpoint

Otherwise we can't match new endpoints.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index 5f6e28420..b937bad48 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -34,7 +34,7 @@ var storagePoolVolumesTypeCmd = Command{
 }
 
 var storagePoolVolumeTypeCmd = Command{
-	name:   "storage-pools/{pool}/volumes/{type}/{name:.*}",
+	name:   "storage-pools/{pool}/volumes/{type}/{name}",
 	post:   storagePoolVolumeTypePost,
 	get:    storagePoolVolumeTypeGet,
 	put:    storagePoolVolumeTypePut,

From 50033b1b8f128b0bc3d32bf8fcd2d310491c827a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 20 Jul 2018 15:11:18 +0200
Subject: [PATCH 18/62] [WIP]: storage: storagePoolVolumeSnapshotsTypePost()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 83 ++++++++++++++++++++++++++++++++-
 1 file changed, 82 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index bf22cd952..ef572b97d 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -1,8 +1,15 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
+	"strings"
+
+	"github.com/gorilla/mux"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 var storagePoolVolumeSnapshotsTypeCmd = Command{
@@ -19,7 +26,81 @@ var storagePoolVolumeSnapshotTypeCmd = Command{
 }
 
 func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
-	return NotImplemented(fmt.Errorf("Creating storage pool volume snapshots is not implemented"))
+	// Get the name of the pool.
+	poolName := mux.Vars(r)["pool"]
+
+	// Get the name of the volume type.
+	volumeTypeName := mux.Vars(r)["type"]
+
+	// Get the name of the volume.
+	volumeName := mux.Vars(r)["name"]
+
+	// Parse the request.
+	req := api.StorageVolumeSnapshotsPost{}
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Get a snapshot name.
+	if req.Name == "" {
+		// i := d.cluster.ContainerNextSnapshot(volumeName)
+		i := 0
+		req.Name = fmt.Sprintf("snap%d", i)
+	}
+
+	// Validate the name
+	if strings.Contains(req.Name, "/") {
+		return BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
+	}
+
+	// Convert the volume type name to our internal integer representation.
+	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Check that the storage volume type is valid.
+	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
+		return BadRequest(fmt.Errorf("invalid storage volume type \"%d\"", volumeType))
+	}
+
+	// Retrieve ID of the storage pool (and check if the storage pool
+	// exists).
+	poolID, err := d.cluster.StoragePoolGetID(poolName)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	response := ForwardedResponseIfTargetIsRemote(d, r)
+	if response != nil {
+		return response
+	}
+
+	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if response != nil {
+		return response
+	}
+
+	// Ensure that the storage volume exists.
+	storage, err := storagePoolVolumeInit(d.State(), poolName, volumeName, volumeType)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	// Start the storage.
+	ourMount, err := storage.StoragePoolVolumeMount()
+	if err != nil {
+		return SmartError(err)
+	}
+	if ourMount {
+		defer storage.StoragePoolVolumeUmount()
+	}
+
+	// // Create new snapshot name.
+	// fullName := fmt.Sprintf("%s%s%s", name, shared.SnapshotDelimiter, req.Name)
+
+	return EmptySyncResponse
 }
 
 func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {

From 2b7e056a96112b97e27e54b1af8af62b450e9c1c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 24 Jul 2018 11:40:52 +0200
Subject: [PATCH 19/62] db: add kind argument to StoragePoolVolumeCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go         |  2 +-
 lxd/db/storage_pools.go      | 11 ++++++++---
 lxd/patches.go               | 24 ++++++++++++------------
 lxd/storage_shared.go        |  3 ++-
 lxd/storage_volumes_utils.go |  2 +-
 5 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 534336296..4e7663e9d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -363,7 +363,7 @@ func containerLXCCreate(s *state.State, args db.ContainerArgs) (container, error
 	}
 
 	// Create a new database entry for the container's storage volume
-	_, err = s.Cluster.StoragePoolVolumeCreate(args.Name, "", storagePoolVolumeTypeContainer, poolID, volumeConfig)
+	_, err = s.Cluster.StoragePoolVolumeCreate(args.Name, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, volumeConfig)
 	if err != nil {
 		c.Delete()
 		return nil, err
diff --git a/lxd/db/storage_pools.go b/lxd/db/storage_pools.go
index 4a4f50725..2a14acf41 100644
--- a/lxd/db/storage_pools.go
+++ b/lxd/db/storage_pools.go
@@ -905,8 +905,13 @@ func storagePoolVolumeReplicateIfCeph(tx *sql.Tx, volumeID int64, volumeName str
 
 // StoragePoolVolumeCreate creates a new storage volume attached to a given
 // storage pool.
-func (c *Cluster) StoragePoolVolumeCreate(volumeName, volumeDescription string, volumeType int, poolID int64, volumeConfig map[string]string) (int64, error) {
+func (c *Cluster) StoragePoolVolumeCreate(volumeName, volumeDescription string, volumeType int, volumeKind StorageVolumeKind, poolID int64, volumeConfig map[string]string) (int64, error) {
 	var thisVolumeID int64
+
+	if volumeKind < StorageVolumeKindValid {
+		return -1, fmt.Errorf("Invalid storage volume kind %d specified", volumeKind)
+	}
+
 	err := c.Transaction(func(tx *ClusterTx) error {
 		nodeIDs := []int{int(c.nodeID)}
 		driver, err := storagePoolDriverGet(tx.tx, poolID)
@@ -923,9 +928,9 @@ func (c *Cluster) StoragePoolVolumeCreate(volumeName, volumeDescription string,
 
 		for _, nodeID := range nodeIDs {
 			result, err := tx.tx.Exec(`
-INSERT INTO storage_volumes (storage_pool_id, node_id, type, name, description) VALUES (?, ?, ?, ?, ?)
+INSERT INTO storage_volumes (storage_pool_id, node_id, type, kind, name, description) VALUES (?, ?, ?, ?, ?, ?)
 `,
-				poolID, nodeID, volumeType, volumeName, volumeDescription)
+				poolID, nodeID, volumeType, volumeKind, volumeName, volumeDescription)
 			if err != nil {
 				return err
 			}
diff --git a/lxd/patches.go b/lxd/patches.go
index 51d90f112..1680685a0 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -407,7 +407,7 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, defaultPoolName string,
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, containerPoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -495,7 +495,7 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, defaultPoolName string,
 				}
 			} else if err == db.ErrNoSuchObject {
 				// Insert storage volumes for containers into the database.
-				_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+				_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, snapshotPoolVolumeConfig)
 				if err != nil {
 					logger.Errorf("Could not insert a storage volume for snapshot \"%s\"", cs)
 					return err
@@ -576,7 +576,7 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, defaultPoolName string,
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, db.StorageVolumeKindRegular, poolID, imagePoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
@@ -694,7 +694,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, containerPoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -811,7 +811,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, snapshotPoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for snapshot \"%s\"", cs)
 				return err
@@ -841,7 +841,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, db.StorageVolumeKindRegular, poolID, imagePoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
@@ -1003,7 +1003,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, containerPoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -1158,7 +1158,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 				}
 			} else if err == db.ErrNoSuchObject {
 				// Insert storage volumes for containers into the database.
-				_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+				_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, snapshotPoolVolumeConfig)
 				if err != nil {
 					logger.Errorf("Could not insert a storage volume for snapshot \"%s\"", cs)
 					return err
@@ -1329,7 +1329,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, db.StorageVolumeKindRegular, poolID, imagePoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
@@ -1521,7 +1521,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(ct, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, containerPoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -1607,7 +1607,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 				}
 			} else if err == db.ErrNoSuchObject {
 				// Insert storage volumes for containers into the database.
-				_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+				_, err := d.cluster.StoragePoolVolumeCreate(cs, "", storagePoolVolumeTypeContainer, db.StorageVolumeKindRegular, poolID, snapshotPoolVolumeConfig)
 				if err != nil {
 					logger.Errorf("Could not insert a storage volume for snapshot \"%s\"", cs)
 					return err
@@ -1663,7 +1663,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 			}
 		} else if err == db.ErrNoSuchObject {
 			// Insert storage volumes for containers into the database.
-			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.StoragePoolVolumeCreate(img, "", storagePoolVolumeTypeImage, db.StorageVolumeKindRegular, poolID, imagePoolVolumeConfig)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
diff --git a/lxd/storage_shared.go b/lxd/storage_shared.go
index eae478414..7fa22b53d 100644
--- a/lxd/storage_shared.go
+++ b/lxd/storage_shared.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -108,7 +109,7 @@ func (s *storageShared) createImageDbPoolVolume(fingerprint string) error {
 	}
 
 	// Create a db entry for the storage volume of the image.
-	_, err = s.s.Cluster.StoragePoolVolumeCreate(fingerprint, "", storagePoolVolumeTypeImage, s.poolID, volumeConfig)
+	_, err = s.s.Cluster.StoragePoolVolumeCreate(fingerprint, "", storagePoolVolumeTypeImage, db.StorageVolumeKindRegular, s.poolID, volumeConfig)
 	if err != nil {
 		// Try to delete the db entry on error.
 		s.deleteImageDbPoolVolume(fingerprint)
diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index 6c1916dfa..64b58ef1e 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -496,7 +496,7 @@ func storagePoolVolumeDBCreate(s *state.State, poolName string, volumeName, volu
 	}
 
 	// Create the database entry for the storage volume.
-	_, err = s.Cluster.StoragePoolVolumeCreate(volumeName, volumeDescription, volumeType, poolID, volumeConfig)
+	_, err = s.Cluster.StoragePoolVolumeCreate(volumeName, volumeDescription, volumeType, db.StorageVolumeKindRegular, poolID, volumeConfig)
 	if err != nil {
 		return fmt.Errorf("Error inserting %s of type %s into database: %s", poolName, volumeTypeName, err)
 	}

From 6ffcb21f349d1c0b966dfea43a711ce74177a7ca Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 24 Jul 2018 15:47:13 +0200
Subject: [PATCH 20/62] storage: check volume names do not contain /

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_utils.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage_utils.go b/lxd/storage_utils.go
index 1067ab72f..4bb24c03a 100644
--- a/lxd/storage_utils.go
+++ b/lxd/storage_utils.go
@@ -119,6 +119,10 @@ func tryUnmount(path string, flags int) error {
 }
 
 func storageValidName(value string) error {
+	if strings.Contains(value, "/") {
+		return fmt.Errorf("Invalid storage volume name \"%s\". Storage volumes cannot contain \"/\" in their name", value)
+	}
+
 	return nil
 }
 

From 9405e23657ddf5c13c655ed4bedbec28c56261ba Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 24 Jul 2018 16:22:52 +0200
Subject: [PATCH 21/62] storage: add kind arg to storagePoolVolumeDBCreate

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_utils.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index 64b58ef1e..2f3ed4810 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -452,7 +452,7 @@ func profilesUsingPoolVolumeGetNames(db *db.Cluster, volumeName string, volumeTy
 	return usedBy, nil
 }
 
-func storagePoolVolumeDBCreate(s *state.State, poolName string, volumeName, volumeDescription string, volumeTypeName string, volumeConfig map[string]string) error {
+func storagePoolVolumeDBCreate(s *state.State, poolName string, volumeName, volumeDescription string, volumeTypeName string, volumeKind db.StorageVolumeKind, volumeConfig map[string]string) error {
 	// Check that the name of the new storage volume is valid. (For example.
 	// zfs pools cannot contain "/" in their names.)
 	err := storageValidName(volumeName)
@@ -496,7 +496,7 @@ func storagePoolVolumeDBCreate(s *state.State, poolName string, volumeName, volu
 	}
 
 	// Create the database entry for the storage volume.
-	_, err = s.Cluster.StoragePoolVolumeCreate(volumeName, volumeDescription, volumeType, db.StorageVolumeKindRegular, poolID, volumeConfig)
+	_, err = s.Cluster.StoragePoolVolumeCreate(volumeName, volumeDescription, volumeType, volumeKind, poolID, volumeConfig)
 	if err != nil {
 		return fmt.Errorf("Error inserting %s of type %s into database: %s", poolName, volumeTypeName, err)
 	}
@@ -529,7 +529,7 @@ func storagePoolVolumeDBCreateInternal(state *state.State, poolName string, vol
 	}
 
 	// Create database entry for new storage volume.
-	err := storagePoolVolumeDBCreate(state, poolName, volumeName, volumeDescription, volumeTypeName, volumeConfig)
+	err := storagePoolVolumeDBCreate(state, poolName, volumeName, volumeDescription, volumeTypeName, db.StorageVolumeKindRegular, volumeConfig)
 	if err != nil {
 		return nil, err
 	}

From eb744f6d94bb2b59860c04b024e173415912952c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 24 Jul 2018 16:28:58 +0200
Subject: [PATCH 22/62] storage: use storageValidName() to check snap name

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index ef572b97d..09835bb96 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
-	"strings"
 
 	"github.com/gorilla/mux"
 
@@ -50,8 +49,9 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Validate the name
-	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
+	err = storageValidName(req.Name)
+	if err != nil {
+		return BadRequest(err)
 	}
 
 	// Convert the volume type name to our internal integer representation.

From 5754e1a11d0d0c1d62f74fd2cdddf9b1e3e0ea67 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 25 Jul 2018 11:44:54 +0200
Subject: [PATCH 23/62] db: add StorageVolumeArgs struct

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/db/storage_volumes.go | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/lxd/db/storage_volumes.go b/lxd/db/storage_volumes.go
index e187d80cd..8657b1e29 100644
--- a/lxd/db/storage_volumes.go
+++ b/lxd/db/storage_volumes.go
@@ -4,10 +4,31 @@ import (
 	"database/sql"
 	"fmt"
 	"sort"
+	"time"
 
 	"github.com/lxc/lxd/lxd/db/query"
 )
 
+// StorageVolumeArgs is a value object holding all db-related details about a
+// storage volume.
+type StorageVolumeArgs struct {
+	Name string
+
+	// At least one of Type or TypeName must be set.
+	Type     int
+	TypeName string
+
+	// At least one of PoolID or PoolName must be set.
+	PoolID   int64
+	PoolName string
+
+	Kind StorageVolumeKind
+
+	Config       map[string]string
+	Description  string
+	CreationDate time.Time
+}
+
 // StorageVolumeType encodes the type of storage volume (either regular or snapshot).
 type StorageVolumeKind int
 

From 952b7ccb5b7148fc889f46263077466ada08b57d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 25 Jul 2018 12:09:44 +0200
Subject: [PATCH 24/62] api: remove empty import

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/api/storage_pool_volume_snapshot.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/shared/api/storage_pool_volume_snapshot.go b/shared/api/storage_pool_volume_snapshot.go
index 0a557e441..373ef5ec4 100644
--- a/shared/api/storage_pool_volume_snapshot.go
+++ b/shared/api/storage_pool_volume_snapshot.go
@@ -1,7 +1,5 @@
 package api
 
-import ()
-
 // StorageVolumeSnapshotsPost represents the fields available for a new LXD storage volume snapshot
 //
 // API extension: storage_api_volume_snapshots

From 60e68dae09ecd7486cd62d08ac6a50a24788b647 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 25 Jul 2018 12:10:04 +0200
Subject: [PATCH 25/62] storage: add
 storagePoolVolumeSnapshotDBCreateInternal()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_utils.go | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)

diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index 2f3ed4810..f7bc3b980 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -453,13 +453,6 @@ func profilesUsingPoolVolumeGetNames(db *db.Cluster, volumeName string, volumeTy
 }
 
 func storagePoolVolumeDBCreate(s *state.State, poolName string, volumeName, volumeDescription string, volumeTypeName string, volumeKind db.StorageVolumeKind, volumeConfig map[string]string) error {
-	// Check that the name of the new storage volume is valid. (For example.
-	// zfs pools cannot contain "/" in their names.)
-	err := storageValidName(volumeName)
-	if err != nil {
-		return err
-	}
-
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
@@ -578,3 +571,32 @@ func storagePoolVolumeCreateInternal(state *state.State, poolName string, vol *a
 
 	return nil
 }
+
+func storagePoolVolumeSnapshotDBCreateInternal(state *state.State, dbArgs *db.StorageVolumeArgs) (storage, error) {
+	// Create database entry for new storage volume.
+	err := storagePoolVolumeDBCreate(state, dbArgs.PoolName, dbArgs.Name, dbArgs.Description, dbArgs.TypeName, db.StorageVolumeKindSnapshot, dbArgs.Config)
+	if err != nil {
+		return nil, err
+	}
+
+	// Convert the volume type name to our internal integer representation.
+	poolID, err := state.Cluster.StoragePoolGetID(dbArgs.PoolName)
+	if err != nil {
+		return nil, err
+	}
+
+	volumeType, err := storagePoolVolumeTypeNameToType(dbArgs.TypeName)
+	if err != nil {
+		state.Cluster.StoragePoolVolumeDelete(dbArgs.Name, volumeType, poolID)
+		return nil, err
+	}
+
+	// Initialize new storage volume on the target storage pool.
+	s, err := storagePoolVolumeInit(state, dbArgs.PoolName, dbArgs.Name, volumeType)
+	if err != nil {
+		state.Cluster.StoragePoolVolumeDelete(dbArgs.Name, volumeType, poolID)
+		return nil, err
+	}
+
+	return s, nil
+}

From ce7493537cac6e959dd12a1445a533d05b5b3768 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 25 Jul 2018 12:10:50 +0200
Subject: [PATCH 26/62] storage: use
 storagePoolVolumeSnapshotDBCreateInternal() in
 storagePoolVolumeSnapshotsTypePost()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 32 +++++++++++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 09835bb96..4551bfd1a 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -97,10 +98,35 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 		defer storage.StoragePoolVolumeUmount()
 	}
 
-	// // Create new snapshot name.
-	// fullName := fmt.Sprintf("%s%s%s", name, shared.SnapshotDelimiter, req.Name)
+	volWritable := storage.GetStoragePoolVolumeWritable()
+	fullSnapName := fmt.Sprintf("%s%s%s", volumeName, shared.SnapshotDelimiter, req.Name)
+	req.Name = fullSnapName
+	snapshot := func(op *operation) error {
+		dbArgs := &db.StorageVolumeArgs{
+			Name:        fullSnapName,
+			PoolName:    poolName,
+			TypeName:    volumeTypeName,
+			Kind:        db.StorageVolumeKindSnapshot,
+			Config:      volWritable.Config,
+			Description: volWritable.Description,
+		}
+
+		_, err = storagePoolVolumeSnapshotDBCreateInternal(d.State(), dbArgs)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+
+	resources := map[string][]string{}
+	resources["storage_volumes"] = []string{volumeName}
+
+	op, err := operationCreate(d.cluster, operationClassTask, "Snapshotting storage volume", resources, nil, snapshot, nil, nil)
+	if err != nil {
+		return InternalError(err)
+	}
 
-	return EmptySyncResponse
+	return OperationResponse(op)
 }
 
 func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {

From 3f6a048fb3872db47fa15e3260a7fbeef459655d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 14:22:28 +0200
Subject: [PATCH 27/62] storage: add StoragePoolVolumeSnapshotCreate()

This adds StoragePoolVolumeSnapshotCreate() to the storage interface. All
functions are not implemented.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage.go       | 3 +++
 lxd/storage_btrfs.go | 6 ++++++
 lxd/storage_ceph.go  | 6 ++++++
 lxd/storage_dir.go   | 6 ++++++
 lxd/storage_lvm.go   | 6 ++++++
 lxd/storage_mock.go  | 6 ++++++
 lxd/storage_zfs.go   | 6 ++++++
 7 files changed, 39 insertions(+)

diff --git a/lxd/storage.go b/lxd/storage.go
index 22ad36053..f807e9581 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -164,6 +164,9 @@ type storage interface {
 	SetStoragePoolVolumeWritable(writable *api.StorageVolumePut)
 	GetStoragePoolVolume() *api.StorageVolume
 
+	// Functions dealing with custom storage volume snapshots.
+	StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error
+
 	// Functions dealing with container storage volumes.
 	// ContainerCreate creates an empty container (no rootfs/metadata.yaml)
 	ContainerCreate(container container) error
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index bb77259d1..3589e7c35 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2948,3 +2948,9 @@ func (s *storageBtrfs) GetStoragePoolVolume() *api.StorageVolume {
 func (s *storageBtrfs) GetState() *state.State {
 	return s.s
 }
+
+func (s *storageBtrfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 15b6c6ce9..c7e3a901d 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2710,3 +2710,9 @@ func (s *storageCeph) GetStoragePoolVolume() *api.StorageVolume {
 func (s *storageCeph) GetState() *state.State {
 	return s.s
 }
+
+func (s *storageCeph) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 19f6bbf6a..023eb56d7 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1419,3 +1419,9 @@ func (s *storageDir) GetStoragePoolVolume() *api.StorageVolume {
 func (s *storageDir) GetState() *state.State {
 	return s.s
 }
+
+func (s *storageDir) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 5e3f06ccf..f6d44ef41 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2351,3 +2351,9 @@ func (s *storageLvm) GetStoragePoolVolume() *api.StorageVolume {
 func (s *storageLvm) GetState() *state.State {
 	return s.s
 }
+
+func (s *storageLvm) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index ac3374512..eb9c20ff9 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -273,3 +273,9 @@ func (s *storageMock) GetStoragePoolVolume() *api.StorageVolume {
 func (s *storageMock) GetState() *state.State {
 	return nil
 }
+
+func (s *storageMock) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index ebad938f4..69d4c1f9b 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -3039,3 +3039,9 @@ func (s *storageZfs) GetStoragePoolVolume() *api.StorageVolume {
 func (s *storageZfs) GetState() *state.State {
 	return s.s
 }
+
+func (s *storageZfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}

From 4af30f7904f54e50ee7f875ec97c85fcb353c4f2 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 15:27:04 +0200
Subject: [PATCH 28/62] storage: use StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 4551bfd1a..24a51fbb4 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -111,6 +111,11 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 			Description: volWritable.Description,
 		}
 
+		err = storage.StoragePoolVolumeSnapshotCreate(&req)
+		if err != nil {
+			return err
+		}
+
 		_, err = storagePoolVolumeSnapshotDBCreateInternal(d.State(), dbArgs)
 		if err != nil {
 			return err

From a90648f6fc6c1dd6e481e27cbff0333f339d3ab9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 14:24:05 +0200
Subject: [PATCH 29/62] dir: implement StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_dir.go | 35 ++++++++++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 023eb56d7..62ab0951a 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1421,7 +1421,36 @@ func (s *storageDir) GetState() *state.State {
 }
 
 func (s *storageDir) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Creating DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	_, err := s.StoragePoolMount()
+	if err != nil {
+		return err
+	}
+
+	source := s.pool.Config["source"]
+	if source == "" {
+		return fmt.Errorf("no \"source\" property found for the storage pool")
+	}
+
+	sourceName, _, ok := containerGetParentAndSnapshotName(target.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+
+	targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
+	err = os.MkdirAll(targetPath, 0711)
+	if err != nil {
+		return err
+	}
+
+	sourcePath := getStoragePoolVolumeMountPoint(s.pool.Name, sourceName)
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	msg, err := rsyncLocalCopy(sourcePath, targetPath, bwlimit)
+	if err != nil {
+		return fmt.Errorf("Failed to rsync: %s: %s", string(msg), err)
+	}
+
+	logger.Infof("Created DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From 3c86f0d23b530c63d8d080da24fcf6ab3e68610a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 16:50:36 +0200
Subject: [PATCH 30/62] btrfs: implement StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 32 +++++++++++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 3589e7c35..dc7956b9a 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2950,7 +2950,33 @@ func (s *storageBtrfs) GetState() *state.State {
 }
 
 func (s *storageBtrfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Creating BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	// Create subvolume path on the storage pool.
+	customSubvolumePath := s.getCustomSubvolumePath(s.pool.Name)
+	err := os.MkdirAll(customSubvolumePath, 0700)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	_, _, ok := containerGetParentAndSnapshotName(target.Name)
+	if !ok {
+		return err
+	}
+
+	customSnapshotSubvolumeName := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	err = os.MkdirAll(customSnapshotSubvolumeName, snapshotsDirMode)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	sourcePath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+	targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
+	err = s.btrfsPoolVolumesSnapshot(sourcePath, targetPath, true, true)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Created BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From f3fa57f148ba975b61437a2a18306cc1df4f5ee0 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 17:06:22 +0200
Subject: [PATCH 31/62] [UNTESTED] ceph: implement
 StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_ceph.go | 34 +++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index c7e3a901d..8df068f5f 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2712,7 +2712,35 @@ func (s *storageCeph) GetState() *state.State {
 }
 
 func (s *storageCeph) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Debugf("Creating RBD storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	// This is costly but we need to ensure that all cached data has
+	// been committed to disk. If we don't then the rbd snapshot of
+	// the underlying filesystem can be inconsistent or - worst case
+	// - empty.
+	syscall.Sync()
+	sourcePath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+	msg, fsFreezeErr := shared.TryRunCommand("fsfreeze", "--freeze", sourcePath)
+	logger.Debugf("Trying to freeze the filesystem: %s: %s", msg, fsFreezeErr)
+	if fsFreezeErr == nil {
+		defer shared.TryRunCommand("fsfreeze", "--unfreeze", sourcePath)
+	}
+
+	sourceOnlyName, snapshotOnlyName, _ := containerGetParentAndSnapshotName(target.Name)
+	snapshotName := fmt.Sprintf("snapshot_%s", snapshotOnlyName)
+	err := cephRBDSnapshotCreate(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotName, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to create snapshot for RBD storage volume for image \"%s\" on storage pool \"%s\": %s", sourceOnlyName, s.pool.Name, err)
+		return err
+	}
+
+	targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
+	err = os.MkdirAll(targetPath, snapshotsDirMode)
+	if err != nil {
+		logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", targetPath, s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	logger.Debugf("Created RBD storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From 7ec9ef5af9538a7e6111cf2b7d37ed9092ecf79b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 17:07:07 +0200
Subject: [PATCH 32/62] mock: implement StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_mock.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index eb9c20ff9..f7425ba8e 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -275,7 +275,5 @@ func (s *storageMock) GetState() *state.State {
 }
 
 func (s *storageMock) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	return nil
 }

From dd9a6ec6467bdca6eef5f9886a744d2d7d47a569 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 17:14:12 +0200
Subject: [PATCH 33/62] lvm: implement StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_lvm.go | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index f6d44ef41..7655403b9 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2353,7 +2353,27 @@ func (s *storageLvm) GetState() *state.State {
 }
 
 func (s *storageLvm) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Debugf("Creating LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	poolName := s.getOnDiskPoolName()
+	sourceOnlyName, _, ok := containerGetParentAndSnapshotName(target.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot")
+	}
+
+	targetLvmName := containerNameToLVName(target.Name)
+	_, err := s.createSnapshotLV(poolName, sourceOnlyName, storagePoolVolumeAPIEndpointCustom, targetLvmName, storagePoolVolumeAPIEndpointCustom, true, s.useThinpool)
+	if err != nil {
+		return fmt.Errorf("Failed to create snapshot logical volume %s", err)
+	}
+
+	targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
+	err = os.MkdirAll(targetPath, snapshotsDirMode)
+	if err != nil {
+		logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", targetPath, s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	logger.Debugf("Created LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From c8d749b2436e8ada4b86fc448ba98d234aebb85d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 31 Jul 2018 17:53:15 +0200
Subject: [PATCH 34/62] [UNTESTED] zfs: implement
 StoragePoolVolumeSnapshotCreate()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_zfs.go | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 69d4c1f9b..d679fa254 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -3041,7 +3041,29 @@ func (s *storageZfs) GetState() *state.State {
 }
 
 func (s *storageZfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Creating ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	sourceOnlyName, snapshotOnlyName, ok := containerGetParentAndSnapshotName(target.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+
+	sourceDataset := fmt.Sprintf("custom/%s", sourceOnlyName)
+	poolName := s.getOnDiskPoolName()
+	dataset := fmt.Sprintf("%s/%s", poolName, sourceDataset)
+	snapName := fmt.Sprintf("snapshot-%s", snapshotOnlyName)
+	err := zfsPoolVolumeSnapshotCreate(poolName, dataset, snapName)
+	if err != nil {
+		return err
+	}
+
+	targetPath := getStoragePoolVolumeMountPoint(s.pool.Name, target.Name)
+	err = os.MkdirAll(targetPath, snapshotsDirMode)
+	if err != nil {
+		logger.Errorf("Failed to create mountpoint \"%s\" for ZFS storage volume \"%s\" on storage pool \"%s\": %s", targetPath, s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	logger.Infof("Created ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From 15195410e7ec51b5438b7cb70e3f07b7dd2d2ceb Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 3 Aug 2018 13:20:27 +0200
Subject: [PATCH 35/62] storage: add StoragePoolVolumeSnapshotDelete()

This adds StoragePoolVolumeSnapshotDelete() to the storage interface. All
functions are not implemented.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage.go       | 1 +
 lxd/storage_btrfs.go | 6 ++++++
 lxd/storage_ceph.go  | 6 ++++++
 lxd/storage_dir.go   | 6 ++++++
 lxd/storage_lvm.go   | 6 ++++++
 lxd/storage_mock.go  | 6 ++++++
 lxd/storage_zfs.go   | 6 ++++++
 7 files changed, 37 insertions(+)

diff --git a/lxd/storage.go b/lxd/storage.go
index f807e9581..de23029a7 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -166,6 +166,7 @@ type storage interface {
 
 	// Functions dealing with custom storage volume snapshots.
 	StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error
+	StoragePoolVolumeSnapshotDelete() error
 
 	// Functions dealing with container storage volumes.
 	// ContainerCreate creates an empty container (no rootfs/metadata.yaml)
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index dc7956b9a..63f8c1a75 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2980,3 +2980,9 @@ func (s *storageBtrfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolume
 	logger.Infof("Created BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageBtrfs) StoragePoolVolumeSnapshotDelete() error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 8df068f5f..e5aa02ba8 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2744,3 +2744,9 @@ func (s *storageCeph) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeS
 	logger.Debugf("Created RBD storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageCeph) StoragePoolVolumeSnapshotDelete() error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 62ab0951a..ad42cae29 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1454,3 +1454,9 @@ func (s *storageDir) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSn
 	logger.Infof("Created DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageDir) StoragePoolVolumeSnapshotDelete() error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 7655403b9..16ad288ee 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2377,3 +2377,9 @@ func (s *storageLvm) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSn
 	logger.Debugf("Created LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageLvm) StoragePoolVolumeSnapshotDelete() error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index f7425ba8e..679ccddc8 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -277,3 +277,9 @@ func (s *storageMock) GetState() *state.State {
 func (s *storageMock) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
 	return nil
 }
+
+func (s *storageMock) StoragePoolVolumeSnapshotDelete() error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index d679fa254..7e8fd72c7 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -3067,3 +3067,9 @@ func (s *storageZfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSn
 	logger.Infof("Created ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageZfs) StoragePoolVolumeSnapshotDelete() error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}

From ad2a4146ec651e0d2d6bbe0b38038fe8bc784e8f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 3 Aug 2018 13:26:48 +0200
Subject: [PATCH 36/62] storage: implement
 storagePoolVolumeSnapshotTypeDelete()

This implements tha storagePoolVolumeSnapshotTypeDelete() API endpoint.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 64 ++++++++++++++++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 24a51fbb4..34705a51f 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -147,5 +147,67 @@ func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
 }
 
 func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
-	return NotImplemented(fmt.Errorf("Deleting storage pool volume snapshots is not implemented"))
+	// Get the name of the storage pool the volume is supposed to be
+	// attached to.
+	poolName := mux.Vars(r)["pool"]
+
+	// Get the name of the volume type.
+	volumeTypeName := mux.Vars(r)["type"]
+
+	// Get the name of the storage volume.
+	volumeName := mux.Vars(r)["name"]
+
+	// Get the name of the storage volume.
+	snapshotName := mux.Vars(r)["snapshotName"]
+
+	// Convert the volume type name to our internal integer representation.
+	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Check that the storage volume type is valid.
+	if volumeType != storagePoolVolumeTypeCustom {
+		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+	}
+
+	response := ForwardedResponseIfTargetIsRemote(d, r)
+	if response != nil {
+		return response
+	}
+
+	poolID, _, err := d.cluster.StoragePoolGet(poolName)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
+	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if response != nil {
+		return response
+	}
+
+	s, err := storagePoolVolumeInit(d.State(), poolName, fullSnapshotName, volumeType)
+	if err != nil {
+		return NotFound(err)
+	}
+
+	snapshotDelete := func(op *operation) error {
+		err = s.StoragePoolVolumeSnapshotDelete()
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	resources := map[string][]string{}
+	resources["storage_volume_snapshots"] = []string{volumeName}
+
+	op, err := operationCreate(d.cluster, operationClassTask, "Deleting storage volume snapshot", resources, nil, snapshotDelete, nil, nil)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	return OperationResponse(op)
 }

From f77b710a0e4045d5c70692052c9a85d1621b2fef Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 3 Aug 2018 13:53:45 +0200
Subject: [PATCH 37/62] client: add DeleteStoragePoolVolumeSnapshot()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go          |  1 +
 client/lxd_storage_volumes.go | 22 ++++++++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index a73a092f6..3fc911e81 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -200,6 +200,7 @@ type ContainerServer interface {
 
 	// Storage volume snapshot functions ("storage_api_volume_snapshots" API extension)
 	CreateStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshot api.StorageVolumeSnapshotsPost) (op Operation, err error)
+	DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (op Operation, err error)
 
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index 51262a08b..a1036de00 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -108,6 +108,28 @@ func (r *ProtocolLXD) CreateStoragePoolVolumeSnapshot(pool string, volumeType st
 	return op, nil
 }
 
+// DeleteStoragePoolVolumeSnapshot deletes a storage volume snapshot
+func (r *ProtocolLXD) DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (Operation, error) {
+	if !r.HasExtension("storage_api_volume_snapshots") {
+		return nil, fmt.Errorf("The server is missing the required \"storage\" API extension")
+	}
+
+	// Send the request
+	path := fmt.Sprintf(
+		"/storage-pools/%s/volumes/%s/%s/snapshots/%s",
+		url.QueryEscape(pool), url.QueryEscape(volumeType), url.QueryEscape(volumeName), url.QueryEscape(snapshotName))
+	if r.clusterTarget != "" {
+		path += fmt.Sprintf("?target=%s", r.clusterTarget)
+	}
+
+	op, _, err := r.queryOperation("DELETE", path, nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
 // MigrateStoragePoolVolume requests that LXD prepares for a storage volume migration
 func (r *ProtocolLXD) MigrateStoragePoolVolume(pool string, volume api.StorageVolumePost) (Operation, error) {
 	if !r.HasExtension("storage_api_remote_volume_handling") {

From 861dd11a6c0f3bd9fb51593e41d68f6476d95d95 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 3 Aug 2018 13:32:25 +0200
Subject: [PATCH 38/62] dir: implement StoragePoolVolumeSnapshotDelete()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_dir.go | 34 +++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index ad42cae29..8401b761b 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1456,7 +1456,35 @@ func (s *storageDir) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSn
 }
 
 func (s *storageDir) StoragePoolVolumeSnapshotDelete() error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Deleting DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	source := s.pool.Config["source"]
+	if source == "" {
+		return fmt.Errorf("no \"source\" property found for the storage pool")
+	}
+
+	storageVolumePath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	err := os.RemoveAll(storageVolumePath)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	sourceName, _, _ := containerGetParentAndSnapshotName(s.volume.Name)
+	storageVolumeSnapshotPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
+	empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
+	if err == nil && empty {
+		os.RemoveAll(storageVolumeSnapshotPath)
+	}
+
+	err = s.s.Cluster.StoragePoolVolumeDelete(
+		s.volume.Name,
+		storagePoolVolumeTypeCustom,
+		s.poolID)
+	if err != nil {
+		logger.Errorf(`Failed to delete database entry for DIR storage volume "%s" on storage pool "%s"`,
+			s.volume.Name, s.pool.Name)
+	}
+
+	logger.Infof("Deleted DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From e7d4a1c2870f381ef76a29e269401819f9460996 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 6 Aug 2018 16:10:04 +0200
Subject: [PATCH 39/62] btrfs: implement StoragePoolVolumeSnapshotDelete()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 41 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 38 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 63f8c1a75..d36d0b2e3 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2982,7 +2982,42 @@ func (s *storageBtrfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolume
 }
 
 func (s *storageBtrfs) StoragePoolVolumeSnapshotDelete() error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Deleting BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	source := s.pool.Config["source"]
+	if source == "" {
+		return fmt.Errorf("no \"source\" property found for the storage pool")
+	}
+
+	snapshotSubvolumeName := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	if shared.PathExists(snapshotSubvolumeName) && isBtrfsSubVolume(snapshotSubvolumeName) {
+		err := btrfsSubVolumesDelete(snapshotSubvolumeName)
+		if err != nil {
+			return err
+		}
+	}
+
+	err := os.RemoveAll(snapshotSubvolumeName)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	sourceName, _, _ := containerGetParentAndSnapshotName(s.volume.Name)
+	storageVolumeSnapshotPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
+	empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
+	if err == nil && empty {
+		os.RemoveAll(storageVolumeSnapshotPath)
+	}
+
+	err = s.s.Cluster.StoragePoolVolumeDelete(
+		s.volume.Name,
+		storagePoolVolumeTypeCustom,
+		s.poolID)
+	if err != nil {
+		logger.Errorf(`Failed to delete database entry for BTRFS storage volume "%s" on storage pool "%s"`,
+			s.volume.Name, s.pool.Name)
+	}
+
+	logger.Infof("Deleted BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From 03dd2699d7685729b65f2abe02e760e9d34f2b0b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 3 Aug 2018 15:00:52 +0200
Subject: [PATCH 40/62] lxc: allow to delete custom volume snapshots

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/storage_volume.go | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/lxc/storage_volume.go b/lxc/storage_volume.go
index 98eeee48d..4d7d834b8 100644
--- a/lxc/storage_volume.go
+++ b/lxc/storage_volume.go
@@ -499,7 +499,8 @@ func (c *cmdStorageVolumeDelete) Command() *cobra.Command {
 
 func (c *cmdStorageVolumeDelete) Run(cmd *cobra.Command, args []string) error {
 	// Sanity checks
-	exit, err := c.global.CheckArgs(cmd, args, 2, 2)
+	// Sanity checks
+	exit, err := c.global.CheckArgs(cmd, args, 2, 3)
 	if exit {
 		return err
 	}
@@ -511,7 +512,6 @@ func (c *cmdStorageVolumeDelete) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	resource := resources[0]
-
 	if resource.name == "" {
 		return fmt.Errorf(i18n.G("Missing pool name"))
 	}
@@ -526,13 +526,30 @@ func (c *cmdStorageVolumeDelete) Run(cmd *cobra.Command, args []string) error {
 		client = client.UseTarget(c.storage.flagTarget)
 	}
 
-	// Delete the volume
-	err = client.DeleteStoragePoolVolume(resource.name, volType, volName)
-	if err != nil {
-		return err
+	msg := ""
+	if len(args) < 3 {
+		// Delete the volume
+		err := client.DeleteStoragePoolVolume(resource.name, volType, volName)
+		if err != nil {
+			return err
+		}
+		msg = args[1]
+	} else {
+		// Delete the snapshot
+		op, err := client.DeleteStoragePoolVolumeSnapshot(resource.name, volType, volName, args[2])
+		if err != nil {
+			return err
+		}
+
+		err = op.Wait()
+		if err != nil {
+			return err
+		}
+
+		msg = args[2]
 	}
 
-	fmt.Printf(i18n.G("Storage volume %s deleted")+"\n", args[1])
+	fmt.Printf(i18n.G("Storage volume %s deleted")+"\n", msg)
 
 	return nil
 }

From 21cfc111ee29ef90c610cba02b2f23af4f7b6dca Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 6 Aug 2018 16:22:09 +0200
Subject: [PATCH 41/62] ceph: implement StoragePoolVolumeSnapshotDelete()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_ceph.go | 38 +++++++++++++++++++++++++++++++++++---
 1 file changed, 35 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index e5aa02ba8..5d1e088be 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2746,7 +2746,39 @@ func (s *storageCeph) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeS
 }
 
 func (s *storageCeph) StoragePoolVolumeSnapshotDelete() error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Deleting CEPH storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	sourceName, snapshotOnlyName, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+	snapshotName := fmt.Sprintf("snapshot_%s", snapshotOnlyName)
+
+	rbdVolumeExists := cephRBDSnapshotExists(s.ClusterName, s.OSDPoolName, sourceName, storagePoolVolumeTypeNameCustom, snapshotName, s.UserName)
+	if rbdVolumeExists {
+		ret := cephContainerSnapshotDelete(s.ClusterName, s.OSDPoolName, sourceName, storagePoolVolumeTypeNameCustom, snapshotName, s.UserName)
+		if ret < 0 {
+			msg := fmt.Sprintf("Failed to delete RBD storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+			logger.Errorf(msg)
+			return fmt.Errorf(msg)
+		}
+	}
+
+	storageVolumeSnapshotPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
+	empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
+	if err == nil && empty {
+		os.RemoveAll(storageVolumeSnapshotPath)
+	}
+
+	err = s.s.Cluster.StoragePoolVolumeDelete(
+		s.volume.Name,
+		storagePoolVolumeTypeCustom,
+		s.poolID)
+	if err != nil {
+		logger.Errorf(`Failed to delete database entry for DIR storage volume "%s" on storage pool "%s"`,
+			s.volume.Name, s.pool.Name)
+	}
+
+	logger.Infof("Deleted CEPH storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From d6ee2a44f11ece04d5b69e35abc5b422a9e1f9d2 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 6 Aug 2018 16:31:38 +0200
Subject: [PATCH 42/62] lvm: implement StoragePoolVolumeSnapshotDelete()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_lvm.go | 47 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 44 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 16ad288ee..0bddceb9b 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2379,7 +2379,48 @@ func (s *storageLvm) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSn
 }
 
 func (s *storageLvm) StoragePoolVolumeSnapshotDelete() error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Deleting LVM storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	snapshotLVName := containerNameToLVName(s.volume.Name)
+	storageVolumeSnapshotPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	if shared.IsMountPoint(storageVolumeSnapshotPath) {
+		err := tryUnmount(storageVolumeSnapshotPath, 0)
+		if err != nil {
+			return fmt.Errorf("Failed to unmount snapshot path \"%s\": %s", storageVolumeSnapshotPath, err)
+		}
+	}
+
+	poolName := s.getOnDiskPoolName()
+	snapshotLVDevPath := getLvmDevPath(poolName, storagePoolVolumeAPIEndpointCustom, snapshotLVName)
+	lvExists, _ := storageLVExists(snapshotLVDevPath)
+	if lvExists {
+		err := removeLV(poolName, storagePoolVolumeAPIEndpointCustom, snapshotLVName)
+		if err != nil {
+			return err
+		}
+	}
+
+	err := os.Remove(storageVolumeSnapshotPath)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	sourceName, _, _ := containerGetParentAndSnapshotName(s.volume.Name)
+	storageVolumeSnapshotPath = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
+	empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
+	if err == nil && empty {
+		os.RemoveAll(storageVolumeSnapshotPath)
+	}
+
+	err = s.s.Cluster.StoragePoolVolumeDelete(
+		s.volume.Name,
+		storagePoolVolumeTypeCustom,
+		s.poolID)
+	if err != nil {
+		logger.Errorf(`Failed to delete database entry for LVM storage volume "%s" on storage pool "%s"`,
+			s.volume.Name, s.pool.Name)
+	}
+
+	logger.Infof("Deleted LVM storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From 8ff36c8afc4a9ecdc2e8d2f6e343129ce0386815 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 6 Aug 2018 16:32:09 +0200
Subject: [PATCH 43/62] mock: implement StoragePoolVolumeSnapshotDelete()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_mock.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 679ccddc8..3e13ca3a0 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -279,7 +279,5 @@ func (s *storageMock) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeS
 }
 
 func (s *storageMock) StoragePoolVolumeSnapshotDelete() error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	return nil
 }

From 960c7cae79b1c7100bfbaa912080ca9760c73de9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 6 Aug 2018 16:38:37 +0200
Subject: [PATCH 44/62] zfs: implement StoragePoolVolumeSnapshotDelete()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_zfs.go | 48 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 45 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 7e8fd72c7..dc6f8b391 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -3069,7 +3069,49 @@ func (s *storageZfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSn
 }
 
 func (s *storageZfs) StoragePoolVolumeSnapshotDelete() error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Deleting ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	sourceName, snapshotOnlyName, _ := containerGetParentAndSnapshotName(s.volume.Name)
+	snapshotName := fmt.Sprintf("snapshot-%s", snapshotOnlyName)
+
+	onDiskPoolName := s.getOnDiskPoolName()
+	if zfsFilesystemEntityExists(onDiskPoolName, fmt.Sprintf("custom/%s@%s", sourceName, snapshotName)) {
+		removable, err := zfsPoolVolumeSnapshotRemovable(onDiskPoolName, fmt.Sprintf("custom/%s", sourceName), snapshotName)
+		if err != nil {
+			return err
+		}
+
+		if removable {
+			err = zfsPoolVolumeSnapshotDestroy(onDiskPoolName, fmt.Sprintf("custom/%s", sourceName), snapshotName)
+		} else {
+			err = zfsPoolVolumeSnapshotRename(onDiskPoolName, fmt.Sprintf("custom/%s", sourceName), snapshotName, fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
+		}
+		if err != nil {
+			return err
+		}
+	}
+
+	storageVolumePath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	err := os.RemoveAll(storageVolumePath)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	storageVolumeSnapshotPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
+	empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
+	if err == nil && empty {
+		os.RemoveAll(storageVolumeSnapshotPath)
+	}
+
+	err = s.s.Cluster.StoragePoolVolumeDelete(
+		s.volume.Name,
+		storagePoolVolumeTypeCustom,
+		s.poolID)
+	if err != nil {
+		logger.Errorf(`Failed to delete database entry for DIR storage volume "%s" on storage pool "%s"`,
+			s.volume.Name, s.pool.Name)
+	}
+
+	logger.Infof("Deleted ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
 }

From 54c93a57e18ed6870d2e5802fa3323ed17127093 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 7 Aug 2018 13:32:53 +0200
Subject: [PATCH 45/62] client: add GetStoragePoolVolumeSnapshotNames()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go          |  1 +
 client/lxd_storage_volumes.go | 29 +++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 3fc911e81..14f9418ca 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -201,6 +201,7 @@ type ContainerServer interface {
 	// Storage volume snapshot functions ("storage_api_volume_snapshots" API extension)
 	CreateStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshot api.StorageVolumeSnapshotsPost) (op Operation, err error)
 	DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (op Operation, err error)
+	GetStoragePoolVolumeSnapshotNames(pool string, volumeType string, volumeName string) (names []string, err error)
 
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index a1036de00..5f7bc0f64 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -108,6 +108,35 @@ func (r *ProtocolLXD) CreateStoragePoolVolumeSnapshot(pool string, volumeType st
 	return op, nil
 }
 
+// GetStoragePoolVolumeSnapshotNames returns a list of snapshot names for the
+// storage volume
+func (r *ProtocolLXD) GetStoragePoolVolumeSnapshotNames(pool string, volumeType string, volumeName string) ([]string, error) {
+	if !r.HasExtension("storage_api_volume_snapshots") {
+		return nil, fmt.Errorf("The server is missing the required \"storage_api_volume_snapshots\" API extension")
+	}
+
+	urls := []string{}
+
+	// Send the request
+	path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots",
+		url.QueryEscape(pool),
+		url.QueryEscape(volumeType),
+		url.QueryEscape(volumeName))
+	_, err := r.queryStruct("GET", path, nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, uri := range urls {
+		fields := strings.Split(uri, path)
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
 // DeleteStoragePoolVolumeSnapshot deletes a storage volume snapshot
 func (r *ProtocolLXD) DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (Operation, error) {
 	if !r.HasExtension("storage_api_volume_snapshots") {

From c7b5a6aa135cf3456cb7d81e87012d0240db4925 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 7 Aug 2018 13:33:13 +0200
Subject: [PATCH 46/62] client: add GetStoragePoolVolumeSnapshots()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go          |  1 +
 client/lxd_storage_volumes.go | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 14f9418ca..8f37b6d58 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -202,6 +202,7 @@ type ContainerServer interface {
 	CreateStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshot api.StorageVolumeSnapshotsPost) (op Operation, err error)
 	DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (op Operation, err error)
 	GetStoragePoolVolumeSnapshotNames(pool string, volumeType string, volumeName string) (names []string, err error)
+	GetStoragePoolVolumeSnapshots(pool string, volumeType string, volumeName string) (snapshots []api.StorageVolumeSnapshot, err error)
 
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index 5f7bc0f64..56fd26558 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -137,6 +137,27 @@ func (r *ProtocolLXD) GetStoragePoolVolumeSnapshotNames(pool string, volumeType
 	return names, nil
 }
 
+// GetStoragePoolVolumeSnapshots returns a list of snapshots for the storage
+// volume
+func (r *ProtocolLXD) GetStoragePoolVolumeSnapshots(pool string, volumeType string, volumeName string) ([]api.StorageVolumeSnapshot, error) {
+	if !r.HasExtension("storage_api_volume_snapshots") {
+		return nil, fmt.Errorf("The server is missing the required \"storage_api_volume_snapshots\" API extension")
+	}
+
+	snapshots := []api.StorageVolumeSnapshot{}
+
+	path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots?recursion=1",
+		url.QueryEscape(pool),
+		url.QueryEscape(volumeType),
+		url.QueryEscape(volumeName))
+	_, err := r.queryStruct("GET", path, nil, "", &snapshots)
+	if err != nil {
+		return nil, err
+	}
+
+	return snapshots, nil
+}
+
 // DeleteStoragePoolVolumeSnapshot deletes a storage volume snapshot
 func (r *ProtocolLXD) DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (Operation, error) {
 	if !r.HasExtension("storage_api_volume_snapshots") {

From 1ce23f230da401b730fd2fe184f23c7ee25c4787 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 7 Aug 2018 19:25:41 +0200
Subject: [PATCH 47/62] db: add StoragePoolVolumeSnapshotsGetType()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/db/storage_pools.go | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/lxd/db/storage_pools.go b/lxd/db/storage_pools.go
index 2a14acf41..51c84713e 100644
--- a/lxd/db/storage_pools.go
+++ b/lxd/db/storage_pools.go
@@ -756,6 +756,29 @@ func (c *Cluster) StoragePoolVolumesGetType(volumeType int, poolID, nodeID int64
 	return response, nil
 }
 
+// StoragePoolVolumeSnapshotsGetType get all snapshots of a storage volume
+// attached to a given storage pool of a given volume type, on the given node.
+func (c *Cluster) StoragePoolVolumeSnapshotsGetType(volumeName string, volumeType int, poolID int64) ([]string, error) {
+	result := []string{}
+	regexp := volumeName + shared.SnapshotDelimiter
+	length := len(regexp)
+
+	query := "SELECT name FROM storage_volumes WHERE storage_pool_id=? AND node_id=? AND type=? AND kind=? AND SUBSTR(name,1,?)=?"
+	inargs := []interface{}{poolID, c.nodeID, volumeType, StorageVolumeKindSnapshot, length, regexp}
+	outfmt := []interface{}{volumeName}
+
+	dbResults, err := queryScan(c.db, query, inargs, outfmt)
+	if err != nil {
+		return result, err
+	}
+
+	for _, r := range dbResults {
+		result = append(result, r[0].(string))
+	}
+
+	return result, nil
+}
+
 // StoragePoolNodeVolumesGetType returns all storage volumes attached to a
 // given storage pool of a given volume type, on the current node.
 func (c *Cluster) StoragePoolNodeVolumesGetType(volumeType int, poolID int64) ([]string, error) {

From 88539dfb3e9b5857359c909cb6217645f3cf731c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 7 Aug 2018 19:26:22 +0200
Subject: [PATCH 48/62] storage: add storagePoolVolumeSnapshotsTypeGet()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 74 ++++++++++++++++++++++++++++++++-
 1 file changed, 73 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 34705a51f..277e438dc 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -8,8 +8,10 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/version"
 )
 
 var storagePoolVolumeSnapshotsTypeCmd = Command{
@@ -135,7 +137,77 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 }
 
 func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
-	return NotImplemented(fmt.Errorf("Retrieving storage pool volume snapshots is not implemented"))
+	// Get the name of the pool the storage volume is supposed to be
+	// attached to.
+	poolName := mux.Vars(r)["pool"]
+
+	recursion := util.IsRecursionRequest(r)
+
+	// Get the name of the volume type.
+	volumeTypeName := mux.Vars(r)["type"]
+
+	// Get the name of the volume type.
+	volumeName := mux.Vars(r)["name"]
+
+	// Convert the volume type name to our internal integer representation.
+	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
+	if err != nil {
+		return BadRequest(err)
+	}
+	// Check that the storage volume type is valid.
+	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
+		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+	}
+
+	// Retrieve ID of the storage pool (and check if the storage pool
+	// exists).
+	poolID, err := d.cluster.StoragePoolGetID(poolName)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	// Get the names of all storage volumes of a given volume type currently
+	// attached to the storage pool.
+	volumes, err := d.cluster.StoragePoolVolumeSnapshotsGetType(volumeName, volumeType, poolID)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	resultString := []string{}
+	resultMap := []*api.StorageVolumeSnapshot{}
+	for _, volume := range volumes {
+		if !recursion {
+			apiEndpoint, err := storagePoolVolumeTypeToAPIEndpoint(volumeType)
+			if err != nil {
+				return InternalError(err)
+			}
+			resultString = append(resultString, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s/%s/snapshots/%s", version.APIVersion, poolName, apiEndpoint, volumeName, volume))
+		} else {
+			_, vol, err := d.cluster.StoragePoolNodeVolumeGetType(volume, volumeType, poolID)
+			if err != nil {
+				continue
+			}
+
+			volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), vol.Name, vol.Type)
+			if err != nil {
+				return SmartError(err)
+			}
+			vol.UsedBy = volumeUsedBy
+
+			tmp := &api.StorageVolumeSnapshot{}
+			tmp.Config = vol.Config
+			tmp.Description = vol.Description
+			tmp.Name = vol.Name
+
+			resultMap = append(resultMap, tmp)
+		}
+	}
+
+	if !recursion {
+		return SyncResponse(true, resultString)
+	}
+
+	return SyncResponse(true, resultMap)
 }
 
 func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {

From 2d9853670f939fae8b0fc47586fd2a12022832df Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 7 Aug 2018 21:07:20 +0200
Subject: [PATCH 49/62] storage: add storagePoolVolumeSnapshotTypeGet()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 54 ++++++++++++++++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 277e438dc..2f60c3850 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -215,7 +215,59 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
 }
 
 func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
-	return NotImplemented(fmt.Errorf("Retrieving a storage pool volume snapshot is not implemented"))
+	// Get the name of the storage pool the volume is supposed to be
+	// attached to.
+	poolName := mux.Vars(r)["pool"]
+
+	// Get the name of the volume type.
+	volumeTypeName := mux.Vars(r)["type"]
+
+	// Get the name of the storage volume.
+	volumeName := mux.Vars(r)["name"]
+
+	// Get the name of the storage volume.
+	snapshotName := mux.Vars(r)["snapshotName"]
+
+	// Convert the volume type name to our internal integer representation.
+	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Check that the storage volume type is valid.
+	if volumeType != storagePoolVolumeTypeCustom {
+		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+	}
+
+	response := ForwardedResponseIfTargetIsRemote(d, r)
+	if response != nil {
+		return response
+	}
+
+	poolID, _, err := d.cluster.StoragePoolGet(poolName)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
+	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if response != nil {
+		return response
+	}
+
+	_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(fullSnapshotName, volumeType, poolID)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	snapshot := api.StorageVolumeSnapshot{}
+	snapshot.Config = volume.Config
+	snapshot.Description = volume.Description
+	snapshot.Name = volume.Name
+
+	etag := []interface{}{snapshot.Name, snapshot.Description, snapshot.Config}
+
+	return SyncResponseETag(true, &snapshot, etag)
 }
 
 func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {

From 7ce6ad4aadcc37e04589a28577095324525e84f5 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 7 Aug 2018 21:18:07 +0200
Subject: [PATCH 50/62] client: add GetStoragePoolVolumeSnapshot()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go          |  1 +
 client/lxd_storage_volumes.go | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 8f37b6d58..984da882c 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -203,6 +203,7 @@ type ContainerServer interface {
 	DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (op Operation, err error)
 	GetStoragePoolVolumeSnapshotNames(pool string, volumeType string, volumeName string) (names []string, err error)
 	GetStoragePoolVolumeSnapshots(pool string, volumeType string, volumeName string) (snapshots []api.StorageVolumeSnapshot, err error)
+	GetStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (snapshot *api.StorageVolumeSnapshot, ETag string, err error)
 
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index 56fd26558..fa5f0a7d3 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -158,6 +158,27 @@ func (r *ProtocolLXD) GetStoragePoolVolumeSnapshots(pool string, volumeType stri
 	return snapshots, nil
 }
 
+// GetStoragePoolVolumeSnapshot returns a snapshots for the storage volume
+func (r *ProtocolLXD) GetStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (*api.StorageVolumeSnapshot, string, error) {
+	if !r.HasExtension("storage_api_volume_snapshots") {
+		return nil, "", fmt.Errorf("The server is missing the required \"storage_api_volume_snapshots\" API extension")
+	}
+
+	snapshot := api.StorageVolumeSnapshot{}
+
+	path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots/%s",
+		url.QueryEscape(pool),
+		url.QueryEscape(volumeType),
+		url.QueryEscape(volumeName),
+		url.QueryEscape(snapshotName))
+	etag, err := r.queryStruct("GET", path, nil, "", &snapshot)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &snapshot, etag, nil
+}
+
 // DeleteStoragePoolVolumeSnapshot deletes a storage volume snapshot
 func (r *ProtocolLXD) DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (Operation, error) {
 	if !r.HasExtension("storage_api_volume_snapshots") {

From 3a1e92c1e739d28d3287d3c029e986423e8e353d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 14:52:28 +0200
Subject: [PATCH 51/62] client: add RenameStoragePoolVolumeSnapshot()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go          |  1 +
 client/lxd_storage_volumes.go | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 984da882c..50d86b2b6 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -204,6 +204,7 @@ type ContainerServer interface {
 	GetStoragePoolVolumeSnapshotNames(pool string, volumeType string, volumeName string) (names []string, err error)
 	GetStoragePoolVolumeSnapshots(pool string, volumeType string, volumeName string) (snapshots []api.StorageVolumeSnapshot, err error)
 	GetStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (snapshot *api.StorageVolumeSnapshot, ETag string, err error)
+	RenameStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string, snapshot api.StorageVolumeSnapshotPost) (op Operation, err error)
 
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index fa5f0a7d3..32847fb14 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -179,6 +179,22 @@ func (r *ProtocolLXD) GetStoragePoolVolumeSnapshot(pool string, volumeType strin
 	return &snapshot, etag, nil
 }
 
+// RenameStoragePoolVolume renames a storage volume
+func (r *ProtocolLXD) RenameStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string, snapshot api.StorageVolumeSnapshotPost) (Operation, error) {
+	if !r.HasExtension("storage_api_volume_snapshots") {
+		return nil, fmt.Errorf("The server is missing the required \"storage\" API extension")
+	}
+
+	path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots/%s", url.QueryEscape(pool), url.QueryEscape(volumeType), url.QueryEscape(volumeName), url.QueryEscape(snapshotName))
+	// Send the request
+	op, _, err := r.queryOperation("POST", path, snapshot, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
 // DeleteStoragePoolVolumeSnapshot deletes a storage volume snapshot
 func (r *ProtocolLXD) DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (Operation, error) {
 	if !r.HasExtension("storage_api_volume_snapshots") {

From 3719d50ffebfdc85c4b774e17fcb267c14c96d60 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:03:28 +0200
Subject: [PATCH 52/62] storage: add StoragePoolVolumeSnapshotRename()

This adds StoragePoolVolumeSnapshotRename() to the storage interface. All
functions are not implemented.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage.go       | 1 +
 lxd/storage_btrfs.go | 6 ++++++
 lxd/storage_ceph.go  | 6 ++++++
 lxd/storage_dir.go   | 6 ++++++
 lxd/storage_lvm.go   | 6 ++++++
 lxd/storage_mock.go  | 6 ++++++
 lxd/storage_zfs.go   | 6 ++++++
 7 files changed, 37 insertions(+)

diff --git a/lxd/storage.go b/lxd/storage.go
index de23029a7..4738b7017 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -167,6 +167,7 @@ type storage interface {
 	// Functions dealing with custom storage volume snapshots.
 	StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error
 	StoragePoolVolumeSnapshotDelete() error
+	StoragePoolVolumeSnapshotRename(newName string) error
 
 	// Functions dealing with container storage volumes.
 	// ContainerCreate creates an empty container (no rootfs/metadata.yaml)
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index d36d0b2e3..5440a64e1 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -3021,3 +3021,9 @@ func (s *storageBtrfs) StoragePoolVolumeSnapshotDelete() error {
 	logger.Infof("Deleted BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageBtrfs) StoragePoolVolumeSnapshotRename(newName string) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 5d1e088be..5079e057d 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2782,3 +2782,9 @@ func (s *storageCeph) StoragePoolVolumeSnapshotDelete() error {
 	logger.Infof("Deleted CEPH storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageCeph) StoragePoolVolumeSnapshotRename(newName string) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 8401b761b..b4136e0be 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1488,3 +1488,9 @@ func (s *storageDir) StoragePoolVolumeSnapshotDelete() error {
 	logger.Infof("Deleted DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageDir) StoragePoolVolumeSnapshotRename(newName string) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 0bddceb9b..8b4fa9e87 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2424,3 +2424,9 @@ func (s *storageLvm) StoragePoolVolumeSnapshotDelete() error {
 	logger.Infof("Deleted LVM storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageLvm) StoragePoolVolumeSnapshotRename(newName string) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 3e13ca3a0..76603c3a7 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -281,3 +281,9 @@ func (s *storageMock) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeS
 func (s *storageMock) StoragePoolVolumeSnapshotDelete() error {
 	return nil
 }
+
+func (s *storageMock) StoragePoolVolumeSnapshotRename(newName string) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index dc6f8b391..1ab12c090 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -3115,3 +3115,9 @@ func (s *storageZfs) StoragePoolVolumeSnapshotDelete() error {
 	logger.Infof("Deleted ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 	return nil
 }
+
+func (s *storageZfs) StoragePoolVolumeSnapshotRename(newName string) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}

From 205e22ef8edd6070ced90b0d08d3023931daa5d6 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 14:59:59 +0200
Subject: [PATCH 53/62] storage: implement storagePoolVolumeSnapshotTypePost()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_volumes_snapshot.go | 82 ++++++++++++++++++++++++++++++++-
 1 file changed, 81 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 2f60c3850..dabbe7143 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"strings"
 
 	"github.com/gorilla/mux"
 
@@ -211,7 +212,86 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
 }
 
 func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
-	return NotImplemented(fmt.Errorf("Updating storage pool volume snapshots is not implemented"))
+	// Get the name of the storage pool the volume is supposed to be
+	// attached to.
+	poolName := mux.Vars(r)["pool"]
+
+	// Get the name of the volume type.
+	volumeTypeName := mux.Vars(r)["type"]
+
+	// Get the name of the storage volume.
+	volumeName := mux.Vars(r)["name"]
+
+	// Get the name of the storage volume.
+	snapshotName := mux.Vars(r)["snapshotName"]
+
+	req := api.StorageVolumeSnapshotPost{}
+
+	// Parse the request.
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Sanity checks.
+	if req.Name == "" {
+		return BadRequest(fmt.Errorf("No name provided"))
+	}
+
+	if strings.Contains(req.Name, "/") {
+		return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+	}
+
+	// Convert the volume type name to our internal integer representation.
+	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Check that the storage volume type is valid.
+	if volumeType != storagePoolVolumeTypeCustom {
+		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+	}
+
+	response := ForwardedResponseIfTargetIsRemote(d, r)
+	if response != nil {
+		return response
+	}
+
+	poolID, _, err := d.cluster.StoragePoolGet(poolName)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
+	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if response != nil {
+		return response
+	}
+
+	s, err := storagePoolVolumeInit(d.State(), poolName, fullSnapshotName, volumeType)
+	if err != nil {
+		return NotFound(err)
+	}
+
+	snapshotRename := func(op *operation) error {
+		err = s.StoragePoolVolumeSnapshotRename(req.Name)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	resources := map[string][]string{}
+	resources["storage_volume_snapshots"] = []string{volumeName}
+
+	op, err := operationCreate(d.cluster, operationClassTask, "Deleting storage volume snapshot", resources, nil, snapshotRename, nil, nil)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	return OperationResponse(op)
 }
 
 func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {

From 7fe534ded80d141008b9d7dbe29ff8916f9d5b93 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:16:47 +0200
Subject: [PATCH 54/62] btrfs: implement StoragePoolVolumeSnapshotRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 5440a64e1..5b3a43217 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -3023,7 +3023,22 @@ func (s *storageBtrfs) StoragePoolVolumeSnapshotDelete() error {
 }
 
 func (s *storageBtrfs) StoragePoolVolumeSnapshotRename(newName string) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Renaming BTRFS storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	sourceName, _, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+
+	fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
+	oldPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	newPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
+	err := os.Rename(oldPath, newPath)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Renamed BTRFS storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	return s.s.Cluster.StoragePoolVolumeRename(s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
 }

From b1d3470027853215b79b72368cb15359863bc19b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:14:50 +0200
Subject: [PATCH 55/62] dir: implement StoragePoolVolumeSnapshotRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_dir.go | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index b4136e0be..06493a874 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1490,7 +1490,22 @@ func (s *storageDir) StoragePoolVolumeSnapshotDelete() error {
 }
 
 func (s *storageDir) StoragePoolVolumeSnapshotRename(newName string) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Renaming DIR storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	sourceName, _, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+
+	fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
+	oldPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	newPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
+	err := os.Rename(oldPath, newPath)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Renamed DIR storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	return s.s.Cluster.StoragePoolVolumeRename(s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
 }

From 53930228e3040bd90ac235a5d57745aa3b121d70 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:23:40 +0200
Subject: [PATCH 56/62] ceph: implement StoragePoolVolumeSnapshotRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_ceph.go | 36 +++++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 5079e057d..ba8d9dd0e 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2784,7 +2784,37 @@ func (s *storageCeph) StoragePoolVolumeSnapshotDelete() error {
 }
 
 func (s *storageCeph) StoragePoolVolumeSnapshotRename(newName string) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Renaming CEPH storage volume on OSD storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	// unmap
+	err := cephRBDVolumeUnmap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName, true)
+	if err != nil {
+		logger.Errorf("Failed to unmap RBD storage volume for container \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+	logger.Debugf("Unmapped RBD storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	sourceName, _, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+
+	fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
+	err = cephRBDVolumeRename(s.ClusterName, s.OSDPoolName, storagePoolVolumeTypeNameCustom, s.volume.Name, fullSnapshotName, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to rename RBD storage volume for container \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+	logger.Debugf("Renamed RBD storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+
+	oldPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	newPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
+	err = os.Rename(oldPath, newPath)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Renamed CEPH storage volume on OSD storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	return s.s.Cluster.StoragePoolVolumeRename(s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
 }

From 5a67f4ae4cfa6487fb6f4f66a830e8feeba533c8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:23:53 +0200
Subject: [PATCH 57/62] ceph: fix StoragePoolVolumeRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_ceph.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index ba8d9dd0e..e00531a04 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -686,6 +686,13 @@ func (s *storageCeph) StoragePoolVolumeRename(newName string) error {
 	logger.Debugf(`Mapped RBD storage volume for container "%s" on storage pool "%s"`,
 		newName, s.pool.Name)
 
+	oldPath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+	newPath := getStoragePoolVolumeMountPoint(s.pool.Name, newName)
+	err = os.Rename(oldPath, newPath)
+	if err != nil {
+		return err
+	}
+
 	logger.Infof(`Renamed CEPH storage volume on OSD storage pool "%s" from "%s" to "%s`,
 		s.pool.Name, s.volume.Name, newName)
 

From e82db30ef9a9ccdc45c3713c7f4c6fc3a8d8526d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:24:13 +0200
Subject: [PATCH 58/62] mock: implement StoragePoolVolumeSnapshotRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_mock.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 76603c3a7..065ff7dfb 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -283,7 +283,5 @@ func (s *storageMock) StoragePoolVolumeSnapshotDelete() error {
 }
 
 func (s *storageMock) StoragePoolVolumeSnapshotRename(newName string) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	return nil
 }

From b7679b14fda9c3923961a9f43493b6694d8e6f69 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:27:24 +0200
Subject: [PATCH 59/62] lvm: implement StoragePoolVolumeSnapshotRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_lvm.go | 31 ++++++++++++++++++++++++++++---
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 8b4fa9e87..e918e7f7a 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2426,7 +2426,32 @@ func (s *storageLvm) StoragePoolVolumeSnapshotDelete() error {
 }
 
 func (s *storageLvm) StoragePoolVolumeSnapshotRename(newName string) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Renaming LVM storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	_, err := s.StoragePoolVolumeUmount()
+	if err != nil {
+		return err
+	}
+
+	sourceName, _, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+	fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
+
+	err = s.renameLVByPath(s.volume.Name, fullSnapshotName, storagePoolVolumeAPIEndpointCustom)
+	if err != nil {
+		return fmt.Errorf("Failed to rename logical volume from \"%s\" to \"%s\": %s", s.volume.Name, newName, err)
+	}
+
+	oldPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	newPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
+	err = os.Rename(oldPath, newPath)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Renamed LVM storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	return s.s.Cluster.StoragePoolVolumeRename(s.volume.Name, newName, storagePoolVolumeTypeCustom, s.poolID)
 }

From 00c763d32bd61292addb226bc064c311f8547213 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:27:37 +0200
Subject: [PATCH 60/62] lvm: fix StoragePoolVolumeRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_lvm.go | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index e918e7f7a..6111e6016 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -849,6 +849,18 @@ func (s *storageLvm) StoragePoolVolumeRename(newName string) error {
 			s.volume.Name, newName, err)
 	}
 
+	sourceName, _, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+	fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
+	oldPath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+	newPath := getStoragePoolVolumeMountPoint(s.pool.Name, fullSnapshotName)
+	err = os.Rename(oldPath, newPath)
+	if err != nil {
+		return err
+	}
+
 	logger.Infof(`Renamed ZFS storage volume on storage pool "%s" from "%s" to "%s`,
 		s.pool.Name, s.volume.Name, newName)
 

From 4efa7363576724a1972dad2e35cc10779e5e721a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:33:50 +0200
Subject: [PATCH 61/62] zfs: implement StoragePoolVolumeSnapshotRename()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_zfs.go | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 1ab12c090..8ceb01094 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -3117,7 +3117,21 @@ func (s *storageZfs) StoragePoolVolumeSnapshotDelete() error {
 }
 
 func (s *storageZfs) StoragePoolVolumeSnapshotRename(newName string) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
+	logger.Infof("Renaming ZFS storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	sourceName, snapshotOnlyName, ok := containerGetParentAndSnapshotName(s.volume.Name)
+	if !ok {
+		return fmt.Errorf("Not a snapshot name")
+	}
+
+	oldZfsDatasetName := fmt.Sprintf("snapshot-%s", snapshotOnlyName)
+	newZfsDatasetName := fmt.Sprintf("snapshot-%s", newName)
+	err := zfsPoolVolumeSnapshotRename(s.getOnDiskPoolName(), fmt.Sprintf("custom/%s", sourceName), oldZfsDatasetName, newZfsDatasetName)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Renamed ZFS storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, newName)
+
+	return s.s.Cluster.StoragePoolVolumeRename(s.volume.Name, newName, storagePoolVolumeTypeCustom, s.poolID)
 }

From efd4c9430723d3666de21d918600b396d8fe3306 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 13 Aug 2018 15:43:30 +0200
Subject: [PATCH 62/62] lxc: allow storage volume snapshot rename

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/storage_volume.go | 38 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 37 insertions(+), 1 deletion(-)

diff --git a/lxc/storage_volume.go b/lxc/storage_volume.go
index 4d7d834b8..67511991e 100644
--- a/lxc/storage_volume.go
+++ b/lxc/storage_volume.go
@@ -1062,6 +1062,43 @@ func (c *cmdStorageVolumeRename) Run(cmd *cobra.Command, args []string) error {
 	// Parse the input
 	volName, volType := c.storageVolume.parseVolume("custom", args[1])
 
+	isSnapshot := false
+	fields := strings.Split(volName, "/")
+	if len(fields) > 1 {
+		isSnapshot = true
+	} else if len(fields) > 2 {
+		return fmt.Errorf("Invalid snapshot name")
+	}
+
+	if isSnapshot {
+		// Create the storage volume entry
+		vol := api.StorageVolumeSnapshotPost{}
+		vol.Name = args[2]
+
+		// If a target member was specified, get the volume with the matching
+		// name on that member, if any.
+		if c.storage.flagTarget != "" {
+			client = client.UseTarget(c.storage.flagTarget)
+		}
+
+		if len(fields) != 2 {
+			return fmt.Errorf("Not a snapshot name")
+		}
+
+		op, err := client.RenameStoragePoolVolumeSnapshot(resource.name, volType, fields[0], fields[1], vol)
+		if err != nil {
+			return err
+		}
+
+		err = op.Wait()
+		if err != nil {
+			return err
+		}
+
+		fmt.Printf(i18n.G(`Renamed storage volume from "%s" to "%s"`)+"\n", volName, vol.Name)
+		return nil
+	}
+
 	// Create the storage volume entry
 	vol := api.StorageVolumePost{}
 	vol.Name = args[2]
@@ -1078,7 +1115,6 @@ func (c *cmdStorageVolumeRename) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	fmt.Printf(i18n.G(`Renamed storage volume from "%s" to "%s"`)+"\n", volName, vol.Name)
-
 	return nil
 }
 


More information about the lxc-devel mailing list