The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/8055
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) === These are only used on OVN NICs now.
From 71caae866af04c142dbb48cc19a4d7a126e3657a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 15:56:33 +0100 Subject: [PATCH 01/15] lxd/network/driver/ovn: Only call Validate in FillConfig if state is set 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 ea1616d269..cfc5e7fff2 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1168,7 +1168,7 @@ func (n *ovn) FillConfig(config map[string]string) error { changedConfig = true } - if changedConfig { + if changedConfig && n.state != nil { return n.Validate(config) } From f6add9084341a88c0847398eb4db5610e7e5f1bd Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 15:56:15 +0100 Subject: [PATCH 02/15] lxd/db/projects: Adds GetProject function Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/db/projects.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lxd/db/projects.go b/lxd/db/projects.go index cf03765ea5..ff613611d1 100644 --- a/lxd/db/projects.go +++ b/lxd/db/projects.go @@ -189,3 +189,22 @@ func (c *ClusterTx) InitProjectWithoutImages(project string) error { _, err = c.tx.Exec(stmt, defaultProfileID) return err } + +// GetProject returns the project with the given key. +func (c *Cluster) GetProject(projectName string) (*api.Project, error) { + var err error + var p *api.Project + err = c.Transaction(func(tx *ClusterTx) error { + p, err = tx.GetProject(projectName) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return p, nil +} From fa2da560919d515ecd24a2aebc40b4a213c34fea Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 13:56:53 +0100 Subject: [PATCH 03/15] lxd/network/driver/ovn: Converts instance port functions to exported So they can be accessed by OVN NIC directly. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index cfc5e7fff2..25780813e1 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1887,8 +1887,8 @@ func (n *ovn) getInstanceDevicePortName(instanceID int, deviceName string) openv return openvswitch.OVNSwitchPort(fmt.Sprintf("%s-%d-%s", n.getIntSwitchInstancePortPrefix(), instanceID, deviceName)) } -// instanceDevicePortAdd adds an instance device port to the internal logical switch and returns the port name. -func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) (openvswitch.OVNSwitchPort, error) { +// InstanceDevicePortAdd adds an instance device port to the internal logical switch and returns the port name. +func (n *ovn) InstanceDevicePortAdd(instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) (openvswitch.OVNSwitchPort, error) { var dhcpV4ID, dhcpv6ID string revert := revert.New() @@ -2065,8 +2065,8 @@ func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceN return instancePortName, nil } -// instanceDevicePortIPs returns the dynamically allocated IPs for a device port. -func (n *ovn) instanceDevicePortDynamicIPs(instanceID int, deviceName string) ([]net.IP, error) { +// InstanceDevicePortDynamicIPs returns the dynamically allocated IPs for a device port. +func (n *ovn) InstanceDevicePortDynamicIPs(instanceID int, deviceName string) ([]net.IP, error) { instancePortName := n.getInstanceDevicePortName(instanceID, deviceName) client, err := n.getClient() @@ -2077,8 +2077,8 @@ func (n *ovn) instanceDevicePortDynamicIPs(instanceID int, deviceName string) ([ return client.LogicalSwitchPortDynamicIPs(instancePortName) } -// instanceDevicePortDelete deletes an instance device port from the internal logical switch. -func (n *ovn) instanceDevicePortDelete(instanceID int, deviceName string, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) error { +// InstanceDevicePortDelete deletes an instance device port from the internal logical switch. +func (n *ovn) InstanceDevicePortDelete(instanceID int, deviceName string, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) error { instancePortName := n.getInstanceDevicePortName(instanceID, deviceName) client, err := n.getClient() From d5351219834cbb9bb12faba3cf653f0751765898 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 14:02:16 +0100 Subject: [PATCH 04/15] lxd/network/driver/ovn: Removes ipv4.routes.external and ipv6.routes.external Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index 25780813e1..91463e5477 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -133,13 +133,11 @@ func (n *ovn) Validate(config map[string]string) error { return validate.Optional(validate.IsNetworkAddressCIDRV6)(value) }, - "ipv6.dhcp.stateful": validate.Optional(validate.IsBool), - "ipv4.routes.external": validate.Optional(validate.IsNetworkV4List), - "ipv6.routes.external": validate.Optional(validate.IsNetworkV6List), - "ipv4.nat": validate.Optional(validate.IsBool), - "ipv6.nat": validate.Optional(validate.IsBool), - "dns.domain": validate.IsAny, - "dns.search": validate.IsAny, + "ipv6.dhcp.stateful": validate.Optional(validate.IsBool), + "ipv4.nat": validate.Optional(validate.IsBool), + "ipv6.nat": validate.Optional(validate.IsBool), + "dns.domain": validate.IsAny, + "dns.search": validate.IsAny, // Volatile keys populated automatically as needed. ovnVolatileUplinkIPv4: validate.Optional(validate.IsNetworkAddressV4), @@ -232,28 +230,6 @@ func (n *ovn) Validate(config map[string]string) error { } } - // Check IP external routes are within the uplink network's routes and project's subnet restrictions. - if config["ipv4.routes.external"] != "" || config["ipv6.routes.external"] != "" { - // Parse and validate our external routes. - for _, k := range []string{"ipv4.routes.external", "ipv6.routes.external"} { - if config[k] == "" { - continue - } - - routeSubnetList, err := SubnetParseAppend([]*net.IPNet{}, strings.Split(config[k], ",")...) - if err != nil { - return err - } - - for _, routeSubnet := range routeSubnetList { - err = n.validateExternalSubnet(uplinkRoutes, projectRestrictedSubnets, routeSubnet) - if err != nil { - return err - } - } - } - } - return nil } From e1668b6f4f92365854ad48eb4ee243186c87fa15 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:00:06 +0100 Subject: [PATCH 05/15] lxc/network/driver/ovn: Adds projectRestrictedSubnets and uplinkRoutes functions Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index 91463e5477..1e537c7413 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -78,6 +78,61 @@ func (n *ovn) Info() Info { } } +// uplinkRoutes parses ipv4.routes and ipv6.routes settings for a named uplink network into a slice of *net.IPNet. +func (n *ovn) uplinkRoutes(uplinkNetworkName string) ([]*net.IPNet, error) { + _, uplink, err := n.state.Cluster.GetNetworkInAnyState(project.Default, uplinkNetworkName) + if err != nil { + return nil, err + } + + var uplinkRoutes []*net.IPNet + for _, k := range []string{"ipv4.routes", "ipv6.routes"} { + if uplink.Config[k] == "" { + continue + } + + uplinkRoutes, err = SubnetParseAppend(uplinkRoutes, strings.Split(uplink.Config[k], ",")...) + if err != nil { + return nil, err + } + } + + return uplinkRoutes, nil +} + +// projectRestrictedSubnets parses the restrict.networks.subnets project setting and returns slice of *net.IPNet. +// Returns nil slice if no project restrictions, or empty slice if no allowed subnets. +func (n *ovn) projectRestrictedSubnets(p *api.Project, uplinkNetworkName string) ([]*net.IPNet, error) { + // Parse project's restricted subnets. + var projectRestrictedSubnets []*net.IPNet // Nil value indicates not restricted. + if shared.IsTrue(p.Config["restricted"]) && p.Config["restricted.networks.subnets"] != "" { + projectRestrictedSubnets = []*net.IPNet{} // Empty slice indicates no allowed subnets. + + for _, subnetRaw := range strings.Split(p.Config["restricted.networks.subnets"], ",") { + subnetParts := strings.SplitN(strings.TrimSpace(subnetRaw), ":", 2) + if len(subnetParts) != 2 { + return nil, fmt.Errorf(`Project subnet %q invalid, must be in the format of "<uplink network>:<subnet>"`, subnetRaw) + } + + subnetUplinkName := subnetParts[0] + subnetStr := subnetParts[1] + + if subnetUplinkName != uplinkNetworkName { + continue // Only include subnets for our uplink. + } + + _, restrictedSubnet, err := net.ParseCIDR(subnetStr) + if err != nil { + return nil, err + } + + projectRestrictedSubnets = append(projectRestrictedSubnets, restrictedSubnet) + } + } + + return projectRestrictedSubnets, nil +} + // validateExternalSubnet checks the supplied ipNet is allowed within the uplink routes and project // restricted subnets. If projectRestrictedSubnets is nil, then it is not checked as this indicates project has // no restrictions. Whereas if uplinkRoutes is nil/empty then this will always return an error. From bbfce066807e2491abe1bd281cccc0ac6c8f7b26 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:00:30 +0100 Subject: [PATCH 06/15] lxd/network/driver/ovn: Simplifies Validate by using separate data loader functions Also removes a duplicate project load query from DB. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 60 ++++++--------------------------------- 1 file changed, 9 insertions(+), 51 deletions(-) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index 1e537c7413..8d7b55111f 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -205,69 +205,27 @@ func (n *ovn) Validate(config map[string]string) error { } // Load the project to get uplink network restrictions. - var projectRow *api.Project - err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error { - projectRow, err = tx.GetProject(n.project) - if err != nil { - return err - } - - return nil - }) + p, err := n.state.Cluster.GetProject(n.project) if err != nil { - return errors.Wrapf(err, "Failed to load IP route restrictions for project %q", n.project) + return errors.Wrapf(err, "Failed to load network restrictions from project %q", n.project) } // Check uplink network is valid and allowed in project. - uplinkNetworkName, err := n.validateUplinkNetwork(config["network"]) + uplinkNetworkName, err := n.validateUplinkNetwork(p, config["network"]) if err != nil { return err } - // Load the uplink network to get uplink routes. - _, uplink, err := n.state.Cluster.GetNetworkInAnyState(project.Default, uplinkNetworkName) + // Get uplink routes. + uplinkRoutes, err := n.uplinkRoutes(uplinkNetworkName) if err != nil { return err } - // Parse uplink route subnets. - var uplinkRoutes []*net.IPNet - for _, k := range []string{"ipv4.routes", "ipv6.routes"} { - if uplink.Config[k] == "" { - continue - } - - uplinkRoutes, err = SubnetParseAppend(uplinkRoutes, strings.Split(uplink.Config[k], ",")...) - if err != nil { - return err - } - } - - // Parse project's restricted subnets. - var projectRestrictedSubnets []*net.IPNet // Nil value indicates not restricted. - if shared.IsTrue(projectRow.Config["restricted"]) && projectRow.Config["restricted.networks.subnets"] != "" { - projectRestrictedSubnets = []*net.IPNet{} // Empty slice indicates no allowed subnets. - - for _, subnetRaw := range strings.Split(projectRow.Config["restricted.networks.subnets"], ",") { - subnetParts := strings.SplitN(strings.TrimSpace(subnetRaw), ":", 2) - if len(subnetParts) != 2 { - return fmt.Errorf(`Project subnet %q invalid, must be in the format of "<uplink network>:<subnet>"`, subnetRaw) - } - - uplinkName := subnetParts[0] - subnetStr := subnetParts[1] - - if uplinkName != uplink.Name { - continue // Only include subnets for our uplink. - } - - _, restrictedSubnet, err := net.ParseCIDR(subnetStr) - if err != nil { - return err - } - - projectRestrictedSubnets = append(projectRestrictedSubnets, restrictedSubnet) - } + // Get project restricted routes. + projectRestrictedSubnets, err := n.projectRestrictedSubnets(p, uplinkNetworkName) + if err != nil { + return err } // If NAT disabled, check subnets are within the uplink network's routes and project's subnet restrictions. From d84254815bcb4e822fad4ef67f6a2c84a4c5a76e Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:01:14 +0100 Subject: [PATCH 07/15] lxd/network/driver/ovn: Passes project into allowedUplinkNetworks Rather than load again from DB. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index 8d7b55111f..f401f08fd0 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1180,7 +1180,7 @@ func (n *ovn) Create(clientType cluster.ClientType) error { } // allowedUplinkNetworks returns a list of allowed networks to use as uplinks based on project restrictions. -func (n *ovn) allowedUplinkNetworks() ([]string, error) { +func (n *ovn) allowedUplinkNetworks(p *api.Project) ([]string, error) { // Uplink networks are always from the default project. networks, err := n.state.Cluster.GetNetworks(project.Default) if err != nil { @@ -1200,34 +1200,20 @@ func (n *ovn) allowedUplinkNetworks() ([]string, error) { } } - // Load the project to get uplink network restrictions. - var project *api.Project - err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error { - project, err = tx.GetProject(n.project) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, errors.Wrapf(err, "Failed to load restrictions for project %q", n.project) - } - // If project is not restricted, return full network list. - if !shared.IsTrue(project.Config["restricted"]) { + if !shared.IsTrue(p.Config["restricted"]) { return networks, nil } allowedNetworks := []string{} // There are no allowed networks if restricted.networks.uplinks is not set. - if project.Config["restricted.networks.uplinks"] == "" { + if p.Config["restricted.networks.uplinks"] == "" { return allowedNetworks, nil } // Parse the allowed uplinks and return any that are present in the actual defined networks. - allowedRestrictedUplinks := strings.Split(project.Config["restricted.networks.uplinks"], ",") + allowedRestrictedUplinks := strings.Split(p.Config["restricted.networks.uplinks"], ",") for _, allowedRestrictedUplink := range allowedRestrictedUplinks { allowedRestrictedUplink = strings.TrimSpace(allowedRestrictedUplink) From bfd7ac77861bdc804e742948aa0c7c12b38946c2 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:01:43 +0100 Subject: [PATCH 08/15] lxd/network/driver/ovn: Passes project into validateUplinkNetwork To avoid additional query. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index f401f08fd0..9ea0806cf2 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1229,8 +1229,8 @@ func (n *ovn) allowedUplinkNetworks(p *api.Project) ([]string, error) { // validateUplinkNetwork checks if uplink network is allowed, and if empty string is supplied then tries to select // an uplink network from the allowedUplinkNetworks() list if there is only one allowed network. // Returns chosen uplink network name to use. -func (n *ovn) validateUplinkNetwork(uplinkNetworkName string) (string, error) { - allowedUplinkNetworks, err := n.allowedUplinkNetworks() +func (n *ovn) validateUplinkNetwork(p *api.Project, uplinkNetworkName string) (string, error) { + allowedUplinkNetworks, err := n.allowedUplinkNetworks(p) if err != nil { return "", err } From 44e6104b2a3acccb81bbc4a6c2c8f5eca43da058 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:02:09 +0100 Subject: [PATCH 09/15] lxd/network/driver/ovn: Load project in setup() to pass to n.validateUplinkNetwork() Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index 9ea0806cf2..8f568c54c5 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1276,8 +1276,14 @@ func (n *ovn) setup(update bool) error { // Record updated config so we can store back into DB and n.config variable. updatedConfig := make(map[string]string) + // Load the project to get uplink network restrictions. + p, err := n.state.Cluster.GetProject(n.project) + if err != nil { + return errors.Wrapf(err, "Failed to load network restrictions from project %q", n.project) + } + // Check project restrictions and get uplink network to use. - uplinkNetwork, err := n.validateUplinkNetwork(n.config["network"]) + uplinkNetwork, err := n.validateUplinkNetwork(p, n.config["network"]) if err != nil { return err } From 3efb1adc6d469353639768c632549ca846b65d38 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:02:30 +0100 Subject: [PATCH 10/15] lxd/network/driver/ovn: Adds InstanceDevicePortValidateExternalRoutes function To validate a OVN NIC's external routes against an OVN network's uplink routes and project's restricted subnet. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/driver_ovn.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index 8f568c54c5..1898ad2f4a 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1868,6 +1868,36 @@ func (n *ovn) getInstanceDevicePortName(instanceID int, deviceName string) openv return openvswitch.OVNSwitchPort(fmt.Sprintf("%s-%d-%s", n.getIntSwitchInstancePortPrefix(), instanceID, deviceName)) } +// InstanceDevicePortValidateExternalRoutes validates the external routes for an OVN instance port. +func (n *ovn) InstanceDevicePortValidateExternalRoutes(externalRoutes []*net.IPNet) error { + // Load the project to get uplink network restrictions. + p, err := n.state.Cluster.GetProject(n.project) + if err != nil { + return errors.Wrapf(err, "Failed to load network restrictions from project %q", n.project) + } + + // Get uplink routes. + uplinkRoutes, err := n.uplinkRoutes(n.config["network"]) + if err != nil { + return err + } + + // Get project restricted routes. + projectRestrictedSubnets, err := n.projectRestrictedSubnets(p, n.config["network"]) + if err != nil { + return err + } + + for _, externalRoute := range externalRoutes { + err = n.validateExternalSubnet(uplinkRoutes, projectRestrictedSubnets, externalRoute) + if err != nil { + return err + } + } + + return nil +} + // InstanceDevicePortAdd adds an instance device port to the internal logical switch and returns the port name. func (n *ovn) InstanceDevicePortAdd(instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) (openvswitch.OVNSwitchPort, error) { var dhcpV4ID, dhcpv6ID string From 131a644a8507295a9c25882e88a48ba952e125bf Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 13:55:55 +0100 Subject: [PATCH 11/15] lxd/network/network/utils/ovn: Remvoes unused functions Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/network/network_utils_ovn.go | 42 -------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 lxd/network/network_utils_ovn.go diff --git a/lxd/network/network_utils_ovn.go b/lxd/network/network_utils_ovn.go deleted file mode 100644 index 41b8e89d27..0000000000 --- a/lxd/network/network_utils_ovn.go +++ /dev/null @@ -1,42 +0,0 @@ -package network - -import ( - "fmt" - "net" - - "github.com/lxc/lxd/lxd/network/openvswitch" -) - -// OVNInstanceDevicePortAdd adds a logical port to the OVN network's internal switch and returns the logical -// port name for use linking an OVS port on the integration bridge to the logical switch port. -func OVNInstanceDevicePortAdd(network Network, instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) (openvswitch.OVNSwitchPort, error) { - // Check network is of type OVN. - n, ok := network.(*ovn) - if !ok { - return "", fmt.Errorf("Network is not OVN type") - } - - return n.instanceDevicePortAdd(instanceID, instanceName, deviceName, mac, ips, internalRoutes, externalRoutes) -} - -// OVNInstanceDevicePortDynamicIPs gets a logical port's dynamic IPs stored in the OVN network's internal switch. -func OVNInstanceDevicePortDynamicIPs(network Network, instanceID int, deviceName string) ([]net.IP, error) { - // Check network is of type OVN. - n, ok := network.(*ovn) - if !ok { - return nil, fmt.Errorf("Network is not OVN type") - } - - return n.instanceDevicePortDynamicIPs(instanceID, deviceName) -} - -// OVNInstanceDevicePortDelete deletes a logical port from the OVN network's internal switch. -func OVNInstanceDevicePortDelete(network Network, instanceID int, deviceName string, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) error { - // Check network is of type OVN. - n, ok := network.(*ovn) - if !ok { - return fmt.Errorf("Network is not OVN type") - } - - return n.instanceDevicePortDelete(instanceID, deviceName, internalRoutes, externalRoutes) -} From 08415e9d128c15abf020089325ba5c090ceb0e7d Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 13:58:09 +0100 Subject: [PATCH 12/15] lxd/device/nic/ovn: Adds ovnNet interface and use OVN instance port functions directly from network Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/nic_ovn.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go index 091888ab60..99c49ea622 100644 --- a/lxd/device/nic_ovn.go +++ b/lxd/device/nic_ovn.go @@ -24,10 +24,20 @@ import ( log "github.com/lxc/lxd/shared/log15" ) +// ovnNet defines an interface for accessing instance specific functions on OVN network. +type ovnNet interface { + network.Network + + InstanceDevicePortValidateExternalRoutes(externalRoutes []*net.IPNet) error + InstanceDevicePortAdd(instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) (openvswitch.OVNSwitchPort, error) + InstanceDevicePortDelete(instanceID int, deviceName string, internalRoutes []*net.IPNet, externalRoutes []*net.IPNet) error + InstanceDevicePortDynamicIPs(instanceID int, deviceName string) ([]net.IP, error) +} + type nicOVN struct { deviceCommon - network network.Network // Populated in validateConfig(). + network ovnNet // Populated in validateConfig(). } // getIntegrationBridgeName returns the OVS integration bridge to use. @@ -91,7 +101,12 @@ func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error { } } - d.network = n // Stored loaded instance for use by other functions. + ovnNet, ok := n.(ovnNet) + if !ok { + return fmt.Errorf("Network is not OVN type") + } + + d.network = ovnNet // Stored loaded instance for use by other functions. netConfig := d.network.Config() if d.config["ipv4.address"] != "" { @@ -317,14 +332,12 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) { } // Add new OVN logical switch port for instance. - logicalPortName, err := network.OVNInstanceDevicePortAdd(d.network, d.inst.ID(), d.inst.Name(), d.name, mac, ips, internalRoutes, externalRoutes) + logicalPortName, err := d.network.InstanceDevicePortAdd(d.inst.ID(), d.inst.Name(), d.name, mac, ips, internalRoutes, externalRoutes) if err != nil { return nil, errors.Wrapf(err, "Failed adding OVN port") } - revert.Add(func() { - network.OVNInstanceDevicePortDelete(d.network, d.inst.ID(), d.name, internalRoutes, externalRoutes) - }) + revert.Add(func() { d.network.InstanceDevicePortDelete(d.inst.ID(), d.name, internalRoutes, externalRoutes) }) // Attach host side veth interface to bridge. integrationBridge, err := d.getIntegrationBridgeName() @@ -455,7 +468,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) { } } - err = network.OVNInstanceDevicePortDelete(d.network, d.inst.ID(), d.name, internalRoutes, externalRoutes) + err = d.network.InstanceDevicePortDelete(d.inst.ID(), d.name, internalRoutes, externalRoutes) if err != nil { // Don't fail here as we still want the postStop hook to run to clean up the local veth pair. d.logger.Error("Failed to remove OVN device port", log.Ctx{"err": err}) @@ -531,7 +544,7 @@ func (d *nicOVN) State() (*api.InstanceStateNetwork, error) { // OVN only supports dynamic IP allocation if neither IPv4 or IPv6 are statically set. if d.config["ipv4.address"] == "" && d.config["ipv6.address"] == "" { - dynamicIPs, err := network.OVNInstanceDevicePortDynamicIPs(d.network, d.inst.ID(), d.name) + dynamicIPs, err := d.network.InstanceDevicePortDynamicIPs(d.inst.ID(), d.name) if err == nil { for _, dynamicIP := range dynamicIPs { family := "inet" From b9b1964381a0851d00c552fac3519dd02daf28ad Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:03:40 +0100 Subject: [PATCH 13/15] lxd/device/nic/ovn: Removes validation of external routes against network's external routes Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/nic_ovn.go | 50 ------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go index 99c49ea622..16b1dff6cd 100644 --- a/lxd/device/nic_ovn.go +++ b/lxd/device/nic_ovn.go @@ -145,56 +145,6 @@ func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error { } } - // Check IP external routes are within the network's external routes. - if d.config["ipv4.routes.external"] != "" || d.config["ipv6.routes.external"] != "" { - // Parse network external route subnets. - var networkRoutes []*net.IPNet - for _, k := range []string{"ipv4.routes.external", "ipv6.routes.external"} { - if netConfig[k] == "" { - continue - } - - networkRoutes, err = network.SubnetParseAppend(networkRoutes, strings.Split(netConfig[k], ",")...) - if err != nil { - return err - } - } - - // Parse and validate our external routes. - for _, k := range []string{"ipv4.routes.external", "ipv6.routes.external"} { - if d.config[k] == "" { - continue - } - - externalRoutes, err := network.SubnetParseAppend([]*net.IPNet{}, strings.Split(d.config[k], ",")...) - if err != nil { - return err - } - - for _, externalRoute := range externalRoutes { - rOnes, rBits := externalRoute.Mask.Size() - if rBits > 32 && rOnes < 122 { - return fmt.Errorf("External route %q is too large. Maximum size for IPv6 external route is /122", externalRoute.String()) - } else if rOnes < 26 { - return fmt.Errorf("External route %q is too large. Maximum size for IPv4 external route is /26", externalRoute.String()) - } - - // Check that the external route is within the network's routes. - foundMatch := false - for _, networkRoute := range networkRoutes { - if network.SubnetContains(networkRoute, externalRoute) { - foundMatch = true - break - } - } - - if !foundMatch { - return fmt.Errorf("Network %q doesn't contain %q in its external routes", n.Name(), externalRoute.String()) - } - } - } - } - // Apply network level config options to device config before validation. d.config["mtu"] = fmt.Sprintf("%s", netConfig["bridge.mtu"]) From 78d4ec8e820ad4f41f42887565b2f112442f3fff Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:04:11 +0100 Subject: [PATCH 14/15] lxd/device/nic/ovn: Validate NICs external routes using d.network.InstanceDevicePortValidateExternalRoutes Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/nic_ovn.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go index 16b1dff6cd..f743083165 100644 --- a/lxd/device/nic_ovn.go +++ b/lxd/device/nic_ovn.go @@ -156,6 +156,35 @@ func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error { return err } + // Check IP external routes are within the network's external routes. + var externalRoutes []*net.IPNet + for _, k := range []string{"ipv4.routes.external", "ipv6.routes.external"} { + if d.config[k] == "" { + continue + } + + externalRoutes, err = network.SubnetParseAppend(externalRoutes, strings.Split(d.config[k], ",")...) + if err != nil { + return err + } + } + + if len(externalRoutes) > 0 { + for _, externalRoute := range externalRoutes { + rOnes, rBits := externalRoute.Mask.Size() + if rBits > 32 && rOnes < 122 { + return fmt.Errorf("External route %q is too large. Maximum size for IPv6 external route is /122", externalRoute.String()) + } else if rOnes < 26 { + return fmt.Errorf("External route %q is too large. Maximum size for IPv4 external route is /26", externalRoute.String()) + } + } + + err = d.network.InstanceDevicePortValidateExternalRoutes(externalRoutes) + if err != nil { + return err + } + } + return nil } From 4a2a968c0e4e330d00d2c65d434bf9101f883c43 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Mon, 19 Oct 2020 17:06:19 +0100 Subject: [PATCH 15/15] doc/networks: Removes ipv4.routes.external and ipv6.routes.external from ovn network Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- doc/networks.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/networks.md b/doc/networks.md index d03da41f9d..dc6e109a2e 100644 --- a/doc/networks.md +++ b/doc/networks.md @@ -298,11 +298,9 @@ dns.domain | string | - | lxd dns.search | string | - | - | Full comma separated domain search list, defaulting to `dns.domain` value ipv4.address | string | standard mode | random unused subnet | IPv4 address for the bridge (CIDR notation). Use "none" to turn off IPv4 or "auto" to generate a new one ipv4.nat | boolean | ipv4 address | false | Whether to NAT (will default to true if unset and a random ipv4.address is generated) -ipv4.routes.external | string | ipv4 address | - | Comma separated list of additional external IPv4 CIDR subnets that are allowed for OVN NICs ipv4.routes.external setting 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 ipv6.nat | boolean | ipv6 address | false | Whether to NAT (will default to true if unset and a random ipv6.address is generated) -ipv6.routes.external | string | ipv6 address | - | Comma separated list of additional external IPv6 CIDR subnets that are allowed for OVN NICs ipv6.routes.external setting network | string | - | - | Uplink network to use for external network access ## network: physical
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel