[lxc-devel] [lxd/master] Network: Updates NICType() to retrieve type from database

tomponline on Github lxc-bot at linuxcontainers.org
Tue Jul 21 13:08:19 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 417 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200721/fb4dc752/attachment-0001.bin>
-------------- next part --------------
From 2b029019b2372afd695502cccd4fe09dc140a4b3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 20 Jul 2020 12:29:20 +0100
Subject: [PATCH 01/29] lxd/networks: Various comment and error quoting
 consistency fixes

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 42 +++++++++++++++++++-----------------------
 1 file changed, 19 insertions(+), 23 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 7b8b7202cc..ff627669fb 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -97,13 +97,13 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 
 	req := api.NetworksPost{}
 
-	// Parse the request
+	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
 		return response.BadRequest(err)
 	}
 
-	// Sanity checks
+	// Sanity checks.
 	if req.Name == "" {
 		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
@@ -145,20 +145,20 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 
 	targetNode := queryParam(r, "target")
 	if targetNode != "" {
-		// A targetNode was specified, let's just define the node's
-		// network without actually creating it. The only legal key
-		// value for the storage config is 'bridge.external_interfaces'.
+		// A targetNode was specified, let's just define the node's network without actually creating it.
+		// Check that only NodeSpecificNetworkConfig keys are specified.
 		for key := range req.Config {
 			if !shared.StringInSlice(key, db.NodeSpecificNetworkConfig) {
-				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))
 			}
 		}
+
 		err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
 			return tx.CreatePendingNetwork(targetNode, req.Name, dbNetType, req.Config)
 		})
 		if err != nil {
 			if err == db.ErrAlreadyDefined {
-				return response.BadRequest(fmt.Errorf("The network already defined on node %s", targetNode))
+				return response.BadRequest(fmt.Errorf("The network already defined on node %q", targetNode))
 			}
 			return response.SmartError(err)
 		}
@@ -180,8 +180,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		return resp
 	}
 
-	// No targetNode was specified and we're either a single-node cluster or not clustered at all,
-	// so create the network immediately.
+	// Non-clustered network creation.
 	err = network.FillConfig(&req)
 	if err != nil {
 		return response.SmartError(err)
@@ -199,7 +198,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 	// Create the database entry.
 	_, err = d.cluster.CreateNetwork(req.Name, req.Description, dbNetType, req.Config)
 	if err != nil {
-		return response.SmartError(fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
+		return response.SmartError(errors.Wrapf(err, "Error inserting %q into database", req.Name))
 	}
 
 	// Create network and pass false to clusterNotification so the database record is removed on error.
@@ -225,8 +224,7 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 		return err
 	}
 
-	// Check that the network is properly defined, fetch the node-specific
-	// configs and insert the global config.
+	// Check that the network is properly defined, get the node-specific configs and merge with global config.
 	var configs map[string]map[string]string
 	var nodeName string
 	var networkID int64
@@ -271,9 +269,8 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 		return err
 	}
 
-	// We need to mark the network as created now, because the
-	// network.LoadByName call invoked by doNetworksCreate would fail with
-	// not-found otherwise.
+	// We need to mark the network as created now, because the network.LoadByName call invoked by
+	// doNetworksCreate would fail with not-found otherwise.
 	createErr := d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		return tx.NetworkCreated(req.Name)
 	})
@@ -468,8 +465,7 @@ 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.
+	// Check if the network is pending, if so we just need to delete it from the database.
 	_, dbNetwork, err := d.cluster.GetNetworkInAnyState(name)
 	if err != nil {
 		return response.SmartError(err)
@@ -482,7 +478,7 @@ func networkDelete(d *Daemon, r *http.Request) response.Response {
 		return response.EmptySyncResponse
 	}
 
-	// Get the existing network
+	// Get the existing network.
 	n, err := network.LoadByName(state, name)
 	if err != nil {
 		return response.NotFound(err)
@@ -510,13 +506,13 @@ func networkDelete(d *Daemon, r *http.Request) response.Response {
 		}
 	}
 
-	// Delete the network
+	// Delete the network.
 	err = n.Delete(clusterNotification)
 	if err != nil {
 		return response.SmartError(err)
 	}
 
-	// Cleanup storage
+	// Cleanup storage.
 	if shared.PathExists(shared.VarPath("networks", n.Name())) {
 		os.RemoveAll(shared.VarPath("networks", n.Name()))
 	}
@@ -866,13 +862,13 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 }
 
 func networkStartup(s *state.State) error {
-	// Get a list of managed networks
+	// Get a list of managed networks.
 	networks, err := s.Cluster.GetNonPendingNetworks()
 	if err != nil {
 		return err
 	}
 
-	// Bring them all up
+	// Bring them all up.
 	for _, name := range networks {
 		n, err := network.LoadByName(s, name)
 		if err != nil {
@@ -881,7 +877,7 @@ func networkStartup(s *state.State) error {
 
 		err = n.Start()
 		if err != nil {
-			// Don't cause LXD to fail to start entirely on network bring up failure
+			// Don't cause LXD to fail to start entirely on network start up failure.
 			logger.Error("Failed to bring up network", log.Ctx{"err": err, "name": name})
 		}
 	}

From 743024c41121f7e5993ff8b2c93b90f48525f927 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 20 Jul 2020 12:29:54 +0100
Subject: [PATCH 02/29] lxd/networks: Validate network name earlier in
 networksPost

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index ff627669fb..d1795c6b5f 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -108,6 +108,11 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	err = network.ValidNetworkName(req.Name)
+	if err != nil {
+		return response.BadRequest(err)
+	}
+
 	if req.Type == "" {
 		req.Type = "bridge"
 	}
@@ -116,11 +121,6 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		req.Config = map[string]string{}
 	}
 
-	err = network.Validate(req.Name, req.Type, req.Config)
-	if err != nil {
-		return response.BadRequest(err)
-	}
-
 	// Convert requested network type to DB type code.
 	var dbNetType db.NetworkType
 	switch req.Type {

From 16e85ab9ac939b6f2a2545e6501abda26fe6858d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 20 Jul 2020 12:30:20 +0100
Subject: [PATCH 03/29] lxc/networks: Validate config in doNetworksCreate

This ensures config is validated on each cluster node after local node fields have been applied.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index d1795c6b5f..e45880cc4b 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -322,6 +322,12 @@ func doNetworksCreate(d *Daemon, req api.NetworksPost, clusterNotification bool)
 		return err
 	}
 
+	// Validate so that when run on a cluster node the full config (including node specific config) is checked.
+	err = n.Validate(n.Config())
+	if err != nil {
+		return err
+	}
+
 	err = n.Start()
 	if err != nil {
 		n.Delete(clusterNotification)

From 0f305042c41c7629deed8bb64b4632b4b13802d8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:35:42 +0100
Subject: [PATCH 04/29] lxd/db/networks: Ensure that network type matches
 existing pending network in CreatePendingNetwork

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/db/networks.go | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 5965f60e46..8c849fdcdb 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -151,20 +151,21 @@ WHERE networks.id = ? AND networks.state = ?
 func (c *ClusterTx) CreatePendingNetwork(node, name string, netType NetworkType, conf map[string]string) error {
 	// 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
+		id      int64
+		state   int
+		netType NetworkType
 	}{}
 
 	var errConsistency error
 	dest := func(i int) []interface{} {
-		// Sanity check that there is at most one pool with the given name.
+		// Sanity check that there is at most one network with the given name.
 		if i != 0 {
 			errConsistency = fmt.Errorf("More than one network exists with the given name")
 		}
-		return []interface{}{&network.id, &network.state}
+		return []interface{}{&network.id, &network.state, &network.netType}
 	}
 
-	stmt, err := c.tx.Prepare("SELECT id, state FROM networks WHERE name=?")
+	stmt, err := c.tx.Prepare("SELECT id, state, type FROM networks WHERE name=?")
 	if err != nil {
 		return err
 	}
@@ -193,6 +194,11 @@ func (c *ClusterTx) CreatePendingNetwork(node, name string, netType NetworkType,
 		if network.state != networkPending && network.state != networkErrored {
 			return fmt.Errorf("Network is not in pending or errored state")
 		}
+
+		// Check that the existing network type matches the requested type.
+		if network.netType != netType {
+			return fmt.Errorf("Requested network type doesn't match type in existing database record")
+		}
 	}
 
 	// Get the ID of the node with the given name.

From ecb28a6ba327927366559c63e1587b04d47455dd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:36:08 +0100
Subject: [PATCH 05/29] lxd/db/networks: Remove errored state on successful
 update in UpdateNetwork

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/db/networks.go | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 8c849fdcdb..e5733e832b 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -526,7 +526,7 @@ func (c *Cluster) CreateNetwork(name, description string, netType NetworkType, c
 
 // UpdateNetwork updates the network with the given name.
 func (c *Cluster) UpdateNetwork(name, description string, config map[string]string) error {
-	id, _, err := c.GetNetworkInAnyState(name)
+	id, netInfo, err := c.GetNetworkInAnyState(name)
 	if err != nil {
 		return err
 	}
@@ -546,6 +546,15 @@ func (c *Cluster) UpdateNetwork(name, description string, config map[string]stri
 		if err != nil {
 			return err
 		}
+
+		// Update network status if change applied successfully.
+		if netInfo.Status == api.NetworkStatusErrored {
+			err = tx.NetworkCreated(name)
+			if err != nil {
+				return err
+			}
+		}
+
 		return nil
 	})
 

From 536ded624040f2aaf1247226b823729b5e5e7590 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:37:09 +0100
Subject: [PATCH 06/29] lxd/network/driver/bridge: Adds targetNode arg to
 Update

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

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index b302b3e79f..aaa64dda15 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -1333,8 +1333,8 @@ func (n *bridge) Stop() error {
 
 // Update updates the network. Accepts notification boolean indicating if this update request is coming from a
 // cluster notification, in which case do not update the database, just apply local changes needed.
-func (n *bridge) Update(newNetwork api.NetworkPut, clusterNotification bool) error {
-	n.logger.Debug("Update", log.Ctx{"clusterNotification": clusterNotification})
+func (n *bridge) Update(newNetwork api.NetworkPut, targetNode string, clusterNotification bool) error {
+	n.logger.Debug("Update", log.Ctx{"clusterNotification": clusterNotification, "newNetwork": newNetwork})
 
 	// When switching to a fan bridge, auto-detect the underlay if not specified.
 	if newNetwork.Config["bridge.mode"] == "fan" {
@@ -1364,7 +1364,7 @@ func (n *bridge) Update(newNetwork api.NetworkPut, clusterNotification bool) err
 	// Define a function which reverts everything.
 	revert.Add(func() {
 		// Reset changes to all nodes and database.
-		n.common.update(oldNetwork, clusterNotification)
+		n.common.update(oldNetwork, targetNode, clusterNotification)
 
 		// Reset any change that was made to local bridge.
 		n.setup(newNetwork.Config)
@@ -1402,7 +1402,7 @@ func (n *bridge) Update(newNetwork api.NetworkPut, clusterNotification bool) err
 	}
 
 	// Apply changes to database.
-	err = n.common.update(newNetwork, clusterNotification)
+	err = n.common.update(newNetwork, targetNode, clusterNotification)
 	if err != nil {
 		return err
 	}

From b49a9e7f7ce8fa6dd533667940abc564c41b0818 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:37:34 +0100
Subject: [PATCH 07/29] lxd/network/network/interface: Adds targetNode arg to
 Update

Signed-off-by: Thomas Parrott <thomas.parrott at 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 2691d424f9..06acb5192b 100644
--- a/lxd/network/network_interface.go
+++ b/lxd/network/network_interface.go
@@ -28,7 +28,7 @@ type Network interface {
 	Start() error
 	Stop() error
 	Rename(name string) error
-	Update(newNetwork api.NetworkPut, clusterNotification bool) error
+	Update(newNetwork api.NetworkPut, targetNode string, clusterNotification bool) error
 	HandleHeartbeat(heartbeatData *cluster.APIHeartbeat) error
 	Delete(clusterNotification bool) error
 }

From 75679a6773fd4fe489e168691b3860394fffc030 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:38:41 +0100
Subject: [PATCH 08/29] lxd/network/driver/common: Tweaks to update function in
 cluster environment

- Strip node-specific keys from forwarded request
- Only forward request when no target node specified

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_common.go | 38 ++++++++++++++++++++++++------------
 1 file changed, 26 insertions(+), 12 deletions(-)

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
index 1cff49e438..562a10cffb 100644
--- a/lxd/network/driver_common.go
+++ b/lxd/network/driver_common.go
@@ -10,6 +10,7 @@ import (
 
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
@@ -197,7 +198,7 @@ func (n *common) DHCPv6Ranges() []DHCPRange {
 }
 
 // update the internal config variables, and if not cluster notification, notifies all nodes and updates database.
-func (n *common) update(applyNetwork api.NetworkPut, clusterNotification bool) error {
+func (n *common) update(applyNetwork api.NetworkPut, targetNode string, clusterNotification bool) error {
 	// Update internal config before database has been updated (so that if update is a notification we apply
 	// the config being supplied and not that in the database).
 	n.description = applyNetwork.Description
@@ -206,21 +207,34 @@ func (n *common) update(applyNetwork api.NetworkPut, clusterNotification bool) e
 	// If this update isn't coming via a cluster notification itself, then notify all nodes of change and then
 	// update the database.
 	if !clusterNotification {
-		// Notify all other nodes to update the network.
-		notifier, err := cluster.NewNotifier(n.state, n.state.Endpoints.NetworkCert(), cluster.NotifyAll)
-		if err != nil {
-			return err
-		}
+		if targetNode == "" {
+			// Notify all other nodes to update the network if no target specified.
+			notifier, err := cluster.NewNotifier(n.state, n.state.Endpoints.NetworkCert(), cluster.NotifyAll)
+			if err != nil {
+				return err
+			}
 
-		err = notifier(func(client lxd.InstanceServer) error {
-			return client.UpdateNetwork(n.name, applyNetwork, "")
-		})
-		if err != nil {
-			return err
+			sendNetwork := applyNetwork
+			sendNetwork.Config = make(map[string]string)
+			for k, v := range applyNetwork.Config {
+				// Don't forward node specific keys (these will be merged in on recipient node).
+				if shared.StringInSlice(k, db.NodeSpecificNetworkConfig) {
+					continue
+				}
+
+				sendNetwork.Config[k] = v
+			}
+
+			err = notifier(func(client lxd.InstanceServer) error {
+				return client.UpdateNetwork(n.name, sendNetwork, "")
+			})
+			if err != nil {
+				return err
+			}
 		}
 
 		// Update the database.
-		err = n.state.Cluster.UpdateNetwork(n.name, applyNetwork.Description, applyNetwork.Config)
+		err := n.state.Cluster.UpdateNetwork(n.name, applyNetwork.Description, applyNetwork.Config)
 		if err != nil {
 			return err
 		}

From 599979bfdfcaa4860489cc7cd920e085b6aa1dc6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:40:34 +0100
Subject: [PATCH 09/29] lxd/networks: networksPost error response tweaks

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

diff --git a/lxd/networks.go b/lxd/networks.go
index e45880cc4b..7848fed03b 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -149,7 +149,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		// Check that only NodeSpecificNetworkConfig keys are specified.
 		for key := range req.Config {
 			if !shared.StringInSlice(key, db.NodeSpecificNetworkConfig) {
-				return response.SmartError(fmt.Errorf("Config key %q may not be used as node-specific key", key))
+				return response.BadRequest(fmt.Errorf("Config key %q may not be used as node-specific key", key))
 			}
 		}
 
@@ -158,7 +158,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		})
 		if err != nil {
 			if err == db.ErrAlreadyDefined {
-				return response.BadRequest(fmt.Errorf("The network already defined on node %q", targetNode))
+				return response.BadRequest(fmt.Errorf("The network is already defined on node %q", targetNode))
 			}
 			return response.SmartError(err)
 		}

From d760c286d58b5b01d776a6e886dfe249f657f3c1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:41:26 +0100
Subject: [PATCH 10/29] lxd/networks: Updates networksPostCluster

- Checks network type being created matches the pending network type.
- Adds revert in place of goto.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 45 +++++++++++++++++++++++++++++----------------
 1 file changed, 29 insertions(+), 16 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 7848fed03b..07aaa25002 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -21,6 +21,7 @@ import (
 	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/revert"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -218,8 +219,19 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 		}
 	}
 
+	// 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(req.Name)
+	if err != nil && err != db.ErrNoSuchObject {
+		return err
+	}
+
+	if err != db.ErrNoSuchObject && req.Type != netInfo.Type {
+		return fmt.Errorf("Requested network type %q doesn't match type in existing database record %q", req.Type, netInfo.Type)
+	}
+
 	// Add default values.
-	err := network.FillConfig(&req)
+	err = network.FillConfig(&req)
 	if err != nil {
 		return err
 	}
@@ -254,6 +266,7 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 		if err == db.ErrNoSuchObject {
 			return fmt.Errorf("Network not pending on any node (use --target <node> first)")
 		}
+
 		return err
 	}
 
@@ -269,13 +282,22 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 		return err
 	}
 
+	revert := revert.New()
+	defer revert.Fail()
+
+	revert.Add(func() {
+		d.cluster.Transaction(func(tx *db.ClusterTx) error {
+			return tx.NetworkErrored(req.Name)
+		})
+	})
+
 	// We need to mark the network as created now, because the network.LoadByName call invoked by
 	// doNetworksCreate would fail with not-found otherwise.
-	createErr := d.cluster.Transaction(func(tx *db.ClusterTx) error {
+	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		return tx.NetworkCreated(req.Name)
 	})
-	if createErr != nil {
-		goto error
+	if err != nil {
+		return err
 	}
 
 	err = doNetworksCreate(d, nodeReq, false)
@@ -283,7 +305,7 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 		return err
 	}
 
-	createErr = notifier(func(client lxd.InstanceServer) error {
+	err = notifier(func(client lxd.InstanceServer) error {
 		server, _, err := client.GetServer()
 		if err != nil {
 			return err
@@ -296,21 +318,12 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 
 		return client.CreateNetwork(nodeReq)
 	})
-	if createErr != nil {
-		goto error
-	}
-
-	return nil
-
-error:
-	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
-		return tx.NetworkErrored(req.Name)
-	})
 	if err != nil {
 		return err
 	}
 
-	return createErr
+	revert.Success()
+	return nil
 }
 
 // Create the network on the system. The clusterNotification flag is used to indicate whether creation request

From b6c96e047df4166c2c82d7e30252efad6fbf1a85 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 21 Jul 2020 13:42:54 +0100
Subject: [PATCH 11/29] lxd/networks: Unifies networkPut and networkPatch

- Calls networkPut from networkPatch, as both behave the same now.
- In order to accomodate forwarding clustered network updates and per-node validation, the existing per-node network config is now merged with the requested updated keys, and then fed into the network.Update() function.
- This means that put and patch are now functionality equivalent.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 97 +++++++++++++++++++++++--------------------------
 1 file changed, 46 insertions(+), 51 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 07aaa25002..18a925945d 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -601,9 +601,15 @@ func networkPost(d *Daemon, r *http.Request) response.Response {
 }
 
 func networkPut(d *Daemon, r *http.Request) response.Response {
+	// If a target was specified, forward the request to the relevant node.
+	resp := forwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
+	}
+
 	name := mux.Vars(r)["name"]
 
-	// Get the existing network
+	// Get the existing network.
 	_, dbInfo, err := d.cluster.GetNetworkInAnyState(name)
 	if err != nil {
 		return response.SmartError(err)
@@ -615,95 +621,84 @@ func networkPut(d *Daemon, r *http.Request) response.Response {
 		return response.SmartError(err)
 	}
 
-	// If no target node is specified and the daemon is clustered, we omit
-	// the node-specific fields.
+	// If no target node is specified and the daemon is clustered, we omit the node-specific fields so that
+	// the e-tag can be generated correctly. This is because the GET request used to populate the request
+	// will also remove node-specific keys when no target is specified.
 	if targetNode == "" && clustered {
 		for _, key := range db.NodeSpecificNetworkConfig {
 			delete(dbInfo.Config, key)
 		}
 	}
 
-	// Validate the ETag
+	// Validate the ETag.
 	etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, dbInfo.Description, dbInfo.Config}
-
 	err = util.EtagCheck(r, etag)
 	if err != nil {
 		return response.PreconditionFailed(err)
 	}
 
+	// Decode the request.
 	req := api.NetworkPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return response.BadRequest(err)
 	}
 
-	return doNetworkUpdate(d, name, dbInfo.Config, req, isClusterNotification(r))
-}
-
-func networkPatch(d *Daemon, r *http.Request) response.Response {
-	name := mux.Vars(r)["name"]
-
-	// Get the existing network
-	_, dbInfo, err := d.cluster.GetNetworkInAnyState(name)
-	if err != nil {
-		return response.SmartError(err)
-	}
-
-	targetNode := queryParam(r, "target")
-	clustered, err := cluster.Enabled(d.db)
-	if err != nil {
-		return response.SmartError(err)
-	}
-
-	// If no target node is specified and the daemon is clustered, we omit
-	// the node-specific fields.
-	if targetNode == "" && clustered {
-		for _, key := range db.NodeSpecificNetworkConfig {
-			delete(dbInfo.Config, key)
+	// In clustered mode, we differentiate between node specific and non-node specific config keys based on
+	// whether the user has specified a target to apply the config to.
+	if clustered {
+		if targetNode == "" {
+			// If no target is specified, then ensure only non-node-specific config keys are changed.
+			for k := range req.Config {
+				if shared.StringInSlice(k, db.NodeSpecificNetworkConfig) {
+					return response.BadRequest(fmt.Errorf("Config key %q is node-specific", k))
+				}
+			}
+		} else {
+			// If a target is specified, then ensure only node-specific config keys are changed.
+			for k, v := range req.Config {
+				if !shared.StringInSlice(k, db.NodeSpecificNetworkConfig) && dbInfo.Config[k] != v {
+					return response.BadRequest(fmt.Errorf("Config key %q may not be used as node-specific key", k))
+				}
+			}
 		}
 	}
 
-	// Validate the ETag
-	etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, dbInfo.Description, dbInfo.Config}
+	return doNetworkUpdate(d, name, req, targetNode, isClusterNotification(r))
+}
 
-	err = util.EtagCheck(r, etag)
-	if err != nil {
-		return response.PreconditionFailed(err)
-	}
+func networkPatch(d *Daemon, r *http.Request) response.Response {
+	return networkPut(d, r)
+}
 
-	req := api.NetworkPut{}
-	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return response.BadRequest(err)
+// doNetworkUpdate loads the current local network config, merges with the requested network config, validates
+// and applies the changes. Will also notify other cluster nodes of non-node specific config if needed.
+func doNetworkUpdate(d *Daemon, name string, req api.NetworkPut, targetNode string, clusterNotification bool) response.Response {
+	// Load the local node-specific network.
+	n, err := network.LoadByName(d.State(), name)
+	if err != nil {
+		return response.NotFound(err)
 	}
 
-	// Config stacking
 	if req.Config == nil {
 		req.Config = map[string]string{}
 	}
 
-	for k, v := range dbInfo.Config {
+	// Merge the current node-specific network config with the submitted config to allow validation.
+	for k, v := range n.Config() {
 		_, ok := req.Config[k]
 		if !ok {
 			req.Config[k] = v
 		}
 	}
 
-	return doNetworkUpdate(d, name, dbInfo.Config, req, isClusterNotification(r))
-}
-
-func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req api.NetworkPut, clusterNotification bool) response.Response {
-	// Load the network
-	n, err := network.LoadByName(d.State(), name)
-	if err != nil {
-		return response.NotFound(err)
-	}
-
-	// Validate the configuration
+	// Validate the merged configuration.
 	err = network.Validate(name, n.Type(), req.Config)
 	if err != nil {
 		return response.BadRequest(err)
 	}
 
-	err = n.Update(req, clusterNotification)
+	// Apply the new configuration (will also notify other cluster nodes if needed).
+	err = n.Update(req, targetNode, clusterNotification)
 	if err != nil {
 		return response.SmartError(err)
 	}

From a396aa82b850ce3b192d2dc103abf9164c255e24 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:51:18 +0100
Subject: [PATCH 12/29] lxd/device/nictype: Adds small package to resolve NIC
 device nictype from network

This is a separate package due to various circular dependency issues preventing it being added to network, device, device/config, db and state packages.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nictype/nictype.go | 45 +++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 lxd/device/nictype/nictype.go

diff --git a/lxd/device/nictype/nictype.go b/lxd/device/nictype/nictype.go
new file mode 100644
index 0000000000..fb8e02530d
--- /dev/null
+++ b/lxd/device/nictype/nictype.go
@@ -0,0 +1,45 @@
+// Package nictype is a small package to allow resolving NIC "network" key to "nictype" key.
+// It is it's own package to avoid circular dependency issues.
+package nictype
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+
+	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/state"
+)
+
+// NICType resolves the NIC Type for the supplied NIC device config.
+// If the device "type" is "nic" and the "network" property is specified in the device config, then NIC type is
+// resolved from the network's type. Otherwise the device's "nictype" property is returned (which may be empty if
+// used with non-NIC device configs).
+func NICType(s *state.State, d deviceConfig.Device) (string, error) {
+	// NIC devices support resolving their "nictype" from their "network" property.
+	if d["type"] == "nic" {
+		if d["network"] != "" {
+			_, netInfo, err := s.Cluster.GetNetworkInAnyState(d["network"])
+			if err != nil {
+				return "", errors.Wrapf(err, "Failed to load network %q", d["network"])
+			}
+
+			var nicType string
+			switch netInfo.Type {
+			case "bridge":
+				nicType = "bridged"
+			case "macvlan":
+				nicType = "macvlan"
+			default:
+				return "", fmt.Errorf("Unrecognised NIC network type for network %q", d["network"])
+			}
+
+			return nicType, nil
+		}
+
+	}
+
+	// Infiniband devices use "nictype" without supporting "network" property, so just return it directly,
+	// which is the same as accessing the property directly from the config.
+	return d["nictype"], nil
+}

From 03fe1fa219c10d156377ed4ed5676f65ac9b55ca Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:28:50 +0100
Subject: [PATCH 13/29] lxd/device/config/devices: Removes NICType

Will be moved to own package to allow network DB lookups.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/config/devices.go | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/lxd/device/config/devices.go b/lxd/device/config/devices.go
index d574e19adc..f6ec31c1e4 100644
--- a/lxd/device/config/devices.go
+++ b/lxd/device/config/devices.go
@@ -19,21 +19,6 @@ func (device Device) Clone() Device {
 	return copy
 }
 
-// NICType returns the derived NIC Type for a NIC device.
-// If the "network" property is specified then this implicitly (at least for now) means the nictype is "bridged".
-// Otherwise the "nictype" property is returned. If the device type is not a NIC then an empty string is returned.
-func (device Device) NICType() string {
-	if device["type"] == "nic" {
-		if device["network"] != "" {
-			return "bridged"
-		}
-
-		return device["nictype"]
-	}
-
-	return ""
-}
-
 // Validate accepts a map of field/validation functions to run against the device's config.
 func (device Device) Validate(rules map[string]func(value string) error) error {
 	checkedFields := map[string]struct{}{}

From cd2125c4dcde9b49b719d23c47704d676315c41f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:29:24 +0100
Subject: [PATCH 14/29] lxd/device/config/devices: Improves comment on Update

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/config/devices.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/device/config/devices.go b/lxd/device/config/devices.go
index f6ec31c1e4..be805f9c88 100644
--- a/lxd/device/config/devices.go
+++ b/lxd/device/config/devices.go
@@ -84,7 +84,8 @@ func (list Devices) Contains(k string, d Device) bool {
 	return deviceEquals(old, d)
 }
 
-// Update returns the difference between two sets
+// Update returns the difference between two sets. Accepts a function to detect which devices have been updated,
+// which prevents them being removed and re-added if they're config has changed, but the device supports hot plug.
 func (list Devices) Update(newlist Devices, updateFields func(Device, Device) []string) (map[string]Device, map[string]Device, map[string]Device, []string) {
 	rmlist := map[string]Device{}
 	addlist := map[string]Device{}

From 09c05d0d0e7086113ca4bf5bf143cca1db94808c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:37:24 +0100
Subject: [PATCH 15/29] lxd/device/device/load: Removes devTypes map and
 updates load to use NICType function

Switches to an multi-level case statement inside `load` to simplify what was starting to become an overcomplicated device load process, due to the new requirement to perform a DB lookup when resolving NIC types.

This approach keeps all instantiation of different device types together in one place, and combined with the removal of the helper functions is equivalent amounts of code.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_load.go | 68 +++++++++++++++++++++++++++------------
 1 file changed, 48 insertions(+), 20 deletions(-)

diff --git a/lxd/device/device_load.go b/lxd/device/device_load.go
index 1227dba7bb..1a76ee70e7 100644
--- a/lxd/device/device_load.go
+++ b/lxd/device/device_load.go
@@ -4,39 +4,67 @@ import (
 	"fmt"
 
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/state"
 )
 
-// devTypes defines supported top-level device type creation functions.
-var devTypes = map[string]func(deviceConfig.Device) device{
-	"nic":          nicLoadByType,
-	"infiniband":   infinibandLoadByType,
-	"proxy":        func(c deviceConfig.Device) device { return &proxy{} },
-	"gpu":          func(c deviceConfig.Device) device { return &gpu{} },
-	"usb":          func(c deviceConfig.Device) device { return &usb{} },
-	"unix-char":    func(c deviceConfig.Device) device { return &unixCommon{} },
-	"unix-block":   func(c deviceConfig.Device) device { return &unixCommon{} },
-	"unix-hotplug": func(c deviceConfig.Device) device { return &unixHotplug{} },
-	"disk":         func(c deviceConfig.Device) device { return &disk{} },
-	"none":         func(c deviceConfig.Device) device { return &none{} },
-}
-
 // load instantiates a device and initialises its internal state. It does not validate the config supplied.
 func load(inst instance.Instance, state *state.State, name string, conf deviceConfig.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) (device, error) {
 	if conf["type"] == "" {
 		return nil, fmt.Errorf("Missing device type for device %q", name)
 	}
 
-	devFunc := devTypes[conf["type"]]
+	// NIC type is required to lookup network devices.
+	nicType, err := nictype.NICType(state, conf)
+	if err != nil {
+		return nil, err
+	}
 
-	// Check if top-level type is recognised, if it is known type it will return a function.
-	if devFunc == nil {
-		return nil, ErrUnsupportedDevType
+	// Lookup device type implementation.
+	var dev device
+	switch conf["type"] {
+	case "nic":
+		switch nicType {
+		case "physical":
+			dev = &nicPhysical{}
+		case "ipvlan":
+			dev = &nicIPVLAN{}
+		case "p2p":
+			dev = &nicP2P{}
+		case "bridged":
+			dev = &nicBridged{}
+		case "routed":
+			dev = &nicRouted{}
+		case "macvlan":
+			dev = &nicMACVLAN{}
+		case "sriov":
+			dev = &nicSRIOV{}
+		}
+	case "infiniband":
+		switch nicType {
+		case "physical":
+			dev = &infinibandPhysical{}
+		case "sriov":
+			dev = &infinibandSRIOV{}
+		}
+	case "proxy":
+		dev = &proxy{}
+	case "gpu":
+		dev = &gpu{}
+	case "usb":
+		dev = &usb{}
+	case "unix-char", "unix-block":
+		dev = &unixCommon{}
+	case "unix-hotplig":
+		dev = &unixHotplug{}
+	case "disk":
+		dev = &disk{}
+	case "none":
+		dev = &none{}
 	}
 
-	// Run the device create function and check it succeeds.
-	dev := devFunc(conf)
+	// Check a valid device type has been found.
 	if dev == nil {
 		return nil, ErrUnsupportedDevType
 	}

From 743bc197973a78d75e36c153dac96f2bf720dc77 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:39:42 +0100
Subject: [PATCH 16/29] lxd/device: Removes device load helpers

No longer needed as have combined all device type loading in the `load` function.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/infiniband.go | 20 --------------------
 lxd/device/nic.go        | 21 ---------------------
 2 files changed, 41 deletions(-)
 delete mode 100644 lxd/device/infiniband.go

diff --git a/lxd/device/infiniband.go b/lxd/device/infiniband.go
deleted file mode 100644
index adcb411c87..0000000000
--- a/lxd/device/infiniband.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package device
-
-import (
-	deviceConfig "github.com/lxc/lxd/lxd/device/config"
-)
-
-// infinibandTypes defines the supported infiniband type devices and defines their creation functions.
-var infinibandTypes = map[string]func() device{
-	"physical": func() device { return &infinibandPhysical{} },
-	"sriov":    func() device { return &infinibandSRIOV{} },
-}
-
-// infinibandLoadByType returns an Infiniband device instantiated with supplied config.
-func infinibandLoadByType(c deviceConfig.Device) device {
-	f := infinibandTypes[c.NICType()]
-	if f != nil {
-		return f()
-	}
-	return nil
-}
diff --git a/lxd/device/nic.go b/lxd/device/nic.go
index 8c6184028d..b50c19138f 100644
--- a/lxd/device/nic.go
+++ b/lxd/device/nic.go
@@ -1,30 +1,9 @@
 package device
 
 import (
-	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared"
 )
 
-// nicTypes defines the supported nic type devices and defines their creation functions.
-var nicTypes = map[string]func() device{
-	"physical": func() device { return &nicPhysical{} },
-	"ipvlan":   func() device { return &nicIPVLAN{} },
-	"p2p":      func() device { return &nicP2P{} },
-	"bridged":  func() device { return &nicBridged{} },
-	"routed":   func() device { return &nicRouted{} },
-	"macvlan":  func() device { return &nicMACVLAN{} },
-	"sriov":    func() device { return &nicSRIOV{} },
-}
-
-// nicLoadByType returns a NIC device instantiated with supplied config.
-func nicLoadByType(c deviceConfig.Device) device {
-	f := nicTypes[c.NICType()]
-	if f != nil {
-		return f()
-	}
-	return nil
-}
-
 // nicValidationRules returns config validation rules for nic devices.
 func nicValidationRules(requiredFields []string, optionalFields []string) map[string]func(value string) error {
 	// Define a set of default validators for each field name.

From cd2316c8bff8adc75c975c7cf6a944d84ed9bb71 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:41:36 +0100
Subject: [PATCH 17/29] lxd/device/device/utils/network: nictype.NICType usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_network.go | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index ed46a4a964..935edc043c 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -14,6 +14,7 @@ import (
 	"github.com/pkg/errors"
 
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/network"
@@ -335,7 +336,7 @@ func networkCreateTap(hostName string, m deviceConfig.Device) error {
 }
 
 // networkSetupHostVethDevice configures a nic device's host side veth settings.
-func networkSetupHostVethDevice(device deviceConfig.Device, oldDevice deviceConfig.Device, v map[string]string) error {
+func networkSetupHostVethDevice(s *state.State, device deviceConfig.Device, oldDevice deviceConfig.Device, v map[string]string) error {
 	// If not configured, check if volatile data contains the most recently added host_name.
 	if device["host_name"] == "" {
 		device["host_name"] = v["host_name"]
@@ -369,11 +370,11 @@ func networkSetupHostVethDevice(device deviceConfig.Device, oldDevice deviceConf
 			oldDevice["hwaddr"] = v["hwaddr"]
 		}
 
-		networkRemoveVethRoutes(oldDevice)
+		networkRemoveVethRoutes(s, oldDevice)
 	}
 
 	// Setup static routes to container.
-	err = networkSetVethRoutes(device)
+	err = networkSetVethRoutes(s, device)
 	if err != nil {
 		return err
 	}
@@ -382,10 +383,16 @@ func networkSetupHostVethDevice(device deviceConfig.Device, oldDevice deviceConf
 }
 
 // networkSetVethRoutes applies any static routes configured from the host to the container nic.
-func networkSetVethRoutes(m deviceConfig.Device) error {
+func networkSetVethRoutes(s *state.State, m deviceConfig.Device) error {
 	// Decide whether the route should point to the veth parent or the bridge parent.
 	routeDev := m["host_name"]
-	if m.NICType() == "bridged" {
+
+	nicType, err := nictype.NICType(s, m)
+	if err != nil {
+		return err
+	}
+
+	if nicType == "bridged" {
 		routeDev = m["parent"]
 	}
 
@@ -420,16 +427,22 @@ func networkSetVethRoutes(m deviceConfig.Device) error {
 
 // networkRemoveVethRoutes removes any routes created for this device on the host that were first added
 // with networkSetVethRoutes(). Expects to be passed the device config from the oldExpandedDevices.
-func networkRemoveVethRoutes(m deviceConfig.Device) {
+func networkRemoveVethRoutes(s *state.State, m deviceConfig.Device) {
 	// Decide whether the route should point to the veth parent or the bridge parent
 	routeDev := m["host_name"]
-	if m.NICType() == "bridged" {
+	nicType, err := nictype.NICType(s, m)
+	if err != nil {
+		logger.Errorf("Failed to get NIC type for %q", m["name"])
+		return
+	}
+
+	if nicType == "bridged" {
 		routeDev = m["parent"]
 	}
 
 	if m["ipv4.routes"] != "" || m["ipv6.routes"] != "" {
 		if routeDev == "" {
-			logger.Errorf("Failed to remove static routes as route dev isn't set")
+			logger.Errorf("Failed to remove static routes as route dev isn't set for %q", m["name"])
 			return
 		}
 

From aec6803b6451909578c23f061684366658a6809e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:42:45 +0100
Subject: [PATCH 18/29] lxd/device/nic/bridged: Updates usage of functions whos
 signatures changed due to NICType

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index eed02222de..2922af38b7 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -265,7 +265,7 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 	revert.Add(func() { NetworkRemoveInterface(saveData["host_name"]) })
 
 	// Apply and host-side limits and routes.
-	err = networkSetupHostVethDevice(d.config, nil, saveData)
+	err = networkSetupHostVethDevice(d.state, d.config, nil, saveData)
 	if err != nil {
 		return nil, err
 	}
@@ -358,7 +358,7 @@ func (d *nicBridged) Update(oldDevices deviceConfig.Devices, isRunning bool) err
 		}
 
 		// Apply and host-side limits and routes.
-		err = networkSetupHostVethDevice(d.config, oldConfig, v)
+		err = networkSetupHostVethDevice(d.state, d.config, oldConfig, v)
 		if err != nil {
 			return err
 		}
@@ -432,7 +432,7 @@ func (d *nicBridged) postStop() error {
 		}
 	}
 
-	networkRemoveVethRoutes(d.config)
+	networkRemoveVethRoutes(d.state, d.config)
 	d.removeFilters(d.config)
 
 	return nil

From f43726ef96a204045ef75ef1c26bc6b235d0a341 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:43:29 +0100
Subject: [PATCH 19/29] lxd/device/nic/p2p: Updates usage of functions that
 changed signature due to NICType

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

diff --git a/lxd/device/nic_p2p.go b/lxd/device/nic_p2p.go
index ea2e2bae4c..341ae1ea13 100644
--- a/lxd/device/nic_p2p.go
+++ b/lxd/device/nic_p2p.go
@@ -85,7 +85,7 @@ func (d *nicP2P) Start() (*deviceConfig.RunConfig, error) {
 	}
 
 	// Apply and host-side limits and routes.
-	err = networkSetupHostVethDevice(d.config, nil, saveData)
+	err = networkSetupHostVethDevice(d.state, d.config, nil, saveData)
 	if err != nil {
 		NetworkRemoveInterface(saveData["host_name"])
 		return nil, err
@@ -131,7 +131,7 @@ func (d *nicP2P) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
 	v := d.volatileGet()
 
 	// Apply and host-side limits and routes.
-	err = networkSetupHostVethDevice(d.config, oldConfig, v)
+	err = networkSetupHostVethDevice(d.state, d.config, oldConfig, v)
 	if err != nil {
 		return err
 	}

From 8aaa5d952a4954b52d67b4900dcadb6da0fc0b70 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:44:05 +0100
Subject: [PATCH 20/29] lxd/device/proxy: nictype.NICType usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/proxy.go | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/lxd/device/proxy.go b/lxd/device/proxy.go
index 39958f4891..cc1cd2b41e 100644
--- a/lxd/device/proxy.go
+++ b/lxd/device/proxy.go
@@ -17,6 +17,7 @@ import (
 	liblxc "gopkg.in/lxc/go-lxc.v2"
 
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/project"
@@ -322,7 +323,16 @@ func (d *proxy) setupNAT() error {
 	var hostName string
 
 	for devName, devConfig := range d.inst.ExpandedDevices() {
-		if devConfig["type"] != "nic" || (devConfig["type"] == "nic" && devConfig.NICType() != "bridged") {
+		if devConfig["type"] != "nic" {
+			continue
+		}
+
+		nicType, err := nictype.NICType(d.state, devConfig)
+		if err != nil {
+			return err
+		}
+
+		if nicType != "bridged" {
 			continue
 		}
 

From 6b891f8d160ceb79b6ce03c3eec6ce8713a108c6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:45:15 +0100
Subject: [PATCH 21/29] lxd/instance/drivers/driver/lxc: nictype.NICType usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance/drivers/driver_lxc.go | 33 +++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index d0a5d59ead..12fd09298d 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -33,6 +33,7 @@ import (
 	"github.com/lxc/lxd/lxd/db/query"
 	"github.com/lxc/lxd/lxd/device"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/instance/operationlock"
@@ -1766,10 +1767,20 @@ func (c *lxc) deviceResetVolatile(devName string, oldConfig, newConfig deviceCon
 	volatileClear := make(map[string]string)
 	devicePrefix := fmt.Sprintf("volatile.%s.", devName)
 
+	newNICType, err := nictype.NICType(c.state, newConfig)
+	if err != nil {
+		return err
+	}
+
+	oldNICType, err := nictype.NICType(c.state, oldConfig)
+	if err != nil {
+		return err
+	}
+
 	// If the device type has changed, remove all old volatile keys.
 	// This will occur if the newConfig is empty (i.e the device is actually being removed) or
 	// if the device type is being changed but keeping the same name.
-	if newConfig["type"] != oldConfig["type"] || newConfig.NICType() != oldConfig.NICType() {
+	if newConfig["type"] != oldConfig["type"] || newNICType != oldNICType {
 		for k := range c.localConfig {
 			if !strings.HasPrefix(k, devicePrefix) {
 				continue
@@ -3962,7 +3973,18 @@ func (c *lxc) Update(args db.InstanceArgs, userRequested bool) error {
 		// between oldDevice and newDevice. The result of this is that as long as the
 		// devices are otherwise identical except for the fields returned here, then the
 		// device is considered to be being "updated" rather than "added & removed".
-		if oldDevice["type"] != newDevice["type"] || oldDevice.NICType() != newDevice.NICType() {
+
+		oldNICType, err := nictype.NICType(c.state, newDevice)
+		if err != nil {
+			return []string{} // Cannot hot-update due to config error.
+		}
+
+		newNICType, err := nictype.NICType(c.state, oldDevice)
+		if err != nil {
+			return []string{} // Cannot hot-update due to config error.
+		}
+
+		if oldDevice["type"] != newDevice["type"] || oldNICType != newNICType {
 			return []string{} // Device types aren't the same, so this cannot be an update.
 		}
 
@@ -6363,8 +6385,13 @@ func (c *lxc) FillNetworkDevice(name string, m deviceConfig.Device) (deviceConfi
 		return nil
 	}
 
+	nicType, err := nictype.NICType(c.state, m)
+	if err != nil {
+		return nil, err
+	}
+
 	// Fill in the MAC address
-	if !shared.StringInSlice(m.NICType(), []string{"physical", "ipvlan", "sriov"}) && m["hwaddr"] == "" {
+	if !shared.StringInSlice(nicType, []string{"physical", "ipvlan", "sriov"}) && m["hwaddr"] == "" {
 		configKey := fmt.Sprintf("volatile.%s.hwaddr", name)
 		volatileHwaddr := c.localConfig[configKey]
 		if volatileHwaddr == "" {

From 2ef3fd2d6e1cee4f257674dd8db1870a369df2d6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:45:45 +0100
Subject: [PATCH 22/29] lxd/instance/drivers/driver/qemu: nictype.NICType usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance/drivers/driver_qemu.go | 39 ++++++++++++++++++++++++++---
 1 file changed, 35 insertions(+), 4 deletions(-)

diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index bf38aeefdd..5e671ceb52 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -33,6 +33,7 @@ import (
 	"github.com/lxc/lxd/lxd/db/query"
 	"github.com/lxc/lxd/lxd/device"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/drivers/qmp"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
@@ -2896,7 +2897,17 @@ func (vm *qemu) Update(args db.InstanceArgs, userRequested bool) error {
 		// between oldDevice and newDevice. The result of this is that as long as the
 		// devices are otherwise identical except for the fields returned here, then the
 		// device is considered to be being "updated" rather than "added & removed".
-		if oldDevice["type"] != newDevice["type"] || oldDevice.NICType() != newDevice.NICType() {
+		oldNICType, err := nictype.NICType(vm.state, newDevice)
+		if err != nil {
+			return []string{} // Cannot hot-update due to config error.
+		}
+
+		newNICType, err := nictype.NICType(vm.state, oldDevice)
+		if err != nil {
+			return []string{} // Cannot hot-update due to config error.
+		}
+
+		if oldDevice["type"] != newDevice["type"] || oldNICType != newNICType {
 			return []string{} // Device types aren't the same, so this cannot be an update.
 		}
 
@@ -3076,10 +3087,20 @@ func (vm *qemu) deviceResetVolatile(devName string, oldConfig, newConfig deviceC
 	volatileClear := make(map[string]string)
 	devicePrefix := fmt.Sprintf("volatile.%s.", devName)
 
+	newNICType, err := nictype.NICType(vm.state, newConfig)
+	if err != nil {
+		return err
+	}
+
+	oldNICType, err := nictype.NICType(vm.state, oldConfig)
+	if err != nil {
+		return err
+	}
+
 	// If the device type has changed, remove all old volatile keys.
 	// This will occur if the newConfig is empty (i.e the device is actually being removed) or
 	// if the device type is being changed but keeping the same name.
-	if newConfig["type"] != oldConfig["type"] || newConfig.NICType() != oldConfig.NICType() {
+	if newConfig["type"] != oldConfig["type"] || newNICType != oldNICType {
 		for k := range vm.localConfig {
 			if !strings.HasPrefix(k, devicePrefix) {
 				continue
@@ -4036,9 +4057,14 @@ func (vm *qemu) RenderState() (*api.InstanceState, error) {
 			status.Processes = -1
 			networks := map[string]api.InstanceStateNetwork{}
 			for k, m := range vm.ExpandedDevices() {
+				nicType, err := nictype.NICType(vm.state, m)
+				if err != nil {
+					return nil, err
+				}
+
 				// We only care about bridged nics as these can use a local DHCP server that allows
 				// us to parse the leases file below.
-				if m["type"] != "nic" || m.NICType() != "bridged" {
+				if m["type"] != "nic" || nicType != "bridged" {
 					continue
 				}
 
@@ -4429,8 +4455,13 @@ func (vm *qemu) FillNetworkDevice(name string, m deviceConfig.Device) (deviceCon
 		return nil
 	}
 
+	nicType, err := nictype.NICType(vm.state, m)
+	if err != nil {
+		return nil, err
+	}
+
 	// Fill in the MAC address
-	if !shared.StringInSlice(m.NICType(), []string{"physical", "ipvlan", "sriov"}) && m["hwaddr"] == "" {
+	if !shared.StringInSlice(nicType, []string{"physical", "ipvlan", "sriov"}) && m["hwaddr"] == "" {
 		configKey := fmt.Sprintf("volatile.%s.hwaddr", name)
 		volatileHwaddr := vm.localConfig[configKey]
 		if volatileHwaddr == "" {

From 89b377dc964559bab3ce0537df355c933e181678 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:46:22 +0100
Subject: [PATCH 23/29] lxd/network/driver/bridge: Usage of functions that
 changed signature due to NICType

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_bridge.go | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index aaa64dda15..934bfba113 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -297,7 +297,12 @@ func (n *bridge) Rename(newName string) error {
 	n.logger.Debug("Rename", log.Ctx{"newName": newName})
 
 	// Sanity checks.
-	if n.IsUsed() {
+	inUse, err := n.IsUsed()
+	if err != nil {
+		return err
+	}
+
+	if inUse {
 		return fmt.Errorf("The network is currently in use")
 	}
 
@@ -319,7 +324,7 @@ func (n *bridge) Rename(newName string) error {
 	}
 
 	// Rename common steps.
-	err := n.common.rename(newName)
+	err = n.common.rename(newName)
 	if err != nil {
 		return err
 	}

From 72229310afa156abc3e4e5964b1976958078840e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:47:29 +0100
Subject: [PATCH 24/29] lxd/network/driver/common: Updates IsUsed for NICType
 signature changes and checks for profile usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_common.go | 44 ++++++++++++++++++++++++++++++------
 1 file changed, 37 insertions(+), 7 deletions(-)

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
index 562a10cffb..b79735c336 100644
--- a/lxd/network/driver_common.go
+++ b/lxd/network/driver_common.go
@@ -119,21 +119,51 @@ func (n *common) Config() map[string]string {
 	return n.config
 }
 
-// IsUsed returns whether the network is used by any instances.
-func (n *common) IsUsed() bool {
-	// Look for instances using the interface
+// IsUsed returns whether the network is used by any instances or profiles.
+func (n *common) IsUsed() (bool, error) {
+	// Look for instances using the network.
 	insts, err := instance.LoadFromAllProjects(n.state)
 	if err != nil {
-		return true
+		return false, err
 	}
 
 	for _, inst := range insts {
-		if IsInUseByInstance(inst, n.name) {
-			return true
+		inUse, err := IsInUseByInstance(n.state, inst, n.name)
+		if err != nil {
+			return false, err
+		}
+
+		if inUse {
+			return true, nil
 		}
 	}
 
-	return false
+	// Look for profiles using the network.
+	var profiles []db.Profile
+	err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
+		profiles, err = tx.GetProfiles(db.ProfileFilter{})
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		return false, err
+	}
+
+	for _, profile := range profiles {
+		inUse, err := IsInUseByProfile(n.state, *db.ProfileToAPI(&profile), n.name)
+		if err != nil {
+			return false, err
+		}
+
+		if inUse {
+			return true, nil
+		}
+	}
+
+	return false, nil
 }
 
 // HasDHCPv4 indicates whether the network has DHCPv4 enabled.

From e8d5654ade1d45ad553e706ed8b0a935d5eca982 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:48:40 +0100
Subject: [PATCH 25/29] lxd/network/network/interface: Signature change of
 IsUsed to accomodate NICType

Signed-off-by: Thomas Parrott <thomas.parrott at 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 06acb5192b..1a558b380d 100644
--- a/lxd/network/network_interface.go
+++ b/lxd/network/network_interface.go
@@ -18,7 +18,7 @@ type Network interface {
 	Type() string
 	Status() string
 	Config() map[string]string
-	IsUsed() bool
+	IsUsed() (bool, error)
 	HasDHCPv4() bool
 	HasDHCPv6() bool
 	DHCPv4Ranges() []DHCPRange

From 34152a6b173c348d4a20d4d155ece80ab761d537 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:49:22 +0100
Subject: [PATCH 26/29] lxd/network/network/utils: Usage of nictype.NICType and
 signature changes to accomodate it

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_utils.go | 29 ++++++++++++++++++++---------
 1 file changed, 20 insertions(+), 9 deletions(-)

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index 7f47cf77e4..0706d0a542 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -20,6 +20,7 @@ import (
 	"github.com/pkg/errors"
 
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/dnsmasq"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
@@ -74,23 +75,28 @@ func networkValidPort(value string) error {
 
 // IsInUseByInstance indicates if network is referenced by an instance's NIC devices.
 // Checks if the device's parent or network properties match the network name.
-func IsInUseByInstance(c instance.Instance, networkName string) bool {
-	return isInUseByDevices(c.ExpandedDevices(), networkName)
+func IsInUseByInstance(s *state.State, c instance.Instance, networkName string) (bool, error) {
+	return isInUseByDevices(s, c.ExpandedDevices(), networkName)
 }
 
 // IsInUseByProfile indicates if network is referenced by a profile's NIC devices.
 // Checks if the device's parent or network properties match the network name.
-func IsInUseByProfile(profile api.Profile, networkName string) bool {
-	return isInUseByDevices(deviceConfig.NewDevices(profile.Devices), networkName)
+func IsInUseByProfile(s *state.State, profile api.Profile, networkName string) (bool, error) {
+	return isInUseByDevices(s, deviceConfig.NewDevices(profile.Devices), networkName)
 }
 
-func isInUseByDevices(devices deviceConfig.Devices, networkName string) bool {
+func isInUseByDevices(s *state.State, devices deviceConfig.Devices, networkName string) (bool, error) {
 	for _, d := range devices {
 		if d["type"] != "nic" {
 			continue
 		}
 
-		if !shared.StringInSlice(d.NICType(), []string{"bridged", "macvlan", "ipvlan", "physical", "sriov"}) {
+		nicType, err := nictype.NICType(s, d)
+		if err != nil {
+			return false, err
+		}
+
+		if !shared.StringInSlice(nicType, []string{"bridged", "macvlan", "ipvlan", "physical", "sriov"}) {
 			continue
 		}
 
@@ -104,11 +110,11 @@ func isInUseByDevices(devices deviceConfig.Devices, networkName string) bool {
 		}
 
 		if GetHostDevice(d["parent"], d["vlan"]) == networkName {
-			return true
+			return true, nil
 		}
 	}
 
-	return false
+	return false, nil
 }
 
 // GetIP returns a net.IP representing the IP belonging to the subnet for the host number supplied.
@@ -304,7 +310,12 @@ func UpdateDNSMasqStatic(s *state.State, networkName string) error {
 		// Go through all its devices (including profiles).
 		for k, d := range inst.ExpandedDevices() {
 			// Skip uninteresting entries.
-			if d["type"] != "nic" || d.NICType() != "bridged" {
+			if d["type"] != "nic" {
+				continue
+			}
+
+			nicType, err := nictype.NICType(s, d)
+			if err != nil || nicType != "bridged" {
 				continue
 			}
 

From 526d27200103c2833ea811d0eeab47bb78a3aa4f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:50:43 +0100
Subject: [PATCH 27/29] lxd/networks: nictype.NICType usage and comment
 improvements

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 67 ++++++++++++++++++++++++++++++++-----------------
 1 file changed, 44 insertions(+), 23 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 18a925945d..fa2fdf8342 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -17,6 +17,7 @@ import (
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/project"
@@ -438,7 +439,12 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 		}
 
 		for _, inst := range insts {
-			if network.IsInUseByInstance(inst, n.Name) {
+			inUse, err := network.IsInUseByInstance(d.State(), inst, n.Name)
+			if err != nil {
+				return api.Network{}, err
+			}
+
+			if inUse {
 				uri := fmt.Sprintf("/%s/instances/%s", version.APIVersion, inst.Name())
 				if inst.Project() != project.Default {
 					uri += fmt.Sprintf("?project=%s", inst.Project())
@@ -462,7 +468,12 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 		}
 
 		for _, profile := range profiles {
-			if network.IsInUseByProfile(*db.ProfileToAPI(&profile), n.Name) {
+			inUse, err := network.IsInUseByProfile(d.State(), *db.ProfileToAPI(&profile), n.Name)
+			if err != nil {
+				return api.Network{}, err
+			}
+
+			if inUse {
 				uri := fmt.Sprintf("/%s/profiles/%s", version.APIVersion, profile.Name)
 				if profile.Project != project.Default {
 					uri += fmt.Sprintf("?project=%s", profile.Project)
@@ -508,7 +519,12 @@ func networkDelete(d *Daemon, r *http.Request) response.Response {
 		clusterNotification = true // We just want to delete the network from the system.
 	} else {
 		// Sanity checks
-		if n.IsUsed() {
+		inUse, err := n.IsUsed()
+		if err != nil {
+			return response.SmartError(err)
+		}
+
+		if inUse {
 			return response.BadRequest(fmt.Errorf("The network is currently in use"))
 		}
 
@@ -733,48 +749,53 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 		}
 
 		for _, inst := range instances {
-			// Go through all its devices (including profiles
-			for k, d := range inst.ExpandedDevices() {
-				// Skip uninteresting entries
-				if d["type"] != "nic" || d.NICType() != "bridged" {
+			// Go through all its devices (including profiles).
+			for k, dev := range inst.ExpandedDevices() {
+				// Skip uninteresting entries.
+				if dev["type"] != "nic" {
+					continue
+				}
+
+				nicType, err := nictype.NICType(d.State(), dev)
+				if err != nil || nicType != "bridged" {
 					continue
 				}
 
 				// Temporarily populate parent from network setting if used.
-				if d["network"] != "" {
-					d["parent"] = d["network"]
+				if dev["network"] != "" {
+					dev["parent"] = dev["network"]
 				}
 
-				if d["parent"] != name {
+				if dev["parent"] != name {
 					continue
 				}
 
-				// Fill in the hwaddr from volatile
-				if d["hwaddr"] == "" {
-					d["hwaddr"] = inst.LocalConfig()[fmt.Sprintf("volatile.%s.hwaddr", k)]
+				// Fill in the hwaddr from volatile.
+				if dev["hwaddr"] == "" {
+					dev["hwaddr"] = inst.LocalConfig()[fmt.Sprintf("volatile.%s.hwaddr", k)]
 				}
 
-				// Record the MAC
-				if d["hwaddr"] != "" {
-					projectMacs = append(projectMacs, d["hwaddr"])
+				// Record the MAC.
+				if dev["hwaddr"] != "" {
+					projectMacs = append(projectMacs, dev["hwaddr"])
 				}
 
-				// Add the lease
-				if d["ipv4.address"] != "" {
+				// Add the lease.
+				if dev["ipv4.address"] != "" {
 					leases = append(leases, api.NetworkLease{
 						Hostname: inst.Name(),
-						Address:  d["ipv4.address"],
-						Hwaddr:   d["hwaddr"],
+						Address:  dev["ipv4.address"],
+						Hwaddr:   dev["hwaddr"],
 						Type:     "static",
 						Location: inst.Location(),
 					})
 				}
 
-				if d["ipv6.address"] != "" {
+				if dev["ipv6.address"] != "" {
 					leases = append(leases, api.NetworkLease{
 						Hostname: inst.Name(),
-						Address:  d["ipv6.address"],
-						Hwaddr:   d["hwaddr"],
+						Address:  dev["ipv6.address"],
+						Hwaddr:   dev["hwaddr"],
 						Type:     "static",
 						Location: inst.Location(),
 					})

From ec157c8dededb3362c0331ea3404fedff2299dd3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:51:02 +0100
Subject: [PATCH 28/29] lxd/networks: Comment ending consistency

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/networks.go | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index fa2fdf8342..7c777be94b 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -804,7 +804,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 		}
 	}
 
-	// Local server name
+	// Local server name.
 	var serverName string
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		serverName, err = tx.GetLocalNodeName()
@@ -814,7 +814,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 		return response.SmartError(err)
 	}
 
-	// Get dynamic leases
+	// Get dynamic leases.
 	leaseFile := shared.VarPath("networks", name, "dnsmasq.leases")
 	if !shared.PathExists(leaseFile) {
 		return response.SyncResponse(true, leases)
@@ -828,7 +828,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 	for _, lease := range strings.Split(string(content), "\n") {
 		fields := strings.Fields(lease)
 		if len(fields) >= 5 {
-			// Parse the MAC
+			// Parse the MAC.
 			mac := network.GetMACSlice(fields[1])
 			macStr := strings.Join(mac, ":")
 
@@ -836,7 +836,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 				macStr = fields[4][len(fields[4])-17:]
 			}
 
-			// Look for an existing static entry
+			// Look for an existing static entry.
 			found := false
 			for _, entry := range leases {
 				if entry.Hwaddr == macStr && entry.Address == fields[2] {
@@ -849,7 +849,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 				continue
 			}
 
-			// Add the lease to the list
+			// Add the lease to the list.
 			leases = append(leases, api.NetworkLease{
 				Hostname: fields[3],
 				Address:  fields[2],
@@ -860,7 +860,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 		}
 	}
 
-	// Collect leases from other servers
+	// Collect leases from other servers.
 	if !isClusterNotification(r) {
 		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
 		if err != nil {
@@ -880,7 +880,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 			return response.SmartError(err)
 		}
 
-		// Filter based on project
+		// Filter based on project.
 		filteredLeases := []api.NetworkLease{}
 		for _, lease := range leases {
 			if !shared.StringInSlice(lease.Hwaddr, projectMacs) {

From 314fa698fdfb9b8cdb23dc03c601dcc3614d701f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 20 Jul 2020 11:21:57 +0100
Subject: [PATCH 29/29] test: Updates tests to delete profiles before networks

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 test/includes/lxd.sh                                 | 12 ++++++------
 test/suites/container_devices_nic_bridged.sh         |  2 +-
 .../container_devices_nic_bridged_filtering.sh       |  4 ++--
 test/suites/container_devices_nic_bridged_vlan.sh    |  2 +-
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/test/includes/lxd.sh b/test/includes/lxd.sh
index ccc561d5ca..a7abb0307d 100644
--- a/test/includes/lxd.sh
+++ b/test/includes/lxd.sh
@@ -152,18 +152,18 @@ kill_lxd() {
             lxc image delete "${image}" --force-local || true
         done
 
-        # Delete all networks
-        echo "==> Deleting all networks"
-        for network in $(lxc network list --force-local | grep YES | grep "^| " | cut -d' ' -f2); do
-            lxc network delete "${network}" --force-local || true
-        done
-
         # Delete all profiles
         echo "==> Deleting all profiles"
         for profile in $(lxc profile list --force-local | tail -n+3 | grep "^| " | cut -d' ' -f2); do
             lxc profile delete "${profile}" --force-local || true
         done
 
+        # Delete all networks
+        echo "==> Deleting all networks"
+        for network in $(lxc network list --force-local | grep YES | grep "^| " | cut -d' ' -f2); do
+            lxc network delete "${network}" --force-local || true
+        done
+
         # Clear config of the default profile since the profile itself cannot
         # be deleted.
         echo "==> Clearing config of default profile"
diff --git a/test/suites/container_devices_nic_bridged.sh b/test/suites/container_devices_nic_bridged.sh
index 94799525f0..959bbe7bc9 100644
--- a/test/suites/container_devices_nic_bridged.sh
+++ b/test/suites/container_devices_nic_bridged.sh
@@ -517,6 +517,6 @@ test_container_devices_nic_bridged() {
 
   # Cleanup.
   lxc delete "${ctName}" -f
-  lxc network delete "${brName}"
   lxc profile delete "${ctName}"
+  lxc network delete "${brName}"
 }
diff --git a/test/suites/container_devices_nic_bridged_filtering.sh b/test/suites/container_devices_nic_bridged_filtering.sh
index 5d12e9ef33..290554a722 100644
--- a/test/suites/container_devices_nic_bridged_filtering.sh
+++ b/test/suites/container_devices_nic_bridged_filtering.sh
@@ -255,8 +255,8 @@ test_container_devices_nic_bridged_filtering() {
     echo "br_netfilter didn't load, skipping IPv6 filter checks"
     lxc delete -f "${ctPrefix}A"
     lxc delete -f "${ctPrefix}B"
-    lxc network delete "${brName}"
     lxc profile delete "${ctPrefix}"
+    lxc network delete "${brName}"
     return
   fi
 
@@ -665,6 +665,6 @@ test_container_devices_nic_bridged_filtering() {
   fi
 
   # Cleanup.
-  lxc network delete "${brName}"
   lxc profile delete "${ctPrefix}"
+  lxc network delete "${brName}"
 }
diff --git a/test/suites/container_devices_nic_bridged_vlan.sh b/test/suites/container_devices_nic_bridged_vlan.sh
index 698c049fbf..4221d926d6 100644
--- a/test/suites/container_devices_nic_bridged_vlan.sh
+++ b/test/suites/container_devices_nic_bridged_vlan.sh
@@ -173,6 +173,6 @@ test_container_devices_nic_bridged_vlan() {
   # Cleanup.
   lxc delete -f "${prefix}-ctA"
   lxc delete -f "${prefix}-ctB"
-  lxc network delete "${prefix}"
   lxc profile delete "${prefix}"
+  lxc network delete "${prefix}"
 }


More information about the lxc-devel mailing list