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

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) ===
- Aims to prevent concurrent OVN network creation from allocating the same IP on the parent network by using transactions. 
- Relies on the sqlite snapshot behaviour in WAL mode to detect if a the IP info of other networks has changed when writing the allocated IP back to the database.
- Adds `GetNonPendingNetworks` and `UpdateNetwork` to `ClusterTx` type so that all network related queries can be done inside a transaction.
- Updates `setupParentPortBridge` to start a transaction and pass it to its associated functions.
From 3d8cf6fdae11c5c9b3fda5c7b553a4e0aea4a0df Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 17 Aug 2020 14:43:37 +0100
Subject: [PATCH 1/5] lxd/db/networks: Separates network type and status
 conversion into separate functions

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 88a3e175fb..9e60c995c8 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -356,6 +356,20 @@ func (c *Cluster) getNetwork(name string, onlyCreated 
bool) (int64, *api.Network
        network.Description = description.String
        network.Config = config
 
+       // Populate Status and Type fields by converting from DB values.
+       networkFillStatus(&network, state)
+       networkFillType(&network, netType)
+
+       nodes, err := c.networkNodes(id)
+       if err != nil {
+               return -1, nil, err
+       }
+       network.Locations = nodes
+
+       return id, &network, nil
+}
+
+func networkFillStatus(network *api.Network, state int) {
        switch state {
        case networkPending:
                network.Status = api.NetworkStatusPending
@@ -366,7 +380,9 @@ func (c *Cluster) getNetwork(name string, onlyCreated bool) 
(int64, *api.Network
        default:
                network.Status = api.NetworkStatusUnknown
        }
+}
 
+func networkFillType(network *api.Network, netType NetworkType) {
        switch netType {
        case NetworkTypeBridge:
                network.Type = "bridge"
@@ -379,14 +395,6 @@ func (c *Cluster) getNetwork(name string, onlyCreated 
bool) (int64, *api.Network
        default:
                network.Type = "" // Unknown
        }
-
-       nodes, err := c.networkNodes(id)
-       if err != nil {
-               return -1, nil, err
-       }
-       network.Locations = nodes
-
-       return id, &network, nil
 }
 
 // Return the names of the nodes the given network is defined on.

From 984b076140a02a12dad74a520a3c69a4ac040082 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 17 Aug 2020 14:44:53 +0100
Subject: [PATCH 2/5] lxd/db/networks: Adds ClusterTx.GetNonPendingNetworks
 function

Returns non-pending networks and their configs using a transaction connection.

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index 9e60c995c8..c1bb3f82af 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -66,6 +66,60 @@ func (c *ClusterTx) GetNonPendingNetworkIDs() 
(map[string]int64, error) {
        return ids, nil
 }
 
+// GetNonPendingNetworks returns a map of api.Network associated to network ID.
+//
+// Pending networks are skipped.
+func (c *ClusterTx) GetNonPendingNetworks() (map[int64]api.Network, error) {
+       stmt, err := c.tx.Prepare("SELECT id, name, description, type, state 
FROM networks WHERE state != ?")
+       if err != nil {
+               return nil, err
+       }
+       defer stmt.Close()
+
+       rows, err := stmt.Query(networkPending)
+       if err != nil {
+               return nil, err
+       }
+       defer rows.Close()
+
+       networks := make(map[int64]api.Network)
+
+       for i := 0; rows.Next(); i++ {
+               var networkID int64
+               var networkType NetworkType
+               var networkState int
+               var network api.Network
+
+               err := rows.Scan(&networkID, &network.Name, 
&network.Description, &networkType, &networkState)
+               if err != nil {
+                       return nil, err
+               }
+
+               // Populate Status and Type fields by converting from DB values.
+               networkFillStatus(&network, networkState)
+               networkFillType(&network, networkType)
+
+               networks[networkID] = network
+       }
+       err = rows.Err()
+       if err != nil {
+               return nil, err
+       }
+
+       // Populate config.
+       for networkID, network := range networks {
+               networkConfig, err := query.SelectConfig(c.tx, 
"networks_config", "network_id=? AND (node_id=? OR node_id IS NULL)", 
networkID, c.nodeID)
+               if err != nil {
+                       return nil, err
+               }
+
+               network.Config = networkConfig
+               networks[networkID] = network
+       }
+
+       return networks, nil
+}
+
 // GetNetworkID returns the ID of the network with the given name.
 func (c *ClusterTx) GetNetworkID(name string) (int64, error) {
        stmt := "SELECT id FROM networks WHERE name=?"

From 304a344deff1eeeac1f7e150e7fec45616b9e732 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 17 Aug 2020 14:45:53 +0100
Subject: [PATCH 3/5] lxd/db/networks: Adds ClusterTx.UpdateNetwork function

And updates existing UpdateNetwork function to use it.

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

diff --git a/lxd/db/networks.go b/lxd/db/networks.go
index c1bb3f82af..2755eb35d9 100644
--- a/lxd/db/networks.go
+++ b/lxd/db/networks.go
@@ -312,6 +312,26 @@ func (c *ClusterTx) networkState(name string, state int) 
error {
        return nil
 }
 
+// UpdateNetwork updates the network with the given ID.
+func (c *ClusterTx) UpdateNetwork(id int64, description string, config 
map[string]string) error {
+       err := updateNetworkDescription(c.tx, id, description)
+       if err != nil {
+               return err
+       }
+
+       err = clearNetworkConfig(c.tx, id, c.nodeID)
+       if err != nil {
+               return err
+       }
+
+       err = networkConfigAdd(c.tx, id, c.nodeID, config)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
 // GetNetworks returns the names of existing networks.
 func (c *Cluster) GetNetworks() ([]string, error) {
        return c.networks("")
@@ -603,17 +623,7 @@ func (c *Cluster) UpdateNetwork(name, description string, 
config map[string]stri
        }
 
        err = c.Transaction(func(tx *ClusterTx) error {
-               err = updateNetworkDescription(tx.tx, id, description)
-               if err != nil {
-                       return err
-               }
-
-               err = clearNetworkConfig(tx.tx, id, c.nodeID)
-               if err != nil {
-                       return err
-               }
-
-               err = networkConfigAdd(tx.tx, id, c.nodeID, config)
+               err = tx.UpdateNetwork(id, description, config)
                if err != nil {
                        return err
                }

From 05af94244e81f3c8b2312515f53e819529f078a6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 17 Aug 2020 14:46:54 +0100
Subject: [PATCH 4/5] lxd/network/driver/ovn: Use DB transactions to safely
 allocate OVN external IPs on parent network

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 9d0cca9a26..fc49a5ce5a 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -15,6 +15,7 @@ import (
        "github.com/pkg/errors"
 
        "github.com/lxc/lxd/lxd/cluster"
+       "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/dnsmasq"
        "github.com/lxc/lxd/lxd/locking"
        "github.com/lxc/lxd/lxd/network/openvswitch"
@@ -288,58 +289,74 @@ func (n *ovn) setupParentPortBridge(parentNet Network, 
routerMAC net.HardwareAdd
                v.routerExtGwIPv6 = parentIPv6
        }
 
-       // Retrieve and parse existing allocated IPs for this network on the 
parent network.
+       // 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])
 
        // Decide whether we need to allocate new IP(s) and go to the expense 
of retrieving all allocated IPs.
        if (parentIPv4Net != nil && routerExtPortIPv4 == nil) || (parentIPv6Net 
!= nil && routerExtPortIPv6 == nil) {
-               allAllocatedIPv4, allAllocatedIPv6, err := 
n.parentAllAllocatedIPs(parentNet.Name())
-               if err != nil {
-                       return nil, errors.Wrapf(err, "Failed to get all 
allocated IPs for parent")
-               }
-
-               if parentIPv4Net != nil && routerExtPortIPv4 == nil {
-                       ipRanges, err := 
parseIPRanges(parentNetConf["ipv4.ovn.ranges"], parentNet.DHCPv4Subnet())
-                       if err != nil {
-                               return nil, errors.Wrapf(err, "Failed to parse 
parent IPv4 OVN ranges")
-                       }
-
-                       routerExtPortIPv4, err = n.parentAllocateIP(ipRanges, 
allAllocatedIPv4)
+               err := n.state.Cluster.Transaction(func(tx *db.ClusterTx) error 
{
+                       allAllocatedIPv4, allAllocatedIPv6, err := 
n.parentAllAllocatedIPs(tx, parentNet.Name())
                        if err != nil {
-                               return nil, errors.Wrapf(err, "Failed to 
allocate parent IPv4 address")
+                               return errors.Wrapf(err, "Failed to get all 
allocated IPs for parent")
                        }
 
-                       n.config[ovnVolatileParentIPv4] = 
routerExtPortIPv4.String()
-               }
+                       if parentIPv4Net != nil && routerExtPortIPv4 == nil {
+                               if parentNetConf["ipv4.ovn.ranges"] == "" {
+                                       return fmt.Errorf(`Missing required 
"ipv4.ovn.ranges" config key on parent network`)
+                               }
 
-               if parentIPv6Net != nil && routerExtPortIPv6 == nil {
-                       // If IPv6 OVN ranges are specified by the parent, 
allocate from them.
-                       if parentNetConf["ipv6.ovn.ranges"] != "" {
-                               ipRanges, err := 
parseIPRanges(parentNetConf["ipv6.ovn.ranges"], parentNet.DHCPv6Subnet())
+                               ipRanges, err := 
parseIPRanges(parentNetConf["ipv4.ovn.ranges"], parentNet.DHCPv4Subnet())
                                if err != nil {
-                                       return nil, errors.Wrapf(err, "Failed 
to parse parent IPv6 OVN ranges")
+                                       return errors.Wrapf(err, "Failed to 
parse parent IPv4 OVN ranges")
                                }
 
-                               routerExtPortIPv6, err = 
n.parentAllocateIP(ipRanges, allAllocatedIPv6)
+                               routerExtPortIPv4, err = 
n.parentAllocateIP(ipRanges, allAllocatedIPv4)
                                if err != nil {
-                                       return nil, errors.Wrapf(err, "Failed 
to allocate parent IPv6 address")
+                                       return errors.Wrapf(err, "Failed to 
allocate parent IPv4 address")
                                }
 
-                       } else {
-                               // Otherwise use EUI64 derived from MAC address.
-                               routerExtPortIPv6, err = 
eui64.ParseMAC(parentIPv6Net.IP, routerMAC)
-                               if err != nil {
-                                       return nil, err
+                               n.config[ovnVolatileParentIPv4] = 
routerExtPortIPv4.String()
+                       }
+
+                       if parentIPv6Net != nil && routerExtPortIPv6 == nil {
+                               // If IPv6 OVN ranges are specified by the 
parent, allocate from them.
+                               if parentNetConf["ipv6.ovn.ranges"] != "" {
+                                       ipRanges, err := 
parseIPRanges(parentNetConf["ipv6.ovn.ranges"], parentNet.DHCPv6Subnet())
+                                       if err != nil {
+                                               return errors.Wrapf(err, 
"Failed to parse parent IPv6 OVN ranges")
+                                       }
+
+                                       routerExtPortIPv6, err = 
n.parentAllocateIP(ipRanges, allAllocatedIPv6)
+                                       if err != nil {
+                                               return errors.Wrapf(err, 
"Failed to allocate parent IPv6 address")
+                                       }
+
+                               } else {
+                                       // Otherwise use EUI64 derived from MAC 
address.
+                                       routerExtPortIPv6, err = 
eui64.ParseMAC(parentIPv6Net.IP, routerMAC)
+                                       if err != nil {
+                                               return err
+                                       }
                                }
+
+                               n.config[ovnVolatileParentIPv6] = 
routerExtPortIPv6.String()
                        }
 
-                       n.config[ovnVolatileParentIPv6] = 
routerExtPortIPv6.String()
-               }
+                       networkID, err := tx.GetNetworkID(n.name)
+                       if err != nil {
+                               return errors.Wrapf(err, "Failed to get network 
ID for network %q", n.name)
+                       }
 
-               err = n.state.Cluster.UpdateNetwork(n.name, n.description, 
n.config)
+                       err = tx.UpdateNetwork(networkID, n.description, 
n.config)
+                       if err != nil {
+                               return errors.Wrapf(err, "Failed saving 
allocated parent network IPs")
+                       }
+
+                       return nil
+               })
                if err != nil {
-                       return nil, errors.Wrapf(err, "Failed saving allocated 
parent network IPs")
+                       return nil, err
                }
        }
 
@@ -364,9 +381,9 @@ func (n *ovn) setupParentPortBridge(parentNet Network, 
routerMAC net.HardwareAdd
 }
 
 // parentAllAllocatedIPs gets a list of all IPv4 and IPv6 addresses allocated 
to OVN networks connected to parent.
-func (n *ovn) parentAllAllocatedIPs(parentNetName string) ([]net.IP, []net.IP, 
error) {
-       // Get a list of managed networks.
-       networks, err := n.state.Cluster.GetNonPendingNetworks()
+func (n *ovn) parentAllAllocatedIPs(tx *db.ClusterTx, parentNetName string) 
([]net.IP, []net.IP, error) {
+       // Get all managed networks.
+       networks, err := tx.GetNonPendingNetworks()
        if err != nil {
                return nil, nil, err
        }
@@ -374,12 +391,7 @@ func (n *ovn) parentAllAllocatedIPs(parentNetName string) 
([]net.IP, []net.IP, e
        v4IPs := make([]net.IP, 0)
        v6IPs := make([]net.IP, 0)
 
-       for _, name := range networks {
-               _, netInfo, err := n.state.Cluster.GetNetworkInAnyState(name)
-               if err != nil {
-                       return nil, nil, err
-               }
-
+       for _, netInfo := range networks {
                if netInfo.Type != "ovn" || netInfo.Config["parent"] != 
parentNetName {
                        continue
                }

From 245443955eabede3bf8a5c91e20c4514363bf116 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 17 Aug 2020 14:47:23 +0100
Subject: [PATCH 5/5] lxd/network/driver/ovn: Include last IP in OVN range for
 allocatable IPs

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 fc49a5ce5a..cabe01f2f8 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -436,7 +436,7 @@ func (n *ovn) parentAllocateIP(ipRanges []*shared.IPRange, 
allAllocated []net.IP
 
                // Iterate through IPs in range, return the first unallocated 
one found.
                for {
-                       if startBig.Cmp(endBig) >= 0 {
+                       if startBig.Cmp(endBig) > 0 {
                                break
                        }
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to