[lxc-devel] [lxd/master] Add support for on-pool backups/images files
stgraber on Github
lxc-bot at linuxcontainers.org
Wed Aug 14 13:03:04 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 314 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190814/2c1cbe9a/attachment.bin>
-------------- next part --------------
From b101362e97c2e41b94a5517cea5d283dc7288516 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 12 Aug 2019 00:14:38 -0400
Subject: [PATCH 1/5] api: Add daemon_storage extension
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
doc/api-extensions.md | 5 +++++
shared/version/api.go | 1 +
2 files changed, 6 insertions(+)
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 06249be844..dc4ef31970 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -818,3 +818,8 @@ This makes use of shiftfs as an overlay filesystem.
## resources\_infiniband
Export infiniband character device information (issm, umad, uverb) as part of the resources API.
+
+## daemon\_storage
+This introduces two new configuration keys `storage.images\_volume` and
+`storage.backups\_volume` to allow for a storage volume on an existing
+pool be used for storing the daemon-wide images and backups artifacts.
diff --git a/shared/version/api.go b/shared/version/api.go
index 0e3f9c892e..201e834828 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -163,6 +163,7 @@ var APIExtensions = []string{
"container_disk_shift",
"storage_shifted",
"resources_infiniband",
+ "daemon_storage",
}
// APIExtensionsCount returns the number of available API extensions.
From 4d37105c1a49315c82e01a351ffc9a2a56c19ff7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 12 Aug 2019 00:14:52 -0400
Subject: [PATCH 2/5] doc: Add daemon storage keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
doc/server.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/server.md b/doc/server.md
index 18628dd7c8..68d238c05c 100644
--- a/doc/server.md
+++ b/doc/server.md
@@ -46,6 +46,8 @@ rbac.agent.private\_key | string | global | - | rbac
rbac.api.expiry | integer | global | - | rbac | RBAC macaroon expiry in seconds
rbac.api.key | string | global | - | rbac | Public key of the RBAC server (required for HTTP-only servers)
rbac.api.url | string | global | - | rbac | URL of the external RBAC server
+storage.backups\_volume | string | local | - | daemon\_storage | Volume to use to store the backup tarballs (syntax is POOL/VOLUME)
+storage.images\_volume | string | local | - | daemon\_storage | Volume to use to store the image tarballs (syntax is POOL/VOLUME)
Those keys can be set using the lxc tool with:
From 2366f593189df4802ac6bf30f5cba8167d195f86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 12 Aug 2019 00:15:32 -0400
Subject: [PATCH 3/5] bash: Add daemon storage keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
scripts/bash/lxd-client | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/bash/lxd-client b/scripts/bash/lxd-client
index b8613c4e0c..6a78584688 100644
--- a/scripts/bash/lxd-client
+++ b/scripts/bash/lxd-client
@@ -75,7 +75,8 @@ _have lxc && {
images.compression_algorithm images.remote_cache_expiry \
maas.api.url maas.api.key maas.machine cluster.images_minimal_replica \
rbac.agent.url rbac.agent.username rbac.agent.public_key \
- rbac.agent.private_key rbac.api.expiry rbac.api.key rbac.api.url"
+ rbac.agent.private_key rbac.api.expiry rbac.api.key rbac.api.url \
+ storage.backups_volume storage.images_volume"
container_keys="boot.autostart boot.autostart.delay \
boot.autostart.priority boot.stop.priority \
From a50b8608f11bac5f6d72d17561c29eef78644ae6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 12 Aug 2019 00:27:28 -0400
Subject: [PATCH 4/5] lxd: Add daemon storage keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/node/config.go | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/lxd/node/config.go b/lxd/node/config.go
index c67814c030..c478791faa 100644
--- a/lxd/node/config.go
+++ b/lxd/node/config.go
@@ -54,6 +54,16 @@ func (c *Config) MAASMachine() string {
return c.m.GetString("maas.machine")
}
+// StorageBackupsVolume returns the name of the pool/volume to use for storing backup tarballs
+func (c *Config) StorageBackupsVolume() string {
+ return c.m.GetString("storage.backups_volume")
+}
+
+// StorageImagesVolume returns the name of the pool/volume to use for storing image tarballs
+func (c *Config) StorageImagesVolume() string {
+ return c.m.GetString("storage.images_volume")
+}
+
// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]interface{} {
@@ -150,4 +160,8 @@ var ConfigSchema = config.Schema{
// MAAS machine this LXD instance is associated with
"maas.machine": {},
+
+ // Storage volumes to store backups/images on
+ "storage.backups_volume": {},
+ "storage.images_volume": {},
}
From 29a065245b7529ba20f941d512c301101f879d13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 13 Aug 2019 22:45:57 -0400
Subject: [PATCH 5/5] lxd/daemon: Support storing images/backups on pool
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/api_1.0.go | 37 +++-
lxd/daemon.go | 6 +
lxd/daemon_storage.go | 348 ++++++++++++++++++++++++++++++++
lxd/storage_volumes.go | 19 +-
lxd/storage_volumes_snapshot.go | 14 +-
lxd/storage_volumes_utils.go | 10 +
6 files changed, 426 insertions(+), 8 deletions(-)
create mode 100644 lxd/daemon_storage.go
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index c794c2ff6f..0b6e1a9db4 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -321,6 +321,8 @@ func api10Patch(d *Daemon, r *http.Request) Response {
}
func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
+ s := d.State()
+
// First deal with config specific to the local daemon
nodeValues := map[string]interface{}{}
@@ -350,6 +352,21 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
return fmt.Errorf("Changing cluster.https_address is currently not supported")
}
+ // Validate the storage volumes
+ if nodeValues["storage.backups_volume"] != nil && nodeValues["storage.backups_volume"] != newNodeConfig.StorageBackupsVolume() {
+ err := daemonStorageValidate(s, nodeValues["storage.backups_volume"].(string))
+ if err != nil {
+ return err
+ }
+ }
+
+ if nodeValues["storage.images_volume"] != nil && nodeValues["storage.images_volume"] != newNodeConfig.StorageImagesVolume() {
+ err := daemonStorageValidate(s, nodeValues["storage.images_volume"].(string))
+ if err != nil {
+ return err
+ }
+ }
+
if patch {
nodeChanged, err = newNodeConfig.Patch(nodeValues)
} else {
@@ -411,7 +428,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
}
// Notify the other nodes about changes
- notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
+ notifier, err := cluster.NewNotifier(s, d.endpoints.NetworkCert(), cluster.NotifyAlive)
if err != nil {
return SmartError(err)
}
@@ -442,6 +459,8 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
}
func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]string, nodeConfig *node.Config, clusterConfig *cluster.Config) error {
+ s := d.State()
+
maasChanged := false
candidChanged := false
rbacChanged := false
@@ -525,6 +544,22 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str
}
}
+ value, ok = nodeChanged["storage.backups_volume"]
+ if ok {
+ err := daemonStorageMove(s, "backups", value)
+ if err != nil {
+ return err
+ }
+ }
+
+ value, ok = nodeChanged["storage.images_volume"]
+ if ok {
+ err := daemonStorageMove(s, "images", value)
+ if err != nil {
+ return err
+ }
+ }
+
if maasChanged {
url, key := clusterConfig.MAASController()
machine := nodeConfig.MAASMachine()
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 64b2abb3de..0aad1da9f9 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -777,6 +777,12 @@ func (d *Daemon) init() error {
return err
}
+ // Mount any daemon storage
+ err = daemonStorageMount(d.State())
+ if err != nil {
+ return err
+ }
+
/* Apply all patches */
err = patchesApplyAll(d)
if err != nil {
diff --git a/lxd/daemon_storage.go b/lxd/daemon_storage.go
new file mode 100644
index 0000000000..ef55306190
--- /dev/null
+++ b/lxd/daemon_storage.go
@@ -0,0 +1,348 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/pkg/errors"
+
+ "github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/node"
+ "github.com/lxc/lxd/lxd/state"
+ "github.com/lxc/lxd/shared"
+ "github.com/lxc/lxd/shared/logger"
+)
+
+func daemonStorageMount(s *state.State) error {
+ var storageBackups string
+ var storageImages string
+ err := s.Node.Transaction(func(tx *db.NodeTx) error {
+ nodeConfig, err := node.ConfigLoad(tx)
+ if err != nil {
+ return err
+ }
+
+ storageBackups = nodeConfig.StorageBackupsVolume()
+ storageImages = nodeConfig.StorageImagesVolume()
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ mount := func(storageType string, source string) error {
+ // Parse the source
+ fields := strings.Split(source, "/")
+ if len(fields) != 2 {
+ return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
+ }
+
+ poolName := fields[0]
+ volumeName := fields[1]
+
+ // Mount volume
+ volume, err := storageInit(s, "default", poolName, volumeName, storagePoolVolumeTypeCustom)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage volume \"%s\"", source)
+ }
+
+ _, err = volume.StoragePoolVolumeMount()
+ if err != nil {
+ return errors.Wrapf(err, "Failed to mount storage volume \"%s\"", source)
+ }
+
+ return nil
+ }
+
+ if storageBackups != "" {
+ err := mount("backups", storageBackups)
+ if err != nil {
+ return errors.Wrap(err, "Failed to mount backups storage")
+ }
+ }
+
+ if storageImages != "" {
+ err := mount("images", storageImages)
+ if err != nil {
+ return errors.Wrap(err, "Failed to mount images storage")
+ }
+ }
+
+ return nil
+}
+
+func daemonStorageUsed(s *state.State, poolName string, volumeName string) (bool, error) {
+ var storageBackups string
+ var storageImages string
+ err := s.Node.Transaction(func(tx *db.NodeTx) error {
+ nodeConfig, err := node.ConfigLoad(tx)
+ if err != nil {
+ return err
+ }
+
+ storageBackups = nodeConfig.StorageBackupsVolume()
+ storageImages = nodeConfig.StorageImagesVolume()
+
+ return nil
+ })
+ if err != nil {
+ return false, err
+ }
+
+ fullName := fmt.Sprintf("%s/%s", poolName, volumeName)
+ if storageBackups == fullName || storageImages == fullName {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func daemonStorageValidate(s *state.State, target string) error {
+ // Check syntax
+ if target == "" {
+ return nil
+ }
+
+ fields := strings.Split(target, "/")
+ if len(fields) != 2 {
+ return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
+ }
+
+ poolName := fields[0]
+ volumeName := fields[1]
+
+ // Validate pool exists
+ poolID, pool, err := s.Cluster.StoragePoolGet(poolName)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage pool \"%s\"", poolName)
+ }
+
+ // Validate pool driver (can't be CEPH or CEPHFS)
+ if pool.Driver == "ceph" || pool.Driver == "cephfs" {
+ return fmt.Errorf("Server storage volumes cannot be stored on Ceph")
+ }
+
+ // Confirm volume exists
+ volume, err := storageInit(s, "default", poolName, volumeName, storagePoolVolumeTypeCustom)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage volume \"%s\"", target)
+ }
+
+ snapshots, err := s.Cluster.StoragePoolVolumeSnapshotsGetType(volumeName, storagePoolVolumeTypeCustom, poolID)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage volume snapshots \"%s\"", target)
+ }
+
+ if len(snapshots) != 0 {
+ return fmt.Errorf("Storage volumes for use by LXD itself cannot have snapshots")
+ }
+
+ // Mount volume
+ ourMount, err := volume.StoragePoolVolumeMount()
+ if err != nil {
+ return errors.Wrapf(err, "Failed to mount storage volume \"%s\"", target)
+ }
+ if ourMount {
+ defer volume.StoragePoolUmount()
+ }
+
+ // Validate volume is empty (ignore lost+found)
+ mountpoint := shared.VarPath("storage-pools", poolName, "custom", volumeName)
+
+ entries, err := ioutil.ReadDir(mountpoint)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to list \"%s\"", mountpoint)
+ }
+
+ for _, entry := range entries {
+ entryName := entry.Name()
+
+ if entryName == "lost+found" {
+ continue
+ }
+
+ return fmt.Errorf("Storage volume \"%s\" isn't empty", target)
+ }
+
+ return nil
+}
+
+func daemonStorageMove(s *state.State, storageType string, target string) error {
+ logger.Errorf("stgraber: daemonStorageMove for %s at %s", storageType, target)
+ destPath := shared.VarPath(storageType)
+
+ // Track down the current storage
+ var sourcePool string
+ var sourceVolume string
+
+ sourcePath, err := os.Readlink(destPath)
+ if err != nil {
+ sourcePath = destPath
+ } else {
+ fields := strings.Split(sourcePath, "/")
+ sourcePool = fields[len(fields)-3]
+ sourceVolume = fields[len(fields)-1]
+ }
+
+ moveContent := func(source string, target string) error {
+ // Copy the content
+ _, err := rsyncLocalCopy(source, target, "", false)
+ if err != nil {
+ return err
+ }
+
+ // Remove the source content
+ entries, err := ioutil.ReadDir(source)
+ if err != nil {
+ return err
+ }
+
+ for _, entry := range entries {
+ err := os.RemoveAll(filepath.Join(source, entry.Name()))
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+
+ // Deal with unsetting
+ if target == "" {
+ // Things already look correct
+ if sourcePath == destPath {
+ return nil
+ }
+
+ // Remove the symlink
+ err = os.Remove(destPath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to delete storage symlink at \"%s\"", destPath)
+ }
+
+ // Re-create as a directory
+ err = os.MkdirAll(destPath, 0700)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to create directory \"%s\"", destPath)
+ }
+
+ // Move the data across
+ err = moveContent(sourcePath, destPath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to move data over to directory \"%s\"", destPath)
+ }
+
+ // Unmount old volume
+ volume, err := storageInit(s, "default", sourcePool, sourceVolume, storagePoolVolumeTypeCustom)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage volume \"%s/%s\"", sourcePool, sourceVolume)
+ }
+
+ _, err = volume.StoragePoolVolumeUmount()
+ if err != nil {
+ return errors.Wrapf(err, "Failed to umount storage volume \"%s/%s\"", sourcePool, sourceVolume)
+ }
+
+ return nil
+ }
+
+ // Parse the target
+ fields := strings.Split(target, "/")
+ if len(fields) != 2 {
+ return fmt.Errorf("Invalid syntax for volume, must be <pool>/<volume>")
+ }
+
+ poolName := fields[0]
+ volumeName := fields[1]
+
+ // Mount volume
+ volume, err := storageInit(s, "default", poolName, volumeName, storagePoolVolumeTypeCustom)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage volume \"%s\"", target)
+ }
+
+ _, err = volume.StoragePoolVolumeMount()
+ if err != nil {
+ return errors.Wrapf(err, "Failed to mount storage volume \"%s\"", target)
+ }
+
+ // Set ownership & mode
+ mountpoint := shared.VarPath("storage-pools", poolName, "custom", volumeName)
+ destPath = mountpoint
+
+ err = os.Chmod(mountpoint, 0700)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to set permissions on \"%s\"", mountpoint)
+ }
+
+ err = os.Chown(mountpoint, 0, 0)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to set ownership on \"%s\"", mountpoint)
+ }
+
+ // Handle changes
+ if sourcePath != shared.VarPath(storageType) {
+ // Remove the symlink
+ err := os.Remove(shared.VarPath(storageType))
+ if err != nil {
+ return errors.Wrapf(err, "Failed to remove the new symlink at \"%s\"", shared.VarPath(storageType))
+ }
+
+ // Create the new symlink
+ err = os.Symlink(destPath, shared.VarPath(storageType))
+ if err != nil {
+ return errors.Wrapf(err, "Failed to create the new symlink at \"%s\"", shared.VarPath(storageType))
+ }
+
+ // Move the data across
+ err = moveContent(sourcePath, destPath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to move data over to directory \"%s\"", destPath)
+ }
+
+ // Unmount old volume
+ volume, err := storageInit(s, "default", sourcePool, sourceVolume, storagePoolVolumeTypeCustom)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to load storage volume \"%s/%s\"", sourcePool, sourceVolume)
+ }
+
+ _, err = volume.StoragePoolVolumeUmount()
+ if err != nil {
+ return errors.Wrapf(err, "Failed to umount storage volume \"%s/%s\"", sourcePool, sourceVolume)
+ }
+
+ return nil
+ }
+
+ sourcePath = shared.VarPath(storageType) + ".temp"
+
+ // Rename the existing storage
+ err = os.Rename(shared.VarPath(storageType), sourcePath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to rename existing storage \"%s\"", shared.VarPath(storageType))
+ }
+
+ // Create the new symlink
+ err = os.Symlink(destPath, shared.VarPath(storageType))
+ if err != nil {
+ return errors.Wrapf(err, "Failed to create the new symlink at \"%s\"", shared.VarPath(storageType))
+ }
+
+ // Move the data across
+ err = moveContent(sourcePath, destPath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to move data over to directory \"%s\"", destPath)
+ }
+
+ // Remove the old data
+ err = os.RemoveAll(sourcePath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to cleanup old directory \"%s\"", sourcePath)
+ }
+
+ return nil
+}
diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index 38250235d0..ce02541471 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -452,7 +452,7 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
// Handle volume
volumeName = fields[0]
} else {
- return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+ return BadRequest(fmt.Errorf("Invalid storage volume %s", mux.Vars(r)["name"]))
}
// Get the name of the storage pool the volume is supposed to be
@@ -560,8 +560,7 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
}
// Check that the name isn't already in use.
- _, err = d.cluster.StoragePoolNodeVolumeGetTypeID(req.Name,
- storagePoolVolumeTypeCustom, poolID)
+ _, err = d.cluster.StoragePoolNodeVolumeGetTypeID(req.Name, storagePoolVolumeTypeCustom, poolID)
if err != db.ErrNoSuchObject {
if err != nil {
return InternalError(err)
@@ -571,6 +570,17 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
}
doWork := func() error {
+ // Check if the daemon itself is using it
+ used, err := daemonStorageUsed(d.State(), poolName, volumeName)
+ if err != nil {
+ return err
+ }
+
+ if used {
+ return fmt.Errorf("Volume is used by LXD itself and cannot be renamed")
+ }
+
+ // Check if a running container is using it
ctsUsingVolume, err := storagePoolVolumeUsedByRunningContainersWithProfilesGet(d.State(), poolName, volumeName, storagePoolVolumeTypeNameCustom, true)
if err != nil {
return err
@@ -1007,8 +1017,7 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
"/%s/images/%s",
version.APIVersion,
volumeName) {
- return BadRequest(fmt.Errorf(`The storage volume is ` +
- `still in use by containers or profiles`))
+ return BadRequest(fmt.Errorf("The storage volume is still in use"))
}
}
diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 3b2b2cfa10..8e87a6e925 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -56,7 +56,7 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type \"%d\"", volumeType))
+ return BadRequest(fmt.Errorf("Invalid storage volume type \"%d\"", volumeType))
}
// Get a snapshot name.
@@ -71,6 +71,16 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
return BadRequest(err)
}
+ // Check that this isn't a restricted volume
+ used, err := daemonStorageUsed(d.State(), poolName, volumeName)
+ if err != nil {
+ return InternalError(err)
+ }
+
+ if used {
+ return BadRequest(fmt.Errorf("Volumes used by LXD itself cannot have snapshots"))
+ }
+
// Retrieve ID of the storage pool (and check if the storage pool
// exists).
poolID, err := d.cluster.StoragePoolGetID(poolName)
@@ -94,7 +104,7 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
return SmartError(err)
}
- // Ensure that it doens't already fucking exist
+ // Ensure that the snapshot doens't already exist
_, _, err = d.cluster.StoragePoolNodeVolumeGetType(fmt.Sprintf("%s/%s", volumeName, req.Name), volumeType, poolID)
if err != db.ErrNoSuchObject {
if err != nil {
diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index 538b4704fe..f7d32012b0 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -447,6 +447,16 @@ func storagePoolVolumeUsedByGet(s *state.State, project, poolName string, volume
return []string{fmt.Sprintf("/%s/images/%s", version.APIVersion, volumeName)}, nil
}
+ // Check if the daemon itself is using it
+ used, err := daemonStorageUsed(s, poolName, volumeName)
+ if err != nil {
+ return []string{}, err
+ }
+
+ if used {
+ return []string{fmt.Sprintf("/%s", version.APIVersion)}, nil
+ }
+
// Look for containers using this volume
ctsUsingVolume, err := storagePoolVolumeUsedByContainersGet(s, project, poolName, volumeName)
if err != nil {
More information about the lxc-devel
mailing list