[lxc-devel] [lxd/master] Network: Adds ovn.name setting to allow chassis group add during cluster pre-join phase
tomponline on Github
lxc-bot at linuxcontainers.org
Fri Sep 4 10:07:40 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 855 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200904/32f421a7/attachment.bin>
-------------- next part --------------
From a3620edb4c9deeca588ab005321e9935098b2d61 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 3 Sep 2020 18:22:02 +0100
Subject: [PATCH 1/5] lxd/network/driver/ovn: Add and delete local chassis ID
to HA chassis group on start/stop
Allows each node to add its local OVS chassis ID to the logical OVN chassis group.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/driver_ovn.go | 97 ++++++++++++++++++++++++++++++---------
1 file changed, 75 insertions(+), 22 deletions(-)
diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 8414132368..2ec0c2576f 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -825,19 +825,6 @@ func (n *ovn) setup(update bool) error {
revert.Add(func() { client.ChassisGroupDelete(n.getChassisGroupName()) })
- // Add local chassis to chassis group.
- ovs := openvswitch.NewOVS()
- chassisID, err := ovs.ChassisID()
- if err != nil {
- return errors.Wrapf(err, "Failed getting OVS Chassis ID")
- }
-
- var priority uint = ovnChassisPriorityMax
- err = client.ChassisGroupChassisAdd(n.getChassisGroupName(), chassisID, priority)
- if err != nil {
- return errors.Wrapf(err, "Failed adding OVS chassis %q with priority %d to chassis group %q", chassisID, priority, n.getChassisGroupName())
- }
-
// Create logical router.
if update {
client.LogicalRouterDelete(n.getRouterName())
@@ -1068,10 +1055,60 @@ func (n *ovn) setup(update bool) error {
return nil
}
+// addChassisGroupEntry adds an entry for the local OVS chassis to the OVN logical network's chassis group.
+func (n *ovn) addChassisGroupEntry() error {
+ client, err := n.getClient()
+ if err != nil {
+ return err
+ }
+
+ // Add local chassis to chassis group.
+ ovs := openvswitch.NewOVS()
+ chassisID, err := ovs.ChassisID()
+ if err != nil {
+ return errors.Wrapf(err, "Failed getting OVS Chassis ID")
+ }
+
+ var priority uint = ovnChassisPriorityMax
+ err = client.ChassisGroupChassisAdd(n.getChassisGroupName(), chassisID, priority)
+ if err != nil {
+ return errors.Wrapf(err, "Failed adding OVS chassis %q with priority %d to chassis group %q", chassisID, priority, n.getChassisGroupName())
+ }
+
+ return nil
+}
+
+// deleteChassisGroupEntry deletes an entry for the local OVS chassis from the OVN logical network's chassis group.
+func (n *ovn) deleteChassisGroupEntry() error {
+ client, err := n.getClient()
+ if err != nil {
+ return err
+ }
+
+ // Add local chassis to chassis group.
+ ovs := openvswitch.NewOVS()
+ chassisID, err := ovs.ChassisID()
+ if err != nil {
+ return errors.Wrapf(err, "Failed getting OVS Chassis ID")
+ }
+
+ err = client.ChassisGroupChassisDelete(n.getChassisGroupName(), chassisID)
+ if err != nil {
+ return errors.Wrapf(err, "Failed deleting OVS chassis %q from chassis group %q", chassisID, n.getChassisGroupName())
+ }
+
+ return nil
+}
+
// Delete deletes a network.
func (n *ovn) Delete(clientType cluster.ClientType) error {
n.logger.Debug("Delete", log.Ctx{"clientType": clientType})
+ err := n.Stop()
+ if err != nil {
+ return err
+ }
+
if clientType == cluster.ClientTypeNormal {
client, err := n.getClient()
if err != nil {
@@ -1125,12 +1162,6 @@ func (n *ovn) Delete(clientType cluster.ClientType) error {
}
}
- // Delete local parent uplink port.
- err := n.deleteParentPort()
- if err != nil {
- return err
- }
-
return n.common.delete(clientType)
}
@@ -1157,7 +1188,7 @@ func (n *ovn) Rename(newName string) error {
return nil
}
-// Start starts configures the local OVS parent uplink port.
+// Start starts adds the local OVS chassis ID to the OVN chass group and starts the local OVS parent uplink port.
func (n *ovn) Start() error {
n.logger.Debug("Start")
@@ -1165,7 +1196,13 @@ func (n *ovn) Start() error {
return fmt.Errorf("Cannot start pending network")
}
- err := n.startParentPort()
+ // Add local node's OVS chassis ID to logical chassis group.
+ err := n.addChassisGroupEntry()
+ if err != nil {
+ return err
+ }
+
+ err = n.startParentPort()
if err != nil {
return err
}
@@ -1173,10 +1210,26 @@ func (n *ovn) Start() error {
return nil
}
-// Stop stops is a no-op.
+// Stop deletes the local OVS parent uplink port (if unused) and deletes the local OVS chassis ID from the
+// OVN chass group
func (n *ovn) Stop() error {
n.logger.Debug("Stop")
+ // Delete local OVS chassis ID from logical OVN HA chassis group.
+ err := n.deleteChassisGroupEntry()
+ if err != nil {
+ return err
+ }
+
+ // Delete local parent uplink port.
+ // This must occur after the local OVS chassis ID is removed from the OVN HA chassis group so that the
+ // OVN patch port connection is removed for this network and we can correctly detect whether there are
+ // any other OVN networks using this uplink bridge before removing it.
+ err = n.deleteParentPort()
+ if err != nil {
+ return err
+ }
+
return nil
}
From ec2c7eaf3a9f7c4f700f3da43953c1c5465861ed Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 3 Sep 2020 18:21:22 +0100
Subject: [PATCH 2/5] lxd/network/openvswitch/ovn: Adds
ChassisGroupChassisDelete function
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/openvswitch/ovn.go | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index e5bb67b062..aebe5e210f 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -823,3 +823,33 @@ func (o *OVN) ChassisGroupChassisAdd(haChassisGroupName OVNChassisGroup, chassis
return nil
}
+
+// ChassisGroupChassisDelete deletes a chassis ID from an HA chassis group.
+func (o *OVN) ChassisGroupChassisDelete(haChassisGroupName OVNChassisGroup, chassisID string) error {
+ // Check if chassis group exists. ovn-nbctl doesn't provide an "--if-exists" option for this.
+ existingChassisGroup, err := o.nbctl("--no-headings", "--data=bare", "--colum=name", "find", "ha_chassis_group", fmt.Sprintf("name=%s", string(haChassisGroupName)))
+ if err != nil {
+ return err
+ }
+
+ // Nothing to do if chassis group doesn't exist.
+ if strings.TrimSpace(existingChassisGroup) == "" {
+ return nil
+ }
+
+ // Check if chassis exists. ovn-nbctl doesn't provide an "--if-exists" option for this.
+ existingChassis, err := o.nbctl("--no-headings", "--data=bare", "--colum=chassis_name", "find", "ha_chassis", fmt.Sprintf("chassis_name=%s", chassisID))
+ if err != nil {
+ return err
+ }
+
+ // Remove chassis from group if exists.
+ if strings.TrimSpace(existingChassis) != "" {
+ _, err := o.nbctl("ha-chassis-group-remove-chassis", string(haChassisGroupName), chassisID)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
From a3ce774924f0ce526e44b9173b5a934081d65650 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 4 Sep 2020 09:33:12 +0100
Subject: [PATCH 3/5] lxd/network/driver/ovn: Adds ovn.name setting to store
OVN logical network name
- Stored at network setup time so that if a cluster node is joined in the future it can access the correct OVN network name in the pre-join phase before the database IDs are synced.
- Moves ovnParentOVSBridge config populated into setupParentPort as is not parent network type specific.
- Wraps setupParentPort and the population of ovn.name in a single transaction so it is done atomically.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/network/driver_ovn.go | 181 ++++++++++++++++++++++----------------
1 file changed, 103 insertions(+), 78 deletions(-)
diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 2ec0c2576f..b22642d5c0 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -38,6 +38,10 @@ const ovnVolatileParentIPv6 = "volatile.network.ipv6.address"
// associated veth interfaces when using the parent network as an OVN uplink.
const ovnParentOVSBridge = "ovn.ovs_bridge"
+// ovnName setting on the OVN network config indicating the name of the logical OVN network. Allows the consistent
+// use of the OVN logical network name in cluster node pre-join phase before node joins clustered database.
+const ovnName = "ovn.name"
+
// ovnParentVars OVN object variables derived from parent network.
type ovnParentVars struct {
// Router.
@@ -95,7 +99,8 @@ func (n *ovn) Validate(config map[string]string) error {
"dns.domain": validate.IsAny,
"dns.search": validate.IsAny,
- // Volatile keys populated automatically as needed.
+ // Populated automatically at network setup time.
+ ovnName: validate.IsAny,
ovnVolatileParentIPv4: validate.Optional(validate.IsNetworkAddressV4),
ovnVolatileParentIPv6: validate.Optional(validate.IsNetworkAddressV6),
}
@@ -141,9 +146,9 @@ func (n *ovn) getNetworkPrefix() string {
return fmt.Sprintf("lxd-net%d", n.id)
}
-// getChassisGroup returns OVN chassis group name to use.
+// getChassisGroup returns OVN chassis group name to use based on ovn.name setting in config.
func (n *ovn) getChassisGroupName() openvswitch.OVNChassisGroup {
- return openvswitch.OVNChassisGroup(n.getNetworkPrefix())
+ return openvswitch.OVNChassisGroup(n.config[ovnName])
}
// getRouterName returns OVN logical router name to use.
@@ -264,23 +269,29 @@ func (n *ovn) getIntSwitchInstancePortPrefix() string {
// setupParentPort initialises the parent uplink connection. Returns the derived ovnParentVars settings used
// during the initial creation of the logical network.
-func (n *ovn) setupParentPort(routerMAC net.HardwareAddr) (*ovnParentVars, error) {
- parentNet, err := LoadByName(n.state, n.config["network"])
- if err != nil {
- return nil, errors.Wrapf(err, "Failed loading parent network")
+func (n *ovn) setupParentPort(tx *db.ClusterTx, parentNet Network, routerMAC net.HardwareAddr) (*ovnParentVars, error) {
+ // Store the OVN OVS bridge name in the parent network config if not set.
+ parentNetConf := parentNet.Config()
+ if parentNetConf[ovnParentOVSBridge] == "" {
+ parentNetConf[ovnParentOVSBridge] = fmt.Sprintf("lxdovn%d", parentNet.ID())
+ err := tx.UpdateNetwork(parentNet.ID(), parentNet.Description(), parentNetConf)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed saving parent network OVN OVS bridge name")
+ }
}
switch parentNet.Type() {
case "bridge":
- return n.setupParentPortBridge(parentNet, routerMAC)
+ return n.setupParentPortBridge(tx, parentNet, routerMAC)
}
return nil, fmt.Errorf("Network type %q unsupported as OVN parent", parentNet.Type())
}
-// setupParentPortBridge allocates external IPs on the parent bridge.
+// setupParentPortBridge allocates external uplink IPs from the parent bridge and stores them in the OVN network
+// config. Also generates the OVS uplink bridge name and stores in the parent network's config.
// Returns the derived ovnParentVars settings.
-func (n *ovn) setupParentPortBridge(parentNet Network, routerMAC net.HardwareAddr) (*ovnParentVars, error) {
+func (n *ovn) setupParentPortBridge(tx *db.ClusterTx, parentNet Network, routerMAC net.HardwareAddr) (*ovnParentVars, error) {
v := &ovnParentVars{}
bridgeNet, ok := parentNet.(*bridge)
@@ -317,68 +328,57 @@ func (n *ovn) setupParentPortBridge(parentNet Network, routerMAC net.HardwareAdd
// 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) {
- err := n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
- allAllocatedIPv4, allAllocatedIPv6, err := n.parentAllAllocatedIPs(tx, parentNet.Name())
+ allAllocatedIPv4, allAllocatedIPv6, err := n.parentAllAllocatedIPs(tx, parentNet.Name())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to get all allocated IPs for parent")
+ }
+
+ if parentIPv4Net != nil && routerExtPortIPv4 == nil {
+ if parentNetConf["ipv4.ovn.ranges"] == "" {
+ return nil, fmt.Errorf(`Missing required "ipv4.ovn.ranges" config key on parent network`)
+ }
+
+ ipRanges, err := parseIPRanges(parentNetConf["ipv4.ovn.ranges"], parentNet.DHCPv4Subnet())
if err != nil {
- return errors.Wrapf(err, "Failed to get all allocated IPs for parent")
+ return nil, errors.Wrapf(err, "Failed to parse parent IPv4 OVN ranges")
}
- if parentIPv4Net != nil && routerExtPortIPv4 == nil {
- if parentNetConf["ipv4.ovn.ranges"] == "" {
- return fmt.Errorf(`Missing required "ipv4.ovn.ranges" config key on parent network`)
- }
+ routerExtPortIPv4, err = n.parentAllocateIP(ipRanges, allAllocatedIPv4)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to allocate parent IPv4 address")
+ }
+
+ n.config[ovnVolatileParentIPv4] = routerExtPortIPv4.String()
+ }
- ipRanges, err := parseIPRanges(parentNetConf["ipv4.ovn.ranges"], parentNet.DHCPv4Subnet())
+ 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 IPv4 OVN ranges")
+ return nil, errors.Wrapf(err, "Failed to parse parent IPv6 OVN ranges")
}
- routerExtPortIPv4, err = n.parentAllocateIP(ipRanges, allAllocatedIPv4)
+ routerExtPortIPv6, err = n.parentAllocateIP(ipRanges, allAllocatedIPv6)
if err != nil {
- return errors.Wrapf(err, "Failed to allocate parent IPv4 address")
+ return nil, errors.Wrapf(err, "Failed to allocate parent IPv6 address")
}
- 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
- }
+ } else {
+ // Otherwise use EUI64 derived from MAC address.
+ routerExtPortIPv6, err = eui64.ParseMAC(parentIPv6Net.IP, routerMAC)
+ if err != nil {
+ return nil, err
}
-
- 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 = tx.UpdateNetwork(networkID, n.description, n.config)
- if err != nil {
- return errors.Wrapf(err, "Failed saving allocated parent network IPs")
- }
+ n.config[ovnVolatileParentIPv6] = routerExtPortIPv6.String()
+ }
- return nil
- })
+ // Store allocated IPs on parent network in network config.
+ err = tx.UpdateNetwork(n.id, n.description, n.config)
if err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "Failed saving allocated parent network IPs")
}
}
@@ -510,21 +510,7 @@ func (n *ovn) parentOperationLockName(parentNet Network) string {
func (n *ovn) parentPortBridgeVars(parentNet Network) (*ovnParentPortBridgeVars, error) {
parentConfig := parentNet.Config()
if parentConfig[ovnParentOVSBridge] == "" {
- // Generate random OVS bridge name for parent uplink.
- parentConfig[ovnParentOVSBridge] = RandomDevName("ovn")
-
- // Store in parent config.
- err := n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
- err := tx.UpdateNetwork(parentNet.ID(), parentNet.Description(), parentConfig)
- if err != nil {
- return errors.Wrapf(err, "Failed saving parent network OVN OVS bridge name")
- }
-
- return nil
- })
- if err != nil {
- return nil, err
- }
+ return nil, fmt.Errorf("Missing %q setting in parent network config", ovnParentOVSBridge)
}
return &ovnParentPortBridgeVars{
@@ -782,8 +768,37 @@ func (n *ovn) setup(update bool) error {
return err
}
- // Setup parent port (do this first to check parent is suitable).
- parent, err := n.setupParentPort(routerMAC)
+ // Parent network must be in default project.
+ parentNet, err := LoadByName(n.state, n.config["network"])
+ if err != nil {
+ return errors.Wrapf(err, "Failed loading parent network %q", n.config["network"])
+ }
+
+ var parent *ovnParentVars
+
+ // Start a transaction as we may need to modify the config in both this network and in the parent network.
+ // So do it in an atomic fashion.
+ err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
+ // Setup parent port (do this first to check parent is suitable).
+ // This may need to modify both the network config and the parent network config.
+ parent, err = n.setupParentPort(tx, parentNet, routerMAC)
+ if err != nil {
+ return err
+ }
+
+ // Set the OVN network name into config if missing (used for joining cluster nodes).
+ if n.config[ovnName] == "" {
+ n.config[ovnName] = n.getNetworkPrefix()
+
+ // Store updated network config.
+ err = tx.UpdateNetwork(n.id, n.description, n.config)
+ if err != nil {
+ return errors.Wrapf(err, "Failed saving OVN network name to config")
+ }
+ }
+
+ return nil
+ })
if err != nil {
return err
}
@@ -1057,6 +1072,11 @@ func (n *ovn) setup(update bool) error {
// addChassisGroupEntry adds an entry for the local OVS chassis to the OVN logical network's chassis group.
func (n *ovn) addChassisGroupEntry() error {
+ chassisGroupName := n.getChassisGroupName()
+ if chassisGroupName == "" {
+ return fmt.Errorf("Cannot add chassis group entry, missing %q setting", ovnName)
+ }
+
client, err := n.getClient()
if err != nil {
return err
@@ -1070,9 +1090,9 @@ func (n *ovn) addChassisGroupEntry() error {
}
var priority uint = ovnChassisPriorityMax
- err = client.ChassisGroupChassisAdd(n.getChassisGroupName(), chassisID, priority)
+ err = client.ChassisGroupChassisAdd(chassisGroupName, chassisID, priority)
if err != nil {
- return errors.Wrapf(err, "Failed adding OVS chassis %q with priority %d to chassis group %q", chassisID, priority, n.getChassisGroupName())
+ return errors.Wrapf(err, "Failed adding OVS chassis %q with priority %d to chassis group %q", chassisID, priority, chassisGroupName)
}
return nil
@@ -1080,6 +1100,11 @@ func (n *ovn) addChassisGroupEntry() error {
// deleteChassisGroupEntry deletes an entry for the local OVS chassis from the OVN logical network's chassis group.
func (n *ovn) deleteChassisGroupEntry() error {
+ chassisGroupName := n.getChassisGroupName()
+ if chassisGroupName == "" {
+ return fmt.Errorf("Cannot delete chassis group entry, missing %q setting", ovnName)
+ }
+
client, err := n.getClient()
if err != nil {
return err
@@ -1092,9 +1117,9 @@ func (n *ovn) deleteChassisGroupEntry() error {
return errors.Wrapf(err, "Failed getting OVS Chassis ID")
}
- err = client.ChassisGroupChassisDelete(n.getChassisGroupName(), chassisID)
+ err = client.ChassisGroupChassisDelete(chassisGroupName, chassisID)
if err != nil {
- return errors.Wrapf(err, "Failed deleting OVS chassis %q from chassis group %q", chassisID, n.getChassisGroupName())
+ return errors.Wrapf(err, "Failed deleting OVS chassis %q from chassis group %q", chassisID, chassisGroupName)
}
return nil
From 1da05127ce526532196400a51fae93bfd751ea37 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 4 Sep 2020 10:52:32 +0100
Subject: [PATCH 4/5] doc/networks: Adds ovn.name to OVN network doc
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
doc/networks.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/networks.md b/doc/networks.md
index 63af6893b9..d2509a5400 100644
--- a/doc/networks.md
+++ b/doc/networks.md
@@ -298,3 +298,4 @@ dns.search | string | - | -
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
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
network | string | - | - | Parent network to use for outbound external network access
+ovn.name | string | - | - | Name of OVN logical network (populated automatically at network setup time)
From 71898bd2654e8e0e977f5ba9c319c5e85b5c69fd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 4 Sep 2020 10:54:31 +0100
Subject: [PATCH 5/5] api: Adds network_ovn_name API extension
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
doc/api-extensions.md | 4 ++++
shared/version/api.go | 1 +
2 files changed, 5 insertions(+)
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index fa749cb371..14d2316208 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -1158,3 +1158,7 @@ Adds the `ovn.ovs_bridge` setting to `bridge` networks to allow the `ovn` networ
If missing, the first `ovn` network to specify a `bridge` network as its parent `network` will cause the
setting to be populated with a random interface name prefixed with "ovn".
+
+## network\_ovn\_name
+Adds the `ovn.name` setting to `ovn` networks to allow nodes joining cluster to access the logical OVN network
+name during the pre-join stage before the node is connected to the cluster database.
diff --git a/shared/version/api.go b/shared/version/api.go
index 4771c18f21..ac07e18827 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -225,6 +225,7 @@ var APIExtensions = []string{
"container_syscall_intercept_bpf_devices",
"network_type_ovn",
"network_bridge_ovn_bridge",
+ "network_ovn_name",
}
// APIExtensionsCount returns the number of available API extensions.
More information about the lxc-devel
mailing list