[lxc-devel] [lxd/master] Handle storage volumes with remote storage

monstermunchkin on Github lxc-bot at linuxcontainers.org
Fri Jul 31 17:11:23 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/20200731/227e2e87/attachment.bin>
-------------- next part --------------
From 2bb4536597033b57628b3138b35a8155466dfd40 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 30 Jul 2020 07:58:04 +0200
Subject: [PATCH 1/2] lxd/db/cluster: Update tables to allow null value for
 node ID

This changes the affected tables to allow the null value for the node
ID.

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

diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index 701847d41a..fc414fa26d 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -84,7 +84,7 @@ CREATE TABLE images_source (
 );
 CREATE TABLE "instances" (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
-    node_id INTEGER NOT NULL,
+    node_id INTEGER,
     name TEXT NOT NULL,
     architecture INTEGER NOT NULL,
     type INTEGER NOT NULL,
@@ -491,7 +491,7 @@ CREATE TABLE "storage_volumes" (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name TEXT NOT NULL,
     storage_pool_id INTEGER NOT NULL,
-    node_id INTEGER NOT NULL,
+    node_id INTEGER,
     type INTEGER NOT NULL,
     description TEXT,
     project_id INTEGER NOT NULL,
@@ -572,5 +572,5 @@ CREATE TABLE storage_volumes_snapshots_config (
     UNIQUE (storage_volume_snapshot_id, key)
 );
 
-INSERT INTO schema (version, updated_at) VALUES (33, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (34, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index c6fd84cbfb..e9d459ca7c 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -70,6 +70,245 @@ var updates = map[int]schema.Update{
 	31: updateFromV30,
 	32: updateFromV31,
 	33: updateFromV32,
+	34: updateFromV33,
+}
+
+// Remove multiple entries of the same volume when using remote storage.
+// Also, allow node ID to be null for the instances and storage_volumes tables, and set it to null
+// for instances and storage volumes using remote storage.
+func updateFromV33(tx *sql.Tx) error {
+	stmts := `
+SELECT storage_volumes.id, storage_volumes.name
+FROM storage_volumes
+JOIN storage_pools ON storage_pools.id=storage_volumes.storage_pool_id
+WHERE storage_pools.driver IN ("ceph", "cephfs")
+ORDER BY storage_volumes.name
+`
+
+	// Get the total number of storage volume rows.
+	count, err := query.Count(tx, "storage_volumes", "")
+	if err != nil {
+		return errors.Wrap(err, "Failed to get storage volumes count")
+	}
+
+	volumes := make([]struct {
+		ID   int
+		Name string
+	}, count)
+	dest := func(i int) []interface{} {
+		return []interface{}{
+			&volumes[i].ID,
+			&volumes[i].Name,
+		}
+	}
+
+	stmt, err := tx.Prepare(stmts)
+	if err != nil {
+		return errors.Wrap(err, "Failed to prepary storage volume query")
+	}
+
+	err = query.SelectObjects(stmt, dest)
+	if err != nil {
+		return errors.Wrap(err, "Failed to fetch storage volumes")
+	}
+
+	// Remove multiple entries of the same volume when using remote storage
+	for i := 1; i < count; i++ {
+		if volumes[i-1].Name == volumes[i].Name {
+			_, err = tx.Exec(`DELETE FROM storage_volumes WHERE id=?`, volumes[i-1].ID)
+			if err != nil {
+				return errors.Wrap(err, "Failed to delete row from storage_volumes")
+			}
+		}
+	}
+
+	stmts = `
+CREATE TABLE instances_new (
+    id INTEGER primary key AUTOINCREMENT NOT NULL,
+    node_id INTEGER,
+    name TEXT NOT NULL,
+    architecture INTEGER NOT NULL,
+    type INTEGER NOT NULL,
+    ephemeral INTEGER NOT NULL DEFAULT 0,
+    creation_date DATETIME NOT NULL DEFAULT 0,
+    stateful INTEGER NOT NULL DEFAULT 0,
+    last_use_date DATETIME,
+    description TEXT,
+    project_id INTEGER NOT NULL,
+    expiry_date DATETIME,
+    UNIQUE (project_id, name),
+    FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE,
+    FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
+);
+
+CREATE TABLE storage_volumes_new (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name TEXT NOT NULL,
+    storage_pool_id INTEGER NOT NULL,
+    node_id INTEGER,
+    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,
+    FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
+);`
+
+	// Create new tables where node ID can be null.
+	_, err = tx.Exec(stmts)
+	if err != nil {
+		return err
+	}
+
+	// Get the total number of instance rows in the instances table.
+	count, err = query.Count(tx, "instances", "")
+	if err != nil {
+		return errors.Wrap(err, "Failed to get instances count")
+	}
+
+	// Fetch all instances rows in the instances table.
+	instances := make([]struct {
+		ID           int
+		NodeID       int
+		Name         string
+		Architecture int
+		Type         int
+		Ephemeral    int
+		CreationDate time.Time
+		Stateful     int
+		LastUseDate  time.Time
+		Description  string
+		ProjectID    int
+		ExpiryDate   time.Time
+	}, count)
+	dest = func(i int) []interface{} {
+		return []interface{}{
+			&instances[i].ID,
+			&instances[i].NodeID,
+			&instances[i].Name,
+			&instances[i].Architecture,
+			&instances[i].Type,
+			&instances[i].Ephemeral,
+			&instances[i].CreationDate,
+			&instances[i].Stateful,
+			&instances[i].LastUseDate,
+			&instances[i].Description,
+			&instances[i].ProjectID,
+			&instances[i].ExpiryDate,
+		}
+	}
+
+	stmt, err = tx.Prepare(`SELECT * FROM instances;`)
+	if err != nil {
+		return errors.Wrap(err, "Failed to prepare instance query")
+	}
+
+	err = query.SelectObjects(stmt, dest)
+	if err != nil {
+		return errors.Wrap(err, "Failed to fetch instances")
+	}
+
+	for _, instance := range instances {
+		_, err = tx.Exec(`INSERT INTO instances_new (id, node_id, name, architecture, type, ephemeral, creation_date, stateful, last_use_date, description, project_id, expiry_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, instance.ID, instance.NodeID, instance.Name, instance.Architecture, instance.Type, instance.Ephemeral, instance.CreationDate, instance.Stateful, instance.LastUseDate, instance.Description, instance.ProjectID, instance.ExpiryDate)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Copy rows from storage_volumes to storage_volumes_new
+	count, err = query.Count(tx, "storage_volumes", "")
+	if err != nil {
+		return errors.Wrap(err, "Failed to get storage_volumes count")
+	}
+
+	storageVolumes := make([]struct {
+		ID            int
+		Name          string
+		StoragePoolID int
+		NodeID        string
+		Type          int
+		Description   string
+		ProjectID     int
+		ContentType   int
+	}, count)
+
+	dest = func(i int) []interface{} {
+		return []interface{}{
+			&storageVolumes[i].ID,
+			&storageVolumes[i].Name,
+			&storageVolumes[i].StoragePoolID,
+			&storageVolumes[i].NodeID,
+			&storageVolumes[i].Type,
+			&storageVolumes[i].Description,
+			&storageVolumes[i].ProjectID,
+			&storageVolumes[i].ContentType,
+		}
+	}
+
+	stmt, err = tx.Prepare(`SELECT * FROM storage_volumes;`)
+	if err != nil {
+		return errors.Wrap(err, "Failed to prepare storage volumes query")
+	}
+
+	err = query.SelectObjects(stmt, dest)
+	if err != nil {
+		return errors.Wrap(err, "Failed to fetch storage volumes")
+	}
+
+	for _, storageVolume := range storageVolumes {
+		_, err = tx.Exec(`INSERT INTO storage_volumes_new (id, name, storage_pool_id, node_id, type, description, project_id, content_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?);`, storageVolume.ID, storageVolume.Name, storageVolume.StoragePoolID, storageVolume.NodeID, storageVolume.Type, storageVolume.Description, storageVolume.ProjectID, storageVolume.ContentType)
+		if err != nil {
+			return err
+		}
+	}
+
+	_, err = tx.Exec(`
+PRAGMA foreign_keys = OFF;
+PRAGMA legacy_alter_table = ON;
+
+DROP TABLE instances;
+ALTER TABLE instances_new RENAME TO instances;
+DROP TABLE storage_volumes;
+ALTER TABLE storage_volumes_new RENAME TO storage_volumes;
+
+UPDATE instances
+SET node_id=null
+WHERE id IN (
+  SELECT instances.id from instances
+  JOIN storage_volumes ON instances.name=storage_volumes.name AND instances.type=storage_volumes.type AND instances.project_id=storage_volumes.project_id
+  JOIN storage_pools ON storage_volumes.storage_pool_id=storage_pools.id
+  WHERE storage_pools.driver IN ("ceph", "cephfs")
+);
+
+UPDATE storage_volumes
+SET node_id=null
+WHERE storage_volumes.node_id IN (
+  SELECT node_id FROM storage_volumes
+  JOIN storage_pools ON storage_volumes.storage_pool_id=storage_pools.id
+  WHERE storage_pools.driver IN ("ceph", "cephfs")
+);
+
+PRAGMA foreign_keys = ON;
+PRAGMA legacy_alter_table = OFF;
+
+CREATE TRIGGER storage_volumes_check_id
+  BEFORE INSERT ON storage_volumes
+  WHEN NEW.id IN (SELECT id FROM storage_volumes_snapshots)
+  BEGIN
+    SELECT RAISE(FAIL, "invalid ID");
+  END;
+CREATE INDEX instances_node_id_idx ON instances (node_id);
+CREATE INDEX instances_project_id_and_name_idx ON instances (project_id, name);
+CREATE INDEX instances_project_id_and_node_id_and_name_idx ON instances (project_id, node_id, name);
+CREATE INDEX instances_project_id_and_node_id_idx ON instances (project_id, node_id);
+CREATE INDEX instances_project_id_idx ON instances (project_id);`)
+	if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 // Add type field to networks.

From 7d528803d07a15ebccecda42f0bf569132bc9605 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 31 Jul 2020 12:09:39 +0200
Subject: [PATCH 2/2] lxd/db: Fix volume listing

Don't fail when a volume cannot be found on a certain node, but check
the other nodes as well.

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

diff --git a/lxd/db/storage_volumes.go b/lxd/db/storage_volumes.go
index 85a045c249..145f0d7202 100644
--- a/lxd/db/storage_volumes.go
+++ b/lxd/db/storage_volumes.go
@@ -106,7 +106,7 @@ SELECT DISTINCT node_id
 	for _, nodeID := range nodeIDs {
 		nodeVolumes, err := c.storagePoolVolumesGet(project, poolID, int64(nodeID), volumeTypes)
 		if err != nil {
-			return nil, err
+			continue
 		}
 		volumes = append(volumes, nodeVolumes...)
 	}


More information about the lxc-devel mailing list