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

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
Links storage volumes update and restore functionality to new storage package and implements for dir driver.
From 1a57754ff99999efe1ec25d5c273b7a2ae2749b2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 23 Oct 2019 16:13:10 +0100
Subject: [PATCH 01/13] lxc/storage/volumes: Links storagePoolVolumeTypePatch
 to new storage pkg

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage_volumes.go | 64 ++++++++++++++++++++++--------------------
 1 file changed, 34 insertions(+), 30 deletions(-)

diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index df2cec6f48..ac5c2cd43f 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -986,23 +986,13 @@ func storagePoolVolumeTypeImagePut(d *Daemon, r 
*http.Request) response.Response
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
 func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName 
string) response.Response {
        // Get the name of the storage volume.
-       var volumeName string
-       fields := strings.Split(mux.Vars(r)["name"], "/")
+       volumeName := mux.Vars(r)["name"]
 
-       if len(fields) == 3 && fields[1] == "snapshots" {
-               // Handle volume snapshots
-               volumeName = fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[2])
-       } else if len(fields) > 1 {
-               volumeName = fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[1])
-       } else if len(fields) > 0 {
-               // Handle volume
-               volumeName = fields[0]
-       } else {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
%s", mux.Vars(r)["name"]))
+       if shared.IsSnapshot(volumeName) {
+               return response.BadRequest(fmt.Errorf("Invalid volume name"))
        }
 
-       // Get the name of the storage pool the volume is supposed to be
-       // attached to.
+       // Get the name of the storage pool the volume is supposed to be 
attached to.
        poolName := mux.Vars(r)["pool"]
 
        // Convert the volume type name to our internal integer representation.
@@ -1010,14 +1000,14 @@ func storagePoolVolumeTypePatch(d *Daemon, r 
*http.Request, volumeTypeName strin
        if err != nil {
                return response.BadRequest(err)
        }
+
        // Check that the storage volume type is valid.
        if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
type %s", volumeTypeName))
+               return response.BadRequest(fmt.Errorf("Invalid storage volume 
type %s", volumeTypeName))
        }
 
-       // Get the ID of the storage pool the storage volume is supposed to be
-       // attached to.
-       poolID, pool, err := d.cluster.StoragePoolGet(poolName)
+       // Get the ID of the storage pool the storage volume is supposed to be 
attached to.
+       poolID, poolRow, err := d.cluster.StoragePoolGet(poolName)
        if err != nil {
                return response.SmartError(err)
        }
@@ -1033,13 +1023,13 @@ func storagePoolVolumeTypePatch(d *Daemon, r 
*http.Request, volumeTypeName strin
        }
 
        // Get the existing storage volume.
-       _, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, 
volumeType, poolID)
+       _, vol, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, 
volumeType, poolID)
        if err != nil {
                return response.SmartError(err)
        }
 
-       // Validate the ETag
-       etag := []interface{}{volumeName, volume.Type, volume.Config}
+       // Validate the ETag.
+       etag := []interface{}{volumeName, vol.Type, vol.Config}
 
        err = util.EtagCheck(r, etag)
        if err != nil {
@@ -1055,22 +1045,36 @@ func storagePoolVolumeTypePatch(d *Daemon, r 
*http.Request, volumeTypeName strin
                req.Config = map[string]string{}
        }
 
-       for k, v := range volume.Config {
+       // Merge current config with requested changes.
+       for k, v := range vol.Config {
                _, ok := req.Config[k]
                if !ok {
                        req.Config[k] = v
                }
        }
 
-       // Validate the configuration
-       err = storagePools.VolumeValidateConfig(volumeName, req.Config, pool)
-       if err != nil {
-               return response.BadRequest(err)
-       }
+       // Check if we can load new storage layer for pool driver type.
+       pool, err := storagePools.GetPoolByName(d.State(), poolName)
+       if err != storageDrivers.ErrUnknownDriver {
+               if err != nil {
+                       return response.SmartError(err)
+               }
 
-       err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, 
volumeType, req.Description, req.Config)
-       if err != nil {
-               return response.SmartError(err)
+               err = pool.UpdateCustomVolume(vol.Name, req.Description, 
req.Config, nil)
+               if err != nil {
+                       return response.SmartError(err)
+               }
+       } else {
+               // Validate the configuration.
+               err = storagePools.VolumeValidateConfig(volumeName, req.Config, 
poolRow)
+               if err != nil {
+                       return response.BadRequest(err)
+               }
+
+               err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, 
volumeType, req.Description, req.Config)
+               if err != nil {
+                       return response.SmartError(err)
+               }
        }
 
        return response.EmptySyncResponse

From c2e5372a7e693ab048ceb724aea73f5be24b8f34 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 30 Oct 2019 15:29:14 +0000
Subject: [PATCH 02/13] lxd/storage/volumes/utils: Links
 storagePoolVolumeUsedByRunningContainersWithProfilesGet to storage pkg

As VolumeUsedByInstancesWithProfiles

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage_volumes_utils.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index 2d9bdc5bf1..c70e8acb8a 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -39,6 +39,10 @@ const (
 var supportedVolumeTypesExceptImages = []int{storagePoolVolumeTypeContainer, 
storagePoolVolumeTypeCustom}
 var supportedVolumeTypes = append(supportedVolumeTypesExceptImages, 
storagePoolVolumeTypeImage)
 
+func init() {
+       storagePools.VolumeUsedByInstancesWithProfiles = 
storagePoolVolumeUsedByRunningContainersWithProfilesGet
+}
+
 func storagePoolVolumeTypeNameToAPIEndpoint(volumeTypeName string) (string, 
error) {
        switch volumeTypeName {
        case storagePoolVolumeTypeNameContainer:

From cb8c96b3023d53a2dc492cbe6a0a4fc0401d6c03 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 30 Oct 2019 15:30:00 +0000
Subject: [PATCH 03/13] lxd/storage/utils: Adds
 VolumeUsedByInstancesWithProfiles

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/utils.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index 5f9d890497..144e3e5e6b 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -28,6 +28,9 @@ var baseDirectories = []string{
        "virtual-machines-snapshots",
 }
 
+// VolumeUsedByInstancesWithProfiles returns a slice containg the names of 
instances using a volume.
+var VolumeUsedByInstancesWithProfiles func(s *state.State, poolName string, 
volumeName string, volumeTypeName string, runningOnly bool) ([]string, error)
+
 func createStorageStructure(path string) error {
        for _, name := range baseDirectories {
                err := os.MkdirAll(filepath.Join(path, name), 0711)

From e4a78b007f5e5f1423b4e63e3982f52f8568b3e9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 30 Oct 2019 15:31:03 +0000
Subject: [PATCH 04/13] lxd/storage/volumes: Links storagePoolVolumeTypePut to
 storage pkg

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage_volumes.go | 100 ++++++++++++++++++++++++++++-------------
 1 file changed, 68 insertions(+), 32 deletions(-)

diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index ac5c2cd43f..e4cd291be9 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -876,21 +876,14 @@ func storagePoolVolumeTypeImageGet(d *Daemon, r 
*http.Request) response.Response
 }
 
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
+// This function does allow limited functionality for non-custom volume types, 
specifically you
+// can modify the volume's description only.
 func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName 
string) response.Response {
        // Get the name of the storage volume.
-       var volumeName string
-       fields := strings.Split(mux.Vars(r)["name"], "/")
+       volumeName := mux.Vars(r)["name"]
 
-       if len(fields) == 3 && fields[1] == "snapshots" {
-               // Handle volume snapshots
-               volumeName = fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[2])
-       } else if len(fields) > 1 {
-               volumeName = fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[1])
-       } else if len(fields) > 0 {
-               // Handle volume
-               volumeName = fields[0]
-       } else {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
%s", mux.Vars(r)["name"]))
+       if shared.IsSnapshot(volumeName) {
+               return response.BadRequest(fmt.Errorf("Invalid volume name"))
        }
 
        // Get the name of the storage pool the volume is supposed to be
@@ -907,7 +900,7 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, 
volumeTypeName string)
                return response.BadRequest(fmt.Errorf("invalid storage volume 
type %s", volumeTypeName))
        }
 
-       poolID, pool, err := d.cluster.StoragePoolGet(poolName)
+       poolID, poolRow, err := d.cluster.StoragePoolGet(poolName)
        if err != nil {
                return response.SmartError(err)
        }
@@ -923,13 +916,13 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, 
volumeTypeName string)
        }
 
        // Get the existing storage volume.
-       _, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, 
volumeType, poolID)
+       _, vol, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, 
volumeType, poolID)
        if err != nil {
                return response.SmartError(err)
        }
 
        // Validate the ETag
-       etag := []interface{}{volumeName, volume.Type, volume.Config}
+       etag := []interface{}{volumeName, vol.Type, vol.Config}
 
        err = util.EtagCheck(r, etag)
        if err != nil {
@@ -941,30 +934,73 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, 
volumeTypeName string)
                return response.BadRequest(err)
        }
 
-       if req.Restore != "" {
-               ctsUsingVolume, err := 
storagePoolVolumeUsedByRunningContainersWithProfilesGet(d.State(), poolName, 
volume.Name, storagePoolVolumeTypeNameCustom, true)
+       // Check if we can load new storage layer for pool driver type.
+       pool, err := storagePools.GetPoolByName(d.State(), poolName)
+       if err != storageDrivers.ErrUnknownDriver {
                if err != nil {
-                       return response.InternalError(err)
+                       return response.SmartError(err)
                }
 
-               if len(ctsUsingVolume) != 0 {
-                       return response.BadRequest(fmt.Errorf("Cannot restore 
custom volume used by running containers"))
-               }
+               if volumeType == db.StoragePoolVolumeTypeCustom {
+                       // Restore custom volume from snapshot if requested. 
This should occur first
+                       // before applying config changes so that changes are 
applied to the
+                       // restored volume.
+                       if req.Restore != "" {
+                               err = pool.RestoreCustomVolume(vol.Name, 
req.Restore, nil)
+                               if err != nil {
+                                       return response.SmartError(err)
+                               }
+                       }
 
-               err = storagePoolVolumeRestore(d.State(), poolName, volumeName, 
volumeType, req.Restore)
-               if err != nil {
-                       return response.SmartError(err)
+                       // Handle update requests.
+                       err = pool.UpdateCustomVolume(vol.Name, 
req.Description, req.Config, nil)
+                       if err != nil {
+                               return response.SmartError(err)
+                       }
+               } else {
+                       // You are only allowed to modify the description for 
non-custom volumes.
+                       // This is a special case because the rootfs devices do 
not provide a way
+                       // to update a non-custom volume's description.
+                       if len(req.Config) > 0 {
+                               return response.BadRequest(fmt.Errorf("Only 
description can be modified for volume type %s", volumeTypeName))
+                       }
+
+                       // There is a bug in the lxc client 
(lxc/storage_volume.go#L829-L865) which
+                       // means that modifying a snapshot's description gets 
routed here rather
+                       // than the dedicated snapshot editing route. So need 
to handle snapshot
+                       // volumes here too.
+                       err = d.cluster.StoragePoolVolumeUpdate(vol.Name, 
volumeType, poolID, req.Description, req.Config)
+                       if err != nil {
+                               return response.SmartError(err)
+                       }
                }
        } else {
-               // Validate the configuration
-               err = storagePools.VolumeValidateConfig(volumeName, req.Config, 
pool)
-               if err != nil {
-                       return response.BadRequest(err)
-               }
 
-               err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, 
volumeType, req.Description, req.Config)
-               if err != nil {
-                       return response.SmartError(err)
+               if req.Restore != "" {
+                       ctsUsingVolume, err := 
storagePoolVolumeUsedByRunningContainersWithProfilesGet(d.State(), poolName, 
vol.Name, storagePoolVolumeTypeNameCustom, true)
+                       if err != nil {
+                               return response.InternalError(err)
+                       }
+
+                       if len(ctsUsingVolume) != 0 {
+                               return response.BadRequest(fmt.Errorf("Cannot 
restore custom volume used by running containers"))
+                       }
+
+                       err = storagePoolVolumeRestore(d.State(), poolName, 
volumeName, volumeType, req.Restore)
+                       if err != nil {
+                               return response.SmartError(err)
+                       }
+               } else {
+                       // Validate the configuration
+                       err = storagePools.VolumeValidateConfig(volumeName, 
req.Config, poolRow)
+                       if err != nil {
+                               return response.BadRequest(err)
+                       }
+
+                       err = storagePoolVolumeUpdate(d.State(), poolName, 
volumeName, volumeType, req.Description, req.Config)
+                       if err != nil {
+                               return response.SmartError(err)
+                       }
                }
        }
 

From 0adc8732af29010cb07ef124f547b7900b808dcc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 30 Oct 2019 15:33:17 +0000
Subject: [PATCH 05/13] lxd/storage/drivers/interface: Updates function
 definitions

- Adds UpdateVolume
- Updates ValidateVolume
- Fixes typo

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/interface.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go
index 13f071a6c2..7e4fadd28f 100644
--- a/lxd/storage/drivers/interface.go
+++ b/lxd/storage/drivers/interface.go
@@ -35,6 +35,7 @@ type Driver interface {
        CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op 
*operations.Operation) error
        DeleteVolume(volType VolumeType, volName string, op 
*operations.Operation) error
        RenameVolume(volType VolumeType, volName string, newName string, op 
*operations.Operation) error
+       UpdateVolume(vol Volume, changedKeys map[string]struct{}) error
 
        // MountVolume mounts a storage volume, returns true if we caused a new 
mount, false if
        // already mounted.
@@ -42,7 +43,7 @@ type Driver interface {
 
        // MountVolumeSnapshot mounts a storage volume snapshot as readonly, 
returns true if we
        // caused a new mount, false if already mounted.
-       MountVolumeSnapshot(volType VolumeType, VolName, snapshotName string, 
op *operations.Operation) (bool, error)
+       MountVolumeSnapshot(volType VolumeType, volName, snapshotName string, 
op *operations.Operation) (bool, error)
 
        // UnmountVolume unmounts a storage volume, returns true if unmounted, 
false if was not
        // mounted.

From 696d911067c696e26420ee17b83d8722f2be6f87 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 30 Oct 2019 15:35:03 +0000
Subject: [PATCH 06/13] lxd/storage/backend: UpdateCustomVolume and
 RestoreCustomVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/backend_lxd.go | 106 +++++++++++++++++++++++++++++++++++--
 1 file changed, 102 insertions(+), 4 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 68c39a6baf..e2a33f178f 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -5,6 +5,7 @@ import (
        "io"
        "os"
        "regexp"
+       "strings"
 
        "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/migration"
@@ -515,16 +516,22 @@ func (b *lxdBackend) RenameCustomVolume(volName string, 
newVolName string, op *o
        return nil
 }
 
-// UpdateCustomVolume applies the supplied config to the volume.
+// UpdateCustomVolume applies the supplied config to the custom volume.
 func (b *lxdBackend) UpdateCustomVolume(volName, newDesc string, newConfig 
map[string]string, op *operations.Operation) error {
+       if shared.IsSnapshot(volName) {
+               return fmt.Errorf("Volume name cannot be a snapshot")
+       }
+
+       vol := b.newVolume(drivers.VolumeTypeCustom, drivers.ContentTypeFS, 
volName, newConfig)
+
        // Validate config.
-       err := b.driver.ValidateVolume(b.newVolume(drivers.VolumeTypeCustom, 
drivers.ContentTypeFS, volName, newConfig), false)
+       err := b.driver.ValidateVolume(vol, false)
        if err != nil {
                return err
        }
 
        // Get current config to compare what has changed.
-       _, _, err = 
b.state.Cluster.StoragePoolNodeVolumeGetTypeByProject("default", volName, 
db.StoragePoolVolumeTypeCustom, b.ID())
+       _, curVol, err := 
b.state.Cluster.StoragePoolNodeVolumeGetTypeByProject("default", volName, 
db.StoragePoolVolumeTypeCustom, b.ID())
        if err != nil {
                if err == db.ErrNoSuchObject {
                        return fmt.Errorf("Volume doesn't exist")
@@ -533,7 +540,71 @@ func (b *lxdBackend) UpdateCustomVolume(volName, newDesc 
string, newConfig map[s
                return err
        }
 
-       return ErrNotImplemented
+       // Diff the configurations.
+       changedKeys := make(map[string]struct{})
+       userOnly := true
+       for key := range curVol.Config {
+               if curVol.Config[key] != newConfig[key] {
+                       if !strings.HasPrefix(key, "user.") {
+                               userOnly = false
+                       }
+
+                       changedKeys[key] = struct{}{}
+               }
+       }
+
+       for key := range newConfig {
+               if curVol.Config[key] != newConfig[key] {
+                       if !strings.HasPrefix(key, "user.") {
+                               userOnly = false
+                       }
+
+                       changedKeys[key] = struct{}{}
+               }
+       }
+
+       // Apply config changes if there are any.
+       if len(changedKeys) != 0 {
+               if !userOnly {
+                       err = b.driver.UpdateVolume(vol, changedKeys)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // Check that security.unmapped and security.shifted aren't set 
together.
+       if shared.IsTrue(newConfig["security.unmapped"]) && 
shared.IsTrue(newConfig["security.shifted"]) {
+               return fmt.Errorf("security.unmapped and security.shifted are 
mutually exclusive")
+       }
+
+       // Confirm that no instances are running when changing shifted state.
+       if newConfig["security.shifted"] != curVol.Config["security.shifted"] {
+               usingVolume, err := VolumeUsedByInstancesWithProfiles(b.state, 
b.Name(), volName, db.StoragePoolVolumeTypeNameCustom, true)
+               if err != nil {
+                       return err
+               }
+
+               if len(usingVolume) != 0 {
+                       return fmt.Errorf("Cannot modify shifting with running 
containers using the volume")
+               }
+       }
+
+       // Unset idmap keys if volume is unmapped.
+       if shared.IsTrue(newConfig["security.unmapped"]) {
+               delete(newConfig, "volatile.idmap.last")
+               delete(newConfig, "volatile.idmap.next")
+       }
+
+       // Update the database if something changed.
+       if len(changedKeys) != 0 || newDesc != curVol.Description {
+               err = b.state.Cluster.StoragePoolVolumeUpdate(volName, 
db.StoragePoolVolumeTypeCustom, b.ID(), newDesc, newConfig)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
 }
 
 // DeleteCustomVolume removes a custom volume and its snapshots.
@@ -707,3 +778,30 @@ func (b *lxdBackend) DeleteCustomVolumeSnapshot(volName 
string, op *operations.O
 
        return nil
 }
+
+// RestoreCustomVolume restores a custom volume from a snapshot.
+func (b *lxdBackend) RestoreCustomVolume(volName string, srcSnapshotName 
string, op *operations.Operation) error {
+       if shared.IsSnapshot(volName) {
+               return fmt.Errorf("Volume cannot be snapshot")
+       }
+
+       if shared.IsSnapshot(srcSnapshotName) {
+               return fmt.Errorf("Invalid snapshot name")
+       }
+
+       usingVolume, err := VolumeUsedByInstancesWithProfiles(b.state, 
b.Name(), volName, db.StoragePoolVolumeTypeNameCustom, true)
+       if err != nil {
+               return err
+       }
+
+       if len(usingVolume) != 0 {
+               return fmt.Errorf("Cannot restore custom volume used by running 
instances")
+       }
+
+       err = b.driver.RestoreVolume(b.newVolume(drivers.VolumeTypeCustom, 
drivers.ContentTypeFS, volName, nil), srcSnapshotName, op)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}

From 0c97316edeb9063a2f1f0b92f8c3943957665858 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 30 Oct 2019 15:36:13 +0000
Subject: [PATCH 07/13] lxd/storage/drivers/driver/dir: Adds UpdateVolume
 function

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_dir.go | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/lxd/storage/drivers/driver_dir.go 
b/lxd/storage/drivers/driver_dir.go
index 61cfb9deb4..cfc0a26a0b 100644
--- a/lxd/storage/drivers/driver_dir.go
+++ b/lxd/storage/drivers/driver_dir.go
@@ -459,6 +459,25 @@ func (d *dir) VolumeSnapshots(volType VolumeType, volName 
string, op *operations
        return snapshots, nil
 }
 
+// UpdateVolume applies config changes to the volume.
+func (d *dir) UpdateVolume(vol Volume, changedKeys map[string]struct{}) error {
+       if _, found := changedKeys["size"]; found {
+               volID, err := d.getVolID(vol.volType, vol.name)
+               if err != nil {
+                       return err
+               }
+
+               // Set the quota if specified in volConfig or pool config.
+               err = d.setQuota(vol.MountPath(), volID, vol.config["size"])
+               if err != nil {
+                       return err
+               }
+
+       }
+
+       return nil
+}
+
 // RenameVolume renames a volume and its snapshots.
 func (d *dir) RenameVolume(volType VolumeType, volName string, newVolName 
string, op *operations.Operation) error {
        vol := NewVolume(d, d.name, volType, ContentTypeFS, volName, nil)

From 33536411be1e700a03102e8a9467527cdcd4104b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 31 Oct 2019 10:34:13 +0000
Subject: [PATCH 08/13] lxd/storage/volumes: Consistent casing on error
 messages

- Moves volume name from URL extraction a common function.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage_volumes.go | 49 ++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 21 deletions(-)

diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index e4cd291be9..0277b83c03 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -174,7 +174,7 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) 
response.Response {
        }
        // Check that the storage volume type is valid.
        if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
type %s", volumeTypeName))
+               return response.BadRequest(fmt.Errorf("Invalid storage volume 
type %s", volumeTypeName))
        }
 
        // Retrieve ID of the storage pool (and check if the storage pool
@@ -794,25 +794,32 @@ func storagePoolVolumeTypeImagePost(d *Daemon, r 
*http.Request) response.Respons
        return storagePoolVolumeTypePost(d, r, "image")
 }
 
+// storageGetVolumeNameFromURL retrieves the volume name from the URL name 
segment.
+func storageGetVolumeNameFromURL(r *http.Request) (string, error) {
+       fields := strings.Split(mux.Vars(r)["name"], "/")
+
+       if len(fields) == 3 && fields[1] == "snapshots" {
+               // Handle volume snapshots.
+               return fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[2]), nil
+       } else if len(fields) > 1 {
+               return fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[1]), nil
+       } else if len(fields) > 0 {
+               // Handle volume.
+               return fields[0], nil
+       }
+
+       return "", fmt.Errorf("Invalid storage volume %s", mux.Vars(r)["name"])
+}
+
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
 // Get storage volume of a given volume type on a given storage pool.
 func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName 
string) response.Response {
        project := projectParam(r)
 
        // Get the name of the storage volume.
-       var volumeName string
-       fields := strings.Split(mux.Vars(r)["name"], "/")
-
-       if len(fields) == 3 && fields[1] == "snapshots" {
-               // Handle volume snapshots
-               volumeName = fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[2])
-       } else if len(fields) > 1 {
-               volumeName = fmt.Sprintf("%s%s%s", fields[0], 
shared.SnapshotDelimiter, fields[1])
-       } else if len(fields) > 0 {
-               // Handle volume
-               volumeName = fields[0]
-       } else {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
%s", mux.Vars(r)["name"]))
+       volumeName, err := storageGetVolumeNameFromURL(r)
+       if err != nil {
+               return response.BadRequest(err)
        }
 
        // Get the name of the storage pool the volume is supposed to be
@@ -826,7 +833,7 @@ func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, 
volumeTypeName string)
        }
        // Check that the storage volume type is valid.
        if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
type %s", volumeTypeName))
+               return response.BadRequest(fmt.Errorf("Invalid storage volume 
type %s", volumeTypeName))
        }
 
        // Get the ID of the storage pool the storage volume is supposed to be
@@ -880,10 +887,9 @@ func storagePoolVolumeTypeImageGet(d *Daemon, r 
*http.Request) response.Response
 // can modify the volume's description only.
 func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName 
string) response.Response {
        // Get the name of the storage volume.
-       volumeName := mux.Vars(r)["name"]
-
-       if shared.IsSnapshot(volumeName) {
-               return response.BadRequest(fmt.Errorf("Invalid volume name"))
+       volumeName, err := storageGetVolumeNameFromURL(r)
+       if err != nil {
+               return response.BadRequest(err)
        }
 
        // Get the name of the storage pool the volume is supposed to be
@@ -895,9 +901,10 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, 
volumeTypeName string)
        if err != nil {
                return response.BadRequest(err)
        }
+
        // Check that the storage volume type is valid.
        if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
type %s", volumeTypeName))
+               return response.BadRequest(fmt.Errorf("Invalid storage volume 
type %s", volumeTypeName))
        }
 
        poolID, poolRow, err := d.cluster.StoragePoolGet(poolName)
@@ -1150,7 +1157,7 @@ func storagePoolVolumeTypeDelete(d *Daemon, r 
*http.Request, volumeTypeName stri
        }
        // Check that the storage volume type is valid.
        if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-               return response.BadRequest(fmt.Errorf("invalid storage volume 
type %s", volumeTypeName))
+               return response.BadRequest(fmt.Errorf("Invalid storage volume 
type %s", volumeTypeName))
        }
 
        resp := ForwardedResponseIfTargetIsRemote(d, r)

From bc59e01fde299d7136f94ada8ca2fb448b88b39a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 31 Oct 2019 10:36:04 +0000
Subject: [PATCH 09/13] lxd/storage/utils: Consistent casing on error messages

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/utils.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index 144e3e5e6b..c1f74c7934 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -400,7 +400,7 @@ func VolumeTypeNameToType(volumeTypeName string) (int, 
error) {
                return db.StoragePoolVolumeTypeCustom, nil
        }
 
-       return -1, fmt.Errorf("invalid storage volume type name")
+       return -1, fmt.Errorf("Invalid storage volume type name")
 }
 
 // VolumeTypeToDBType converts volume type to internal code.
@@ -414,7 +414,7 @@ func VolumeTypeToDBType(volType drivers.VolumeType) (int, 
error) {
                return db.StoragePoolVolumeTypeCustom, nil
        }
 
-       return -1, fmt.Errorf("invalid storage volume type")
+       return -1, fmt.Errorf("Invalid storage volume type")
 }
 
 // VolumeDBCreate creates a volume in the database.

From b742c05b8f2c1b93e7d93dbaa65432af1a54466e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 31 Oct 2019 10:36:20 +0000
Subject: [PATCH 10/13] lxd/storage/interfaces: Adds RestoreCustomVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/interfaces.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/storage/interfaces.go b/lxd/storage/interfaces.go
index d1d3cbdbba..17eeab8409 100644
--- a/lxd/storage/interfaces.go
+++ b/lxd/storage/interfaces.go
@@ -85,6 +85,7 @@ type Pool interface {
        CreateCustomVolumeSnapshot(volName string, newSnapshotName string, op 
*operations.Operation) error
        RenameCustomVolumeSnapshot(volName string, newSnapshotName string, op 
*operations.Operation) error
        DeleteCustomVolumeSnapshot(volName string, op *operations.Operation) 
error
+       RestoreCustomVolume(volName string, srcSnapshotName string, op 
*operations.Operation) error
 
        // Custom volume migration.
        MigrationTypes(contentType drivers.ContentType) []migration.Type

From 81341ae6201a9812f422ee6a11b9e2fe232e19de Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 31 Oct 2019 10:36:37 +0000
Subject: [PATCH 11/13] lxd/storage/drivers/interface: Adds RestoreVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/interface.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go
index 7e4fadd28f..c10fd183b2 100644
--- a/lxd/storage/drivers/interface.go
+++ b/lxd/storage/drivers/interface.go
@@ -57,6 +57,7 @@ type Driver interface {
        DeleteVolumeSnapshot(volType VolumeType, volName string, snapshotName 
string, op *operations.Operation) error
        RenameVolumeSnapshot(volType VolumeType, volName string, snapshotName 
string, newSnapshotName string, op *operations.Operation) error
        VolumeSnapshots(volType VolumeType, volName string, op 
*operations.Operation) ([]string, error)
+       RestoreVolume(vol Volume, srcSnapshotName string, op 
*operations.Operation) error
 
        // Migration.
        MigrationTypes(contentType ContentType) []migration.Type

From 601e96c4217aaa7b47a2a8da5fdfad391277ae69 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 31 Oct 2019 10:36:54 +0000
Subject: [PATCH 12/13] lxd/storage/drivers/driver/dir: Implements
 RestoreVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_dir.go | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/lxd/storage/drivers/driver_dir.go 
b/lxd/storage/drivers/driver_dir.go
index cfc0a26a0b..78cd2efde0 100644
--- a/lxd/storage/drivers/driver_dir.go
+++ b/lxd/storage/drivers/driver_dir.go
@@ -550,6 +550,25 @@ func (d *dir) RenameVolume(volType VolumeType, volName 
string, newVolName string
        return nil
 }
 
+// RestoreVolume restores a volume from a snapshot.
+func (d *dir) RestoreVolume(vol Volume, srcSnapshotName string, op 
*operations.Operation) error {
+       srcPath := GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, srcSnapshotName))
+       if !shared.PathExists(srcPath) {
+               return fmt.Errorf("Snapshot not found")
+       }
+
+       volPath := vol.MountPath()
+
+       // Restore using rsync.
+       bwlimit := d.config["rsync.bwlimit"]
+       output, err := rsync.LocalCopy(srcPath, volPath, bwlimit, true)
+       if err != nil {
+               return fmt.Errorf("Failed to rsync volume: %s: %s", 
string(output), err)
+       }
+
+       return nil
+}
+
 // DeleteVolume deletes a volume of the storage device. If any snapshots of 
the volume remain then
 // this function will return an error.
 func (d *dir) DeleteVolume(volType VolumeType, volName string, op 
*operations.Operation) error {

From b89796f11cb98f6de02a8da49b2e3a2f358d294e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 31 Oct 2019 10:37:08 +0000
Subject: [PATCH 13/13] lxd/storage/backend/mock: Adds RestoreCustomVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/backend_mock.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go
index 86562bc73e..86e18253bd 100644
--- a/lxd/storage/backend_mock.go
+++ b/lxd/storage/backend_mock.go
@@ -203,3 +203,7 @@ func (b *mockBackend) RenameCustomVolumeSnapshot(volName 
string, newName string,
 func (b *mockBackend) DeleteCustomVolumeSnapshot(volName string, op 
*operations.Operation) error {
        return nil
 }
+
+func (b *mockBackend) RestoreCustomVolume(volName string, srcSnapshotName 
string, op *operations.Operation) error {
+       return nil
+}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to