The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7990

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
- Adds `physical` network type.
- Updates `ovn` networks to allow the use of a `physical` network as an uplink network.

Includes https://github.com/lxc/lxd/pull/7989
From efffd128341001511c5b3bbeb82af6178c71fd3d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:31:37 +0100
Subject: [PATCH 01/14] shares/validate: Whitespace

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 shared/validate/validate.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared/validate/validate.go b/shared/validate/validate.go
index 356a7d8744..4217ecf89f 100644
--- a/shared/validate/validate.go
+++ b/shared/validate/validate.go
@@ -226,6 +226,7 @@ func IsNetworkAddressV4List(value string) error {
                        return err
                }
        }
+
        return nil
 }
 

From 896679be6effa830345a836920db173bd3935210 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:33:00 +0100
Subject: [PATCH 02/14] lxd/network/openvswitch/ovn: Updates RecursiveDNSServer
 to be list of IPs

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/openvswitch/ovn.go | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index ef181d8706..e0a0d0dc44 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -66,7 +66,7 @@ type OVNDHCPv4Opts struct {
        ServerID           net.IP
        ServerMAC          net.HardwareAddr
        Router             net.IP
-       RecursiveDNSServer net.IP
+       RecursiveDNSServer []net.IP
        DomainName         string
        LeaseTime          time.Duration
        MTU                uint32
@@ -75,7 +75,7 @@ type OVNDHCPv4Opts struct {
 // OVNDHCPv6Opts IPv6 DHCP option set that can be created (and then applied to 
a switch port by resulting ID).
 type OVNDHCPv6Opts struct {
        ServerID           net.HardwareAddr
-       RecursiveDNSServer net.IP
+       RecursiveDNSServer []net.IP
        DNSSearchList      []string
 }
 
@@ -358,7 +358,16 @@ func (o *OVN) LogicalSwitchDHCPv4OptionsSet(switchName 
OVNSwitch, uuid string, s
        }
 
        if opts.RecursiveDNSServer != nil {
-               args = append(args, fmt.Sprintf("dns_server=%s", 
opts.RecursiveDNSServer.String()))
+               nsIPs := make([]string, 0, len(opts.RecursiveDNSServer))
+               for _, nsIP := range opts.RecursiveDNSServer {
+                       if nsIP.To4() == nil {
+                               continue // Only include IPv4 addresses.
+                       }
+
+                       nsIPs = append(nsIPs, nsIP.String())
+               }
+
+               args = append(args, fmt.Sprintf("dns_server={%s}", 
strings.Join(nsIPs, ",")))
        }
 
        if opts.DomainName != "" {
@@ -416,7 +425,16 @@ func (o *OVN) LogicalSwitchDHCPv6OptionsSet(switchName 
OVNSwitch, uuid string, s
        }
 
        if opts.RecursiveDNSServer != nil {
-               args = append(args, fmt.Sprintf("dns_server=%s", 
opts.RecursiveDNSServer.String()))
+               nsIPs := make([]string, 0, len(opts.RecursiveDNSServer))
+               for _, nsIP := range opts.RecursiveDNSServer {
+                       if nsIP.To4() != nil {
+                               continue // Only include IPv6 addresses.
+                       }
+
+                       nsIPs = append(nsIPs, nsIP.String())
+               }
+
+               args = append(args, fmt.Sprintf("dns_server={%s}", 
strings.Join(nsIPs, ",")))
        }
 
        _, err = o.nbctl(args...)

From c775eceac72df08927c5f6799e9b88244b19ea9f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:35:07 +0100
Subject: [PATCH 03/14] lxd/network/driver/ovn: Updates allocateParentPortIPs
 to detect the parent network IP address and DNS settings

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_ovn.go | 46 +++++++++++++++++++++++++++++++++------
 1 file changed, 39 insertions(+), 7 deletions(-)

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 99423d4b3c..3080c9da3f 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -44,8 +44,8 @@ type ovnParentVars struct {
        extSwitchProviderName string
 
        // DNS.
-       dnsIPv6 net.IP
-       dnsIPv4 net.IP
+       dnsIPv6 []net.IP
+       dnsIPv4 []net.IP
 }
 
 // ovnParentPortBridgeVars parent bridge port variables used for start/stop.
@@ -391,7 +391,7 @@ func (n *ovn) setupParentPortBridge(parentNet Network, 
routerMAC net.HardwareAdd
 
 // allocateParentPortIPs attempts to find a free IP in the parent network's 
OVN ranges and then stores it in
 // ovnVolatileParentIPv4 and ovnVolatileParentIPv6 config keys on this 
network. Returns ovnParentVars settings.
-func (n *ovn) allocateParentPortIPs(parentNet Network, v4CIDRKey string, 
v6CIDRKey string, routerMAC net.HardwareAddr) (*ovnParentVars, error) {
+func (n *ovn) allocateParentPortIPs(parentNet Network, routerMAC 
net.HardwareAddr) (*ovnParentVars, error) {
        v := &ovnParentVars{}
 
        parentNetConf := parentNet.Config()
@@ -399,19 +399,51 @@ func (n *ovn) allocateParentPortIPs(parentNet Network, 
v4CIDRKey string, v6CIDRK
        // Parent derived settings.
        v.extSwitchProviderName = parentNet.Name()
 
+       // Detect parent gateway setting.
+       parentIPv4CIDR := parentNetConf["ipv4.address"]
+       if parentIPv4CIDR == "" {
+               parentIPv4CIDR = parentNetConf["ipv4.gateway"]
+       }
+
+       parentIPv6CIDR := parentNetConf["ipv6.address"]
+       if parentIPv6CIDR == "" {
+               parentIPv6CIDR = parentNetConf["ipv6.gateway"]
+       }
+
        // Optional parent values.
-       parentIPv4, parentIPv4Net, err := 
net.ParseCIDR(parentNetConf[v4CIDRKey])
+       parentIPv4, parentIPv4Net, err := net.ParseCIDR(parentIPv4CIDR)
        if err == nil {
-               v.dnsIPv4 = parentIPv4
+               v.dnsIPv4 = []net.IP{parentIPv4}
                v.routerExtGwIPv4 = parentIPv4
        }
 
-       parentIPv6, parentIPv6Net, err := 
net.ParseCIDR(parentNetConf[v6CIDRKey])
+       parentIPv6, parentIPv6Net, err := net.ParseCIDR(parentIPv6CIDR)
        if err == nil {
-               v.dnsIPv6 = parentIPv6
+               v.dnsIPv6 = []net.IP{parentIPv6}
                v.routerExtGwIPv6 = parentIPv6
        }
 
+       // Detect optional DNS server list.
+       if parentNetConf["dns.nameservers"] != "" {
+               // Reset nameservers.
+               v.dnsIPv4 = nil
+               v.dnsIPv6 = nil
+
+               nsList := strings.Split(parentNetConf["dns.nameservers"], ",")
+               for _, ns := range nsList {
+                       nsIP := net.ParseIP(strings.TrimSpace(ns))
+                       if nsIP == nil {
+                               return nil, fmt.Errorf("Invalid parent 
nameserver")
+                       }
+
+                       if nsIP.To4() == nil {
+                               v.dnsIPv6 = append(v.dnsIPv6, nsIP)
+                       } else {
+                               v.dnsIPv4 = append(v.dnsIPv4, nsIP)
+                       }
+               }
+       }
+
        // Parse existing allocated IPs for this network on the parent network 
(if not set yet, will be nil).
        routerExtPortIPv4 := net.ParseIP(n.config[ovnVolatileParentIPv4])
        routerExtPortIPv6 := net.ParseIP(n.config[ovnVolatileParentIPv6])

From 6c99bdfd32f4cce8b6cb3032eda69113ee8b0b92 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:36:54 +0100
Subject: [PATCH 04/14] lxd/network/driver/ovn: Updates n.allocateParentPortIPs
 usage

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 3080c9da3f..4af51c3092 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -381,7 +381,7 @@ func (n *ovn) setupParentPortBridge(parentNet Network, 
routerMAC net.HardwareAdd
                return nil, errors.Wrapf(err, "Network %q is not suitable for 
use as OVN parent", bridgeNet.name)
        }
 
-       v, err := n.allocateParentPortIPs(parentNet, "ipv4.address", 
"ipv6.address", routerMAC)
+       v, err := n.allocateParentPortIPs(parentNet, routerMAC)
        if err != nil {
                return nil, errors.Wrapf(err, "Failed allocating parent port 
IPs on network %q", parentNet.Name())
        }

From 11a0289883dbeb856344a803535cafb169e8edb3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:37:32 +0100
Subject: [PATCH 05/14] lxd/network/driver/ovn: Updates setup IPv6 RDNSS
 setting

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_ovn.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 4af51c3092..445e555433 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1257,11 +1257,16 @@ func (n *ovn) setup(update bool) error {
                        adressMode = openvswitch.OVNIPv6AddressModeDHCPStateful
                }
 
+               var recursiveDNSServer net.IP
+               if len(parent.dnsIPv6) > 0 {
+                       recursiveDNSServer = parent.dnsIPv6[0] // OVN only 
supports 1 RA DNS server.
+               }
+
                err = 
client.LogicalRouterPortSetIPv6Advertisements(n.getRouterIntPortName(), 
&openvswitch.OVNIPv6RAOpts{
                        AddressMode:        adressMode,
                        SendPeriodic:       true,
                        DNSSearchList:      n.getDNSSearchList(),
-                       RecursiveDNSServer: parent.dnsIPv6,
+                       RecursiveDNSServer: recursiveDNSServer,
                        MTU:                bridgeMTU,
 
                        // Keep these low until we support DNS search domains 
via DHCPv4, as otherwise RA DNSSL

From e61c4857d7cf2f753fd51880ba00a237809c4a04 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:31:26 +0100
Subject: [PATCH 06/14] shared/validate: Adds IsNetworkAddressList function

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 shared/validate/validate.go | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/shared/validate/validate.go b/shared/validate/validate.go
index 4217ecf89f..10067d8c26 100644
--- a/shared/validate/validate.go
+++ b/shared/validate/validate.go
@@ -171,6 +171,19 @@ func IsNetworkAddress(value string) error {
        return nil
 }
 
+// IsNetworkAddressList validates a comma delimited list of IPv4 or IPv6 
addresses.
+func IsNetworkAddressList(value string) error {
+       for _, v := range strings.Split(value, ",") {
+               v = strings.TrimSpace(v)
+               err := IsNetworkAddress(v)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
 // IsNetworkV4 validates an IPv4 CIDR string. If string is empty, returns 
valid.
 func IsNetworkV4(value string) error {
        ip, subnet, err := net.ParseCIDR(value)

From c8fa53c40537fc1b39c1e1e138474166146b669f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:31:54 +0100
Subject: [PATCH 07/14] lxd/network/network/physical: Adds physical driver

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/network_load.go     |   9 +-
 lxd/network/network_physical.go | 141 ++++++++++++++++++++++++++++++++
 2 files changed, 146 insertions(+), 4 deletions(-)
 create mode 100644 lxd/network/network_physical.go

diff --git a/lxd/network/network_load.go b/lxd/network/network_load.go
index cf59074663..7098181a57 100644
--- a/lxd/network/network_load.go
+++ b/lxd/network/network_load.go
@@ -5,10 +5,11 @@ import (
 )
 
 var drivers = map[string]func() Network{
-       "bridge":  func() Network { return &bridge{} },
-       "macvlan": func() Network { return &macvlan{} },
-       "sriov":   func() Network { return &sriov{} },
-       "ovn":     func() Network { return &ovn{} },
+       "bridge":   func() Network { return &bridge{} },
+       "macvlan":  func() Network { return &macvlan{} },
+       "sriov":    func() Network { return &sriov{} },
+       "ovn":      func() Network { return &ovn{} },
+       "physical": func() Network { return &physical{} },
 }
 
 // LoadByType loads a network by driver type.
diff --git a/lxd/network/network_physical.go b/lxd/network/network_physical.go
new file mode 100644
index 0000000000..2ea04f7ed4
--- /dev/null
+++ b/lxd/network/network_physical.go
@@ -0,0 +1,141 @@
+package network
+
+import (
+       "fmt"
+       "net"
+
+       "github.com/lxc/lxd/lxd/cluster"
+       "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/revert"
+       "github.com/lxc/lxd/shared/api"
+       log "github.com/lxc/lxd/shared/log15"
+       "github.com/lxc/lxd/shared/validate"
+)
+
+// physical represents a LXD physical network.
+type physical struct {
+       common
+}
+
+// Type returns the network type.
+func (n *physical) Type() string {
+       return "physical"
+}
+
+// DBType returns the network type DB ID.
+func (n *physical) DBType() db.NetworkType {
+       return db.NetworkTypePhysical
+}
+
+// Validate network config.
+func (n *physical) Validate(config map[string]string) error {
+       rules := map[string]func(value string) error{
+               "parent":           validate.Required(validate.IsNotEmpty, 
validInterfaceName),
+               "mtu":              validate.Optional(validate.IsNetworkMTU),
+               "vlan":             validate.Optional(validate.IsNetworkVLAN),
+               "maas.subnet.ipv4": validate.IsAny,
+               "maas.subnet.ipv6": validate.IsAny,
+               "ipv4.gateway":     
validate.Optional(validate.IsNetworkAddressCIDRV4),
+               "ipv6.gateway":     
validate.Optional(validate.IsNetworkAddressCIDRV6),
+               "ipv4.ovn.ranges":  
validate.Optional(validate.IsNetworkRangeV4List),
+               "ipv6.ovn.ranges":  
validate.Optional(validate.IsNetworkRangeV6List),
+               "dns.nameservers":  
validate.Optional(validate.IsNetworkAddressList),
+       }
+
+       err := n.validate(config, rules)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// Delete deletes a network.
+func (n *physical) Delete(clientType cluster.ClientType) error {
+       n.logger.Debug("Delete", log.Ctx{"clientType": clientType})
+       return n.common.delete(clientType)
+}
+
+// Rename renames a network.
+func (n *physical) Rename(newName string) error {
+       n.logger.Debug("Rename", log.Ctx{"newName": newName})
+
+       // Rename common steps.
+       err := n.common.rename(newName)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// Start starts is a no-op.
+func (n *physical) Start() error {
+       n.logger.Debug("Start")
+
+       if n.status == api.NetworkStatusPending {
+               return fmt.Errorf("Cannot start pending network")
+       }
+
+       return nil
+}
+
+// Stop stops is a no-op.
+func (n *physical) Stop() error {
+       n.logger.Debug("Stop")
+
+       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 *physical) Update(newNetwork api.NetworkPut, targetNode string, 
clientType cluster.ClientType) error {
+       n.logger.Debug("Update", log.Ctx{"clientType": clientType, 
"newNetwork": newNetwork})
+
+       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, targetNode, clientType)
+       })
+
+       // Apply changes to database.
+       err = n.common.update(newNetwork, targetNode, clientType)
+       if err != nil {
+               return err
+       }
+
+       revert.Success()
+       return nil
+}
+
+// DHCPv4Subnet returns the DHCPv4 subnet (if DHCP is enabled on network).
+func (n *physical) DHCPv4Subnet() *net.IPNet {
+       _, subnet, err := net.ParseCIDR(n.config["ipv4.gateway"])
+       if err != nil {
+               return nil
+       }
+
+       return subnet
+}
+
+// DHCPv6Subnet returns the DHCPv6 subnet (if DHCP or SLAAC is enabled on 
network).
+func (n *physical) DHCPv6Subnet() *net.IPNet {
+       _, subnet, err := net.ParseCIDR(n.config["ipv6.gateway"])
+       if err != nil {
+               return nil
+       }
+
+       return subnet
+}

From c6efa4013d6710a3cf83bf15e12f0e36c43cb00c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:32:25 +0100
Subject: [PATCH 08/14] lxd/db/networks: Adds physical network type constant

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 27eb62964d..3b10447bde 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -425,10 +425,11 @@ type NetworkType int
 
 // Network types.
 const (
-       NetworkTypeBridge  NetworkType = iota // Network type bridge.
-       NetworkTypeMacvlan                    // Network type macvlan.
-       NetworkTypeSriov                      // Network type sriov.
-       NetworkTypeOVN                        // Network type ovn.
+       NetworkTypeBridge   NetworkType = iota // Network type bridge.
+       NetworkTypeMacvlan                     // Network type macvlan.
+       NetworkTypeSriov                       // Network type sriov.
+       NetworkTypeOVN                         // Network type ovn.
+       NetworkTypePhysical                    // Network type physical.
 )
 
 // GetNetworkInAnyState returns the network with the given name.
@@ -510,6 +511,8 @@ func networkFillType(network *api.Network, netType 
NetworkType) {
                network.Type = "sriov"
        case NetworkTypeOVN:
                network.Type = "ovn"
+       case NetworkTypePhysical:
+               network.Type = "physical"
        default:
                network.Type = "" // Unknown
        }

From 5578e5c1a5aea1d218de1af55b9b26755e91ce54 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 17:24:51 +0100
Subject: [PATCH 09/14] api: Adds network_type_physical extension

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 doc/api-extensions.md | 7 ++++++-
 shared/version/api.go | 1 +
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index d7c233c3e6..bc3ee9e2d2 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -1185,4 +1185,9 @@ when restoring a custom volume backup.
 
 ## storage\_rsync\_compression
 Adds `rsync.compression` config key to storage pools. This key can be used
-to disable compression in rsync while migrating storage pools.
\ No newline at end of file
+to disable compression in rsync while migrating storage pools.
+
+## network\_type\_physical
+Adds support for additional network type `physical` that can be used as an 
uplink for `ovn` networks.
+
+The interface specified by `parent` on the `physical` network will be 
connected to the `ovn` network's gateway.
diff --git a/shared/version/api.go b/shared/version/api.go
index ba9380b676..d5035f7161 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -229,6 +229,7 @@ var APIExtensions = []string{
        "custom_volume_backup",
        "backup_override_name",
        "storage_rsync_compression",
+       "network_type_physical",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From d2fee1c90a17d64859e33363645420c219eb4421 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 18:09:24 +0100
Subject: [PATCH 10/14] lxd/network/network/utils: Adds VLANInterfaceCreate
 function

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/network_utils.go | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index 010a00978c..c9b97be607 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -26,6 +26,7 @@ import (
        "github.com/lxc/lxd/lxd/instance/instancetype"
        "github.com/lxc/lxd/lxd/project"
        "github.com/lxc/lxd/lxd/state"
+       "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/lxc/lxd/shared/logger"
@@ -1014,3 +1015,33 @@ func parseIPRanges(ipRangesList string, allowedNets 
...*net.IPNet) ([]*shared.IP
 
        return netIPRanges, nil
 }
+
+// VLANInterfaceCreate creates a VLAN interface on parent interface (if 
needed).
+// Returns boolean indicating if VLAN interface was created.
+func VLANInterfaceCreate(parent string, vlanDevice string, vlanID string) 
(bool, error) {
+       if vlanID == "" {
+               return false, nil
+       }
+
+       if shared.PathExists(fmt.Sprintf("/sys/class/net/%s", vlanDevice)) {
+               return false, nil
+       }
+
+       // Bring the parent interface up so we can add a vlan to it.
+       _, err := shared.RunCommand("ip", "link", "set", "dev", parent, "up")
+       if err != nil {
+               return false, errors.Wrapf(err, "Failed to bring up parent %q", 
parent)
+       }
+
+       // Add VLAN interface on top of parent.
+       _, err = shared.RunCommand("ip", "link", "add", "link", parent, "name", 
vlanDevice, "up", "type", "vlan", "id", vlanID)
+       if err != nil {
+               return false, errors.Wrapf(err, "Failed to create VLAN 
interface %q on %q", vlanDevice, parent)
+       }
+
+       // Attempt to disable IPv6 router advertisement acceptance.
+       util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", vlanDevice), 
"0")
+
+       // We created a new vlan interface, return true.
+       return true, nil
+}

From 1c12992a05b67cd70ab44b2072cb8c30df2ffaf9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 18:09:42 +0100
Subject: [PATCH 11/14] lxd/device/device/utils/network:
 network.VLANInterfaceCreate usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/device_utils_network.go | 22 +++++-----------------
 1 file changed, 5 insertions(+), 17 deletions(-)

diff --git a/lxd/device/device_utils_network.go 
b/lxd/device/device_utils_network.go
index eb95d3501b..6db9656885 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -18,7 +18,6 @@ import (
        "github.com/lxc/lxd/lxd/project"
        "github.com/lxc/lxd/lxd/revert"
        "github.com/lxc/lxd/lxd/state"
-       "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/logger"
        "github.com/lxc/lxd/shared/units"
@@ -111,23 +110,12 @@ func networkRemoveInterfaceIfNeeded(state *state.State, 
nic string, current inst
 // networkCreateVlanDeviceIfNeeded creates a VLAN device if doesn't already 
exist.
 func networkCreateVlanDeviceIfNeeded(state *state.State, parent string, 
vlanDevice string, vlanID string) (string, error) {
        if vlanID != "" {
-               if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", 
vlanDevice)) {
-                       // Bring the parent interface up so we can add a vlan 
to it.
-                       _, err := shared.RunCommand("ip", "link", "set", "dev", 
parent, "up")
-                       if err != nil {
-                               return "", fmt.Errorf("Failed to bring up 
parent %s: %v", parent, err)
-                       }
-
-                       // Add VLAN interface on top of parent.
-                       _, err = shared.RunCommand("ip", "link", "add", "link", 
parent, "name", vlanDevice, "up", "type", "vlan", "id", vlanID)
-                       if err != nil {
-                               return "", err
-                       }
-
-                       // Attempt to disable IPv6 router advertisement 
acceptance.
-                       
util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", vlanDevice), "0")
+               created, err := network.VLANInterfaceCreate(parent, vlanDevice, 
vlanID)
+               if err != nil {
+                       return "", err
+               }
 
-                       // We created a new vlan interface, return true.
+               if created {
                        return "created", nil
                }
 

From f229e6614a3d544a5798c58a1957efa2199c3669 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 17:31:24 +0100
Subject: [PATCH 12/14] doc/networks: Clarifies use of ovn ranges settings in
 bridge network

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 doc/networks.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/networks.md b/doc/networks.md
index 47d3386963..a9d3cb6c25 100644
--- a/doc/networks.md
+++ b/doc/networks.md
@@ -83,7 +83,7 @@ ipv4.firewall                   | boolean   | ipv4 address    
      | true
 ipv4.nat.address                | string    | ipv4 address          | -        
                 | The source address used for outbound traffic from the bridge
 ipv4.nat                        | boolean   | ipv4 address          | false    
                 | Whether to NAT (will default to true if unset and a random 
ipv4.address is generated)
 ipv4.nat.order                  | string    | ipv4 address          | before   
                 | Whether to add the required NAT rules before or after any 
pre-existing rules
-ipv4.ovn.ranges                 | string    | -                     | none     
                 | Comma separate list of IPv4 ranges to use for child OVN 
networks (FIRST-LAST format)
+ipv4.ovn.ranges                 | string    | -                     | none     
                 | Comma separate list of IPv4 ranges to use for child OVN 
network routers (FIRST-LAST format)
 ipv4.routes                     | string    | ipv4 address          | -        
                 | Comma separated list of additional IPv4 CIDR subnets to 
route to the bridge
 ipv4.routing                    | boolean   | ipv4 address          | true     
                 | Whether to route traffic in and out of the bridge
 ipv6.address                    | string    | standard mode         | random 
unused subnet      | IPv6 address for the bridge (CIDR notation). Use "none" to 
turn off IPv6 or "auto" to generate a new one
@@ -95,7 +95,7 @@ ipv6.firewall                   | boolean   | ipv6 address    
      | true
 ipv6.nat.address                | string    | ipv6 address          | -        
                 | The source address used for outbound traffic from the bridge
 ipv6.nat                        | boolean   | ipv6 address          | false    
                 | Whether to NAT (will default to true if unset and a random 
ipv6.address is generated)
 ipv6.nat.order                  | string    | ipv6 address          | before   
                 | Whether to add the required NAT rules before or after any 
pre-existing rules
-ipv6.ovn.ranges                 | string    | -                     | none     
                 | Comma separate list of IPv6 ranges to use for child OVN 
networks (FIRST-LAST format)
+ipv6.ovn.ranges                 | string    | -                     | none     
                 | Comma separate list of IPv6 ranges to use for child OVN 
network routers (FIRST-LAST format)
 ipv6.routes                     | string    | ipv6 address          | -        
                 | Comma separated list of additional IPv6 CIDR subnets to 
route to the bridge
 ipv6.routing                    | boolean   | ipv6 address          | true     
                 | Whether to route traffic in and out of the bridge
 maas.subnet.ipv4                | string    | ipv4 address          | -        
                 | MAAS IPv4 subnet to register instances in (when using 
`network` property on nic)

From 08abb33dfaaa4971d59d6e435cc69ec722c05699 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 17:31:45 +0100
Subject: [PATCH 13/14] doc/networks: Adds docs for physical network type

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 doc/networks.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/doc/networks.md b/doc/networks.md
index a9d3cb6c25..6df04b13c4 100644
--- a/doc/networks.md
+++ b/doc/networks.md
@@ -6,6 +6,7 @@ LXD supports the following network types:
  - [macvlan](#network-macvlan): Provides preset configuration to use when 
connecting instances to a parent macvlan interface.
  - [sriov](#network-sriov): Provides preset configuration to use when 
connecting instances to a parent SR-IOV interface.
  - [ovn](#network-ovn): Creates a logical network using the OVN software 
defined networking system.
+ - [physical](#network-physical): Provides preset configuration to use when 
connecting OVN networks to a parent interface.
 
 The desired type can be specified using the `--type` argument, e.g.
 
@@ -299,3 +300,22 @@ ipv4.address                    | string    | standard 
mode         | random unu
 ipv6.address                    | string    | standard mode         | random 
unused subnet      | IPv6 address for the bridge (CIDR notation). Use "none" to 
turn off IPv6 or "auto" to generate a new one
 ipv6.dhcp.stateful              | boolean   | ipv6 dhcp             | false    
                 | Whether to allocate addresses using DHCP
 network                         | string    | -                     | -        
                 | Parent network to use for outbound external network access
+
+## network: physical
+
+The physical network type allows one to specify presets to use when connecting 
OVN networks to a parent interface.
+
+Network configuration properties:
+
+Key                             | Type      | Condition             | Default  
                 | Description
+:--                             | :--       | :--                   | :--      
                 | :--
+maas.subnet.ipv4                | string    | ipv4 address          | -        
                 | MAAS IPv4 subnet to register instances in (when using 
`network` property on nic)
+maas.subnet.ipv6                | string    | ipv6 address          | -        
                 | MAAS IPv6 subnet to register instances in (when using 
`network` property on nic)
+mtu                             | integer   | -                     | -        
                 | The MTU of the new interface
+parent                          | string    | -                     | -        
                 | Parent interface to create sriov NICs on
+vlan                            | integer   | -                     | -        
                 | The VLAN ID to attach to
+ipv4.gateway                    | string    | standard mode         | -        
                 | IPv4 address for the gateway and network (CIDR notation)
+ipv4.ovn.ranges                 | string    | -                     | none     
                 | Comma separate list of IPv4 ranges to use for child OVN 
network routers (FIRST-LAST format)
+ipv6.gateway                    | string    | standard mode         | -        
                 | IPv6 address for the gateway and network  (CIDR notation)
+ipv6.ovn.ranges                 | string    | -                     | none     
                 | Comma separate list of IPv6 ranges to use for child OVN 
network routers (FIRST-LAST format)
+dns.nameservers                 | string    | standard mode         | -        
                 | List of DNS server IPs on physical network

From 6a377d286621cd0682ee5d9fcc3b2e3556bab5bf Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Tue, 6 Oct 2020 16:50:15 +0100
Subject: [PATCH 14/14] lxd/network/driver/ovn: Adds support for physical
 network as uplink

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/network/driver_ovn.go | 115 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 115 insertions(+)

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 445e555433..c748c0d734 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -363,6 +363,8 @@ func (n *ovn) setupParentPort(routerMAC net.HardwareAddr) 
(*ovnParentVars, error
        switch parentNet.Type() {
        case "bridge":
                return n.setupParentPortBridge(parentNet, routerMAC)
+       case "physical":
+               return n.setupParentPortPhysical(parentNet, routerMAC)
        }
 
        return nil, fmt.Errorf("Failed setting up parent port, network type %q 
unsupported as OVN parent", parentNet.Type())
@@ -389,6 +391,17 @@ func (n *ovn) setupParentPortBridge(parentNet Network, 
routerMAC net.HardwareAdd
        return v, nil
 }
 
+// setupParentPortPhysical allocates external IPs on the parent network.
+// Returns the derived ovnParentVars settings.
+func (n *ovn) setupParentPortPhysical(parentNet Network, routerMAC 
net.HardwareAddr) (*ovnParentVars, error) {
+       v, err := n.allocateParentPortIPs(parentNet, routerMAC)
+       if err != nil {
+               return nil, errors.Wrapf(err, "Failed allocating parent port 
IPs on network %q", parentNet.Name())
+       }
+
+       return v, nil
+}
+
 // allocateParentPortIPs attempts to find a free IP in the parent network's 
OVN ranges and then stores it in
 // ovnVolatileParentIPv4 and ovnVolatileParentIPv6 config keys on this 
network. Returns ovnParentVars settings.
 func (n *ovn) allocateParentPortIPs(parentNet Network, routerMAC 
net.HardwareAddr) (*ovnParentVars, error) {
@@ -632,6 +645,8 @@ func (n *ovn) startParentPort() error {
        switch parentNet.Type() {
        case "bridge":
                return n.startParentPortBridge(parentNet)
+       case "physical":
+               return n.startParentPortPhysical(parentNet)
        }
 
        return fmt.Errorf("Failed starting parent port, network type %q 
unsupported as OVN parent", parentNet.Type())
@@ -745,6 +760,59 @@ func (n *ovn) startParentPortBridge(parentNet Network) 
error {
        return nil
 }
 
+// startParentPortPhysical creates OVS bridge (if doesn't exist) and connects 
parent interface to the OVS bridge.
+func (n *ovn) startParentPortPhysical(parentNet Network) error {
+       vars := n.parentPortBridgeVars(parentNet)
+
+       // Do this after gaining lock so that on failure we revert before 
release locking.
+       revert := revert.New()
+       defer revert.Fail()
+
+       parentConfig := parentNet.Config()
+       uplinkDev := GetHostDevice(parentConfig["parent"], parentConfig["vlan"])
+
+       _, err := VLANInterfaceCreate(parentConfig["parent"], uplinkDev, 
parentConfig["vlan"])
+       if err != nil {
+               return err
+       }
+
+       // Ensure correct sysctls are set on uplink interface to avoid getting 
IPv6 link-local addresses.
+       _, err = shared.RunCommand("sysctl",
+               fmt.Sprintf("net/ipv6/conf/%s/disable_ipv6=1", uplinkDev),
+               fmt.Sprintf("net/ipv6/conf/%s/forwarding=0", uplinkDev),
+       )
+       if err != nil {
+               return errors.Wrapf(err, "Failed to configure uplink interface 
%q", uplinkDev)
+       }
+
+       // Create parent OVS bridge if needed.
+       ovs := openvswitch.NewOVS()
+       err = ovs.BridgeAdd(vars.ovsBridge, true)
+       if err != nil {
+               return errors.Wrapf(err, "Failed to create parent uplink OVS 
bridge %q", vars.ovsBridge)
+       }
+
+       // Connect OVS end veth interface to OVS bridge.
+       err = ovs.BridgePortAdd(vars.ovsBridge, uplinkDev, true)
+       if err != nil {
+               return errors.Wrapf(err, "Failed to connect uplink VF interface 
%q to parent OVS bridge %q", uplinkDev, vars.ovsBridge)
+       }
+
+       // Associate OVS bridge to logical OVN provider.
+       err = ovs.OVNBridgeMappingAdd(vars.ovsBridge, parentNet.Name())
+       if err != nil {
+               return errors.Wrapf(err, "Failed to associate parent OVS bridge 
%q to OVN provider %q", vars.ovsBridge, parentNet.Name())
+       }
+
+       _, err = shared.RunCommand("ip", "link", "set", uplinkDev, "up")
+       if err != nil {
+               return errors.Wrapf(err, "Failed to bring up parent interface 
%q", uplinkDev)
+       }
+
+       revert.Success()
+       return nil
+}
+
 // deleteParentPort deletes the parent uplink connection.
 func (n *ovn) deleteParentPort() error {
        // Parent network must be in default project.
@@ -761,6 +829,8 @@ func (n *ovn) deleteParentPort() error {
                switch parentNet.Type() {
                case "bridge":
                        return n.deleteParentPortBridge(parentNet)
+               case "physical":
+                       return n.deleteParentPortPhysical(parentNet)
                }
 
                return fmt.Errorf("Failed deleting parent port, network type %q 
unsupported as OVN parent", parentNet.Type())
@@ -819,6 +889,51 @@ func (n *ovn) deleteParentPortBridge(parentNet Network) 
error {
        return nil
 }
 
+// deleteParentPortPhysical deletes parent uplink OVS bridge, OVN bridge 
mappings if not in use.
+func (n *ovn) deleteParentPortPhysical(parentNet Network) error {
+       // Check OVS uplink bridge exists, if it does, check how many ports it 
has.
+       releaseIF := false
+       vars := n.parentPortBridgeVars(parentNet)
+       if shared.PathExists(fmt.Sprintf("/sys/class/net/%s", vars.ovsBridge)) {
+               ovs := openvswitch.NewOVS()
+               ports, err := ovs.BridgePortList(vars.ovsBridge)
+               if err != nil {
+                       return err
+               }
+
+               // If the OVS bridge has only 1 port (the parent interface) or 
fewer connected then delete it.
+               if len(ports) <= 1 {
+                       releaseIF = true
+
+                       err = ovs.OVNBridgeMappingDelete(vars.ovsBridge, 
parentNet.Name())
+                       if err != nil {
+                               return err
+                       }
+
+                       err = ovs.BridgeDelete(vars.ovsBridge)
+                       if err != nil {
+                               return err
+                       }
+               }
+       } else {
+               releaseIF = true // Remove the veths if OVS bridge already gone.
+       }
+
+       // Remove the veth interfaces if they exist.
+       if releaseIF {
+               parentConfig := parentNet.Config()
+               parentDev := GetHostDevice(parentConfig["parent"], 
parentConfig["vlan"])
+               if parentDev != "" && 
shared.PathExists(fmt.Sprintf("/sys/class/net/%s", parentDev)) {
+                       _, err := shared.RunCommand("ip", "link", "set", 
parentDev, "down")
+                       if err != nil {
+                               return errors.Wrapf(err, "Failed to bring down 
parent interface %q", parentDev)
+                       }
+               }
+       }
+
+       return nil
+}
+
 // FillConfig fills requested config with any default values.
 func (n *ovn) FillConfig(config map[string]string) error {
        if config["ipv4.address"] == "" {
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to