[lxc-devel] [lxd/master] Storage Update and Restore volumes
tomponline on Github
lxc-bot at linuxcontainers.org
Thu Oct 31 10:45:07 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 409 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191031/c167d2f6/attachment-0001.bin>
-------------- next part --------------
From 1a57754ff99999efe1ec25d5c273b7a2ae2749b2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at canonical.com>
---
lxd/storage/interfaces.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/storage/interfaces.go b/lxd/storage/interfaces.go
index 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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
+}
More information about the lxc-devel
mailing list