[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