[lxc-devel] [lxd/master] network: Adds physical network type and adds OVN support for using it as an uplink
tomponline on Github
lxc-bot at linuxcontainers.org
Tue Oct 6 17:15:49 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 470 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201006/30a12609/attachment-0001.bin>
-------------- next part --------------
From efffd128341001511c5b3bbeb82af6178c71fd3d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 6 Oct 2020 16:31:37 +0100
Subject: [PATCH 01/14] shares/validate: Whitespace
Signed-off-by: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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"] == "" {
More information about the lxc-devel
mailing list