[lxc-devel] [lxd/master] Network: Adds network property support to macvlan NIC driver

tomponline on Github lxc-bot at linuxcontainers.org
Fri Jul 17 15:00:37 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1029 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200717/6503fe08/attachment-0001.bin>
-------------- next part --------------
From 8551197d770cb5906b496ff755cca8b8f700d2b3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 16:31:34 +0100
Subject: [PATCH 01/24] doc: Updates clustering docs with network parent
 optional per-node key

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 doc/clustering.md | 28 ++++++++++++----------------
 1 file changed, 12 insertions(+), 16 deletions(-)

diff --git a/doc/clustering.md b/doc/clustering.md
index e7137350a1..e37f44e5f6 100644
--- a/doc/clustering.md
+++ b/doc/clustering.md
@@ -401,24 +401,22 @@ lxc storage volume show default web --target node2
 
 ## Networks
 
-As mentioned above, all nodes must have identical networks defined. The only
-difference between networks on different nodes might be their
-`bridge.external_interfaces` optional configuration key (see also documentation
-about [network configuration](networks.md)).
+As mentioned above, all nodes must have identical networks defined.
 
-To create a new network, you first have to define it across all
-nodes, for example:
+The only difference between networks on different nodes might be their optional configuration keys.
+When defining a new network on a specific clustered node the only valid optional configuration keys you can pass
+are `bridge.external_interfaces` and `parent`, as these can be different on each node (see documentation about
+[network configuration](networks.md) for a definition of each).
+
+To create a new network, you first have to define it across all nodes, for example:
 
 ```bash
 lxc network create --target node1 my-network
 lxc network create --target node2 my-network
 ```
 
-Note that when defining a new network on a node the only valid configuration
-key you can pass is `bridge.external_interfaces`, as mentioned above.
-
-At this point the network hasn't been actually created yet, but just
-defined (it's state is marked as Pending if you run `lxc network list`).
+At this point the network hasn't been actually created yet, but just defined
+(it's state is marked as Pending if you run `lxc network list`).
 
 Now run:
 
@@ -426,12 +424,10 @@ Now run:
 lxc network create my-network
 ```
 
-and the network will be instantiated on all nodes. If you didn't
-define it on a particular node, or a node is down, an error will be
-returned.
+The network will be instantiated on all nodes. If you didn't define it on a particular node, or a node is down,
+an error will be returned.
 
-You can pass to this final ``network create`` command any configuration key
-which is not node-specific (see above).
+You can pass to this final ``network create`` command any configuration key which is not node-specific (see above).
 
 ## Separate REST API and clustering networks
 

From 2d0f79dcf22520ca5995b377c5486de166e7a1bd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 14 Jul 2020 16:32:00 +0100
Subject: [PATCH 02/24] lxd/db/networks: Adds parent as optional per-node
 network key

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 558c788f9b..30efb33f66 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -634,4 +634,5 @@ func (c *Cluster) RenameNetwork(oldName string, newName string) error {
 // NodeSpecificNetworkConfig lists all network config keys which are node-specific.
 var NodeSpecificNetworkConfig = []string{
 	"bridge.external_interfaces",
+	"parent",
 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

From bb728fca7b1cc36fffa0ace2c3459ab8eee0e84f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 17 Jul 2020 15:42:16 +0100
Subject: [PATCH 09/24] lxd/device/nic/bridged: Validates network is type
 bridge

Now that different NIC types support the "network" key.

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

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 622deda0ed..0a9ea62cbd 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -82,6 +82,11 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 		if err != nil {
 			return errors.Wrapf(err, "Error loading network config for %q", d.config["network"])
 		}
+
+		if n.Type() != "bridge" {
+			return fmt.Errorf("Specified network must be of type bridge")
+		}
+
 		netConfig := n.Config()
 
 		if d.config["ipv4.address"] != "" {
@@ -128,6 +133,7 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 			d.config["mtu"] = netConfig["bridge.mtu"]
 		}
 
+		// Copy certain keys verbatim from the network's settings.
 		inheritKeys := []string{"maas.subnet.ipv4", "maas.subnet.ipv6"}
 		for _, inheritKey := range inheritKeys {
 			if _, found := netConfig[inheritKey]; found {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

diff --git a/lxd/network/network_interface.go b/lxd/network/network_interface.go
index 9c2fd7424b..0f9e912e28 100644
--- a/lxd/network/network_interface.go
+++ b/lxd/network/network_interface.go
@@ -17,7 +17,7 @@ type Network interface {
 	Name() string
 	Type() string
 	Config() map[string]string
-	IsUsed() bool
+	IsUsed() (bool, error)
 	HasDHCPv4() bool
 	HasDHCPv6() bool
 	DHCPv4Ranges() []DHCPRange

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

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

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

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

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

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

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

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

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

From df15f918d7d58aa7247a5cf341f4fe969874e094 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 16 Jul 2020 15:50:15 +0100
Subject: [PATCH 21/24] lxd/db/networks: Adds constant for NetworkTypeMacvlan

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 30efb33f66..de7ad40167 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -302,7 +302,8 @@ type NetworkType int
 
 // Network types.
 const (
-	NetworkTypeBridge NetworkType = iota // Network type bridge.
+	NetworkTypeBridge  NetworkType = iota // Network type bridge.
+	NetworkTypeMacvlan                    // Network type macvlan.
 )
 
 // GetNetwork returns the network with the given name.
@@ -369,6 +370,8 @@ func (c *Cluster) getNetwork(name string, onlyCreated bool) (int64, *api.Network
 	switch netType {
 	case NetworkTypeBridge:
 		network.Type = "bridge"
+	case NetworkTypeMacvlan:
+		network.Type = "macvlan"
 	default:
 		network.Type = "" // Unknown
 	}

From 57dfe2eca3c590f2c33b501fc5937aa8c30c58e5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 15 Jul 2020 16:39:32 +0100
Subject: [PATCH 22/24] lxd/network/network/load: Adds macvlan driver as
 supported network type

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

diff --git a/lxd/network/network_load.go b/lxd/network/network_load.go
index 132e6933c5..5519347253 100644
--- a/lxd/network/network_load.go
+++ b/lxd/network/network_load.go
@@ -6,7 +6,8 @@ import (
 )
 
 var drivers = map[string]func() Network{
-	"bridge": func() Network { return &bridge{} },
+	"bridge":  func() Network { return &bridge{} },
+	"macvlan": func() Network { return &macvlan{} },
 }
 
 // LoadByName loads the network info from the database by name.

From 0dab6fcae336a8d600e7359862ea565422b4be2e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 16 Jul 2020 16:35:50 +0100
Subject: [PATCH 23/24] lxd/networks: Adds macvlan support to networksPost

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

diff --git a/lxd/networks.go b/lxd/networks.go
index 7955603223..edf41bd94d 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -127,6 +127,8 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 	switch req.Type {
 	case "bridge":
 		dbNetType = db.NetworkTypeBridge
+	case "macvlan":
+		dbNetType = db.NetworkTypeMacvlan
 	default:
 		return response.BadRequest(fmt.Errorf("Unrecognised network type"))
 	}

From 58266f4562504de35cf95afac92cc843e616c3da Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 16 Jul 2020 16:35:58 +0100
Subject: [PATCH 24/24] lxd/network/driver/macvlan: macvlan driver
 implementation

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_macvlan.go     |  45 +++++++++++++-
 lxd/network/driver_macvlan.go | 111 ++++++++++++++++++++++++++++++++++
 2 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100644 lxd/network/driver_macvlan.go

diff --git a/lxd/device/nic_macvlan.go b/lxd/device/nic_macvlan.go
index 6050e763d5..d76c324285 100644
--- a/lxd/device/nic_macvlan.go
+++ b/lxd/device/nic_macvlan.go
@@ -3,6 +3,8 @@ package device
 import (
 	"fmt"
 
+	"github.com/pkg/errors"
+
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
@@ -21,9 +23,11 @@ func (d *nicMACVLAN) validateConfig(instConf instance.ConfigReader) error {
 		return ErrUnsupportedDevType
 	}
 
-	requiredFields := []string{"parent"}
+	var requiredFields []string
 	optionalFields := []string{
 		"name",
+		"network",
+		"parent",
 		"mtu",
 		"hwaddr",
 		"vlan",
@@ -31,6 +35,45 @@ func (d *nicMACVLAN) validateConfig(instConf instance.ConfigReader) error {
 		"maas.subnet.ipv6",
 		"boot.priority",
 	}
+
+	// Check that if network proeperty is set that conflicting keys are not present.
+	if d.config["network"] != "" {
+		requiredFields = append(requiredFields, "network")
+
+		bannedKeys := []string{"nictype", "parent", "mtu", "maas.subnet.ipv4", "maas.subnet.ipv6"}
+		for _, bannedKey := range bannedKeys {
+			if d.config[bannedKey] != "" {
+				return fmt.Errorf("Cannot use %q property in conjunction with %q property", bannedKey, "network")
+			}
+		}
+
+		// If network property is specified, lookup network settings and apply them to the device's config.
+		n, err := network.LoadByName(d.state, d.config["network"])
+		if err != nil {
+			return errors.Wrapf(err, "Error loading network config for %q", d.config["network"])
+		}
+
+		if n.Type() != "macvlan" {
+			return fmt.Errorf("Specified network must be of type macvlan")
+		}
+
+		netConfig := n.Config()
+
+		// Get actual parent device from network's parent setting.
+		d.config["parent"] = netConfig["parent"]
+
+		// Copy certain keys verbatim from the network's settings.
+		inheritKeys := []string{"maas.subnet.ipv4", "maas.subnet.ipv6"}
+		for _, inheritKey := range inheritKeys {
+			if _, found := netConfig[inheritKey]; found {
+				d.config[inheritKey] = netConfig[inheritKey]
+			}
+		}
+	} else {
+		// If no network property supplied, then parent property is required.
+		requiredFields = append(requiredFields, "parent")
+	}
+
 	err := d.config.Validate(nicValidationRules(requiredFields, optionalFields))
 	if err != nil {
 		return err
diff --git a/lxd/network/driver_macvlan.go b/lxd/network/driver_macvlan.go
new file mode 100644
index 0000000000..289c39fe26
--- /dev/null
+++ b/lxd/network/driver_macvlan.go
@@ -0,0 +1,111 @@
+package network
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+
+	"github.com/lxc/lxd/lxd/revert"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	log "github.com/lxc/lxd/shared/log15"
+)
+
+// macvlan represents a LXD macvlan network.
+type macvlan struct {
+	common
+}
+
+// Validate network config.
+func (n *macvlan) Validate(config map[string]string) error {
+	rules := map[string]func(value string) error{
+		"parent": func(value string) error {
+			if err := ValidNetworkName(value); err != nil {
+				return errors.Wrapf(err, "Invalid interface name %q", value)
+			}
+
+			return nil
+		},
+		"maas.subnet.ipv4": shared.IsAny,
+		"maas.subnet.ipv6": shared.IsAny,
+	}
+
+	err := n.validate(config, rules)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Delete deletes a network.
+func (n *macvlan) Delete(clusterNotification bool) error {
+	n.logger.Debug("Delete", log.Ctx{"clusterNotification": clusterNotification})
+	return n.common.delete(clusterNotification)
+}
+
+// Rename renames a network.
+func (n *macvlan) Rename(newName string) error {
+	n.logger.Debug("Rename", log.Ctx{"newName": newName})
+
+	// Sanity checks.
+	inUse, err := n.IsUsed()
+	if err != nil {
+		return err
+	}
+
+	if inUse {
+		return fmt.Errorf("The network is currently in use")
+	}
+
+	// Rename common steps.
+	err = n.common.rename(newName)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Start starts is a no-op.
+func (n *macvlan) Start() error {
+	return nil
+}
+
+// Stop stops is a no-op.
+func (n *macvlan) Stop() error {
+	return nil
+}
+
+// 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 *macvlan) Update(newNetwork api.NetworkPut, clusterNotification bool) error {
+	n.logger.Debug("Update", log.Ctx{"clusterNotification": clusterNotification})
+
+	dbUpdateNeeeded, _, oldNetwork, err := n.common.configChanged(newNetwork)
+	if err != nil {
+		return err
+	}
+
+	if !dbUpdateNeeeded {
+		return nil // Nothing changed.
+	}
+
+	revert := revert.New()
+	defer revert.Fail()
+
+	// Define a function which reverts everything.
+	revert.Add(func() {
+		// Reset changes to all nodes and database.
+		n.common.update(oldNetwork, clusterNotification)
+	})
+
+	// Apply changes to database.
+	err = n.common.update(newNetwork, clusterNotification)
+	if err != nil {
+		return err
+	}
+
+	revert.Success()
+	return nil
+}


More information about the lxc-devel mailing list