[lxc-devel] [lxd/master] Network: Cluster updates

tomponline on Github lxc-bot at linuxcontainers.org
Tue Jul 21 12:55:44 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1033 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200721/448f4053/attachment.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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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 6b27be6bde979408d66c08170646ab9cd5253a99 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/11] 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..31b7172e2a 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 specfic 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)
 	}


More information about the lxc-devel mailing list