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

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

=== Description (from pull-request) ===

From adb80a78d309599789a95f8eeb1e1fc1fd9dd94f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 24 Sep 2019 18:34:15 -0400
Subject: [PATCH 1/8] api: Add clustering_roles extension
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 doc/api-extensions.md | 4 ++++
 shared/version/api.go | 1 +
 2 files changed, 5 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 5708222afe..b67824ee19 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -838,3 +838,7 @@ Extends the disk resource API struct to include:
  - Block size
  - Firmware version
  - Serial number
+
+## clustering\_roles
+This adds a new `roles` attribute to cluster entries, exposing a list of
+roles that the member serves in the cluster.
diff --git a/shared/version/api.go b/shared/version/api.go
index d6d182821d..ac9350f740 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -167,6 +167,7 @@ var APIExtensions = []string{
        "instances",
        "image_types",
        "resources_disk_sata",
+       "clustering_roles",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From d060931c1a245d75b47fbe3e125e733b21387722 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 24 Sep 2019 18:34:31 -0400
Subject: [PATCH 2/8] shared/api: Add clustering roles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 shared/api/cluster.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/shared/api/cluster.go b/shared/api/cluster.go
index 9f707ed7d8..c773929c8d 100644
--- a/shared/api/cluster.go
+++ b/shared/api/cluster.go
@@ -57,4 +57,7 @@ type ClusterMember struct {
        Database   bool   `json:"database" yaml:"database"`
        Status     string `json:"status" yaml:"status"`
        Message    string `json:"message" yaml:"message"`
+
+       // API extension: clustering_roles
+       Roles []string `json:"roles" yaml:"roles"`
 }

From 96e9d1c2ee868415b6150fb71a5a0edcfc8c6d9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 24 Sep 2019 15:18:55 -0400
Subject: [PATCH 3/8] lxd/db: Add nodes_roles table
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/db/cluster/schema.go |  8 +++++++-
 lxd/db/cluster/update.go | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index 78534e25b6..33fe82b2e0 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -306,6 +306,12 @@ CREATE TABLE nodes (
     UNIQUE (name),
     UNIQUE (address)
 );
+CREATE TABLE nodes_roles (
+    node_id INTEGER NOT NULL,
+    role INTEGER NOT NULL,
+    FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE,
+    UNIQUE (node_id, role)
+);
 CREATE TABLE "operations" (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     uuid TEXT NOT NULL,
@@ -481,5 +487,5 @@ CREATE TABLE storage_volumes_config (
     FOREIGN KEY (storage_volume_id) REFERENCES storage_volumes (id) ON DELETE 
CASCADE
 );
 
-INSERT INTO schema (version, updated_at) VALUES (17, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (18, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 67e9a1ca20..461672ad67 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -52,6 +52,21 @@ var updates = map[int]schema.Update{
        15: updateFromV14,
        16: updateFromV15,
        17: updateFromV16,
+       18: updateFromV17,
+}
+
+// Add nodes_roles table
+func updateFromV17(tx *sql.Tx) error {
+       stmts := `
+CREATE TABLE nodes_roles (
+    node_id INTEGER NOT NULL,
+    role INTEGER NOT NULL,
+    FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE,
+    UNIQUE (node_id, role)
+);
+`
+       _, err := tx.Exec(stmts)
+       return err
 }
 
 // Add image type column

From f935ae8093f15044e2cda99710e864dda2a2553e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 24 Sep 2019 18:34:50 -0400
Subject: [PATCH 4/8] lxd/db: Add support for clustering roles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #6172

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/cluster/membership.go |  1 +
 lxd/db/node.go            | 55 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/lxd/cluster/membership.go b/lxd/cluster/membership.go
index 98744d8311..bf9fceffab 100644
--- a/lxd/cluster/membership.go
+++ b/lxd/cluster/membership.go
@@ -798,6 +798,7 @@ func List(state *state.State) ([]api.ClusterMember, error) {
                result[i].ServerName = node.Name
                result[i].URL = fmt.Sprintf("https://%s";, node.Address)
                result[i].Database = shared.StringInSlice(node.Address, 
addresses)
+               result[i].Roles = node.Roles
                if node.IsOffline(offlineThreshold) {
                        result[i].Status = "Offline"
                        result[i].Message = fmt.Sprintf(
diff --git a/lxd/db/node.go b/lxd/db/node.go
index 563b158d4a..c01fe0ff32 100644
--- a/lxd/db/node.go
+++ b/lxd/db/node.go
@@ -13,6 +13,11 @@ import (
        "github.com/pkg/errors"
 )
 
+// ClusterRoles maps role ids into human-readable names.
+var ClusterRoles = map[int]string{
+       0: "database",
+}
+
 // NodeInfo holds information about a single LXD instance in a cluster.
 type NodeInfo struct {
        ID            int64     // Stable node identifier
@@ -22,6 +27,7 @@ type NodeInfo struct {
        Schema        int       // Schema version of the LXD code running the 
node
        APIExtensions int       // Number of API extensions of the LXD code 
running on the node
        Heartbeat     time.Time // Timestamp of the last heartbeat
+       Roles         []string  // List of cluster roles
 }
 
 // IsOffline returns true if the last successful heartbeat time of the node is
@@ -207,6 +213,39 @@ func (c *ClusterTx) NodeRename(old, new string) error {
 
 // Nodes returns all LXD nodes part of the cluster.
 func (c *ClusterTx) nodes(pending bool, where string, args ...interface{}) 
([]NodeInfo, error) {
+       // Get node roles
+       sql := "SELECT node_id, role FROM nodes_roles;"
+
+       rows, err := c.tx.Query(sql)
+       if err != nil {
+               return nil, err
+       }
+       defer rows.Close()
+
+       nodeRoles := map[int64][]string{}
+       for i := 0; rows.Next(); i++ {
+               var nodeID int64
+               var role int
+               err := rows.Scan(&nodeID, &role)
+               if err != nil {
+                       return nil, err
+               }
+
+               if nodeRoles[nodeID] == nil {
+                       nodeRoles[nodeID] = []string{}
+               }
+
+               roleName := ClusterRoles[role]
+
+               nodeRoles[nodeID] = append(nodeRoles[nodeID], roleName)
+       }
+
+       err = rows.Err()
+       if err != nil {
+               return nil, err
+       }
+
+       // Process node entries
        nodes := []NodeInfo{}
        dest := func(i int) []interface{} {
                nodes = append(nodes, NodeInfo{})
@@ -225,21 +264,33 @@ func (c *ClusterTx) nodes(pending bool, where string, 
args ...interface{}) ([]No
        } else {
                args = append([]interface{}{0}, args...)
        }
-       sql := `
-SELECT id, name, address, description, schema, api_extensions, heartbeat FROM 
nodes WHERE pending=? `
+
+       // Get the node entries
+       sql = "SELECT id, name, address, description, schema, api_extensions, 
heartbeat FROM nodes WHERE pending=?"
        if where != "" {
                sql += fmt.Sprintf("AND %s ", where)
        }
        sql += "ORDER BY id"
+
        stmt, err := c.tx.Prepare(sql)
        if err != nil {
                return nil, err
        }
        defer stmt.Close()
+
        err = query.SelectObjects(stmt, dest, args...)
        if err != nil {
                return nil, errors.Wrap(err, "Failed to fetch nodes")
        }
+
+       // Add the roles
+       for i, node := range nodes {
+               roles, ok := nodeRoles[node.ID]
+               if ok {
+                       nodes[i].Roles = roles
+               }
+       }
+
        return nodes, nil
 }
 

From 4617374ef2139e0ccd98154a08363f63ff7b0117 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 25 Sep 2019 15:26:11 -0400
Subject: [PATCH 5/8] lxd/db: Add NodeAddRole
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/db/node.go | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/lxd/db/node.go b/lxd/db/node.go
index c01fe0ff32..71fe7ae925 100644
--- a/lxd/db/node.go
+++ b/lxd/db/node.go
@@ -339,6 +339,30 @@ func (c *ClusterTx) NodeUpdate(id int64, name string, 
address string) error {
        return nil
 }
 
+// NodeAddRole adds a role to the node.
+func (c *ClusterTx) NodeAddRole(id int64, role string) error {
+       // Translate role names to ids
+       roleID := -1
+       for k, v := range ClusterRoles {
+               if v == role {
+                       roleID = k
+                       break
+               }
+       }
+
+       if roleID < 0 {
+               return fmt.Errorf("Invalid role: %v", role)
+       }
+
+       // Update the database record
+       _, err := c.tx.Exec("INSERT INTO nodes_roles (node_id, role) VALUES (?, 
?)", id, roleID)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
 // NodeRemove removes the node with the given id.
 func (c *ClusterTx) NodeRemove(id int64) error {
        result, err := c.tx.Exec("DELETE FROM nodes WHERE id=?", id)

From dcc668491c9b85549c7ac9bb6ca49a7b2783a8d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Wed, 25 Sep 2019 18:57:44 -0400
Subject: [PATCH 6/8] lxd/cluster: Switch to using roles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/cluster/membership.go | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/lxd/cluster/membership.go b/lxd/cluster/membership.go
index bf9fceffab..863ae313c8 100644
--- a/lxd/cluster/membership.go
+++ b/lxd/cluster/membership.go
@@ -757,21 +757,7 @@ func Purge(cluster *db.Cluster, name string) error {
 
 // List the nodes of the cluster.
 func List(state *state.State) ([]api.ClusterMember, error) {
-       addresses := []string{} // Addresses of database nodes
-       err := state.Node.Transaction(func(tx *db.NodeTx) error {
-               nodes, err := tx.RaftNodes()
-               if err != nil {
-                       return errors.Wrap(err, "failed to fetch current raft 
nodes")
-               }
-               for _, node := range nodes {
-                       addresses = append(addresses, node.Address)
-               }
-               return nil
-       })
-       if err != nil {
-               return nil, err
-       }
-
+       var err error
        var nodes []db.NodeInfo
        var offlineThreshold time.Duration
 
@@ -780,6 +766,7 @@ func List(state *state.State) ([]api.ClusterMember, error) {
                if err != nil {
                        return err
                }
+
                offlineThreshold, err = tx.NodeOfflineThreshold()
                if err != nil {
                        return err
@@ -797,7 +784,7 @@ func List(state *state.State) ([]api.ClusterMember, error) {
        for i, node := range nodes {
                result[i].ServerName = node.Name
                result[i].URL = fmt.Sprintf("https://%s";, node.Address)
-               result[i].Database = shared.StringInSlice(node.Address, 
addresses)
+               result[i].Database = shared.StringInSlice("database", 
node.Roles)
                result[i].Roles = node.Roles
                if node.IsOffline(offlineThreshold) {
                        result[i].Status = "Offline"

From 28677e41c6f17c16a42b69be01b0dc58d68e67a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Thu, 26 Sep 2019 16:52:47 -0400
Subject: [PATCH 7/8] lxc/cluster: Update roles on join/promote
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/cluster/membership.go | 30 ++++++++++++++++++++++++------
 1 file changed, 24 insertions(+), 6 deletions(-)

diff --git a/lxd/cluster/membership.go b/lxd/cluster/membership.go
index 863ae313c8..cf2413ca91 100644
--- a/lxd/cluster/membership.go
+++ b/lxd/cluster/membership.go
@@ -80,6 +80,12 @@ func Bootstrap(state *state.State, gateway *Gateway, name 
string) error {
                        return errors.Wrap(err, "failed to update cluster node")
                }
 
+               // Update our role list.
+               err = tx.NodeAddRole(1, "database")
+               if err != nil {
+                       return errors.Wrapf(err, "Failed to add database role 
for the node")
+               }
+
                return nil
        })
        if err != nil {
@@ -432,6 +438,14 @@ func Join(state *state.State, gateway *Gateway, cert 
*shared.CertInfo, name stri
                        return errors.Wrapf(err, "failed to unmark the node as 
pending")
                }
 
+               // Update our role list if needed.
+               if id != "" {
+                       err = tx.NodeAddRole(node.ID, "database")
+                       if err != nil {
+                               return errors.Wrapf(err, "Failed to add 
database role for the node")
+                       }
+               }
+
                // Generate partial heartbeat request containing just a raft 
node list.
                hbState := &APIHeartbeat{}
                hbState.Update(false, raftNodes, []db.NodeInfo{}, 
offlineThreshold)
@@ -560,11 +574,11 @@ func Promote(state *state.State, gateway *Gateway, nodes 
[]db.RaftNode) error {
 
        // Figure out our raft node ID, and an existing target raft node that
        // we'll contact to add ourselves as member.
-       id := ""
+       id := int64(-1)
        target := ""
        for _, node := range nodes {
                if node.Address == address {
-                       id = strconv.Itoa(int(node.ID))
+                       id = node.ID
                } else {
                        target = node.Address
                }
@@ -572,7 +586,7 @@ func Promote(state *state.State, gateway *Gateway, nodes 
[]db.RaftNode) error {
 
        // Sanity check that our address was actually included in the given
        // list of raft nodes.
-       if id == "" {
+       if id == -1 {
                return fmt.Errorf("this node is not included in the given list 
of database nodes")
        }
 
@@ -625,20 +639,24 @@ func Promote(state *state.State, gateway *Gateway, nodes 
[]db.RaftNode) error {
                return errors.Wrap(err, "Failed to connect to cluster leader")
        }
        defer client.Close()
+
        err = client.Add(ctx, gateway.raft.info)
        if err != nil {
                return errors.Wrap(err, "Failed to join cluster")
        }
 
-       // Unlock regular access to our cluster database, and make sure our
-       // gateway still works correctly.
+       // Unlock regular access to our cluster database and add the database 
role.
        err = state.Cluster.ExitExclusive(func(tx *db.ClusterTx) error {
-               _, err := tx.Nodes()
+               err = tx.NodeAddRole(id, "database")
+               if err != nil {
+                       return errors.Wrapf(err, "Failed to add database role 
for the node")
+               }
                return err
        })
        if err != nil {
                return errors.Wrap(err, "cluster database initialization 
failed")
        }
+
        return nil
 }
 

From 88579beac1d5687c8115f3741883b645626442a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Thu, 26 Sep 2019 17:03:07 -0400
Subject: [PATCH 8/8] lxd/patches: Add database role
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/patches.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/lxd/patches.go b/lxd/patches.go
index e5253902b4..28562e3411 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -72,6 +72,7 @@ var patches = []patch{
        {name: "storage_api_rename_container_snapshots_dir_again", run: 
patchStorageApiRenameContainerSnapshotsDir},
        {name: "storage_api_rename_container_snapshots_links_again", run: 
patchStorageApiUpdateContainerSnapshots},
        {name: "storage_api_rename_container_snapshots_dir_again_again", run: 
patchStorageApiRenameContainerSnapshotsDir},
+       {name: "clustering_add_roles", run: patchClusteringAddRoles},
 }
 
 type patch struct {
@@ -3335,6 +3336,53 @@ func patchStorageApiUpdateContainerSnapshots(name 
string, d *Daemon) error {
        return nil
 }
 
+func patchClusteringAddRoles(name string, d *Daemon) error {
+       addresses := []string{}
+       err := d.State().Node.Transaction(func(tx *db.NodeTx) error {
+               nodes, err := tx.RaftNodes()
+               if err != nil {
+                       return errors.Wrap(err, "Failed to fetch current raft 
nodes")
+               }
+
+               for _, node := range nodes {
+                       addresses = append(addresses, node.Address)
+               }
+
+               return nil
+       })
+       if err != nil {
+               return err
+       }
+
+       var nodes []db.NodeInfo
+       err = d.State().Cluster.Transaction(func(tx *db.ClusterTx) error {
+               nodes, err = tx.Nodes()
+               if err != nil {
+                       return err
+               }
+
+               for _, node := range nodes {
+                       if node.Address == "0.0.0.0" {
+                               continue
+                       }
+
+                       if shared.StringInSlice(node.Address, addresses) && 
!shared.StringInSlice("database", node.Roles) {
+                               err = tx.NodeAddRole(node.ID, "database")
+                               if err != nil {
+                                       return err
+                               }
+                       }
+               }
+
+               return nil
+       })
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
 // Patches end here
 
 // Here are a couple of legacy patches that were originally in
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to