[lxc-devel] [lxd/master] Network: Adds support for external subnets for OVN networks
tomponline on Github
lxc-bot at linuxcontainers.org
Fri Oct 16 13:57:55 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1031 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201016/d13b1a1d/attachment-0001.bin>
-------------- next part --------------
From 05044a7c2567cd52534bc48ac61e207363f56556 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:31:22 +0100
Subject: [PATCH 1/6] lxd/network/openvswitch/ovn: Adds LogicalSwitchPortGetDNS
to return switch port DNS info
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/openvswitch/ovn.go | 35 ++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index bb08c505af..475398d23e 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -791,6 +791,41 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
return dnsIPv4, dnsIPv6, nil
}
+// LogicalSwitchPortGetDNS returns the logical switch port DNS info (UUID, name and IPs).
+func (o *OVN) LogicalSwitchPortGetDNS(portName OVNSwitchPort) (string, string, []net.IP, error) {
+ // Get UUID and DNS IPs for a switch port in the format: "<DNS UUID>,<DNS NAME>=<IP> <IP>"
+ output, err := o.nbctl("--format=csv", "--no-headings", "--data=bare", "--colum=_uuid,records", "find", "dns",
+ fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
+ )
+ if err != nil {
+ return "", "", nil, err
+ }
+
+ parts := strings.Split(strings.TrimSpace(output), ",")
+ dnsUUID := strings.TrimSpace(parts[0])
+
+ var dnsName string
+ var ips []net.IP
+
+ // Try and parse the DNS name and IPs.
+ if len(parts) > 1 {
+ dnsParts := strings.SplitN(strings.TrimSpace(parts[1]), "=", 2)
+ if len(dnsParts) == 2 {
+ dnsName = strings.TrimSpace(dnsParts[0])
+ ipParts := strings.Split(dnsParts[1], " ")
+ for _, ipPart := range ipParts {
+ ip := net.ParseIP(strings.TrimSpace(ipPart))
+ if ip != nil {
+ ips = append(ips, ip)
+ }
+ }
+ }
+
+ }
+
+ return dnsUUID, dnsName, ips, nil
+}
+
// LogicalSwitchPortDeleteDNS removes DNS records for a switch port.
func (o *OVN) LogicalSwitchPortDeleteDNS(switchName OVNSwitch, portName OVNSwitchPort) error {
// Check if existing DNS record exists for switch port.
From 835968cbbdc2e9f104422e93102cea9f8c063408 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:30:47 +0100
Subject: [PATCH 2/6] lxd/network/openvswitch/ovn: Updates
LogicalSwitchPortDeleteDNS to only accept DNS UUID rather than port name
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/openvswitch/ovn.go | 25 +++++++------------------
1 file changed, 7 insertions(+), 18 deletions(-)
diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index 475398d23e..b6a05abf18 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -827,28 +827,17 @@ func (o *OVN) LogicalSwitchPortGetDNS(portName OVNSwitchPort) (string, string, [
}
// LogicalSwitchPortDeleteDNS removes DNS records for a switch port.
-func (o *OVN) LogicalSwitchPortDeleteDNS(switchName OVNSwitch, portName OVNSwitchPort) error {
- // Check if existing DNS record exists for switch port.
- dnsUUID, err := o.nbctl("--format=csv", "--no-headings", "--data=bare", "--colum=_uuid", "find", "dns",
- fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
- )
+func (o *OVN) LogicalSwitchPortDeleteDNS(switchName OVNSwitch, dnsUUID string) error {
+ // Remove DNS record association from switch.
+ _, err := o.nbctl("remove", "logical_switch", string(switchName), "dns_records", dnsUUID)
if err != nil {
return err
}
- dnsUUID = strings.TrimSpace(dnsUUID)
- if dnsUUID != "" {
- // Remove DNS record association from switch.
- _, err = o.nbctl("remove", "logical_switch", string(switchName), "dns_records", dnsUUID)
- if err != nil {
- return err
- }
-
- // Remove DNS record entry itself.
- _, err = o.nbctl("destroy", "dns", dnsUUID)
- if err != nil {
- return err
- }
+ // Remove DNS record entry itself.
+ _, err = o.nbctl("destroy", "dns", dnsUUID)
+ if err != nil {
+ return err
}
return nil
From cf1670c565cb62f9d22348fe81078fcc55fcdf22 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:28:49 +0100
Subject: [PATCH 3/6] lxd/network/openvswitch/ovn: Updates
LogicalSwitchPortSetDNS to return the DNS UUID record ID
Uses for for reverting a record created when LogicalSwitchPortSetDNS is updated to only accept DNS UUID.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/openvswitch/ovn.go | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index b6a05abf18..a52cc5e595 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -681,8 +681,8 @@ func (o *OVN) LogicalSwitchPortDynamicIPs(portName OVNSwitchPort) ([]net.IP, err
// LogicalSwitchPortSetDNS sets up the switch DNS records for the DNS name resolving to the IPs of the switch port.
// Attempts to find at most one IP for each IP protocol, preferring static addresses over dynamic.
-// Returns the IPv4 and IPv6 addresses used for DNS records.
-func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPort, dnsName string) (net.IP, net.IP, error) {
+// Returns the DNS record UUID, IPv4 and IPv6 addresses used for DNS records.
+func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPort, dnsName string) (string, net.IP, net.IP, error) {
var dnsIPv4, dnsIPv6 net.IP
// checkAndStoreIP checks if the supplied IP is valid and can be used for a missing DNS IP variable.
@@ -705,7 +705,7 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
// Get static and dynamic IPs for switch port.
staticAddressesRaw, err := o.nbctl("lsp-get-addresses", string(portName))
if err != nil {
- return nil, nil, err
+ return "", nil, nil, err
}
staticAddresses := strings.Split(strings.TrimSpace(staticAddressesRaw), " ")
@@ -729,7 +729,7 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
if hasDynamic && (dnsIPv4 == nil || dnsIPv6 == nil) {
dynamicIPs, err := o.LogicalSwitchPortDynamicIPs(portName)
if err != nil {
- return nil, nil, err
+ return "", nil, nil, err
}
for _, dynamicIP := range dynamicIPs {
@@ -757,7 +757,7 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
)
if err != nil {
- return nil, nil, err
+ return "", nil, nil, err
}
cmdArgs := []string{
@@ -771,13 +771,13 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
// Update existing record if exists.
_, err = o.nbctl(append([]string{"set", "dns", dnsUUID}, cmdArgs...)...)
if err != nil {
- return nil, nil, err
+ return "", nil, nil, err
}
} else {
// Create new record if needed.
dnsUUID, err = o.nbctl(append([]string{"create", "dns"}, cmdArgs...)...)
if err != nil {
- return nil, nil, err
+ return "", nil, nil, err
}
dnsUUID = strings.TrimSpace(dnsUUID)
}
@@ -785,10 +785,10 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
// Add DNS record to switch DNS records.
_, err = o.nbctl("add", "logical_switch", string(switchName), "dns_records", dnsUUID)
if err != nil {
- return nil, nil, err
+ return "", nil, nil, err
}
- return dnsIPv4, dnsIPv6, nil
+ return dnsUUID, dnsIPv4, dnsIPv6, nil
}
// LogicalSwitchPortGetDNS returns the logical switch port DNS info (UUID, name and IPs).
From 631c1e64f0dd47ac61aaa93b655da9b00f3fc17a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:32:48 +0100
Subject: [PATCH 4/6] lxd/network/driver/ovn: Generates static EUI64 IPv6
address for instance switch ports in instanceDevicePortAdd
When only static IPv4 addresses have been added to a logical switch port.
This ensures that the switch port has an IPv6 address, as OVN has a limitation that prevents a port from being statically addressed for IPv4 and dynamically allocated for IPv6.
This in turn meant that if using the `ipv4.address` key without an associated `ipv6.address` key, then AAAA DNS record would not be created.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/driver_ovn.go | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index a6dc816a7a..47f1c0daf9 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1873,6 +1873,30 @@ func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceN
if err != nil {
return "", err
}
+
+ // If port isn't going to have fully dynamic IPs allocated by OVN, and instead only static IPv4
+ // addresses have been added, then add an EUI64 static IPv6 address so that the switch port has an
+ // IPv6 address that will be used to generate a DNS record. This works around a limitation in OVN
+ // that prevents us requesting dynamic IPv6 address allocation when static IPv4 allocation is used.
+ if len(ips) > 0 {
+ hasIPv6 := false
+ for _, ip := range ips {
+ if ip.To4() == nil {
+ hasIPv6 = true
+ break
+ }
+ }
+
+ if !hasIPv6 {
+ eui64IP, err := eui64.ParseMAC(routerIntPortIPv6Net.IP, mac)
+ if err != nil {
+ return "", errors.Wrapf(err, "Failed generating EUI64 for instance port %q", mac.String())
+ }
+
+ // Add EUI64 to list of static IPs for instance port.
+ ips = append(ips, eui64IP)
+ }
+ }
}
instancePortName := n.getInstanceDevicePortName(instanceID, deviceName)
From 512fc6bad083c6ba698e8f28b15f44b624bb9ee1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:27:44 +0100
Subject: [PATCH 5/6] lxd/network/driver/ovn: Adds support for publishing
instance port IPs to uplink network
Uses the IPs in the DNS record for a switch port for publishing.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/driver_ovn.go | 63 +++++++++++++++++++++++++++++++++++++--
1 file changed, 60 insertions(+), 3 deletions(-)
diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 47f1c0daf9..9c41271966 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1922,12 +1922,50 @@ func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceN
return "", err
}
- dnsIPv4, dnsIPv6, err := client.LogicalSwitchPortSetDNS(n.getIntSwitchName(), instancePortName, fmt.Sprintf("%s.%s", instanceName, n.getDomainName()))
+ dnsUUID, dnsIPv4, dnsIPv6, err := client.LogicalSwitchPortSetDNS(n.getIntSwitchName(), instancePortName, fmt.Sprintf("%s.%s", instanceName, n.getDomainName()))
if err != nil {
return "", err
}
- revert.Add(func() { client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), instancePortName) })
+ revert.Add(func() { client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), dnsUUID) })
+
+ // Parse the network's external routes so we can check if the port's IPs fall within them and should be
+ // published to the uplink network.
+ for _, k := range []string{"ipv4.routes.external", "ipv6.routes.external"} {
+ if n.config[k] == "" {
+ continue
+ }
+
+ var ip net.IP
+
+ // Select the correct destination IP and check that NAT is disabled on the network.
+ if k == "ipv4.routes.external" && !shared.IsTrue(n.config["ipv4.nat"]) {
+ ip = dnsIPv4
+ } else if k == "ipv6.routes.external" && !shared.IsTrue(n.config["ipv6.nat"]) {
+ ip = dnsIPv6
+ }
+
+ if ip == nil {
+ continue //No qualifying target IP to check.
+ }
+
+ netExternalRoutes, err := SubnetParseAppend([]*net.IPNet{}, strings.Split(n.config[k], ",")...)
+ if err != nil {
+ return "", err
+ }
+
+ for _, netExternalRoute := range netExternalRoutes {
+ if netExternalRoute.Contains(ip) {
+ err = client.LogicalRouterDNATSNATAdd(n.getRouterName(), ip, ip, true, true)
+ if err != nil {
+ return "", err
+ }
+
+ revert.Add(func() { client.LogicalRouterDNATSNATDelete(n.getRouterName(), ip) })
+ break // Confirmed IP should be published on uplink, no need to look further.
+ }
+ }
+ }
// Add each internal route (using the IPs set for DNS as target).
for _, internalRoute := range internalRoutes {
@@ -2015,11 +2053,30 @@ func (n *ovn) instanceDevicePortDelete(instanceID int, deviceName string, intern
return err
}
- err = client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), instancePortName)
+ // Delete DNS records.
+ dnsUUID, _, dnsIPs, err := client.LogicalSwitchPortGetDNS(instancePortName)
+ if err != nil {
+ return err
+ }
+
+ err = client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), dnsUUID)
if err != nil {
return err
}
+ // Delete any associated external IP DNAT rules for the DNS IPs (if NAT disabled).
+ for _, dnsIP := range dnsIPs {
+ isV6 := dnsIP.To4() == nil
+
+ // Atempt to remove any externally published IP rules if the associated IP NAT setting is disabled.
+ if (!isV6 && !shared.IsTrue(n.config["ipv4.nat"])) || (isV6 && !shared.IsTrue(n.config["ipv6.nat"])) {
+ err = client.LogicalRouterDNATSNATDelete(n.getRouterName(), dnsIP)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
// Delete each internal route.
for _, internalRoute := range internalRoutes {
err = client.LogicalRouterRouteDelete(n.getRouterName(), internalRoute, nil)
From 13613e869eb58f30225b93285bbc62f8f71814d7 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 10:18:27 +0100
Subject: [PATCH 6/6] lxd/device/nic/ovn: Improved error messages
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/device/nic_ovn.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go
index 766adbcdf0..091888ab60 100644
--- a/lxd/device/nic_ovn.go
+++ b/lxd/device/nic_ovn.go
@@ -300,7 +300,7 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
internalRoutes, err = network.SubnetParseAppend(internalRoutes, strings.Split(d.config[key], ",")...)
if err != nil {
- return nil, errors.Wrapf(err, "Invalid %s", key)
+ return nil, errors.Wrapf(err, "Invalid %q value", key)
}
}
@@ -312,7 +312,7 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
externalRoutes, err = network.SubnetParseAppend(externalRoutes, strings.Split(d.config[key], ",")...)
if err != nil {
- return nil, errors.Wrapf(err, "Invalid %s", key)
+ return nil, errors.Wrapf(err, "Invalid %q value", key)
}
}
@@ -439,7 +439,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) {
internalRoutes, err = network.SubnetParseAppend(internalRoutes, strings.Split(d.config[key], ",")...)
if err != nil {
- return nil, errors.Wrapf(err, "Invalid %s", key)
+ return nil, errors.Wrapf(err, "Invalid %q value", key)
}
}
@@ -451,7 +451,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) {
externalRoutes, err = network.SubnetParseAppend(externalRoutes, strings.Split(d.config[key], ",")...)
if err != nil {
- return nil, errors.Wrapf(err, "Invalid %s", key)
+ return nil, errors.Wrapf(err, "Invalid %q value", key)
}
}
More information about the lxc-devel
mailing list