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