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

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) ===
- Adds state column to networks_nodes table.
- Modifies network create process to allow per-node state tracking.
- Allows fixing of per-node creation blockers when creating a network in a cluster.

Related to https://github.com/lxc/lxd/issues/8111
From 94f106b017fbb3c22805898981cbeace5d67b4fe Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 20 Nov 2020 11:44:34 +0000
Subject: [PATCH 01/37] lxd/storage/pools/utils: Updates comment and error for
 storagePoolCreateLocal

Makes more accurate.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage_pools_utils.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lxd/storage_pools_utils.go b/lxd/storage_pools_utils.go
index 57cc08b74f..6ea29eb5c2 100644
--- a/lxd/storage_pools_utils.go
+++ b/lxd/storage_pools_utils.go
@@ -3,6 +3,8 @@ package main
 import (
        "fmt"
 
+       "github.com/pkg/errors"
+
        "github.com/lxc/lxd/lxd/state"
        storagePools "github.com/lxc/lxd/lxd/storage"
        "github.com/lxc/lxd/shared"
@@ -95,7 +97,7 @@ func storagePoolCreateGlobal(state *state.State, req 
api.StoragePoolsPost) error
        return nil
 }
 
-// This performs all non-db related work needed to create the pool.
+// This performs local pool setup and updates DB record if config was changed 
during pool setup.
 func storagePoolCreateLocal(state *state.State, id int64, req 
api.StoragePoolsPost, isNotification bool) (map[string]string, error) {
        tryUndo := true
 
@@ -145,7 +147,7 @@ func storagePoolCreateLocal(state *state.State, id int64, 
req api.StoragePoolsPo
                // Create the database entry for the storage pool.
                err = state.Cluster.UpdateStoragePool(req.Name, 
req.Description, updatedConfig)
                if err != nil {
-                       return nil, fmt.Errorf("Error inserting %s into 
database: %s", req.Name, err)
+                       return nil, errors.Wrapf(err, "Error updating storage 
pool config after local create for %q", req.Name)
                }
        }
 

From ecc1cdb3022219c536aba6b02b2191e0fcf059a5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 20 Nov 2020 11:46:21 +0000
Subject: [PATCH 02/37] lxd/storage/pools: Error quoting

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage_pools.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go
index c631d7cbc7..2e3631e676 100644
--- a/lxd/storage_pools.go
+++ b/lxd/storage_pools.go
@@ -182,7 +182,7 @@ func storagePoolsPost(d *Daemon, r *http.Request) 
response.Response {
        // storage config are the ones in StoragePoolNodeConfigKeys.
        for key := range req.Config {
                if !shared.StringInSlice(key, db.StoragePoolNodeConfigKeys) {
-                       return response.SmartError(fmt.Errorf("Config key '%s' 
may not be used as node-specific key", key))
+                       return response.SmartError(fmt.Errorf("Config key %q 
may not be used as node-specific key", key))
                }
        }
 
@@ -196,7 +196,7 @@ func storagePoolsPost(d *Daemon, r *http.Request) 
response.Response {
        })
        if err != nil {
                if err == db.ErrAlreadyDefined {
-                       return response.BadRequest(fmt.Errorf("The storage pool 
already defined on node %s", targetNode))
+                       return response.BadRequest(fmt.Errorf("The storage pool 
already defined on node %q", targetNode))
                }
 
                return response.SmartError(err)
@@ -209,7 +209,7 @@ func storagePoolsPostCluster(d *Daemon, req 
api.StoragePoolsPost) error {
        // Check that no node-specific config key has been defined.
        for key := range req.Config {
                if shared.StringInSlice(key, db.StoragePoolNodeConfigKeys) {
-                       return fmt.Errorf("Config key '%s' is node-specific", 
key)
+                       return fmt.Errorf("Config key %q is node-specific", key)
                }
        }
 
@@ -525,7 +525,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) 
response.Response {
 func storagePoolValidateClusterConfig(reqConfig map[string]string) error {
        for key := range reqConfig {
                if shared.StringInSlice(key, db.StoragePoolNodeConfigKeys) {
-                       return fmt.Errorf("node-specific config key %s can't be 
changed", key)
+                       return fmt.Errorf("Node-specific config key %q can't be 
changed", key)
                }
        }
        return nil

From 43d0cba9443e07e7f828b9495e94ae5684db4e7a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 20 Nov 2020 14:37:47 +0000
Subject: [PATCH 03/37] lxd/db/cluster: Adds state column to
 storage_pools_nodes and networks_nodes table and set existing rows to state=1
 (created)

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/cluster/schema.go |  4 +++-
 lxd/db/cluster/update.go | 13 +++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index 3e9227899b..8d0c38bdb8 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -299,6 +299,7 @@ CREATE TABLE "networks_nodes" (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     network_id INTEGER NOT NULL,
     node_id INTEGER NOT NULL,
+    state INTEGER NOT NULL DEFAULT 0,
     UNIQUE (network_id, node_id),
     FOREIGN KEY (network_id) REFERENCES "networks" (id) ON DELETE CASCADE,
     FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
@@ -489,6 +490,7 @@ CREATE TABLE storage_pools_nodes (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     storage_pool_id INTEGER NOT NULL,
     node_id INTEGER NOT NULL,
+    state INTEGER NOT NULL DEFAULT 0,
     UNIQUE (storage_pool_id, node_id),
     FOREIGN KEY (storage_pool_id) REFERENCES storage_pools (id) ON DELETE 
CASCADE,
     FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
@@ -589,5 +591,5 @@ CREATE TABLE storage_volumes_snapshots_config (
     UNIQUE (storage_volume_snapshot_id, key)
 );
 
-INSERT INTO schema (version, updated_at) VALUES (39, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (40, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 750097c693..cfb300b95d 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -76,6 +76,19 @@ var updates = map[int]schema.Update{
        37: updateFromV36,
        38: updateFromV37,
        39: updateFromV38,
+       40: updateFromV39,
+}
+
+// Add state column to storage_pools_nodes and networks_nodes tables. Set 
existing row's state to 1 ("created").
+func updateFromV39(tx *sql.Tx) error {
+       stmt := `
+               ALTER TABLE storage_pools_nodes ADD COLUMN state INTEGER NOT 
NULL DEFAULT 0;
+               UPDATE storage_pools_nodes SET state = 1;
+               ALTER TABLE networks_nodes ADD COLUMN state INTEGER NOT NULL 
DEFAULT 0;
+               UPDATE networks_nodes SET state = 1;
+       `
+       _, err := tx.Exec(stmt)
+       return err
 }
 
 // Add storage_volumes_backups table.

From 20283679c22da495d2090fa7806c193a49016c31 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:17:12 +0000
Subject: [PATCH 04/37] lxd/db/networks: Populate node state column in
 NetworkNodeJoin

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index b2549cabd5..c0fecfc5ce 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -171,8 +171,9 @@ func (c *ClusterTx) CreateNetworkConfig(networkID, nodeID 
int64, config map[stri
 // assume that the relevant network has already been created on the joining 
node,
 // and we just need to track it.
 func (c *ClusterTx) NetworkNodeJoin(networkID, nodeID int64) error {
-       columns := []string{"network_id", "node_id"}
-       values := []interface{}{networkID, nodeID}
+       columns := []string{"network_id", "node_id", "state"}
+       // Create network node with "created" state as we expect the network to 
already be setup.
+       values := []interface{}{networkID, nodeID, networkCreated}
        _, err := query.UpsertObject(c.tx, "networks_nodes", columns, values)
        return err
 }

From cd243854421b89c79611273020235a28139c49bf Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:17:36 +0000
Subject: [PATCH 05/37] lxd/db/networks: Populate node state column in
 CreatePendingNetwork

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index c0fecfc5ce..f4964a99da 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -299,9 +299,9 @@ func (c *ClusterTx) CreatePendingNetwork(node string, 
projectName string, name s
                return ErrAlreadyDefined
        }
 
-       // Insert the node-specific configuration.
-       columns := []string{"network_id", "node_id"}
-       values := []interface{}{networkID, nodeInfo.ID}
+       // Insert the node-specific configuration with state "pending".
+       columns := []string{"network_id", "node_id", "state"}
+       values := []interface{}{networkID, nodeInfo.ID, networkPending}
        _, err = query.UpsertObject(c.tx, "networks_nodes", columns, values)
        if err != nil {
                return err

From 726620ad3e975a8c458914effb3361ef8f1eedfb Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:18:01 +0000
Subject: [PATCH 06/37] lxd/db/networks: Adds networkNodeState and
 NetworkNodeCreated functiond

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index f4964a99da..ba74ef7d72 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -341,6 +341,29 @@ func (c *ClusterTx) networkState(project string, name 
string, state int) error {
        return nil
 }
 
+// NetworkNodeCreated sets the state of the given network for the local member 
to "Created".
+func (c *ClusterTx) NetworkNodeCreated(networkID int64) error {
+       return c.networkNodeState(networkID, networkCreated)
+}
+
+// networkNodeState updates the network member state for the local member and 
specified network ID.
+func (c *ClusterTx) networkNodeState(networkID int64, state int) error {
+       stmt := "UPDATE networks_nodes SET state=? WHERE network_id = ? and 
node_id = ?"
+       result, err := c.tx.Exec(stmt, state, networkID, c.nodeID)
+       if err != nil {
+               return err
+       }
+       n, err := result.RowsAffected()
+       if err != nil {
+               return err
+       }
+       if n != 1 {
+               return ErrNoSuchObject
+       }
+
+       return nil
+}
+
 // UpdateNetwork updates the network with the given ID.
 func (c *ClusterTx) UpdateNetwork(id int64, description string, config 
map[string]string) error {
        err := updateNetworkDescription(c.tx, id, description)

From 0973a46ecabb4d5a83612624940e0f2fde9852f6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:18:37 +0000
Subject: [PATCH 07/37] lxd/db/networks: Comments

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index ba74ef7d72..b06411e610 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -456,15 +456,12 @@ const (
        NetworkTypePhysical                    // Network type physical.
 )
 
-// GetNetworkInAnyState returns the network with the given name.
-//
-// The network can be in any state.
+// GetNetworkInAnyState returns the network with the given name. The network 
can be in any state.
 func (c *Cluster) GetNetworkInAnyState(project string, name string) (int64, 
*api.Network, error) {
        return c.getNetwork(project, name, false)
 }
 
-// Get the network with the given name. If onlyCreated is true, only return
-// networks in the created state.
+// Get the network with the given name. If onlyCreated is true, only return 
networks in the created state.
 func (c *Cluster) getNetwork(project string, name string, onlyCreated bool) 
(int64, *api.Network, error) {
        description := sql.NullString{}
        id := int64(-1)
@@ -562,8 +559,7 @@ func (c *Cluster) networkNodes(networkID int64) ([]string, 
error) {
        return nodes, nil
 }
 
-// GetNetworkWithInterface returns the network associated with the interface 
with
-// the given name.
+// GetNetworkWithInterface returns the network associated with the interface 
with the given name.
 func (c *Cluster) GetNetworkWithInterface(devName string) (int64, 
*api.Network, error) {
        id := int64(-1)
        name := ""
@@ -656,6 +652,7 @@ func (c *Cluster) getNetworkConfig(id int64) 
(map[string]string, error) {
 func (c *Cluster) CreateNetwork(projectName string, name string, description 
string, netType NetworkType, config map[string]string) (int64, error) {
        var id int64
        err := c.Transaction(func(tx *ClusterTx) error {
+               // Insert a new network record with state "created".
                result, err := tx.tx.Exec("INSERT INTO networks (project_id, 
name, description, state, type) VALUES ((SELECT id FROM projects WHERE name = 
?), ?, ?, ?, ?)",
                        projectName, name, description, networkCreated, netType)
                if err != nil {

From e8c987b6ffea5e0c2296701ae71e4b6fd28b32b6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:18:49 +0000
Subject: [PATCH 08/37] lxd/db/networks: Populate node state column in
 CreateNetwork

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index b06411e610..a879a23728 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -664,9 +664,9 @@ func (c *Cluster) CreateNetwork(projectName string, name 
string, description str
                        return err
                }
 
-               // Insert a node-specific entry pointing to ourselves.
-               columns := []string{"network_id", "node_id"}
-               values := []interface{}{id, c.nodeID}
+               // Insert a node-specific entry pointing to ourselves with 
state "pending".
+               columns := []string{"network_id", "node_id", "state"}
+               values := []interface{}{id, c.nodeID, networkPending}
                _, err = query.UpsertObject(tx.tx, "networks_nodes", columns, 
values)
                if err != nil {
                        return err

From c9d06cbfc278966704a801cea8016858d9e6a537 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:19:31 +0000
Subject: [PATCH 09/37] lxd/network/driver: Remove check that prevents starting
 network in pending state

This is needed as we now need to check if the network can start OK before 
marking the network created.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_bridge.go   | 4 ----
 lxd/network/driver_macvlan.go  | 6 ------
 lxd/network/driver_ovn.go      | 4 ----
 lxd/network/driver_physical.go | 4 ----
 lxd/network/driver_sriov.go    | 6 ------
 5 files changed, 24 deletions(-)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index ec30df98ca..8014bdb256 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -487,10 +487,6 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 
        n.logger.Debug("Setting up network")
 
-       if n.status == api.NetworkStatusPending {
-               return fmt.Errorf("Cannot start pending network")
-       }
-
        // Create directory.
        if !shared.PathExists(shared.VarPath("networks", n.name)) {
                err := os.MkdirAll(shared.VarPath("networks", n.name), 0711)
diff --git a/lxd/network/driver_macvlan.go b/lxd/network/driver_macvlan.go
index 09c15a174f..9c9a159cc5 100644
--- a/lxd/network/driver_macvlan.go
+++ b/lxd/network/driver_macvlan.go
@@ -1,8 +1,6 @@
 package network
 
 import (
-       "fmt"
-
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/revert"
@@ -67,10 +65,6 @@ func (n *macvlan) Rename(newName string) error {
 func (n *macvlan) Start() error {
        n.logger.Debug("Start")
 
-       if n.status == api.NetworkStatusPending {
-               return fmt.Errorf("Cannot start pending network")
-       }
-
        return nil
 }
 
diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 03dd522eb7..097aaeb011 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1836,10 +1836,6 @@ func (n *ovn) Rename(newName string) error {
 func (n *ovn) Start() error {
        n.logger.Debug("Start")
 
-       if n.status == api.NetworkStatusPending {
-               return fmt.Errorf("Cannot start pending network")
-       }
-
        // Add local node's OVS chassis ID to logical chassis group.
        err := n.addChassisGroupEntry()
        if err != nil {
diff --git a/lxd/network/driver_physical.go b/lxd/network/driver_physical.go
index 3d24584924..4ee105c8f2 100644
--- a/lxd/network/driver_physical.go
+++ b/lxd/network/driver_physical.go
@@ -143,10 +143,6 @@ func (n *physical) Rename(newName string) error {
 func (n *physical) Start() error {
        n.logger.Debug("Start")
 
-       if n.status == api.NetworkStatusPending {
-               return fmt.Errorf("Cannot start pending network")
-       }
-
        revert := revert.New()
        defer revert.Fail()
 
diff --git a/lxd/network/driver_sriov.go b/lxd/network/driver_sriov.go
index 299e2dd2df..6a745cbb18 100644
--- a/lxd/network/driver_sriov.go
+++ b/lxd/network/driver_sriov.go
@@ -1,8 +1,6 @@
 package network
 
 import (
-       "fmt"
-
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/revert"
@@ -67,10 +65,6 @@ func (n *sriov) Rename(newName string) error {
 func (n *sriov) Start() error {
        n.logger.Debug("Start")
 
-       if n.status == api.NetworkStatusPending {
-               return fmt.Errorf("Cannot start pending network")
-       }
-
        return nil
 }
 

From cc6f8548c20af0bb9112ec1daab8ee04080206f0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 11:20:38 +0000
Subject: [PATCH 10/37] lxd/networks: Whitespace

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 09ea358629..a4fec44216 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -279,7 +279,6 @@ func networksPost(d *Daemon, r *http.Request) 
response.Response {
        }
 
        // Non-clustered network creation.
-
        networks, err := d.cluster.GetNetworks(projectName)
        if err != nil {
                return response.InternalError(err)

From 8fe254d8c90b9b8cabc58bd88572e07dabf83ce4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:02:25 +0000
Subject: [PATCH 11/37] lxd/network/network/interface: Updates init to take
 api.Network and network nodes map

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/network_interface.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/network/network_interface.go b/lxd/network/network_interface.go
index b9ba1e89d1..1df3fe001c 100644
--- a/lxd/network/network_interface.go
+++ b/lxd/network/network_interface.go
@@ -24,7 +24,7 @@ type Network interface {
        Type
 
        // Load.
-       init(state *state.State, id int64, projectName string, name string, 
netType string, description string, config map[string]string, status string)
+       init(state *state.State, id int64, projectName string, netInfo 
*api.Network, netNodes map[int64]db.NetworkNode)
 
        // Config.
        Validate(config map[string]string) error

From 1bf2a21a6d885274a1a173d91cad1d924814a0c1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:02:57 +0000
Subject: [PATCH 12/37] lxd/network/network/interface: Adds LocalStatus

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/network_interface.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/network/network_interface.go b/lxd/network/network_interface.go
index 1df3fe001c..c5f60ca4ca 100644
--- a/lxd/network/network_interface.go
+++ b/lxd/network/network_interface.go
@@ -32,6 +32,7 @@ type Network interface {
        Name() string
        Description() string
        Status() string
+       LocalStatus() string
        Config() map[string]string
        IsUsed() (bool, error)
        DHCPv4Subnet() *net.IPNet

From 3e8161904004bc8af61cd70e9c330b3423c9e584 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:03:26 +0000
Subject: [PATCH 13/37] lxd/network/network/load: Updates LoadByName to pass
 network nodes from s.Cluster.GetNetworkInAnyState to init()

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/network_load.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/network/network_load.go b/lxd/network/network_load.go
index 7098181a57..0b165b63cc 100644
--- a/lxd/network/network_load.go
+++ b/lxd/network/network_load.go
@@ -26,7 +26,7 @@ func LoadByType(driverType string) (Type, error) {
 
 // LoadByName loads an instantiated network from the database by project and 
name.
 func LoadByName(s *state.State, projectName string, name string) (Network, 
error) {
-       id, netInfo, err := s.Cluster.GetNetworkInAnyState(projectName, name)
+       id, netInfo, netNodes, err := 
s.Cluster.GetNetworkInAnyState(projectName, name)
        if err != nil {
                return nil, err
        }
@@ -37,7 +37,7 @@ func LoadByName(s *state.State, projectName string, name 
string) (Network, error
        }
 
        n := driverFunc()
-       n.init(s, id, projectName, name, netInfo.Type, netInfo.Description, 
netInfo.Config, netInfo.Status)
+       n.init(s, id, projectName, netInfo, netNodes)
 
        return n, nil
 }

From 2a10a295e9dad218cc0ab0a0defc21aab48fd771 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:05:56 +0000
Subject: [PATCH 14/37] lxd/db/networks: Adds NetworkState type and uses it in
 place of int

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index a879a23728..66e57968d2 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -94,7 +94,7 @@ func (c *ClusterTx) GetNonPendingNetworks() 
(map[string]map[int64]api.Network, e
                var projectName string
                var networkID int64
                var networkType NetworkType
-               var networkState int
+               var networkState NetworkState
                var network api.Network
 
                err := rows.Scan(&projectName, &networkID, &network.Name, 
&network.Description, &networkType, &networkState)
@@ -230,7 +230,7 @@ func (c *ClusterTx) CreatePendingNetwork(node string, 
projectName string, name s
        // First check if a network with the given name exists, and, if so, 
that it's in the pending state.
        network := struct {
                id      int64
-               state   int
+               state   NetworkState
                netType NetworkType
        }{}
 
@@ -325,7 +325,7 @@ func (c *ClusterTx) NetworkErrored(project string, name 
string) error {
        return c.networkState(project, name, networkErrored)
 }
 
-func (c *ClusterTx) networkState(project string, name string, state int) error 
{
+func (c *ClusterTx) networkState(project string, name string, state 
NetworkState) error {
        stmt := "UPDATE networks SET state=? WHERE project_id = (SELECT id FROM 
projects WHERE name = ?) AND name=?"
        result, err := c.tx.Exec(stmt, state, project, name)
        if err != nil {
@@ -347,7 +347,7 @@ func (c *ClusterTx) NetworkNodeCreated(networkID int64) 
error {
 }
 
 // networkNodeState updates the network member state for the local member and 
specified network ID.
-func (c *ClusterTx) networkNodeState(networkID int64, state int) error {
+func (c *ClusterTx) networkNodeState(networkID int64, state NetworkState) 
error {
        stmt := "UPDATE networks_nodes SET state=? WHERE network_id = ? and 
node_id = ?"
        result, err := c.tx.Exec(stmt, state, networkID, c.nodeID)
        if err != nil {
@@ -437,11 +437,14 @@ func (c *Cluster) networks(project string, where string, 
args ...interface{}) ([
        return response, nil
 }
 
+// NetworkState indicates the state of the network or network node.
+type NetworkState int
+
 // Network state.
 const (
-       networkPending int = iota // Network defined but not yet created.
-       networkCreated            // Network created on all nodes.
-       networkErrored            // Network creation failed on some nodes
+       networkPending NetworkState = iota // Network defined but not yet 
created.
+       networkCreated                     // Network created on all nodes.
+       networkErrored                     // Network creation failed on some 
nodes
 )
 
 // NetworkType indicates type of network.
@@ -465,7 +468,7 @@ func (c *Cluster) GetNetworkInAnyState(project string, name 
string) (int64, *api
 func (c *Cluster) getNetwork(project string, name string, onlyCreated bool) 
(int64, *api.Network, error) {
        description := sql.NullString{}
        id := int64(-1)
-       state := 0
+       var state NetworkState
        var netType NetworkType
 
        q := "SELECT id, description, state, type FROM networks WHERE 
project_id = (SELECT id FROM projects WHERE name = ?) AND name=?"

From 10c53771be5eace214e35282964b81f43a821b2b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:07:37 +0000
Subject: [PATCH 15/37] lxd/db/networks: Renames networkFillStatus to
 NetworkStateToAPIStatus

And updates usage.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 66e57968d2..9fe91ec9f7 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -103,7 +103,7 @@ func (c *ClusterTx) GetNonPendingNetworks() 
(map[string]map[int64]api.Network, e
                }
 
                // Populate Status and Type fields by converting from DB values.
-               networkFillStatus(&network, networkState)
+               network.Status = NetworkStateToAPIStatus(networkState)
                networkFillType(&network, networkType)
 
                if projectNetworks[projectName] != nil {
@@ -500,7 +500,7 @@ func (c *Cluster) getNetwork(project string, name string, 
onlyCreated bool) (int
        network.Config = config
 
        // Populate Status and Type fields by converting from DB values.
-       networkFillStatus(&network, state)
+       network.Status = NetworkStateToAPIStatus(state)
        networkFillType(&network, netType)
 
        nodes, err := c.networkNodes(id)
@@ -512,16 +512,17 @@ func (c *Cluster) getNetwork(project string, name string, 
onlyCreated bool) (int
        return id, &network, nil
 }
 
-func networkFillStatus(network *api.Network, state int) {
+// NetworkStateToAPIStatus converts DB NetworkState to API status string.
+func NetworkStateToAPIStatus(state NetworkState) string {
        switch state {
        case networkPending:
-               network.Status = api.NetworkStatusPending
+               return api.NetworkStatusPending
        case networkCreated:
-               network.Status = api.NetworkStatusCreated
+               return api.NetworkStatusCreated
        case networkErrored:
-               network.Status = api.NetworkStatusErrored
+               return api.NetworkStatusErrored
        default:
-               network.Status = api.NetworkStatusUnknown
+               return api.NetworkStatusUnknown
        }
 }
 

From 31ace3f2ed6d2c3860a058fb992c4ff12d398635 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:09:45 +0000
Subject: [PATCH 16/37] lxd/db/networks: Adds NetworkNode type

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 9fe91ec9f7..4b1e5d3158 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -459,6 +459,13 @@ const (
        NetworkTypePhysical                    // Network type physical.
 )
 
+// NetworkNode represents a network node.
+type NetworkNode struct {
+       ID    int64
+       Name  string
+       State NetworkState
+}
+
 // GetNetworkInAnyState returns the network with the given name. The network 
can be in any state.
 func (c *Cluster) GetNetworkInAnyState(project string, name string) (int64, 
*api.Network, error) {
        return c.getNetwork(project, name, false)

From 52140c740a8e3f4ce5ba59a03e0bdae92e57012f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:10:11 +0000
Subject: [PATCH 17/37] lxd/db/networks: Exports NetworkNodes and updates to
 return map of NetworkNodes

Including node ID, name and state.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 35 +++++++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 4b1e5d3158..5ff4322ed5 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -384,20 +384,35 @@ func (c *ClusterTx) UpdateNetwork(id int64, description 
string, config map[strin
        return nil
 }
 
-// Return the names of the nodes the given network is defined on.
-func (c *ClusterTx) networkNodes(networkID int64) ([]string, error) {
-       var err error
-       stmt := `
-       SELECT nodes.name FROM nodes
-         JOIN networks_nodes ON networks_nodes.node_id = nodes.id
-         WHERE networks_nodes.network_id = ?
-       `
-       nodes, err := query.SelectStrings(c.tx, stmt, networkID)
+// NetworkNodes returns the nodes keyed by node ID that the given network is 
defined on.
+func (c *ClusterTx) NetworkNodes(networkID int64) (map[int64]NetworkNode, 
error) {
+       nodes := []NetworkNode{}
+       dest := func(i int) []interface{} {
+               nodes = append(nodes, NetworkNode{})
+               return []interface{}{&nodes[i].ID, &nodes[i].Name, 
&nodes[i].State}
+       }
+
+       stmt, err := c.tx.Prepare(`
+               SELECT nodes.id, nodes.name, networks_nodes.state FROM nodes
+               JOIN networks_nodes ON networks_nodes.node_id = nodes.id
+               WHERE networks_nodes.network_id = ?
+       `)
+       if err != nil {
+               return nil, err
+       }
+       defer stmt.Close()
+
+       err = query.SelectObjects(stmt, dest, networkID)
        if err != nil {
                return nil, err
        }
 
-       return nodes, nil
+       netNodes := map[int64]NetworkNode{}
+       for _, node := range nodes {
+               netNodes[node.ID] = node
+       }
+
+       return netNodes, nil
 }
 
 // GetNetworks returns the names of existing networks.

From bc3108c16910af7a46274e50b52a0734afd673f6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:11:41 +0000
Subject: [PATCH 18/37] lxd/db/networks: Updates GetNonPendingNetworks usage of
 NetworkNodes()

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 5ff4322ed5..ddeee25093 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -130,11 +130,14 @@ func (c *ClusterTx) GetNonPendingNetworks() 
(map[string]map[int64]api.Network, e
 
                        network.Config = networkConfig
 
-                       nodes, err := c.networkNodes(networkID)
+                       nodes, err := c.NetworkNodes(networkID)
                        if err != nil {
                                return nil, err
                        }
-                       network.Locations = nodes
+
+                       for _, node := range nodes {
+                               network.Locations = append(network.Locations, 
node.Name)
+                       }
 
                        projectNetworks[projectName][networkID] = network
                }

From a0f20b9a19926fec4033572c6d4af887f4423650 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:12:10 +0000
Subject: [PATCH 19/37] lxd/db/networks: Modifies getNetwork and
 GetNetworkInAnyState to return map of NetworkNodes for network

We run the query anyway to populate Network's Location field so cheap to return 
at same time.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index ddeee25093..9c086d0f37 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -485,12 +485,13 @@ type NetworkNode struct {
 }
 
 // GetNetworkInAnyState returns the network with the given name. The network 
can be in any state.
-func (c *Cluster) GetNetworkInAnyState(project string, name string) (int64, 
*api.Network, error) {
+func (c *Cluster) GetNetworkInAnyState(project string, name string) (int64, 
*api.Network, map[int64]NetworkNode, error) {
        return c.getNetwork(project, name, false)
 }
 
-// Get the network with the given name. If onlyCreated is true, only return 
networks in the created state.
-func (c *Cluster) getNetwork(project string, name string, onlyCreated bool) 
(int64, *api.Network, error) {
+// Get the network with the given name. If onlyCreated is true, only return 
networks in the networkCreated state.
+// Also returns a map of the network's nodes keyed by node ID.
+func (c *Cluster) getNetwork(project string, name string, onlyCreated bool) 
(int64, *api.Network, map[int64]NetworkNode, error) {
        description := sql.NullString{}
        id := int64(-1)
        var state NetworkState
@@ -506,15 +507,15 @@ func (c *Cluster) getNetwork(project string, name string, 
onlyCreated bool) (int
        err := dbQueryRowScan(c, q, arg1, arg2)
        if err != nil {
                if err == sql.ErrNoRows {
-                       return -1, nil, ErrNoSuchObject
+                       return -1, nil, nil, ErrNoSuchObject
                }
 
-               return -1, nil, err
+               return -1, nil, nil, err
        }
 
        config, err := c.getNetworkConfig(id)
        if err != nil {
-               return -1, nil, err
+               return -1, nil, nil, err
        }
 
        network := api.Network{
@@ -528,13 +529,16 @@ func (c *Cluster) getNetwork(project string, name string, 
onlyCreated bool) (int
        network.Status = NetworkStateToAPIStatus(state)
        networkFillType(&network, netType)
 
-       nodes, err := c.networkNodes(id)
+       nodes, err := c.NetworkNodes(id)
        if err != nil {
-               return -1, nil, err
+               return -1, nil, nil, err
        }
-       network.Locations = nodes
 
-       return id, &network, nil
+       for _, node := range nodes {
+               network.Locations = append(network.Locations, node.Name)
+       }
+
+       return id, &network, nodes, nil
 }
 
 // NetworkStateToAPIStatus converts DB NetworkState to API status string.

From 36f25d841d4245cf5ab37dcc4ca7d9e019e79647 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:13:07 +0000
Subject: [PATCH 20/37] lxd/db/networks: Exports NetworkNodes

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 9c086d0f37..d0fd2a14b1 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -572,13 +572,13 @@ func networkFillType(network *api.Network, netType 
NetworkType) {
        }
 }
 
-// Return the names of the nodes the given network is defined on.
-func (c *Cluster) networkNodes(networkID int64) ([]string, error) {
-       var nodes []string
+// NetworkNodes returns the nodes keyed by node ID that the given network is 
defined on.
+func (c *Cluster) NetworkNodes(networkID int64) (map[int64]NetworkNode, error) 
{
+       var nodes map[int64]NetworkNode
        var err error
 
        err = c.Transaction(func(tx *ClusterTx) error {
-               nodes, err = tx.networkNodes(networkID)
+               nodes, err = tx.NetworkNodes(networkID)
                if err != nil {
                        return err
                }

From 0daa38e09bff9b01b6725586229828355766803a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:13:49 +0000
Subject: [PATCH 21/37] lxd/db/networks: c.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index d0fd2a14b1..12866e0344 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -720,7 +720,7 @@ func (c *Cluster) CreateNetwork(projectName string, name 
string, description str
 
 // UpdateNetwork updates the network with the given name.
 func (c *Cluster) UpdateNetwork(project string, name, description string, 
config map[string]string) error {
-       id, netInfo, err := c.GetNetworkInAnyState(project, name)
+       id, netInfo, _, err := c.GetNetworkInAnyState(project, name)
        if err != nil {
                return err
        }
@@ -794,7 +794,7 @@ func clearNetworkConfig(tx *sql.Tx, networkID, nodeID 
int64) error {
 
 // DeleteNetwork deletes the network with the given name.
 func (c *Cluster) DeleteNetwork(project string, name string) error {
-       id, _, err := c.GetNetworkInAnyState(project, name)
+       id, _, _, err := c.GetNetworkInAnyState(project, name)
        if err != nil {
                return err
        }
@@ -809,7 +809,7 @@ func (c *Cluster) DeleteNetwork(project string, name 
string) error {
 
 // RenameNetwork renames a network.
 func (c *Cluster) RenameNetwork(project string, oldName string, newName 
string) error {
-       id, _, err := c.GetNetworkInAnyState(project, oldName)
+       id, _, _, err := c.GetNetworkInAnyState(project, oldName)
        if err != nil {
                return err
        }

From b914b831e65e35ed3ce8b81dbbff14accf10bf3f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:14:07 +0000
Subject: [PATCH 22/37] lxd/db/networks: Updates comments to reference state
 constants

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/networks.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 12866e0344..34a75003a6 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -175,7 +175,7 @@ func (c *ClusterTx) CreateNetworkConfig(networkID, nodeID 
int64, config map[stri
 // and we just need to track it.
 func (c *ClusterTx) NetworkNodeJoin(networkID, nodeID int64) error {
        columns := []string{"network_id", "node_id", "state"}
-       // Create network node with "created" state as we expect the network to 
already be setup.
+       // Create network node with networkCreated state as we expect the 
network to already be setup.
        values := []interface{}{networkID, nodeID, networkCreated}
        _, err := query.UpsertObject(c.tx, "networks_nodes", columns, values)
        return err
@@ -276,7 +276,7 @@ func (c *ClusterTx) CreatePendingNetwork(node string, 
projectName string, name s
                        return err
                }
        } else {
-               // Check that the existing network is in the pending state.
+               // Check that the existing network is in the networkPending or 
networkErrored state.
                if network.state != networkPending && network.state != 
networkErrored {
                        return fmt.Errorf("Network is not in pending or errored 
state")
                }
@@ -302,7 +302,7 @@ func (c *ClusterTx) CreatePendingNetwork(node string, 
projectName string, name s
                return ErrAlreadyDefined
        }
 
-       // Insert the node-specific configuration with state "pending".
+       // Insert the node-specific configuration with state networkPending.
        columns := []string{"network_id", "node_id", "state"}
        values := []interface{}{networkID, nodeInfo.ID, networkPending}
        _, err = query.UpsertObject(c.tx, "networks_nodes", columns, values)
@@ -318,12 +318,12 @@ func (c *ClusterTx) CreatePendingNetwork(node string, 
projectName string, name s
        return nil
 }
 
-// NetworkCreated sets the state of the given network to "Created".
+// NetworkCreated sets the state of the given network to networkCreated.
 func (c *ClusterTx) NetworkCreated(project string, name string) error {
        return c.networkState(project, name, networkCreated)
 }
 
-// NetworkErrored sets the state of the given network to "Errored".
+// NetworkErrored sets the state of the given network to networkErrored.
 func (c *ClusterTx) NetworkErrored(project string, name string) error {
        return c.networkState(project, name, networkErrored)
 }
@@ -344,7 +344,7 @@ func (c *ClusterTx) networkState(project string, name 
string, state NetworkState
        return nil
 }
 
-// NetworkNodeCreated sets the state of the given network for the local member 
to "Created".
+// NetworkNodeCreated sets the state of the given network for the local member 
to networkCreated.
 func (c *ClusterTx) NetworkNodeCreated(networkID int64) error {
        return c.networkNodeState(networkID, networkCreated)
 }
@@ -423,7 +423,7 @@ func (c *Cluster) GetNetworks(project string) ([]string, 
error) {
        return c.networks(project, "")
 }
 
-// GetNonPendingNetworks returns the names of all networks that are not 
pending.
+// GetNonPendingNetworks returns the names of all networks that are not in 
state networkPending.
 func (c *Cluster) GetNonPendingNetworks(project string) ([]string, error) {
        return c.networks(project, "NOT state=?", networkPending)
 }
@@ -685,7 +685,7 @@ func (c *Cluster) getNetworkConfig(id int64) 
(map[string]string, error) {
 func (c *Cluster) CreateNetwork(projectName string, name string, description 
string, netType NetworkType, config map[string]string) (int64, error) {
        var id int64
        err := c.Transaction(func(tx *ClusterTx) error {
-               // Insert a new network record with state "created".
+               // Insert a new network record with state networkCreated.
                result, err := tx.tx.Exec("INSERT INTO networks (project_id, 
name, description, state, type) VALUES ((SELECT id FROM projects WHERE name = 
?), ?, ?, ?, ?)",
                        projectName, name, description, networkCreated, netType)
                if err != nil {
@@ -697,7 +697,7 @@ func (c *Cluster) CreateNetwork(projectName string, name 
string, description str
                        return err
                }
 
-               // Insert a node-specific entry pointing to ourselves with 
state "pending".
+               // Insert a node-specific entry pointing to ourselves with 
state networkPending.
                columns := []string{"network_id", "node_id", "state"}
                values := []interface{}{id, c.nodeID, networkPending}
                _, err = query.UpsertObject(tx.tx, "networks_nodes", columns, 
values)

From c0ce479baf45772ff367e66892a50238c3a1b38c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:14:44 +0000
Subject: [PATCH 23/37] lxd/patches: d.cluster.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/patches.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/patches.go b/lxd/patches.go
index 194a5695c9..1e6fa8183c 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -3787,7 +3787,7 @@ func patchNetworkCearBridgeVolatileHwaddr(name string, d 
*Daemon) error {
        }
 
        for _, networkName := range networks {
-               _, net, err := d.cluster.GetNetworkInAnyState(projectName, 
networkName)
+               _, net, _, err := d.cluster.GetNetworkInAnyState(projectName, 
networkName)
                if err != nil {
                        return errors.Wrapf(err, "Failed loading network %q for 
network_clear_bridge_volatile_hwaddr patch", networkName)
                }

From a059aee307388e5c18ecc3b1a82fa8d268064a84 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:16:31 +0000
Subject: [PATCH 24/37] lxd/api/cluster: d.cluster.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/api_cluster.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go
index 7e88bcce2f..3167c0d83f 100644
--- a/lxd/api_cluster.go
+++ b/lxd/api_cluster.go
@@ -436,7 +436,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) 
response.Response {
                        }
 
                        for _, name := range networkNames {
-                               _, network, err := 
d.cluster.GetNetworkInAnyState(p.Name, name)
+                               _, network, _, err := 
d.cluster.GetNetworkInAnyState(p.Name, name)
                                if err != nil {
                                        return err
                                }
@@ -1628,7 +1628,7 @@ func clusterCheckNetworksMatch(cluster *db.Cluster, 
reqNetworks []internalCluste
 
                                found = true
 
-                               _, network, err := 
cluster.GetNetworkInAnyState(networkProjectName, networkName)
+                               _, network, _, err := 
cluster.GetNetworkInAnyState(networkProjectName, networkName)
                                if err != nil {
                                        return err
                                }

From 55e5ce01af334a72e9784bd37ceff3cd4f3774cf Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:16:43 +0000
Subject: [PATCH 25/37] lxd/api/project: s.Cluster.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/api_project.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/api_project.go b/lxd/api_project.go
index 85de1f61b2..cbb088cb85 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -624,7 +624,7 @@ func projectValidateRestrictedSubnets(s *state.State, value 
string) error {
                }
 
                // Check uplink exists and load config to compare subnets.
-               _, uplink, err := 
s.Cluster.GetNetworkInAnyState(project.Default, uplinkName)
+               _, uplink, _, err := 
s.Cluster.GetNetworkInAnyState(project.Default, uplinkName)
                if err != nil {
                        return errors.Wrapf(err, "Invalid uplink network %q", 
uplinkName)
                }

From e89c92f2f969ff910edda33b2a8d12d6427c94aa Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:16:53 +0000
Subject: [PATCH 26/37] lxd/device/nic: d.state.Cluster.GetNetworkInAnyState
 usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/nic_bridged.go     | 2 +-
 lxd/device/nictype/nictype.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index f79fd3549c..83e78ed092 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -492,7 +492,7 @@ func (d *nicBridged) rebuildDnsmasqEntry() error {
        defer dnsmasq.ConfigMutex.Unlock()
 
        // Use project.Default here as bridge networks don't support projects.
-       _, dbInfo, err := d.state.Cluster.GetNetworkInAnyState(project.Default, 
d.config["parent"])
+       _, dbInfo, _, err := 
d.state.Cluster.GetNetworkInAnyState(project.Default, d.config["parent"])
        if err != nil {
                return err
        }
diff --git a/lxd/device/nictype/nictype.go b/lxd/device/nictype/nictype.go
index 5c8ef08209..3848864855 100644
--- a/lxd/device/nictype/nictype.go
+++ b/lxd/device/nictype/nictype.go
@@ -26,7 +26,7 @@ func NICType(s *state.State, deviceProjectName string, d 
deviceConfig.Device) (s
                                return "", errors.Wrapf(err, "Failed to 
translate device project %q into network project", deviceProjectName)
                        }
 
-                       _, netInfo, err := 
s.Cluster.GetNetworkInAnyState(networkProjectName, d["network"])
+                       _, netInfo, _, err := 
s.Cluster.GetNetworkInAnyState(networkProjectName, d["network"])
                        if err != nil {
                                return "", errors.Wrapf(err, "Failed to load 
network %q for project %q", d["network"], networkProjectName)
                        }

From 5223624dea787970919240a11791347ba49d6787 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:18:19 +0000
Subject: [PATCH 27/37] lxd/network/driver/ovn:
 n.state.Cluster.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_ovn.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 097aaeb011..6c855330b3 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -82,7 +82,7 @@ func (n *ovn) Info() Info {
 
 // uplinkRoutes parses ipv4.routes and ipv6.routes settings for a named uplink 
network into a slice of *net.IPNet.
 func (n *ovn) uplinkRoutes(uplinkNetworkName string) ([]*net.IPNet, error) {
-       _, uplink, err := n.state.Cluster.GetNetworkInAnyState(project.Default, 
uplinkNetworkName)
+       _, uplink, _, err := 
n.state.Cluster.GetNetworkInAnyState(project.Default, uplinkNetworkName)
        if err != nil {
                return nil, err
        }

From 9079c21409a0f4b361682fbb8e8fcab60f38817e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:19:29 +0000
Subject: [PATCH 28/37] lxd/network/driver/common: Adds LocalStatus function
 and store node info inside network via init()

Updates init() to accept api.Network and map of NetworkNodes.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_common.go | 24 ++++++++++++++++++------
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
index 23cff9a58c..6c39a6d7b2 100644
--- a/lxd/network/driver_common.go
+++ b/lxd/network/driver_common.go
@@ -36,18 +36,20 @@ type common struct {
        description string
        config      map[string]string
        status      string
+       nodes       map[int64]db.NetworkNode
 }
 
 // init initialise internal variables.
-func (n *common) init(state *state.State, id int64, projectName string, name 
string, netType string, description string, config map[string]string, status 
string) {
-       n.logger = logging.AddContext(logger.Log, log.Ctx{"project": 
projectName, "driver": netType, "network": name})
+func (n *common) init(state *state.State, id int64, projectName string, 
netInfo *api.Network, netNodes map[int64]db.NetworkNode) {
+       n.logger = logging.AddContext(logger.Log, log.Ctx{"project": 
projectName, "driver": netInfo.Type, "network": netInfo.Name})
        n.id = id
        n.project = projectName
-       n.name = name
-       n.config = config
+       n.name = netInfo.Name
+       n.config = netInfo.Config
        n.state = state
-       n.description = description
-       n.status = status
+       n.description = netInfo.Description
+       n.status = netInfo.Status
+       n.nodes = netNodes
 }
 
 // FillConfig fills requested config with any default values, by default this 
is a no-op.
@@ -133,6 +135,16 @@ func (n *common) Status() string {
        return n.status
 }
 
+// LocalStatus returns network status of the local cluster member.
+func (n *common) LocalStatus() string {
+       node, exists := n.nodes[n.state.Cluster.GetNodeID()]
+       if !exists {
+               return api.NetworkStatusUnknown
+       }
+
+       return db.NetworkStateToAPIStatus(node.State)
+}
+
 // Config returns the network config.
 func (n *common) Config() map[string]string {
        return n.config

From 1fed7bc2c9f5142c53910a14b94b8eea8d0bd41f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:17:32 +0000
Subject: [PATCH 29/37] lxd/network/driver/bridge: Only perform local date if
 local status is api.NetworkStatusCreated

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_bridge.go | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index 8014bdb256..0175e930e3 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -414,20 +414,22 @@ func (n *bridge) isRunning() bool {
 func (n *bridge) Delete(clientType cluster.ClientType) error {
        n.logger.Debug("Delete", log.Ctx{"clientType": clientType})
 
-       // Bring the network down.
-       if n.isRunning() {
-               err := n.Stop()
+       // Bring the local network down if created on this node.
+       if n.LocalStatus() == api.NetworkStatusCreated {
+               if n.isRunning() {
+                       err := n.Stop()
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               // Delete apparmor profiles.
+               err := apparmor.NetworkDelete(n.state, n)
                if err != nil {
                        return err
                }
        }
 
-       // Delete apparmor profiles.
-       err := apparmor.NetworkDelete(n.state, n)
-       if err != nil {
-               return err
-       }
-
        return n.common.delete(clientType)
 }
 

From d0b90906fdb44bc612bd739eeb7deb79da51a552 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:18:11 +0000
Subject: [PATCH 30/37] lxd/network/driver/ovn: Only perform local date if
 local status is api.NetworkStatusCreated

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_ovn.go | 88 ++++++++++++++++++++-------------------
 1 file changed, 45 insertions(+), 43 deletions(-)

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 6c855330b3..370104179c 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1758,61 +1758,63 @@ func (n *ovn) deleteChassisGroupEntry() error {
 func (n *ovn) Delete(clientType cluster.ClientType) error {
        n.logger.Debug("Delete", log.Ctx{"clientType": clientType})
 
-       err := n.Stop()
-       if err != nil {
-               return err
-       }
-
-       if clientType == cluster.ClientTypeNormal {
-               client, err := n.getClient()
+       if n.LocalStatus() == api.NetworkStatusCreated {
+               err := n.Stop()
                if err != nil {
                        return err
                }
 
-               err = client.LogicalRouterDelete(n.getRouterName())
-               if err != nil {
-                       return err
-               }
+               if clientType == cluster.ClientTypeNormal {
+                       client, err := n.getClient()
+                       if err != nil {
+                               return err
+                       }
 
-               err = client.LogicalSwitchDelete(n.getExtSwitchName())
-               if err != nil {
-                       return err
-               }
+                       err = client.LogicalRouterDelete(n.getRouterName())
+                       if err != nil {
+                               return err
+                       }
 
-               err = client.LogicalSwitchDelete(n.getIntSwitchName())
-               if err != nil {
-                       return err
-               }
+                       err = client.LogicalSwitchDelete(n.getExtSwitchName())
+                       if err != nil {
+                               return err
+                       }
 
-               err = client.LogicalRouterPortDelete(n.getRouterExtPortName())
-               if err != nil {
-                       return err
-               }
+                       err = client.LogicalSwitchDelete(n.getIntSwitchName())
+                       if err != nil {
+                               return err
+                       }
 
-               err = client.LogicalRouterPortDelete(n.getRouterIntPortName())
-               if err != nil {
-                       return err
-               }
+                       err = 
client.LogicalRouterPortDelete(n.getRouterExtPortName())
+                       if err != nil {
+                               return err
+                       }
 
-               err = 
client.LogicalSwitchPortDelete(n.getExtSwitchRouterPortName())
-               if err != nil {
-                       return err
-               }
+                       err = 
client.LogicalRouterPortDelete(n.getRouterIntPortName())
+                       if err != nil {
+                               return err
+                       }
 
-               err = 
client.LogicalSwitchPortDelete(n.getExtSwitchProviderPortName())
-               if err != nil {
-                       return err
-               }
+                       err = 
client.LogicalSwitchPortDelete(n.getExtSwitchRouterPortName())
+                       if err != nil {
+                               return err
+                       }
 
-               err = 
client.LogicalSwitchPortDelete(n.getIntSwitchRouterPortName())
-               if err != nil {
-                       return err
-               }
+                       err = 
client.LogicalSwitchPortDelete(n.getExtSwitchProviderPortName())
+                       if err != nil {
+                               return err
+                       }
 
-               // Must be done after logical router removal.
-               err = client.ChassisGroupDelete(n.getChassisGroupName())
-               if err != nil {
-                       return err
+                       err = 
client.LogicalSwitchPortDelete(n.getIntSwitchRouterPortName())
+                       if err != nil {
+                               return err
+                       }
+
+                       // Must be done after logical router removal.
+                       err = client.ChassisGroupDelete(n.getChassisGroupName())
+                       if err != nil {
+                               return err
+                       }
                }
        }
 

From 3aa894b481e1c14fe778a5964eea4e17c123c5f5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:20:08 +0000
Subject: [PATCH 31/37] lxd/network/driver/physical: Only perform local date if
 local status is api.NetworkStatusCreated

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_physical.go | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/lxd/network/driver_physical.go b/lxd/network/driver_physical.go
index 4ee105c8f2..09d94f59a4 100644
--- a/lxd/network/driver_physical.go
+++ b/lxd/network/driver_physical.go
@@ -118,9 +118,11 @@ func (n *physical) Create(clientType cluster.ClientType) 
error {
 func (n *physical) Delete(clientType cluster.ClientType) error {
        n.logger.Debug("Delete", log.Ctx{"clientType": clientType})
 
-       err := n.Stop()
-       if err != nil {
-               return err
+       if n.LocalStatus() == api.NetworkStatusCreated {
+               err := n.Stop()
+               if err != nil {
+                       return err
+               }
        }
 
        return n.common.delete(clientType)

From a8e123c0337bb67fa9f5fc9a4ccf618ecbfe1f79 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:24:48 +0000
Subject: [PATCH 32/37] lxd/networks: Updates doNetworksCreate to skip creation
 if node is already marked created

Updates node status to created on successful create.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks.go | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index a4fec44216..4a1205edca 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -444,6 +444,11 @@ func doNetworksCreate(d *Daemon, projectName string, req 
api.NetworksPost, clien
                return err
        }
 
+       if n.LocalStatus() == api.NetworkStatusCreated {
+               logger.Debug("Skipping network create as already created 
locally", log.Ctx{"project": projectName, "network": n.Name()})
+               return nil
+       }
+
        // Run initial creation setup for the network driver.
        err = n.Create(clientType)
        if err != nil {
@@ -457,13 +462,26 @@ func doNetworksCreate(d *Daemon, projectName string, req 
api.NetworksPost, clien
                if err != nil {
                        delErr := n.Delete(clientType)
                        if delErr != nil {
-                               logger.Errorf("Failed clearing up network %q 
after failed create: %v", n.Name(), delErr)
+                               logger.Error("Failed clearing up network after 
failed create", log.Ctx{"project": projectName, "network": n.Name(), "err": 
delErr})
                        }
 
                        return err
                }
        }
 
+       // Mark local as status as networkCreated.
+       err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
+               return tx.NetworkNodeCreated(n.ID())
+       })
+       if err != nil {
+               delErr := n.Delete(clientType)
+               if delErr != nil {
+                       logger.Error("Failed clearing up network after failed 
local status update", log.Ctx{"project": projectName, "network": n.Name(), 
"err": delErr})
+               }
+               return err
+       }
+       logger.Debug("Marked network local status as created", 
log.Ctx{"project": projectName, "network": req.Name})
+
        return nil
 }
 

From 0338b87c317db87c77e0ce0c82c68f7a20f9327a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:25:32 +0000
Subject: [PATCH 33/37] lxd/networks: d.cluster.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 4a1205edca..866d6e4125 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -529,7 +529,7 @@ func doNetworkGet(d *Daemon, projectName string, name 
string) (api.Network, erro
        }
 
        // Get some information.
-       _, dbInfo, _ := d.cluster.GetNetworkInAnyState(projectName, name)
+       _, dbInfo, _, _ := d.cluster.GetNetworkInAnyState(projectName, name)
 
        // Don't allow retrieving info about the local node interfaces when not 
using default project.
        if projectName != project.Default && dbInfo == nil {

From 7863bf7fee38f547e2f32f552fa2502c22819fb0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:26:00 +0000
Subject: [PATCH 34/37] lxd/networks: Don't skip network clean up if network is
 pending in networkDelete()

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks.go | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 866d6e4125..fd8a82ef35 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -601,19 +601,6 @@ func networkDelete(d *Daemon, r *http.Request) 
response.Response {
        name := mux.Vars(r)["name"]
        state := d.State()
 
-       // Check if the network is pending, if so we just need to delete it 
from the database.
-       _, dbNetwork, err := d.cluster.GetNetworkInAnyState(projectName, name)
-       if err != nil {
-               return response.SmartError(err)
-       }
-       if dbNetwork.Status == api.NetworkStatusPending {
-               err := d.cluster.DeleteNetwork(projectName, name)
-               if err != nil {
-                       return response.SmartError(err)
-               }
-               return response.EmptySyncResponse
-       }
-
        // Get the existing network.
        n, err := network.LoadByName(state, projectName, name)
        if err != nil {
@@ -635,7 +622,7 @@ func networkDelete(d *Daemon, r *http.Request) 
response.Response {
                }
        }
 
-       // Delete the network.
+       // Delete the network from each member.
        err = n.Delete(clientType)
        if err != nil {
                return response.SmartError(err)

From 5e551c15a2533b0f2db5c6e7cb5b879e87f26b4e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:26:34 +0000
Subject: [PATCH 35/37] lxd/networks: d.cluster.GetNetworkInAnyState usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index fd8a82ef35..6207073172 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -322,7 +322,7 @@ func networksPostCluster(d *Daemon, projectName string, req 
api.NetworksPost, cl
 
        // Check that the requested network type matches the type created when 
adding the local node config.
        // If network doesn't exist yet, ignore not found error, as this will 
be checked by NetworkNodeConfigs().
-       _, netInfo, err := d.cluster.GetNetworkInAnyState(projectName, req.Name)
+       _, netInfo, _, err := d.cluster.GetNetworkInAnyState(projectName, 
req.Name)
        if err != nil && err != db.ErrNoSuchObject {
                return err
        }
@@ -726,7 +726,7 @@ func networkPut(d *Daemon, r *http.Request) 
response.Response {
        name := mux.Vars(r)["name"]
 
        // Get the existing network.
-       _, dbInfo, err := d.cluster.GetNetworkInAnyState(projectName, name)
+       _, dbInfo, _, err := d.cluster.GetNetworkInAnyState(projectName, name)
        if err != nil {
                return response.SmartError(err)
        }

From 421d5d40291ba03bddf5f87157c6201e1b1e5116 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:41:56 +0000
Subject: [PATCH 36/37] lxd/networks: Updates networksPostCluster to only mark
 global network states as created once all nodes created

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks.go | 52 ++++++++++++++++++++++++-------------------------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 6207073172..4896f116f8 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -312,6 +312,8 @@ func networksPost(d *Daemon, r *http.Request) 
response.Response {
        return resp
 }
 
+// networksPostCluster checks that there is a pending network in the database 
and then attempts to setup the
+// network on each node. If all nodes are successfully setup then the 
network's state is set to created.
 func networksPostCluster(d *Daemon, projectName string, req api.NetworksPost, 
clientType cluster.ClientType, netType network.Type) error {
        // Check that no node-specific config key has been supplied in request.
        for key := range req.Config {
@@ -371,43 +373,27 @@ func networksPostCluster(d *Daemon, projectName string, 
req api.NetworksPost, cl
                return err
        }
 
-       // Create the network on this node.
-       nodeReq := req
-
-       // Merge node specific config items into global config.
-       for key, value := range configs[nodeName] {
-               nodeReq.Config[key] = value
-       }
-
-       // Notify all other nodes to create the network.
+       // Create notifier for other nodes to create the network.
        notifier, err := cluster.NewNotifier(d.State(), 
d.endpoints.NetworkCert(), cluster.NotifyAll)
        if err != nil {
                return err
        }
 
-       revert := revert.New()
-       defer revert.Fail()
-
-       revert.Add(func() {
-               d.cluster.Transaction(func(tx *db.ClusterTx) error {
-                       return tx.NetworkErrored(projectName, req.Name)
-               })
-       })
+       // Create the network on this node.
+       nodeReq := req
 
-       // We need to mark the network as created now, because the 
network.LoadByName call invoked by
-       // doNetworksCreate would fail with not-found otherwise.
-       err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
-               return tx.NetworkCreated(projectName, req.Name)
-       })
-       if err != nil {
-               return err
+       // Merge node specific config items into global config.
+       for key, value := range configs[nodeName] {
+               nodeReq.Config[key] = value
        }
 
        err = doNetworksCreate(d, projectName, nodeReq, clientType)
        if err != nil {
                return err
        }
+       logger.Error("Created network on local cluster member", 
log.Ctx{"project": projectName, "network": req.Name})
 
+       // Notify other nodes to create the network.
        err = notifier(func(client lxd.InstanceServer) error {
                server, _, err := client.GetServer()
                if err != nil {
@@ -419,13 +405,27 @@ func networksPostCluster(d *Daemon, projectName string, 
req api.NetworksPost, cl
                        nodeReq.Config[key] = value
                }
 
-               return client.UseProject(projectName).CreateNetwork(nodeReq)
+               err = client.UseProject(projectName).CreateNetwork(nodeReq)
+               if err != nil {
+                       return err
+               }
+               logger.Error("Created network on cluster member", 
log.Ctx{"project": projectName, "network": req.Name, "member": 
server.Environment.ServerName})
+
+               return nil
        })
        if err != nil {
                return err
        }
 
-       revert.Success()
+       // Mark network global status as networkCreated now that all nodes have 
succeeded.
+       err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
+               return tx.NetworkCreated(projectName, req.Name)
+       })
+       if err != nil {
+               return err
+       }
+       logger.Debug("Marked network global status as created", 
log.Ctx{"project": projectName, "network": req.Name})
+
        return nil
 }
 

From 10fe44e00eb6bfbc72f582dfaac80f42069f593f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 23 Nov 2020 17:51:11 +0000
Subject: [PATCH 37/37] lxd/db/migration/test: cluster.GetNetworkInAnyState
 usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/migration_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db/migration_test.go b/lxd/db/migration_test.go
index fb2c07d16c..1986c355c2 100644
--- a/lxd/db/migration_test.go
+++ b/lxd/db/migration_test.go
@@ -90,7 +90,7 @@ func TestImportPreClusteringData(t *testing.T) {
        networks, err := cluster.GetNetworks(project.Default)
        require.NoError(t, err)
        assert.Equal(t, []string{"lxcbr0"}, networks)
-       id, network, err := cluster.GetNetworkInAnyState(project.Default, 
"lxcbr0")
+       id, network, _, err := cluster.GetNetworkInAnyState(project.Default, 
"lxcbr0")
        require.NoError(t, err)
        assert.Equal(t, int64(1), id)
        assert.Equal(t, "true", network.Config["ipv4.nat"])
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to