[lxc-devel] [lxd/master] Network: Adds Network interface and network type concept

tomponline on Github lxc-bot at linuxcontainers.org
Wed Jul 15 10:23:18 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 553 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200715/b91378d0/attachment-0001.bin>
-------------- next part --------------
From 6eaef0050085ac5be1e1ab9dbffa8b30ee74a892 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 17:15:04 +0100
Subject: [PATCH 01/17] lxc/network: Adds flagType to cmdNetwork

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

diff --git a/lxc/network.go b/lxc/network.go
index e86e50600b..0b993bca45 100644
--- a/lxc/network.go
+++ b/lxc/network.go
@@ -23,6 +23,7 @@ type cmdNetwork struct {
 	global *cmdGlobal
 
 	flagTarget string
+	flagType   string
 }
 
 func (c *cmdNetwork) Command() *cobra.Command {
@@ -251,10 +252,11 @@ func (c *cmdNetworkCreate) Command() *cobra.Command {
 	cmd := &cobra.Command{}
 	cmd.Use = i18n.G("create [<remote>:]<network> [key=value...]")
 	cmd.Short = i18n.G("Create new networks")
-	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
-		`Create new networks`))
+	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(`Create new networks`))
 
 	cmd.Flags().StringVar(&c.network.flagTarget, "target", "", i18n.G("Cluster member name")+"``")
+	cmd.Flags().StringVarP(&c.network.flagType, "type", "t", "bridge", i18n.G("Network type"))
+
 	cmd.RunE = c.Run
 
 	return cmd
@@ -280,6 +282,7 @@ func (c *cmdNetworkCreate) Run(cmd *cobra.Command, args []string) error {
 	network := api.NetworksPost{}
 	network.Name = resource.name
 	network.Config = map[string]string{}
+	network.Type = c.network.flagType
 
 	for i := 1; i < len(args); i++ {
 		entry := strings.SplitN(args[i], "=", 2)

From 4bb584aadce9adf7c1ba308dea3b9182a8e5b67d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 17:15:52 +0100
Subject: [PATCH 02/17] lxd/networks: Validate network types

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

diff --git a/lxd/networks.go b/lxd/networks.go
index d4fe8982ec..c0b99a0de6 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -113,8 +113,10 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		return response.BadRequest(err)
 	}
 
-	if req.Type != "" && req.Type != "bridge" {
-		return response.BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
+	validNetworkTypes := []string{"bridge", "macvlan"}
+
+	if req.Type != "" && !shared.StringInSlice(req.Type, validNetworkTypes) {
+		return response.BadRequest(fmt.Errorf("Invalid network type specified. Must be one of: %s", strings.Join(validNetworkTypes, ", ")))
 	}
 
 	if req.Config == nil {

From 0867d33b8b995eb1dd4e60b2b23726b64b5c384c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 18:04:26 +0100
Subject: [PATCH 03/17] lxd/db/cluster: Adds type field to networks table

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

diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index e107b8885e..701847d41a 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -281,6 +281,7 @@ CREATE TABLE networks (
     name TEXT NOT NULL,
     description TEXT,
     state INTEGER NOT NULL DEFAULT 0,
+    type INTEGER NOT NULL DEFAULT 0,
     UNIQUE (name)
 );
 CREATE TABLE networks_config (
@@ -571,5 +572,5 @@ CREATE TABLE storage_volumes_snapshots_config (
     UNIQUE (storage_volume_snapshot_id, key)
 );
 
-INSERT INTO schema (version, updated_at) VALUES (32, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (33, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 7e721587d7..c6fd84cbfb 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -69,6 +69,17 @@ var updates = map[int]schema.Update{
 	30: updateFromV29,
 	31: updateFromV30,
 	32: updateFromV31,
+	33: updateFromV32,
+}
+
+// Add type field to networks.
+func updateFromV32(tx *sql.Tx) error {
+	_, err := tx.Exec("ALTER TABLE networks ADD COLUMN type INTEGER NOT NULL DEFAULT 0;")
+	if err != nil {
+		return errors.Wrap(err, "Failed to add type column to networks table")
+	}
+
+	return nil
 }
 
 // Add failure_domain column to nodes table.

From 8714b47504dcdde29a0926051a0995a437a8814a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 18:27:46 +0100
Subject: [PATCH 04/17] lxd/db/networks: Adds internal network type constants

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index ce8fcd34d0..70780230ac 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -297,6 +297,14 @@ const (
 	networkErrored            // Network creation failed on some nodes
 )
 
+// NetworkType indicates type of network.
+type NetworkType int
+
+// Network types.
+const (
+	NetworkTypeBridge NetworkType = iota // Network type bridge.
+)
+
 // GetNetwork returns the network with the given name.
 //
 // The network must be in the created stated, not pending.

From eae8e6032072865da7353b998603b07891dd92de Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 18:28:02 +0100
Subject: [PATCH 05/17] lxd/db/networks: Updates CreateNetwork to accept a
 network type

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 70780230ac..d5a3d35f80 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -485,10 +485,10 @@ func (c *Cluster) getNetworkConfig(id int64) (map[string]string, error) {
 }
 
 // CreateNetwork creates a new network.
-func (c *Cluster) CreateNetwork(name, description string, config map[string]string) (int64, error) {
+func (c *Cluster) CreateNetwork(name, description string, netType NetworkType, config map[string]string) (int64, error) {
 	var id int64
 	err := c.Transaction(func(tx *ClusterTx) error {
-		result, err := tx.tx.Exec("INSERT INTO networks (name, description, state) VALUES (?, ?, ?)", name, description, networkCreated)
+		result, err := tx.tx.Exec("INSERT INTO networks (name, description, state, type) VALUES (?, ?, ?, ?)", name, description, networkCreated, netType)
 		if err != nil {
 			return err
 		}

From 6e4cf25dc3b485ceb560d744bdb42bc049138953 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 18:47:59 +0100
Subject: [PATCH 06/17] lxd/db/networks: Updates CreatePendingNetwork to accept
 a network type

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index d5a3d35f80..fe876c0fd8 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -149,7 +149,7 @@ WHERE networks.id = ? AND networks.state = ?
 
 // CreatePendingNetwork creates a new pending network on the node with
 // the given name.
-func (c *ClusterTx) CreatePendingNetwork(node, name string, conf map[string]string) error {
+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 {
@@ -182,8 +182,8 @@ func (c *ClusterTx) CreatePendingNetwork(node, name string, conf map[string]stri
 	if networkID == 0 {
 		// No existing network with the given name was found, let's create
 		// one.
-		columns := []string{"name"}
-		values := []interface{}{name}
+		columns := []string{"name", "type"}
+		values := []interface{}{name, netType}
 		networkID, err = query.UpsertObject(c.tx, "networks", columns, values)
 		if err != nil {
 			return err

From 1853ad6c5db6ee08574a9f0729dc683803f9eb43 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 10:58:39 +0100
Subject: [PATCH 07/17] lxd/db/networks: Populate network type in getNetwork

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index fe876c0fd8..36ec050739 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -325,10 +325,11 @@ func (c *Cluster) getNetwork(name string, onlyCreated bool) (int64, *api.Network
 	description := sql.NullString{}
 	id := int64(-1)
 	state := 0
+	var netType NetworkType
 
-	q := "SELECT id, description, state FROM networks WHERE name=?"
+	q := "SELECT id, description, state, type FROM networks WHERE name=?"
 	arg1 := []interface{}{name}
-	arg2 := []interface{}{&id, &description, &state}
+	arg2 := []interface{}{&id, &description, &state, &netType}
 	if onlyCreated {
 		q += " AND state=?"
 		arg1 = append(arg1, networkCreated)
@@ -350,7 +351,6 @@ func (c *Cluster) getNetwork(name string, onlyCreated bool) (int64, *api.Network
 	network := api.Network{
 		Name:    name,
 		Managed: true,
-		Type:    "bridge",
 	}
 	network.Description = description.String
 	network.Config = config
@@ -366,6 +366,13 @@ func (c *Cluster) getNetwork(name string, onlyCreated bool) (int64, *api.Network
 		network.Status = "Unknown"
 	}
 
+	switch netType {
+	case NetworkTypeBridge:
+		network.Type = "bridge"
+	default:
+		network.Type = "bridge"
+	}
+
 	nodes, err := c.networkNodes(id)
 	if err != nil {
 		return -1, nil, err

From 7762c2ff847b989132b03ab166e373c4fcb45b6d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 18:28:23 +0100
Subject: [PATCH 08/17] lxd/networks: Converts and records network type in
 networksPost

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

diff --git a/lxd/networks.go b/lxd/networks.go
index c0b99a0de6..1e99531eaf 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -113,12 +113,20 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		return response.BadRequest(err)
 	}
 
-	validNetworkTypes := []string{"bridge", "macvlan"}
+	validNetworkTypes := []string{"bridge"}
 
 	if req.Type != "" && !shared.StringInSlice(req.Type, validNetworkTypes) {
 		return response.BadRequest(fmt.Errorf("Invalid network type specified. Must be one of: %s", strings.Join(validNetworkTypes, ", ")))
 	}
 
+	var netType db.NetworkType
+	switch req.Type {
+	case "bridge":
+		netType = db.NetworkTypeBridge
+	default:
+		netType = db.NetworkTypeBridge
+	}
+
 	if req.Config == nil {
 		req.Config = map[string]string{}
 	}
@@ -153,7 +161,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 			}
 		}
 		err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
-			return tx.CreatePendingNetwork(targetNode, req.Name, req.Config)
+			return tx.CreatePendingNetwork(targetNode, req.Name, netType, req.Config)
 		})
 		if err != nil {
 			if err == db.ErrAlreadyDefined {
@@ -197,7 +205,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 	}
 
 	// Create the database entry
-	_, err = d.cluster.CreateNetwork(req.Name, req.Description, req.Config)
+	_, err = d.cluster.CreateNetwork(req.Name, req.Description, netType, req.Config)
 	if err != nil {
 		return response.SmartError(fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}

From 8abb9987ede1ffce2c48f72d249728e521651b11 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:04:20 +0100
Subject: [PATCH 09/17] lxd/network/network/interface: Adds network interface

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_interface.go | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 lxd/network/network_interface.go

diff --git a/lxd/network/network_interface.go b/lxd/network/network_interface.go
new file mode 100644
index 0000000000..3613fae6ad
--- /dev/null
+++ b/lxd/network/network_interface.go
@@ -0,0 +1,31 @@
+package network
+
+import (
+	"github.com/lxc/lxd/lxd/cluster"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Network represents a LXD network.
+type Network interface {
+	// Load.
+	init(state *state.State, id int64, dbInfo *api.Network)
+
+	// Config.
+	Name() string
+	Type() string
+	Config() map[string]string
+	IsUsed() bool
+	HasDHCPv4() bool
+	HasDHCPv6() bool
+	DHCPv4Ranges() []DHCPRange
+	DHCPv6Ranges() []DHCPRange
+
+	// Actions.
+	Start() error
+	Stop() error
+	Rename(name string) error
+	Update(newNetwork api.NetworkPut, notify bool) error
+	HandleHeartbeat(heartbeatData *cluster.APIHeartbeat) error
+	Delete(withDatabase bool) error
+}

From 2d1767ddc79433e4f8d37355544b0ffbcbfd2556 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 10:59:33 +0100
Subject: [PATCH 10/17] lxd/network/network: Renames network to driver_bridge

Makes way for new Network interface.

Updates bridge implementation to operate as an implementation of the Network interface

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/{network.go => driver_bridge.go} | 165 ++++---------------
 1 file changed, 31 insertions(+), 134 deletions(-)
 rename lxd/network/{network.go => driver_bridge.go} (90%)

diff --git a/lxd/network/network.go b/lxd/network/driver_bridge.go
similarity index 90%
rename from lxd/network/network.go
rename to lxd/network/driver_bridge.go
index 6e2fcd3dd6..49d9bd9b44 100644
--- a/lxd/network/network.go
+++ b/lxd/network/driver_bridge.go
@@ -18,9 +18,7 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/daemon"
 	"github.com/lxc/lxd/lxd/dnsmasq"
-	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/node"
-	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -38,60 +36,20 @@ const ForkdnsServersListFile = "servers.conf"
 
 var forkdnsServersLock sync.Mutex
 
-// DHCPRange represents a range of IPs from start to end.
-type DHCPRange struct {
-	Start net.IP
-	End   net.IP
-}
-
-// Network represents a LXD network.
-type Network struct {
-	// Properties
-	state       *state.State
-	id          int64
-	name        string
-	description string
-
-	// config
-	config map[string]string
-}
-
-// Name returns the network name.
-func (n *Network) Name() string {
-	return n.name
-}
-
-// Config returns the network config.
-func (n *Network) Config() map[string]string {
-	return n.config
+// bridge represents a LXD bridge network.
+type bridge struct {
+	common
 }
 
 // IsRunning returns whether the network is up.
-func (n *Network) IsRunning() bool {
+func (n *bridge) isRunning() bool {
 	return shared.PathExists(fmt.Sprintf("/sys/class/net/%s", n.name))
 }
 
-// IsUsed returns whether the network is used by any instances.
-func (n *Network) IsUsed() bool {
-	// Look for instances using the interface
-	insts, err := instance.LoadFromAllProjects(n.state)
-	if err != nil {
-		return true
-	}
-
-	for _, inst := range insts {
-		if IsInUseByInstance(inst, n.name) {
-			return true
-		}
-	}
-
-	return false
-}
-
 // Delete deletes a network.
-func (n *Network) Delete(withDatabase bool) error {
+func (n *bridge) Delete(withDatabase bool) error {
 	// Bring the network down
-	if n.IsRunning() {
+	if n.isRunning() {
 		err := n.Stop()
 		if err != nil {
 			return err
@@ -114,14 +72,14 @@ func (n *Network) Delete(withDatabase bool) error {
 }
 
 // Rename renames a network.
-func (n *Network) Rename(name string) error {
+func (n *bridge) Rename(name string) error {
 	// Sanity checks
 	if n.IsUsed() {
 		return fmt.Errorf("The network is currently in use")
 	}
 
 	// Bring the network down
-	if n.IsRunning() {
+	if n.isRunning() {
 		err := n.Stop()
 		if err != nil {
 			return err
@@ -165,12 +123,12 @@ func (n *Network) Rename(name string) error {
 }
 
 // Start starts the network.
-func (n *Network) Start() error {
+func (n *bridge) Start() error {
 	return n.setup(nil)
 }
 
 // setup restarts the network.
-func (n *Network) setup(oldConfig map[string]string) error {
+func (n *bridge) setup(oldConfig map[string]string) error {
 	// If we are in mock mode, just no-op.
 	if n.state.OS.MockMode {
 		return nil
@@ -185,7 +143,7 @@ func (n *Network) setup(oldConfig map[string]string) error {
 	}
 
 	// Create the bridge interface
-	if !n.IsRunning() {
+	if !n.isRunning() {
 		if n.config["bridge.driver"] == "openvswitch" {
 			_, err := exec.LookPath("ovs-vsctl")
 			if err != nil {
@@ -1071,9 +1029,9 @@ func (n *Network) setup(oldConfig map[string]string) error {
 }
 
 // Stop stops the network.
-func (n *Network) Stop() error {
-	if !n.IsRunning() {
-		return fmt.Errorf("The network is already stopped")
+func (n *bridge) Stop() error {
+	if !n.isRunning() {
+		return nil
 	}
 
 	// Destroy the bridge interface
@@ -1135,7 +1093,7 @@ func (n *Network) Stop() error {
 }
 
 // Update updates the network.
-func (n *Network) Update(newNetwork api.NetworkPut, notify bool) error {
+func (n *bridge) Update(newNetwork api.NetworkPut, notify bool) error {
 	err := fillAuto(newNetwork.Config)
 	if err != nil {
 		return err
@@ -1203,14 +1161,14 @@ func (n *Network) Update(newNetwork api.NetworkPut, notify bool) error {
 
 	// Update the network
 	if !userOnly {
-		if shared.StringInSlice("bridge.driver", changedConfig) && n.IsRunning() {
+		if shared.StringInSlice("bridge.driver", changedConfig) && n.isRunning() {
 			err = n.Stop()
 			if err != nil {
 				return err
 			}
 		}
 
-		if shared.StringInSlice("bridge.external_interfaces", changedConfig) && n.IsRunning() {
+		if shared.StringInSlice("bridge.external_interfaces", changedConfig) && n.isRunning() {
 			devices := []string{}
 			for _, dev := range strings.Split(newConfig["bridge.external_interfaces"], ",") {
 				dev = strings.TrimSpace(dev)
@@ -1273,7 +1231,7 @@ func (n *Network) Update(newNetwork api.NetworkPut, notify bool) error {
 	return nil
 }
 
-func (n *Network) spawnForkDNS(listenAddress string) error {
+func (n *bridge) spawnForkDNS(listenAddress string) error {
 	// Setup the dnsmasq domain
 	dnsDomain := n.config["dns.domain"]
 	if dnsDomain == "" {
@@ -1313,9 +1271,9 @@ func (n *Network) spawnForkDNS(listenAddress string) error {
 	return nil
 }
 
-// RefreshForkdnsServerAddresses retrieves the IPv4 address of each cluster node (excluding ourselves)
+// HandleHeartbeat refreshes forkdns servers. Retrieves the IPv4 address of each cluster node (excluding ourselves)
 // for this network. It then updates the forkdns server list file if there are changes.
-func (n *Network) RefreshForkdnsServerAddresses(heartbeatData *cluster.APIHeartbeat) error {
+func (n *bridge) HandleHeartbeat(heartbeatData *cluster.APIHeartbeat) error {
 	addresses := []string{}
 	localAddress, err := node.HTTPSAddress(n.state.Node)
 	if err != nil {
@@ -1373,7 +1331,7 @@ func (n *Network) RefreshForkdnsServerAddresses(heartbeatData *cluster.APIHeartb
 	return nil
 }
 
-func (n *Network) getTunnels() []string {
+func (n *bridge) getTunnels() []string {
 	tunnels := []string{}
 
 	for k := range n.config {
@@ -1391,7 +1349,7 @@ func (n *Network) getTunnels() []string {
 }
 
 // bootRoutesV4 returns a list of IPv4 boot routes on the network's device.
-func (n *Network) bootRoutesV4() ([]string, error) {
+func (n *bridge) bootRoutesV4() ([]string, error) {
 	routes := []string{}
 	cmd := exec.Command("ip", "-4", "route", "show", "dev", n.name, "proto", "boot")
 	ipOut, err := cmd.StdoutPipe()
@@ -1409,7 +1367,7 @@ func (n *Network) bootRoutesV4() ([]string, error) {
 }
 
 // bootRoutesV6 returns a list of IPv6 boot routes on the network's device.
-func (n *Network) bootRoutesV6() ([]string, error) {
+func (n *bridge) bootRoutesV6() ([]string, error) {
 	routes := []string{}
 	cmd := exec.Command("ip", "-6", "route", "show", "dev", n.name, "proto", "boot")
 	ipOut, err := cmd.StdoutPipe()
@@ -1427,7 +1385,7 @@ func (n *Network) bootRoutesV6() ([]string, error) {
 }
 
 // applyBootRoutesV4 applies a list of IPv4 boot routes to the network's device.
-func (n *Network) applyBootRoutesV4(routes []string) error {
+func (n *bridge) applyBootRoutesV4(routes []string) error {
 	for _, route := range routes {
 		cmd := []string{"-4", "route", "replace", "dev", n.name, "proto", "boot"}
 		cmd = append(cmd, strings.Fields(route)...)
@@ -1441,7 +1399,7 @@ func (n *Network) applyBootRoutesV4(routes []string) error {
 }
 
 // applyBootRoutesV6 applies a list of IPv6 boot routes to the network's device.
-func (n *Network) applyBootRoutesV6(routes []string) error {
+func (n *bridge) applyBootRoutesV6(routes []string) error {
 	for _, route := range routes {
 		cmd := []string{"-6", "route", "replace", "dev", n.name, "proto", "boot"}
 		cmd = append(cmd, strings.Fields(route)...)
@@ -1454,7 +1412,7 @@ func (n *Network) applyBootRoutesV6(routes []string) error {
 	return nil
 }
 
-func (n *Network) fanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, string, string, error) {
+func (n *bridge) fanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, string, string, error) {
 	// Sanity checks
 	underlaySize, _ := underlay.Mask.Size()
 	if underlaySize != 16 && underlaySize != 24 {
@@ -1501,7 +1459,7 @@ func (n *Network) fanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, s
 	return fmt.Sprintf("%s/%d", ipBytes.String(), overlaySize), dev, ipStr, err
 }
 
-func (n *Network) addressForSubnet(subnet *net.IPNet) (net.IP, string, error) {
+func (n *bridge) addressForSubnet(subnet *net.IPNet) (net.IP, string, error) {
 	ifaces, err := net.Interfaces()
 	if err != nil {
 		return net.IP{}, "", err
@@ -1528,7 +1486,7 @@ func (n *Network) addressForSubnet(subnet *net.IPNet) (net.IP, string, error) {
 	return net.IP{}, "", fmt.Errorf("No address found in subnet")
 }
 
-func (n *Network) killForkDNS() error {
+func (n *bridge) killForkDNS() error {
 	// Check if we have a running forkdns at all
 	pidPath := shared.VarPath("networks", n.name, "forkdns.pid")
 
@@ -1552,7 +1510,7 @@ func (n *Network) killForkDNS() error {
 
 // updateForkdnsServersFile takes a list of node addresses and writes them atomically to
 // the forkdns.servers file ready for forkdns to notice and re-apply its config.
-func (n *Network) updateForkdnsServersFile(addresses []string) error {
+func (n *bridge) updateForkdnsServersFile(addresses []string) error {
 	// We don't want to race with ourselves here
 	forkdnsServersLock.Lock()
 	defer forkdnsServersLock.Unlock()
@@ -1585,69 +1543,8 @@ func (n *Network) updateForkdnsServersFile(addresses []string) error {
 	return nil
 }
 
-// HasDHCPv4 indicates whether the network has DHCPv4 enabled.
-func (n *Network) HasDHCPv4() bool {
-	if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) {
-		return true
-	}
-
-	return false
-}
-
-// HasDHCPv6 indicates whether the network has DHCPv6 enabled (includes stateless SLAAC router advertisement mode).
-// Technically speaking stateless SLAAC RA mode isn't DHCPv6, but for consistency with LXD's config paradigm, DHCP
-// here means "an ability to automatically allocate IPs and routes", rather than stateful DHCP with leases.
-// To check if true stateful DHCPv6 is enabled check the "ipv6.dhcp.stateful" config key.
-func (n *Network) HasDHCPv6() bool {
-	if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
-		return true
-	}
-
-	return false
-}
-
-// DHCPv4Ranges returns a parsed set of DHCPv4 ranges for this network.
-func (n *Network) DHCPv4Ranges() []DHCPRange {
-	dhcpRanges := make([]DHCPRange, 0)
-	if n.config["ipv4.dhcp.ranges"] != "" {
-		for _, r := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
-			parts := strings.SplitN(strings.TrimSpace(r), "-", 2)
-			if len(parts) == 2 {
-				startIP := net.ParseIP(parts[0])
-				endIP := net.ParseIP(parts[1])
-				dhcpRanges = append(dhcpRanges, DHCPRange{
-					Start: startIP.To4(),
-					End:   endIP.To4(),
-				})
-			}
-		}
-	}
-
-	return dhcpRanges
-}
-
-// DHCPv6Ranges returns a parsed set of DHCPv6 ranges for this network.
-func (n *Network) DHCPv6Ranges() []DHCPRange {
-	dhcpRanges := make([]DHCPRange, 0)
-	if n.config["ipv6.dhcp.ranges"] != "" {
-		for _, r := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
-			parts := strings.SplitN(strings.TrimSpace(r), "-", 2)
-			if len(parts) == 2 {
-				startIP := net.ParseIP(parts[0])
-				endIP := net.ParseIP(parts[1])
-				dhcpRanges = append(dhcpRanges, DHCPRange{
-					Start: startIP.To16(),
-					End:   endIP.To16(),
-				})
-			}
-		}
-	}
-
-	return dhcpRanges
-}
-
 // HasIPv4Firewall indicates whether the network has IPv4 firewall enabled.
-func (n *Network) HasIPv4Firewall() bool {
+func (n *bridge) HasIPv4Firewall() bool {
 	if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
 		return true
 	}
@@ -1656,7 +1553,7 @@ func (n *Network) HasIPv4Firewall() bool {
 }
 
 // HasIPv6Firewall indicates whether the network has IPv6 firewall enabled.
-func (n *Network) HasIPv6Firewall() bool {
+func (n *bridge) HasIPv6Firewall() bool {
 	if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
 		return true
 	}

From 1682160307f1e54ef3afd73ef92e3952fd416e44 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:00:53 +0100
Subject: [PATCH 11/17] lxd/networks: Remove use of network.IsRunning

As not part of new Network interface (as IsRunning is driver dependent) and network.Stop is now expected to return nil if already stopped.

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

diff --git a/lxd/networks.go b/lxd/networks.go
index 1e99531eaf..e60d0cca24 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -918,10 +918,6 @@ func networkShutdown(s *state.State) error {
 			return err
 		}
 
-		if !n.IsRunning() {
-			continue
-		}
-
 		err = n.Stop()
 		if err != nil {
 			logger.Error("Failed to bring down network", log.Ctx{"err": err, "name": name})

From 10c44099b112666ed551c4d859232caa86e76cad Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:02:02 +0100
Subject: [PATCH 12/17] lxd/network/network/load: Updates LoadByName to use
 Network interface

And load appropriate underlying driver.

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

diff --git a/lxd/network/network_load.go b/lxd/network/network_load.go
index c73a7a26cd..d35217fda6 100644
--- a/lxd/network/network_load.go
+++ b/lxd/network/network_load.go
@@ -4,20 +4,24 @@ import (
 	"github.com/lxc/lxd/lxd/state"
 )
 
+var drivers = map[string]func() Network{
+	"bridge": func() Network { return &bridge{} },
+}
+
 // LoadByName loads the network info from the database by name.
-func LoadByName(s *state.State, name string) (*Network, error) {
+func LoadByName(s *state.State, name string) (Network, error) {
 	id, dbInfo, err := s.Cluster.GetNetwork(name)
 	if err != nil {
 		return nil, err
 	}
 
-	n := &Network{
-		state:       s,
-		id:          id,
-		name:        name,
-		description: dbInfo.Description,
-		config:      dbInfo.Config,
+	driverFunc, ok := drivers[dbInfo.Type]
+	if !ok {
+		return nil, ErrUnknownDriver
 	}
 
+	n := driverFunc()
+	n.init(s, id, dbInfo)
+
 	return n, nil
 }

From af23af4a0a1b22dae7604dd98a1c58e162fa7df0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:02:57 +0100
Subject: [PATCH 13/17] lxd/networks/utils: Updates usage of
 n.RefreshForkdnsServerAddresses to generic n.HandleHearbeat

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

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 5f6a52820f..200d05ba8d 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -164,8 +164,8 @@ func networkUpdateForkdnsServersTask(s *state.State, heartbeatData *cluster.APIH
 			return err
 		}
 
-		if n.Config()["bridge.mode"] == "fan" {
-			err := n.RefreshForkdnsServerAddresses(heartbeatData)
+		if n.Type() == "bridge" && n.Config()["bridge.mode"] == "fan" {
+			err := n.HandleHeartbeat(heartbeatData)
 			if err != nil {
 				return err
 			}

From 28dc6cfb2d1861274bb7202326936d104ef63c48 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:03:39 +0100
Subject: [PATCH 14/17] lxd/network/driver/common: Adds common driver

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_common.go | 133 +++++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)
 create mode 100644 lxd/network/driver_common.go

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
new file mode 100644
index 0000000000..601e2bc520
--- /dev/null
+++ b/lxd/network/driver_common.go
@@ -0,0 +1,133 @@
+package network
+
+import (
+	"net"
+	"strings"
+
+	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// DHCPRange represents a range of IPs from start to end.
+type DHCPRange struct {
+	Start net.IP
+	End   net.IP
+}
+
+// common represents a generic LXD network.
+type common struct {
+	// Properties
+	state       *state.State
+	id          int64
+	name        string
+	netType     string
+	description string
+
+	// config
+	config map[string]string
+}
+
+// init initialise internal variables.
+func (n *common) init(state *state.State, id int64, dbInfo *api.Network) {
+	n.id = id
+	n.name = dbInfo.Name
+	n.netType = dbInfo.Type
+	n.config = dbInfo.Config
+	n.state = state
+	n.description = dbInfo.Description
+}
+
+// Name returns the network name.
+func (n *common) Name() string {
+	return n.name
+}
+
+// Type returns the network type.
+func (n *common) Type() string {
+	return n.netType
+}
+
+// Config returns the network config.
+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
+	insts, err := instance.LoadFromAllProjects(n.state)
+	if err != nil {
+		return true
+	}
+
+	for _, inst := range insts {
+		if IsInUseByInstance(inst, n.name) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// HasDHCPv4 indicates whether the network has DHCPv4 enabled.
+func (n *common) HasDHCPv4() bool {
+	if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) {
+		return true
+	}
+
+	return false
+}
+
+// HasDHCPv6 indicates whether the network has DHCPv6 enabled (includes stateless SLAAC router advertisement mode).
+// Technically speaking stateless SLAAC RA mode isn't DHCPv6, but for consistency with LXD's config paradigm, DHCP
+// here means "an ability to automatically allocate IPs and routes", rather than stateful DHCP with leases.
+// To check if true stateful DHCPv6 is enabled check the "ipv6.dhcp.stateful" config key.
+func (n *common) HasDHCPv6() bool {
+	if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
+		return true
+	}
+
+	return false
+}
+
+// DHCPv4Ranges returns a parsed set of DHCPv4 ranges for this network.
+func (n *common) DHCPv4Ranges() []DHCPRange {
+	dhcpRanges := make([]DHCPRange, 0)
+	if n.config["ipv4.dhcp.ranges"] != "" {
+		for _, r := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
+			parts := strings.SplitN(strings.TrimSpace(r), "-", 2)
+			if len(parts) == 2 {
+				startIP := net.ParseIP(parts[0])
+				endIP := net.ParseIP(parts[1])
+				dhcpRanges = append(dhcpRanges, DHCPRange{
+					Start: startIP.To4(),
+					End:   endIP.To4(),
+				})
+			}
+		}
+	}
+
+	return dhcpRanges
+}
+
+// DHCPv6Ranges returns a parsed set of DHCPv6 ranges for this network.
+func (n *common) DHCPv6Ranges() []DHCPRange {
+	dhcpRanges := make([]DHCPRange, 0)
+	if n.config["ipv6.dhcp.ranges"] != "" {
+		for _, r := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
+			parts := strings.SplitN(strings.TrimSpace(r), "-", 2)
+			if len(parts) == 2 {
+				startIP := net.ParseIP(parts[0])
+				endIP := net.ParseIP(parts[1])
+				dhcpRanges = append(dhcpRanges, DHCPRange{
+					Start: startIP.To16(),
+					End:   endIP.To16(),
+				})
+			}
+		}
+	}
+
+	return dhcpRanges
+}

From 42c204652c9549d08dfa576ee9ad96ffbfff3507 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:04:05 +0100
Subject: [PATCH 15/17] lxd/network/errors: Adds error constants

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

diff --git a/lxd/network/errors.go b/lxd/network/errors.go
new file mode 100644
index 0000000000..b68b4bacc6
--- /dev/null
+++ b/lxd/network/errors.go
@@ -0,0 +1,8 @@
+package network
+
+import (
+	"fmt"
+)
+
+// ErrUnknownDriver is the "Unknown driver" error
+var ErrUnknownDriver = fmt.Errorf("Unknown driver")

From b8016e87a65b9fe0e00e9ece8ab780da3e16e0fd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 10:58:09 +0100
Subject: [PATCH 16/17] lxd/device/nic/bridged: Support Network interface

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 2119de405b..622deda0ed 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -659,7 +659,7 @@ func (d *nicBridged) setFilters() (err error) {
 // networkAllocateVethFilterIPs retrieves previously allocated IPs, or allocate new ones if needed.
 // This function only works with LXD managed networks, and as such, requires the managed network's
 // config to be supplied.
-func (d *nicBridged) allocateFilterIPs(n *network.Network) (net.IP, net.IP, error) {
+func (d *nicBridged) allocateFilterIPs(n network.Network) (net.IP, net.IP, error) {
 	var IPv4, IPv6 net.IP
 
 	// Check if there is a valid static IPv4 address defined.
@@ -798,7 +798,7 @@ func (d *nicBridged) networkDHCPValidIP(subnet *net.IPNet, ranges []network.DHCP
 // getDHCPFreeIPv4 attempts to find a free IPv4 address for the device.
 // It first checks whether there is an existing allocation for the instance.
 // If no previous allocation, then a free IP is picked from the ranges configured.
-func (d *nicBridged) getDHCPFreeIPv4(usedIPs map[[4]byte]dnsmasq.DHCPAllocation, n *network.Network, ctName string, deviceMAC string) (net.IP, error) {
+func (d *nicBridged) getDHCPFreeIPv4(usedIPs map[[4]byte]dnsmasq.DHCPAllocation, n network.Network, ctName string, deviceMAC string) (net.IP, error) {
 	MAC, err := net.ParseMAC(deviceMAC)
 	if err != nil {
 		return nil, err
@@ -872,7 +872,7 @@ func (d *nicBridged) getDHCPFreeIPv4(usedIPs map[[4]byte]dnsmasq.DHCPAllocation,
 // DHCPv6 stateful mode is enabled without custom ranges, then an EUI64 IP is generated from the
 // device's MAC address. Finally if stateful custom ranges are enabled, then a free IP is picked
 // from the ranges configured.
-func (d *nicBridged) getDHCPFreeIPv6(usedIPs map[[16]byte]dnsmasq.DHCPAllocation, n *network.Network, ctName string, deviceMAC string) (net.IP, error) {
+func (d *nicBridged) getDHCPFreeIPv6(usedIPs map[[16]byte]dnsmasq.DHCPAllocation, n network.Network, ctName string, deviceMAC string) (net.IP, error) {
 	netConfig := n.Config()
 	lxdIP, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])
 	if err != nil {

From 68d1af76d8503cd0f700d1e9e081492109206c09 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 11:09:12 +0100
Subject: [PATCH 17/17] lxd: Updates network tests to pass netType

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/db/networks_test.go | 14 +++++++-------
 lxd/instance_test.go    |  4 ++--
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/lxd/db/networks_test.go b/lxd/db/networks_test.go
index 3f83278b62..9624d1c1ce 100644
--- a/lxd/db/networks_test.go
+++ b/lxd/db/networks_test.go
@@ -15,7 +15,7 @@ func TestGetNetworksLocalConfigs(t *testing.T) {
 	cluster, cleanup := db.NewTestCluster(t)
 	defer cleanup()
 
-	_, err := cluster.CreateNetwork("lxdbr0", "", map[string]string{
+	_, err := cluster.CreateNetwork("lxdbr0", "", db.NetworkTypeBridge, map[string]string{
 		"dns.mode":                   "none",
 		"bridge.external_interfaces": "vlan0",
 	})
@@ -45,7 +45,7 @@ func TestCreatePendingNetwork(t *testing.T) {
 	require.NoError(t, err)
 
 	config := map[string]string{"bridge.external_interfaces": "foo"}
-	err = tx.CreatePendingNetwork("buzz", "network1", config)
+	err = tx.CreatePendingNetwork("buzz", "network1", db.NetworkTypeBridge, config)
 	require.NoError(t, err)
 
 	networkID, err := tx.GetNetworkID("network1")
@@ -53,7 +53,7 @@ func TestCreatePendingNetwork(t *testing.T) {
 	assert.True(t, networkID > 0)
 
 	config = map[string]string{"bridge.external_interfaces": "bar"}
-	err = tx.CreatePendingNetwork("rusp", "network1", config)
+	err = tx.CreatePendingNetwork("rusp", "network1", db.NetworkTypeBridge, config)
 	require.NoError(t, err)
 
 	// The initial node (whose name is 'none' by default) is missing.
@@ -61,7 +61,7 @@ func TestCreatePendingNetwork(t *testing.T) {
 	require.EqualError(t, err, "Network not defined on nodes: none")
 
 	config = map[string]string{"bridge.external_interfaces": "egg"}
-	err = tx.CreatePendingNetwork("none", "network1", config)
+	err = tx.CreatePendingNetwork("none", "network1", db.NetworkTypeBridge, config)
 	require.NoError(t, err)
 
 	// Now the storage is defined on all nodes.
@@ -82,10 +82,10 @@ func TestNetworksCreatePending_AlreadyDefined(t *testing.T) {
 	_, err := tx.CreateNode("buzz", "1.2.3.4:666")
 	require.NoError(t, err)
 
-	err = tx.CreatePendingNetwork("buzz", "network1", map[string]string{})
+	err = tx.CreatePendingNetwork("buzz", "network1", db.NetworkTypeBridge, map[string]string{})
 	require.NoError(t, err)
 
-	err = tx.CreatePendingNetwork("buzz", "network1", map[string]string{})
+	err = tx.CreatePendingNetwork("buzz", "network1", db.NetworkTypeBridge, map[string]string{})
 	require.Equal(t, db.ErrAlreadyDefined, err)
 }
 
@@ -94,6 +94,6 @@ func TestNetworksCreatePending_NonExistingNode(t *testing.T) {
 	tx, cleanup := db.NewTestClusterTx(t)
 	defer cleanup()
 
-	err := tx.CreatePendingNetwork("buzz", "network1", map[string]string{})
+	err := tx.CreatePendingNetwork("buzz", "network1", db.NetworkTypeBridge, map[string]string{})
 	require.Equal(t, db.ErrNoSuchObject, err)
 }
diff --git a/lxd/instance_test.go b/lxd/instance_test.go
index 0d72e77955..42d3e13840 100644
--- a/lxd/instance_test.go
+++ b/lxd/instance_test.go
@@ -99,7 +99,7 @@ func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 		Name: "testFoo",
 	}
 
-	_, err := suite.d.State().Cluster.CreateNetwork("unknownbr0", "", nil)
+	_, err := suite.d.State().Cluster.CreateNetwork("unknownbr0", "", db.NetworkTypeBridge, nil)
 	suite.Req.Nil(err)
 
 	c, err := instanceCreateInternal(suite.d.State(), args)
@@ -133,7 +133,7 @@ func (suite *containerTestSuite) TestContainer_LoadFromDB() {
 	}
 	state := suite.d.State()
 
-	_, err := state.Cluster.CreateNetwork("unknownbr0", "", nil)
+	_, err := state.Cluster.CreateNetwork("unknownbr0", "", db.NetworkTypeBridge, nil)
 	suite.Req.Nil(err)
 
 	// Create the container


More information about the lxc-devel mailing list