[lxc-devel] [lxd/master] Support custom block volumes

monstermunchkin on Github lxc-bot at linuxcontainers.org
Tue Jun 2 12:12:24 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200602/839a131b/attachment-0001.bin>
-------------- next part --------------
From 91bda8ef1bd5a676818c8f7d3748e3ea18e06741 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 16:17:38 +0200
Subject: [PATCH 01/14] lxd/db/cluster: Add content_type to storage_volumes

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/db/cluster/schema.go | 12 +++++++----
 lxd/db/cluster/update.go | 43 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 4 deletions(-)

diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index fbf24753fc..138f10b738 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -480,6 +480,7 @@ CREATE TABLE "storage_volumes" (
     type INTEGER NOT NULL,
     description TEXT,
     project_id INTEGER NOT NULL,
+    content_type INTEGER NOT NULL DEFAULT 0,
     UNIQUE (storage_pool_id, node_id, project_id, name, type),
     FOREIGN KEY (storage_pool_id) REFERENCES storage_pools (id) ON DELETE CASCADE,
     FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE,
@@ -492,14 +493,16 @@ CREATE VIEW storage_volumes_all (
          node_id,
          type,
          description,
-         project_id) AS
+         project_id,
+         content_type) AS
   SELECT id,
          name,
          storage_pool_id,
          node_id,
          type,
          description,
-         project_id
+         project_id,
+         content_type
     FROM storage_volumes UNION
   SELECT storage_volumes_snapshots.id,
          printf('%s/%s',
@@ -509,7 +512,8 @@ CREATE VIEW storage_volumes_all (
          storage_volumes.node_id,
          storage_volumes.type,
          storage_volumes_snapshots.description,
-         storage_volumes.project_id
+         storage_volumes.project_id,
+         storage_volumes.content_type
     FROM storage_volumes
     JOIN storage_volumes_snapshots ON storage_volumes.id = storage_volumes_snapshots.storage_volume_id;
 CREATE TRIGGER storage_volumes_check_id
@@ -553,5 +557,5 @@ CREATE TABLE storage_volumes_snapshots_config (
     UNIQUE (storage_volume_snapshot_id, key)
 );
 
-INSERT INTO schema (version, updated_at) VALUES (28, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (29, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index fb2c5c73b7..739c4eee1a 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -65,6 +65,49 @@ var updates = map[int]schema.Update{
 	26: updateFromV25,
 	27: updateFromV26,
 	28: updateFromV27,
+	29: updateFromV28,
+}
+
+// Add content type field to storage volumes
+func updateFromV28(tx *sql.Tx) error {
+	stmts := `ALTER TABLE storage_volumes ADD COLUMN content_type INTEGER NOT NULL DEFAULT 0;
+UPDATE storage_volumes SET content_type = 1 WHERE type = 3;
+DROP VIEW storage_volumes_all;
+CREATE VIEW storage_volumes_all (
+         id,
+         name,
+         storage_pool_id,
+         node_id,
+         type,
+         description,
+         project_id,
+         content_type) AS
+  SELECT id,
+         name,
+         storage_pool_id,
+         node_id,
+         type,
+         description,
+         project_id,
+         content_type
+    FROM storage_volumes UNION
+  SELECT storage_volumes_snapshots.id,
+         printf('%s/%s', storage_volumes.name, storage_volumes_snapshots.name),
+         storage_volumes.storage_pool_id,
+         storage_volumes.node_id,
+         storage_volumes.type,
+         storage_volumes_snapshots.description,
+         storage_volumes.project_id,
+         storage_volumes.content_type
+    FROM storage_volumes
+    JOIN storage_volumes_snapshots ON storage_volumes.id = storage_volumes_snapshots.storage_volume_id;
+`
+	_, err := tx.Exec(stmts)
+	if err != nil {
+		return errors.Wrap(err, "Failed to add storage volume content type")
+	}
+
+	return nil
 }
 
 // Add expiry date to storage volume snapshots

From a8039b596c44a27a740d142ebb54ba4ded24849a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 18:45:25 +0200
Subject: [PATCH 02/14] shared/api: Add ContentType to StorageVolume

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/api/storage_pool_volume.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/shared/api/storage_pool_volume.go b/shared/api/storage_pool_volume.go
index db916ff0fa..3aa881d9af 100644
--- a/shared/api/storage_pool_volume.go
+++ b/shared/api/storage_pool_volume.go
@@ -52,6 +52,9 @@ type StorageVolume struct {
 
 	// API extension: clustering
 	Location string `json:"location" yaml:"location"`
+
+	// API extension: custom_block_volumes
+	ContentType string `json:"content_type" yaml:"content_type"`
 }
 
 // StorageVolumePut represents the modifiable fields of a LXD storage volume.

From 8c11799f3e394eeee8cdc808e80538faf2501aee Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 18:45:48 +0200
Subject: [PATCH 03/14] lxd/db: Add content type to storage volumes

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/db/storage_volumes.go | 51 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/lxd/db/storage_volumes.go b/lxd/db/storage_volumes.go
index af000ef4a6..04062554de 100644
--- a/lxd/db/storage_volumes.go
+++ b/lxd/db/storage_volumes.go
@@ -246,11 +246,21 @@ func (c *Cluster) storagePoolVolumeGetType(project string, volumeName string, vo
 		return -1, nil, err
 	}
 
+	volumeContentType, err := c.GetStorageVolumeContentType(volumeID)
+	if err != nil {
+		return -1, nil, err
+	}
+
 	volumeTypeName, err := storagePoolVolumeTypeToName(volumeType)
 	if err != nil {
 		return -1, nil, err
 	}
 
+	volumeContentTypeName, err := storagePoolVolumeContentTypeToName(volumeContentType)
+	if err != nil {
+		return -1, nil, err
+	}
+
 	storageVolume := api.StorageVolume{
 		Type: volumeTypeName,
 	}
@@ -258,6 +268,7 @@ func (c *Cluster) storagePoolVolumeGetType(project string, volumeName string, vo
 	storageVolume.Description = volumeDescription
 	storageVolume.Config = volumeConfig
 	storageVolume.Location = volumeNode
+	storageVolume.ContentType = volumeContentTypeName
 
 	return volumeID, &storageVolume, nil
 }
@@ -504,6 +515,16 @@ const (
 	StoragePoolVolumeTypeNameCustom    string = "custom"
 )
 
+const (
+	StoragePoolVolumeContentTypeFS = iota
+	StoragePoolVolumeContentTypeBlock
+)
+
+const (
+	StoragePoolVolumeContentTypeNameFS    string = "filesystem"
+	StoragePoolVolumeContentTypeNameBlock string = "block"
+)
+
 // StorageVolumeArgs is a value object holding all db-related details about a
 // storage volume.
 type StorageVolumeArgs struct {
@@ -657,6 +678,24 @@ func (c *Cluster) GetStorageVolumeDescription(volumeID int64) (string, error) {
 	return description.String, nil
 }
 
+// GetStorageVolumeContentType gets the content type of a storage volume.
+func (c *Cluster) GetStorageVolumeContentType(volumeID int64) (int, error) {
+	var contentType int
+	query := "SELECT content_type FROM storage_volumes_all WHERE id=?"
+	inargs := []interface{}{volumeID}
+	outargs := []interface{}{&contentType}
+
+	err := dbQueryRowScan(c, query, inargs, outargs)
+	if err != nil {
+		if err == sql.ErrNoRows {
+			return -1, ErrNoSuchObject
+		}
+		return -1, err
+	}
+
+	return contentType, nil
+}
+
 // GetNextStorageVolumeSnapshotIndex returns the index of the next snapshot of the storage
 // volume with the given name should have.
 //
@@ -887,3 +926,15 @@ func storagePoolVolumeTypeToName(volumeType int) (string, error) {
 
 	return "", fmt.Errorf("Invalid storage volume type")
 }
+
+// Convert a volume integer content type code to its human-readable name.
+func storagePoolVolumeContentTypeToName(contentType int) (string, error) {
+	switch contentType {
+	case StoragePoolVolumeContentTypeFS:
+		return StoragePoolVolumeContentTypeNameFS, nil
+	case StoragePoolVolumeContentTypeBlock:
+		return StoragePoolVolumeContentTypeNameBlock, nil
+	}
+
+	return "", fmt.Errorf("Invalid storage volume content type")
+}

From 173c774c7444cc74bc678998c76258f4ce2e9a26 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 18:46:10 +0200
Subject: [PATCH 04/14] lxc/storage_volume: Show content type when listing
 volumes

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxc/storage_volume.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxc/storage_volume.go b/lxc/storage_volume.go
index 3fde175de5..a1cbba5e65 100644
--- a/lxc/storage_volume.go
+++ b/lxc/storage_volume.go
@@ -1103,7 +1103,7 @@ func (c *cmdStorageVolumeList) Run(cmd *cobra.Command, args []string) error {
 	data := [][]string{}
 	for _, volume := range volumes {
 		usedby := strconv.Itoa(len(volume.UsedBy))
-		entry := []string{volume.Type, volume.Name, volume.Description, usedby}
+		entry := []string{volume.Type, volume.Name, volume.Description, volume.ContentType, usedby}
 		if shared.IsSnapshot(volume.Name) {
 			entry[0] = fmt.Sprintf("%s (snapshot)", volume.Type)
 		}
@@ -1118,6 +1118,7 @@ func (c *cmdStorageVolumeList) Run(cmd *cobra.Command, args []string) error {
 		i18n.G("TYPE"),
 		i18n.G("NAME"),
 		i18n.G("DESCRIPTION"),
+		i18n.G("CONTENT TYPE"),
 		i18n.G("USED BY"),
 	}
 	if resource.server.IsClustered() {

From ad1ab17fa6caa1b68ddc7e11f79723258aafb5aa Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 18:57:10 +0200
Subject: [PATCH 05/14] shared/version/api: Add API extension
 custom_block_volumes

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/version/api.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared/version/api.go b/shared/version/api.go
index 532db6ab0c..d65bf57992 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -213,6 +213,7 @@ var APIExtensions = []string{
 	"network_dns_search",
 	"container_nic_routed_limits",
 	"instance_nic_bridged_vlan",
+	"custom_block_volumes",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 86b724c139007628f31396918ef9b621b3e3a4d4 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 19:09:16 +0200
Subject: [PATCH 06/14] shared/api: Add ContentType to StorageVolumesPost

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/api/storage_pool_volume.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/shared/api/storage_pool_volume.go b/shared/api/storage_pool_volume.go
index 3aa881d9af..85950c8461 100644
--- a/shared/api/storage_pool_volume.go
+++ b/shared/api/storage_pool_volume.go
@@ -11,6 +11,9 @@ type StorageVolumesPost struct {
 
 	// API extension: storage_api_local_volume_handling
 	Source StorageVolumeSource `json:"source" yaml:"source"`
+
+	// API extension: custom_block_volumes
+	ContentType string `json:"content_type" yaml:"content_type"`
 }
 
 // StorageVolumePost represents the fields required to rename a LXD storage pool volume

From 2321a93adb2c1846829bda481e56272bf0e28c1c Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 May 2020 19:10:02 +0200
Subject: [PATCH 07/14] lxc/storage_volume: Add -type flag to create

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxc/storage_volume.go | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/lxc/storage_volume.go b/lxc/storage_volume.go
index a1cbba5e65..a3c6c7d9db 100644
--- a/lxc/storage_volume.go
+++ b/lxc/storage_volume.go
@@ -451,9 +451,10 @@ func (c *cmdStorageVolumeCopy) Run(cmd *cobra.Command, args []string) error {
 
 // Create
 type cmdStorageVolumeCreate struct {
-	global        *cmdGlobal
-	storage       *cmdStorage
-	storageVolume *cmdStorageVolume
+	global          *cmdGlobal
+	storage         *cmdStorage
+	storageVolume   *cmdStorageVolume
+	flagContentType string
 }
 
 func (c *cmdStorageVolumeCreate) Command() *cobra.Command {
@@ -464,6 +465,7 @@ func (c *cmdStorageVolumeCreate) Command() *cobra.Command {
 		`Create new custom storage volumes`))
 
 	cmd.Flags().StringVar(&c.storage.flagTarget, "target", "", i18n.G("Cluster member name")+"``")
+	cmd.Flags().StringVar(&c.flagContentType, "type", "filesystem", i18n.G("Content type, block or filesystem")+"``")
 	cmd.RunE = c.Run
 
 	return cmd
@@ -497,6 +499,7 @@ func (c *cmdStorageVolumeCreate) Run(cmd *cobra.Command, args []string) error {
 	vol := api.StorageVolumesPost{}
 	vol.Name = volName
 	vol.Type = volType
+	vol.ContentType = c.flagContentType
 	vol.Config = map[string]string{}
 
 	for i := 2; i < len(args); i++ {

From 7ccf57d235417fb10e01525a4f7ad641782c860f Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Sat, 30 May 2020 20:00:19 +0200
Subject: [PATCH 08/14] lxd/storage: Pass contentType to CreateCustomVolume

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage/backend_lxd.go    |  4 ++--
 lxd/storage/backend_mock.go   |  2 +-
 lxd/storage/pool_interface.go |  2 +-
 lxd/storage_volumes.go        | 15 ++++++++++++++-
 4 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 2a2cb7873d..f0cd88d2ea 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -2195,8 +2195,8 @@ func (b *lxdBackend) UpdateImage(fingerprint, newDesc string, newConfig map[stri
 }
 
 // CreateCustomVolume creates an empty custom volume.
-func (b *lxdBackend) CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, op *operations.Operation) error {
-	logger := logging.AddContext(b.logger, log.Ctx{"project": projectName, "volName": volName, "desc": desc, "config": config})
+func (b *lxdBackend) CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, contentType drivers.ContentType, op *operations.Operation) error {
+	logger := logging.AddContext(b.logger, log.Ctx{"project": projectName, "volName": volName, "desc": desc, "config": config, "contentType": contentType})
 	logger.Debug("CreateCustomVolume started")
 	defer logger.Debug("CreateCustomVolume finished")
 
diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go
index b5dc13083d..8942aed692 100644
--- a/lxd/storage/backend_mock.go
+++ b/lxd/storage/backend_mock.go
@@ -179,7 +179,7 @@ func (b *mockBackend) UpdateImage(fingerprint, newDesc string, newConfig map[str
 	return nil
 }
 
-func (b *mockBackend) CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, op *operations.Operation) error {
+func (b *mockBackend) CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, contentType drivers.ContentType, op *operations.Operation) error {
 	return nil
 }
 
diff --git a/lxd/storage/pool_interface.go b/lxd/storage/pool_interface.go
index bca6c57fd2..a1048621b7 100644
--- a/lxd/storage/pool_interface.go
+++ b/lxd/storage/pool_interface.go
@@ -67,7 +67,7 @@ type Pool interface {
 	UpdateImage(fingerprint string, newDesc string, newConfig map[string]string, op *operations.Operation) error
 
 	// Custom volumes.
-	CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, op *operations.Operation) error
+	CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, contentType drivers.ContentType, op *operations.Operation) error
 	CreateCustomVolumeFromCopy(projectName string, volName, desc string, config map[string]string, srcPoolName, srcVolName string, srcVolOnly bool, op *operations.Operation) error
 	UpdateCustomVolume(projectName string, volName string, newDesc string, newConfig map[string]string, op *operations.Operation) error
 	RenameCustomVolume(projectName string, volName string, newVolName string, op *operations.Operation) error
diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index f4c194fe2c..8a47a80a4f 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -18,6 +18,7 @@ import (
 	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/state"
 	storagePools "github.com/lxc/lxd/lxd/storage"
+	"github.com/lxc/lxd/lxd/storage/drivers"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -284,6 +285,10 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) response.Response {
 		return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
 	}
 
+	if !shared.StringInSlice(req.ContentType, []string{"block", "filesystem"}) {
+		return response.BadRequest(fmt.Errorf("ContentType needs to be \"block\" or \"filesystem\""))
+	}
+
 	req.Type = mux.Vars(r)["type"]
 
 	// We currently only allow to create storage volumes of type storagePoolVolumeTypeCustom.
@@ -333,9 +338,17 @@ func doVolumeCreateOrCopy(d *Daemon, projectName, poolName string, req *api.Stor
 		return response.SmartError(err)
 	}
 
+	var contentType drivers.ContentType
+
+	if req.ContentType == "filesystem" {
+		contentType = drivers.ContentTypeFS
+	} else if req.ContentType == "block" {
+		contentType = drivers.ContentTypeBlock
+	}
+
 	run = func(op *operations.Operation) error {
 		if req.Source.Name == "" {
-			return pool.CreateCustomVolume(projectName, req.Name, req.Description, req.Config, op)
+			return pool.CreateCustomVolume(projectName, req.Name, req.Description, req.Config, contentType, op)
 		}
 
 		return pool.CreateCustomVolumeFromCopy(projectName, req.Name, req.Description, req.Config, req.Source.Pool, req.Source.Name, req.Source.VolumeOnly, op)

From 148a71943b1a73f01fd5ad27ee1feb8fcf059a5d Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Sat, 30 May 2020 21:42:48 +0200
Subject: [PATCH 09/14] lxd/migration: Add content type to VolumeTargetArgs

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/migration/migration_volumes.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/migration/migration_volumes.go b/lxd/migration/migration_volumes.go
index 261f7eaab0..57db9dbd3b 100644
--- a/lxd/migration/migration_volumes.go
+++ b/lxd/migration/migration_volumes.go
@@ -39,6 +39,7 @@ type VolumeTargetArgs struct {
 	Refresh       bool
 	Live          bool
 	VolumeSize    int64
+	ContentType   string
 }
 
 // TypesToHeader converts one or more Types to a MigrationHeader. It uses the first type argument

From 6a2aa2a2177d0a9b8f8a30165b3fc53902f7f98b Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Sat, 30 May 2020 20:06:54 +0200
Subject: [PATCH 10/14] lxd/storage: Pass contentType to VolumeDBCreate

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage/backend_lxd.go | 24 +++++++++++++++++-------
 lxd/storage/utils.go       |  2 +-
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index f0cd88d2ea..e357f67bc5 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -2040,8 +2040,11 @@ func (b *lxdBackend) EnsureImage(fingerprint string, op *operations.Operation) e
 	// Derive content type from image type. Image types are not the same as instance types, so don't use
 	// instance type constants for comparison.
 	contentType := drivers.ContentTypeFS
+	dbContentType := db.StoragePoolVolumeContentTypeNameFS
+
 	if image.Type == "virtual-machine" {
 		contentType = drivers.ContentTypeBlock
+		dbContentType = db.StoragePoolVolumeContentTypeNameBlock
 	}
 
 	// Try and load any existing volume config on this storage pool so we can compare filesystems if needed.
@@ -2097,7 +2100,7 @@ func (b *lxdBackend) EnsureImage(fingerprint string, op *operations.Operation) e
 		}
 	}
 
-	err = VolumeDBCreate(b.state, project.Default, b.name, fingerprint, "", db.StoragePoolVolumeTypeNameImage, false, volConfig, time.Time{})
+	err = VolumeDBCreate(b.state, project.Default, b.name, fingerprint, "", db.StoragePoolVolumeTypeNameImage, false, volConfig, time.Time{}, dbContentType)
 	if err != nil {
 		return err
 	}
@@ -2211,7 +2214,7 @@ func (b *lxdBackend) CreateCustomVolume(projectName string, volName string, desc
 	}
 
 	// Create database entry for new storage volume.
-	err = VolumeDBCreate(b.state, projectName, b.name, volName, desc, db.StoragePoolVolumeTypeNameCustom, false, vol.Config(), time.Time{})
+	err = VolumeDBCreate(b.state, projectName, b.name, volName, desc, db.StoragePoolVolumeTypeNameCustom, false, vol.Config(), time.Time{}, string(contentType))
 	if err != nil {
 		return err
 	}
@@ -2280,6 +2283,13 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 		desc = srcVolRow.Description
 	}
 
+	// Get the source volume's content type.
+	contentType := drivers.ContentTypeFS
+
+	if srcVolRow.ContentType == "block" {
+		contentType = drivers.ContentTypeBlock
+	}
+
 	// If we are copying snapshots, retrieve a list of snapshots from source volume.
 	snapshotNames := []string{}
 	if !srcVolOnly {
@@ -2323,7 +2333,7 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 		}
 
 		// Create database entry for new storage volume.
-		err = VolumeDBCreate(b.state, projectName, b.name, volName, desc, db.StoragePoolVolumeTypeNameCustom, false, vol.Config(), time.Time{})
+		err = VolumeDBCreate(b.state, projectName, b.name, volName, desc, db.StoragePoolVolumeTypeNameCustom, false, vol.Config(), time.Time{}, string(contentType))
 		if err != nil {
 			return err
 		}
@@ -2335,7 +2345,7 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 				newSnapshotName := drivers.GetSnapshotVolumeName(volName, snapName)
 
 				// Create database entry for new storage volume snapshot.
-				err = VolumeDBCreate(b.state, projectName, b.name, newSnapshotName, desc, db.StoragePoolVolumeTypeNameCustom, true, vol.Config(), time.Time{})
+				err = VolumeDBCreate(b.state, projectName, b.name, newSnapshotName, desc, db.StoragePoolVolumeTypeNameCustom, true, vol.Config(), time.Time{}, string(contentType))
 				if err != nil {
 					return err
 				}
@@ -2471,7 +2481,7 @@ func (b *lxdBackend) CreateCustomVolumeFromMigration(projectName string, conn io
 	}
 
 	// Create database entry for new storage volume.
-	err = VolumeDBCreate(b.state, projectName, b.name, args.Name, args.Description, db.StoragePoolVolumeTypeNameCustom, false, vol.Config(), time.Time{})
+	err = VolumeDBCreate(b.state, projectName, b.name, args.Name, args.Description, db.StoragePoolVolumeTypeNameCustom, false, vol.Config(), time.Time{}, args.ContentType)
 	if err != nil {
 		return err
 	}
@@ -2483,7 +2493,7 @@ func (b *lxdBackend) CreateCustomVolumeFromMigration(projectName string, conn io
 			newSnapshotName := drivers.GetSnapshotVolumeName(args.Name, snapName)
 
 			// Create database entry for new storage volume snapshot.
-			err = VolumeDBCreate(b.state, projectName, b.name, newSnapshotName, args.Description, db.StoragePoolVolumeTypeNameCustom, true, vol.Config(), time.Time{})
+			err = VolumeDBCreate(b.state, projectName, b.name, newSnapshotName, args.Description, db.StoragePoolVolumeTypeNameCustom, true, vol.Config(), time.Time{}, args.ContentType)
 			if err != nil {
 				return err
 			}
@@ -2850,7 +2860,7 @@ func (b *lxdBackend) CreateCustomVolumeSnapshot(projectName, volName string, new
 	}
 
 	// Create database entry for new storage volume snapshot.
-	err = VolumeDBCreate(b.state, projectName, b.name, fullSnapshotName, parentVol.Description, db.StoragePoolVolumeTypeNameCustom, true, parentVol.Config, newExpiryDate)
+	err = VolumeDBCreate(b.state, projectName, b.name, fullSnapshotName, parentVol.Description, db.StoragePoolVolumeTypeNameCustom, true, parentVol.Config, newExpiryDate, parentVol.ContentType)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index 66b1aed6ec..9501aed2a4 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -137,7 +137,7 @@ func InstanceTypeToVolumeType(instType instancetype.Type) (drivers.VolumeType, e
 }
 
 // VolumeDBCreate creates a volume in the database.
-func VolumeDBCreate(s *state.State, project, poolName, volumeName, volumeDescription, volumeTypeName string, snapshot bool, volumeConfig map[string]string, expiryDate time.Time) error {
+func VolumeDBCreate(s *state.State, project, poolName, volumeName, volumeDescription, volumeTypeName string, snapshot bool, volumeConfig map[string]string, expiryDate time.Time, contentTypeName string) error {
 	// Convert the volume type name to our internal integer representation.
 	volDBType, err := VolumeTypeNameToType(volumeTypeName)
 	if err != nil {

From 498732b75225682aea3eaf30a3a7a4f364384694 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 2 Jun 2020 10:18:00 +0200
Subject: [PATCH 11/14] lxd/db: Add contentType arg to CreateStoragePoolVolume

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/db/storage_volumes.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/db/storage_volumes.go b/lxd/db/storage_volumes.go
index 04062554de..c9669f1823 100644
--- a/lxd/db/storage_volumes.go
+++ b/lxd/db/storage_volumes.go
@@ -396,7 +396,7 @@ func storagePoolVolumeReplicateIfCeph(tx *sql.Tx, volumeID int64, project, volum
 
 // CreateStoragePoolVolume creates a new storage volume attached to a given
 // storage pool.
-func (c *Cluster) CreateStoragePoolVolume(project, volumeName, volumeDescription string, volumeType int, poolID int64, volumeConfig map[string]string) (int64, error) {
+func (c *Cluster) CreateStoragePoolVolume(project, volumeName, volumeDescription string, volumeType int, poolID int64, volumeConfig map[string]string, contentType int) (int64, error) {
 	var thisVolumeID int64
 
 	if shared.IsSnapshot(volumeName) {
@@ -421,10 +421,10 @@ func (c *Cluster) CreateStoragePoolVolume(project, volumeName, volumeDescription
 			var volumeID int64
 
 			result, err := tx.tx.Exec(`
-INSERT INTO storage_volumes (storage_pool_id, node_id, type, name, description, project_id)
- VALUES (?, ?, ?, ?, ?, (SELECT id FROM projects WHERE name = ?))
+INSERT INTO storage_volumes (storage_pool_id, node_id, type, name, description, project_id, content_type)
+ VALUES (?, ?, ?, ?, ?, (SELECT id FROM projects WHERE name = ?), ?)
 `,
-				poolID, nodeID, volumeType, volumeName, volumeDescription, project)
+				poolID, nodeID, volumeType, volumeName, volumeDescription, project, contentType)
 			if err != nil {
 				return err
 			}

From dafbf9c11a0fcffe2cd2a406427e24d19c161091 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 2 Jun 2020 10:18:29 +0200
Subject: [PATCH 12/14] *: Pass content type to CreateStoragePoolVolume

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/instance/drivers/driver_lxc.go  |  2 +-
 lxd/instance/drivers/driver_qemu.go |  2 +-
 lxd/patches.go                      | 16 ++++++++--------
 lxd/storage/utils.go                | 18 +++++++++++++++++-
 4 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index bbf0394b0c..c6bcc946bf 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -237,7 +237,7 @@ func lxcCreate(s *state.State, args db.InstanceArgs) (instance.Instance, error)
 	if c.IsSnapshot() {
 		_, err = s.Cluster.CreateStorageVolumeSnapshot(args.Project, args.Name, "", db.StoragePoolVolumeTypeContainer, poolID, volumeConfig, time.Time{})
 	} else {
-		_, err = s.Cluster.CreateStoragePoolVolume(args.Project, args.Name, "", db.StoragePoolVolumeTypeContainer, poolID, volumeConfig)
+		_, err = s.Cluster.CreateStoragePoolVolume(args.Project, args.Name, "", db.StoragePoolVolumeTypeContainer, poolID, volumeConfig, db.StoragePoolVolumeContentTypeFS)
 	}
 	if err != nil {
 		c.Delete()
diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index f798c7239c..d9270d04fa 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -250,7 +250,7 @@ func qemuCreate(s *state.State, args db.InstanceArgs) (instance.Instance, error)
 		_, err = s.Cluster.CreateStorageVolumeSnapshot(args.Project, args.Name, "", db.StoragePoolVolumeTypeVM, poolID, volumeConfig, time.Time{})
 
 	} else {
-		_, err = s.Cluster.CreateStoragePoolVolume(args.Project, args.Name, "", db.StoragePoolVolumeTypeVM, poolID, volumeConfig)
+		_, err = s.Cluster.CreateStoragePoolVolume(args.Project, args.Name, "", db.StoragePoolVolumeTypeVM, poolID, volumeConfig, db.StoragePoolVolumeContentTypeBlock)
 	}
 	if err != nil {
 		return nil, err
diff --git a/lxd/patches.go b/lxd/patches.go
index 6b2293cd2f..ecb445c3ba 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -570,7 +570,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.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -739,7 +739,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.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
@@ -860,7 +860,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.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -1007,7 +1007,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.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
@@ -1169,7 +1169,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.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -1513,7 +1513,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.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
@@ -1705,7 +1705,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.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", ct, "", db.StoragePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for container \"%s\"", ct)
 				return err
@@ -1847,7 +1847,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.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+			_, err := d.cluster.CreateStoragePoolVolume("default", img, "", db.StoragePoolVolumeTypeImage, poolID, imagePoolVolumeConfig, db.StoragePoolVolumeContentTypeFS)
 			if err != nil {
 				logger.Errorf("Could not insert a storage volume for image \"%s\"", img)
 				return err
diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index 9501aed2a4..d4eecd78e0 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -136,6 +136,17 @@ func InstanceTypeToVolumeType(instType instancetype.Type) (drivers.VolumeType, e
 	return "", fmt.Errorf("Invalid instance type")
 }
 
+func VolumeContentTypeNameToContentType(contentTypeName string) (int, error) {
+	switch contentTypeName {
+	case db.StoragePoolVolumeContentTypeNameFS:
+		return db.StoragePoolVolumeContentTypeFS, nil
+	case db.StoragePoolVolumeContentTypeNameBlock:
+		return db.StoragePoolVolumeContentTypeBlock, nil
+	}
+
+	return -1, fmt.Errorf("Invalid storage volume content type name")
+}
+
 // VolumeDBCreate creates a volume in the database.
 func VolumeDBCreate(s *state.State, project, poolName, volumeName, volumeDescription, volumeTypeName string, snapshot bool, volumeConfig map[string]string, expiryDate time.Time, contentTypeName string) error {
 	// Convert the volume type name to our internal integer representation.
@@ -144,6 +155,11 @@ func VolumeDBCreate(s *state.State, project, poolName, volumeName, volumeDescrip
 		return err
 	}
 
+	volDBContentType, err := VolumeContentTypeNameToContentType(contentTypeName)
+	if err != nil {
+		return err
+	}
+
 	// Load storage pool the volume will be attached to.
 	poolID, poolStruct, err := s.Cluster.GetStoragePool(poolName)
 	if err != nil {
@@ -181,7 +197,7 @@ func VolumeDBCreate(s *state.State, project, poolName, volumeName, volumeDescrip
 	if snapshot {
 		_, err = s.Cluster.CreateStorageVolumeSnapshot(project, volumeName, volumeDescription, volDBType, poolID, volumeConfig, expiryDate)
 	} else {
-		_, err = s.Cluster.CreateStoragePoolVolume(project, volumeName, volumeDescription, volDBType, poolID, volumeConfig)
+		_, err = s.Cluster.CreateStoragePoolVolume(project, volumeName, volumeDescription, volDBType, poolID, volumeConfig, volDBContentType)
 	}
 	if err != nil {
 		return fmt.Errorf("Error inserting %q of type %q into database %q", poolName, volumeTypeName, err)

From 36e03b6a483519a97d098b9a5f177e6d6199704a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 2 Jun 2020 13:17:46 +0200
Subject: [PATCH 13/14] lxd/db: Add ContentType to StorageVolumeArgs

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/db/storage_volumes.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/db/storage_volumes.go b/lxd/db/storage_volumes.go
index c9669f1823..ada9134542 100644
--- a/lxd/db/storage_volumes.go
+++ b/lxd/db/storage_volumes.go
@@ -549,6 +549,8 @@ type StorageVolumeArgs struct {
 	// At least on of ProjectID or ProjectName must be set.
 	ProjectID   int64
 	ProjectName string
+
+	ContentType string
 }
 
 // GetStorageVolumeNodeAddresses returns the addresses of all nodes on which the

From f41c516a4a1f8b90dfcb7f18fe7fe8cbaaf1f000 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 2 Jun 2020 13:22:30 +0200
Subject: [PATCH 14/14] lxd/*: Pass correct content type

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/migrate_storage_volumes.go |  1 +
 lxd/storage/backend_lxd.go     | 14 +++++++-------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/lxd/migrate_storage_volumes.go b/lxd/migrate_storage_volumes.go
index e8e5be36f8..b6886d503c 100644
--- a/lxd/migrate_storage_volumes.go
+++ b/lxd/migrate_storage_volumes.go
@@ -274,6 +274,7 @@ func (c *migrationSink) DoStorage(state *state.State, projectName string, poolNa
 			Description:   req.Description,
 			MigrationType: respTypes[0],
 			TrackProgress: true,
+			ContentType:   string(contentType),
 		}
 
 		// A zero length Snapshots slice indicates volume only migration in
diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index e357f67bc5..6027598f34 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -2207,7 +2207,7 @@ func (b *lxdBackend) CreateCustomVolume(projectName string, volName string, desc
 	volStorageName := project.StorageVolume(projectName, volName)
 
 	// Validate config.
-	vol := b.newVolume(drivers.VolumeTypeCustom, drivers.ContentTypeFS, volStorageName, config)
+	vol := b.newVolume(drivers.VolumeTypeCustom, contentType, volStorageName, config)
 	err := b.driver.ValidateVolume(vol, false)
 	if err != nil {
 		return err
@@ -2320,11 +2320,11 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 
 		// Get the volume name on storage.
 		volStorageName := project.StorageVolume(projectName, volName)
-		vol := b.newVolume(drivers.VolumeTypeCustom, drivers.ContentTypeFS, volStorageName, config)
+		vol := b.newVolume(drivers.VolumeTypeCustom, contentType, volStorageName, config)
 
 		// Get the src volume name on storage.
 		srcVolStorageName := project.StorageVolume(projectName, srcVolName)
-		srcVol := b.newVolume(drivers.VolumeTypeCustom, drivers.ContentTypeFS, srcVolStorageName, srcVolRow.Config)
+		srcVol := b.newVolume(drivers.VolumeTypeCustom, contentType, srcVolStorageName, srcVolRow.Config)
 
 		// Check the supplied config and remove any fields not relevant for pool type.
 		err := b.driver.ValidateVolume(vol, true)
@@ -2368,9 +2368,9 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 	logger.Debug("CreateCustomVolumeFromCopy cross-pool mode detected")
 
 	// Negotiate the migration type to use.
-	offeredTypes := srcPool.MigrationTypes(drivers.ContentTypeFS, false)
+	offeredTypes := srcPool.MigrationTypes(contentType, false)
 	offerHeader := migration.TypesToHeader(offeredTypes...)
-	migrationTypes, err := migration.MatchTypes(offerHeader, FallbackMigrationType(drivers.ContentTypeFS), b.MigrationTypes(drivers.ContentTypeFS, false))
+	migrationTypes, err := migration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, false))
 	if err != nil {
 		return fmt.Errorf("Failed to negotiate copy migration type: %v", err)
 	}
@@ -2405,7 +2405,7 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 			Snapshots:     snapshotNames,
 			MigrationType: migrationTypes[0],
 			TrackProgress: false, // Do not use a progress tracker on receiver.
-
+			ContentType:   string(contentType),
 		}, op)
 
 		if err != nil {
@@ -2474,7 +2474,7 @@ func (b *lxdBackend) CreateCustomVolumeFromMigration(projectName string, conn io
 	volStorageName := project.StorageVolume(projectName, args.Name)
 
 	// Check the supplied config and remove any fields not relevant for destination pool type.
-	vol := b.newVolume(drivers.VolumeTypeCustom, drivers.ContentTypeFS, volStorageName, args.Config)
+	vol := b.newVolume(drivers.VolumeTypeCustom, drivers.ContentType(args.ContentType), volStorageName, args.Config)
 	err := b.driver.ValidateVolume(vol, true)
 	if err != nil {
 		return err


More information about the lxc-devel mailing list