Skip to content

Commit 2eb8c75

Browse files
authored
Merge pull request #225 from stackhpc/2025.1-ovn-chassis2
Backport Gateway_Chassis to HA_Chassis_Group migration
2 parents 9abe0cb + 106d4f4 commit 2eb8c75

File tree

14 files changed

+647
-47
lines changed

14 files changed

+647
-47
lines changed

neutron/common/ovn/utils.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
'HAChassisGroupInfo', ['group_name', 'chassis_list', 'az_hints',
6969
'ignore_chassis', 'external_ids'])
7070

71+
_OVS_PERSIST_UUID = _SENTINEL = object()
72+
7173

7274
class OvsdbClientCommand:
7375
_CONNECTION = 0
@@ -903,6 +905,16 @@ def get_chassis_without_azs(chassis_list):
903905
get_chassis_availability_zones(ch)}
904906

905907

908+
def get_chassis_priority(chassis_list):
909+
"""Given a chassis list, returns a dictionary with chassis name and prio
910+
911+
The chassis list is ordered according to the priority: the first one is the
912+
highest priority chassis, the last one is the least priority chassis.
913+
"""
914+
return {chassis: prio + 1 for prio, chassis
915+
in enumerate(reversed(chassis_list))}
916+
917+
906918
def parse_ovn_lb_port_forwarding(ovn_rtr_lb_pfs):
907919
"""Return a dictionary compatible with port forwarding from OVN lb."""
908920
result = {}
@@ -1377,3 +1389,41 @@ def validate_port_forwarding_configuration():
13771389
if any(net_type in provider_network_types
13781390
for net_type in cfg.CONF.ml2.tenant_network_types):
13791391
raise ovn_exc.InvalidPortForwardingConfiguration()
1392+
1393+
1394+
def ovs_persist_uuid_supported(nb_idl):
1395+
# OVS 3.1+ contain the persist_uuid feature that allows choosing the UUID
1396+
# that will be stored in the DB. It was broken prior to 3.1.5/3.2.3/3.3.1
1397+
# so this will return True only for the fixed version. As actually testing
1398+
# the fix requires committing a transaction, an implementation detail is
1399+
# tested. This can be removed once a fixed version is required.
1400+
global _OVS_PERSIST_UUID
1401+
if _OVS_PERSIST_UUID is _SENTINEL:
1402+
_OVS_PERSIST_UUID = isinstance(
1403+
next(iter(nb_idl.tables["NB_Global"].rows.data.values())), list)
1404+
LOG.debug(f"OVS persist_uuid supported={_OVS_PERSIST_UUID}")
1405+
return _OVS_PERSIST_UUID
1406+
1407+
1408+
def get_logical_router_port_ha_chassis(nb_idl, lrp, priorities=None):
1409+
"""Get the list of chassis hosting this Logical_Router_Port.
1410+
1411+
:param nb_idl: (``OvsdbNbOvnIdl``) OVN Northbound IDL
1412+
:param lrp: Logical_Router_Port
1413+
:param priorities: (list of int) a list of HA_Chassis chassis priorities
1414+
to search for
1415+
:return: List of tuples (chassis_name, priority) sorted by priority. If
1416+
``priorities`` is set then only chassis matching of these
1417+
priorities are returned.
1418+
"""
1419+
chassis = []
1420+
lrp = nb_idl.lookup('Logical_Router_Port', lrp.name, default=None)
1421+
if not lrp or not lrp.ha_chassis_group:
1422+
return chassis
1423+
1424+
for hc in lrp.ha_chassis_group[0].ha_chassis:
1425+
if priorities and hc.priority not in priorities:
1426+
continue
1427+
chassis.append((hc.chassis_name, hc.priority))
1428+
1429+
return chassis

neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
7171
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
7272
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
73+
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovs_fixes
7374
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
7475
from neutron import service
7576
from neutron.services.logapi.drivers.ovn import driver as log_driver
@@ -419,6 +420,9 @@ def post_fork_initialize(self, resource, event, trigger, payload=None):
419420
self._post_fork_event.clear()
420421
self._ovn_client_inst = None
421422

423+
# Patch python-ovs for fixes not yet released
424+
ovs_fixes.apply_ovs_fixes()
425+
422426
if worker_class == wsgi.WorkerService:
423427
self._setup_hash_ring()
424428

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py

Lines changed: 137 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@
1313
# under the License.
1414

1515
import abc
16+
import copy
17+
import uuid
1618

1719
from oslo_utils import timeutils
20+
from ovs.db import idl as ovs_idl
1821
from ovsdbapp.backend.ovs_idl import command
1922
from ovsdbapp.backend.ovs_idl import idlutils
23+
from ovsdbapp.backend.ovs_idl import rowview
2024
from ovsdbapp.schema.ovn_northbound import commands as ovn_nb_commands
2125
from ovsdbapp import utils as ovsdbapp_utils
2226

@@ -100,6 +104,49 @@ def _add_gateway_chassis(api, txn, lrp_name, val):
100104
return 'gateway_chassis', uuid_list
101105

102106

107+
def _sync_ha_chassis_group(txn, nb_api, name, chassis_priority,
108+
may_exist=False, table_name='HA_Chassis_Group',
109+
**columns):
110+
result = None
111+
hcg = nb_api.lookup(table_name, name, default=None)
112+
if hcg:
113+
if not may_exist:
114+
raise RuntimeError(_('HA_Chassis_Group %s exists' % name))
115+
else:
116+
hcg = txn.insert(nb_api._tables[table_name])
117+
hcg.name = name
118+
command.BaseCommand.set_columns(hcg, **columns)
119+
result = hcg.uuid
120+
121+
# HA_Chassis registers handling.
122+
# Remove the non-existing chassis in ``self.chassis_priority``
123+
hc_to_remove = []
124+
for hc in getattr(hcg, 'ha_chassis', []):
125+
if hc.chassis_name not in chassis_priority:
126+
hc_to_remove.append(hc)
127+
128+
for hc in hc_to_remove:
129+
hcg.delvalue('ha_chassis', hc)
130+
hc.delete()
131+
132+
# Update the priority of the existing chassis.
133+
for hc in getattr(hcg, 'ha_chassis', []):
134+
hc_priority = chassis_priority.pop(hc.chassis_name)
135+
hc.priority = hc_priority
136+
137+
# Add the non-existing HA_Chassis registers.
138+
for hc_name, priority in chassis_priority.items():
139+
hc = txn.insert(nb_api.tables['HA_Chassis'])
140+
hc.chassis_name = hc_name
141+
hc.priority = priority
142+
hcg.addvalue('ha_chassis', hc)
143+
144+
if not result:
145+
result = rowview.RowView(hcg)
146+
147+
return result
148+
149+
103150
class CheckLivenessCommand(command.BaseCommand):
104151
def run_idl(self, txn):
105152
# txn.pre_commit responsible for updating nb_global.nb_cfg, but
@@ -110,21 +157,64 @@ def run_idl(self, txn):
110157
self.result = self.api.nb_global.nb_cfg
111158

112159

160+
class AddNetworkCommand(command.AddCommand):
161+
table_name = 'Logical_Switch'
162+
163+
def __init__(self, api, network_id, may_exist=False, **columns):
164+
super().__init__(api)
165+
self.network_uuid = uuid.UUID(str(network_id))
166+
self.may_exist = may_exist
167+
self.columns = columns
168+
169+
def run_idl(self, txn):
170+
table = self.api.tables[self.table_name]
171+
try:
172+
ls = table.rows[self.network_uuid]
173+
if self.may_exist:
174+
self.result = rowview.RowView(ls)
175+
return
176+
msg = _("Switch %s already exists") % self.network_uuid
177+
raise RuntimeError(msg)
178+
except KeyError:
179+
# Adding a new LS
180+
if utils.ovs_persist_uuid_supported(txn.idl):
181+
ls = txn.insert(table, new_uuid=self.network_uuid,
182+
persist_uuid=True)
183+
else:
184+
ls = txn.insert(table)
185+
self.set_columns(ls, **self.columns)
186+
ls.name = utils.ovn_name(self.network_uuid)
187+
self.result = ls.uuid
188+
189+
113190
class AddLSwitchPortCommand(command.BaseCommand):
114-
def __init__(self, api, lport, lswitch, may_exist, **columns):
191+
def __init__(self, api, lport, lswitch, may_exist, network_id=None,
192+
**columns):
115193
super().__init__(api)
116194
self.lport = lport
117195
self.lswitch = lswitch
118196
self.may_exist = may_exist
197+
self.network_uuid = uuid.UUID(str(network_id)) if network_id else None
119198
self.columns = columns
120199

121200
def run_idl(self, txn):
122201
try:
202+
# We must look in the local cache first, because the LS may have
203+
# been created as part of the current transaction. or in the case
204+
# of adding an LSP to a LS that was created before persist_uuid
123205
lswitch = idlutils.row_by_value(self.api.idl, 'Logical_Switch',
124206
'name', self.lswitch)
125207
except idlutils.RowNotFound:
126-
msg = _("Logical Switch %s does not exist") % self.lswitch
127-
raise RuntimeError(msg)
208+
if self.network_uuid and utils.ovs_persist_uuid_supported(txn.idl):
209+
# Create a "fake" row with the right UUID so python-ovs creates
210+
# a transaction referencing the Row, even though we might not
211+
# have received the update for the row ourselves.
212+
lswitch = ovs_idl.Row(self.api.idl,
213+
self.api.tables['Logical_Switch'],
214+
uuid=self.network_uuid, data={})
215+
else:
216+
msg = _("Logical Switch %s does not exist") % self.lswitch
217+
raise RuntimeError(msg)
128218
if self.may_exist:
129219
port = idlutils.row_by_value(self.api.idl,
130220
'Logical_Switch_Port', 'name',
@@ -210,8 +300,8 @@ def run_idl(self, txn):
210300
else:
211301
new_port_dhcp_opts.add(dhcpv6_options.result)
212302
port.dhcpv6_options = [dhcpv6_options.result]
213-
for uuid in cur_port_dhcp_opts - new_port_dhcp_opts:
214-
self.api._tables['DHCP_Options'].rows[uuid].delete()
303+
for uuid_ in cur_port_dhcp_opts - new_port_dhcp_opts:
304+
self.api._tables['DHCP_Options'].rows[uuid_].delete()
215305

216306
external_ids_update = self.external_ids_update or {}
217307
external_ids = getattr(port, 'external_ids', {})
@@ -293,8 +383,8 @@ def run_idl(self, txn):
293383
# Delete DHCP_Options records no longer referred by this port.
294384
cur_port_dhcp_opts = get_lsp_dhcp_options_uuids(
295385
lport, self.lport)
296-
for uuid in cur_port_dhcp_opts:
297-
self.api._tables['DHCP_Options'].rows[uuid].delete()
386+
for uuid_ in cur_port_dhcp_opts:
387+
self.api._tables['DHCP_Options'].rows[uuid_].delete()
298388

299389
_delvalue_from_list(lswitch, 'ports', lport)
300390
self.api._tables['Logical_Switch_Port'].rows[lport.uuid].delete()
@@ -347,8 +437,7 @@ def run_idl(self, txn):
347437
az_hints = self.api.get_gateway_chassis_az_hints(self.g_name)
348438
filtered_existing_chassis = (
349439
self.scheduler.filter_existing_chassis(
350-
nb_idl=self.api, gw_chassis=self.all_gw_chassis,
351-
physnet=physnet,
440+
gw_chassis=self.all_gw_chassis, physnet=physnet,
352441
chassis_physnets=self.chassis_with_physnets,
353442
existing_chassis=existing_chassis, az_hints=az_hints,
354443
chassis_with_azs=self.chassis_with_azs))
@@ -388,9 +477,12 @@ def run_idl(self, txn):
388477
# the top.
389478
index = chassis.index(primary)
390479
chassis[0], chassis[index] = chassis[index], chassis[0]
391-
setattr(
392-
lrouter_port,
393-
*_add_gateway_chassis(self.api, txn, self.g_name, chassis))
480+
chassis_priority = utils.get_chassis_priority(chassis)
481+
lrouter_name = lrouter_port.external_ids[
482+
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY]
483+
hcg = _sync_ha_chassis_group(txn, self.api, lrouter_name,
484+
chassis_priority, may_exist=True)
485+
setattr(lrouter_port, 'ha_chassis_group', ovsdbapp_utils.get_uuid(hcg))
394486

395487

396488
class ScheduleNewGatewayCommand(command.BaseCommand):
@@ -415,8 +507,11 @@ def run_idl(self, txn):
415507
self.api, self.sb_api, self.g_name, candidates=candidates,
416508
target_lrouter=lrouter)
417509
if chassis:
418-
setattr(lrouter_port,
419-
*_add_gateway_chassis(self.api, txn, self.g_name, chassis))
510+
chassis_priority = utils.get_chassis_priority(chassis)
511+
hcg = _sync_ha_chassis_group(txn, self.api, self.lrouter_name,
512+
chassis_priority, may_exist=True)
513+
setattr(lrouter_port, 'ha_chassis_group',
514+
ovsdbapp_utils.get_uuid(hcg))
420515

421516

422517
class LrDelCommand(ovn_nb_commands.LrDelCommand):
@@ -465,8 +560,9 @@ def run_idl(self, txn):
465560
if col == 'gateway_chassis':
466561
col, val = _add_gateway_chassis(self.api, txn, self.name,
467562
val)
468-
setattr(lrouter_port, col, val)
563+
self.set_column(lrouter_port, col, val)
469564
_addvalue_to_list(lrouter, 'ports', lrouter_port)
565+
self.result = lrouter_port.uuid
470566

471567

472568
class UpdateLRouterPortCommand(command.BaseCommand):
@@ -491,7 +587,7 @@ def run_idl(self, txn):
491587
if col == 'gateway_chassis':
492588
col, val = _add_gateway_chassis(self.api, txn, self.name,
493589
val)
494-
setattr(lrouter_port, col, val)
590+
self.set_column(lrouter_port, col, val)
495591

496592

497593
class DelLRouterPortCommand(command.BaseCommand):
@@ -1008,15 +1104,17 @@ def run_idl(self, txn):
10081104
# Remove the router pinning to a chassis (if any).
10091105
lrouter.delkey('options', 'chassis')
10101106

1011-
# Remove the HA_Chassis_Group of the router (if any).
1107+
for gw_port in self.api.get_lrouter_gw_ports(lrouter.name):
1108+
gw_port.ha_chassis_group = []
1109+
lrouter.delvalue('ports', gw_port)
1110+
1111+
# Remove the HA_Chassis_Group of the router (if any), after
1112+
# removing it from the gateway Logical_Router_Ports.
10121113
hcg = self.api.lookup('HA_Chassis_Group',
10131114
lrouter.name, default=None)
10141115
if hcg:
10151116
hcg.delete()
10161117

1017-
for gw_port in self.api.get_lrouter_gw_ports(lrouter.name):
1018-
lrouter.delvalue('ports', gw_port)
1019-
10201118

10211119
class SetLSwitchPortToVirtualTypeCommand(command.BaseCommand):
10221120
def __init__(self, api, lport, vip, parent, if_exists):
@@ -1089,3 +1187,22 @@ def run_idl(self, txn):
10891187
virtual_parents)
10901188

10911189
setattr(lsp, 'options', options)
1190+
1191+
1192+
class HAChassisGroupWithHCAddCommand(command.AddCommand):
1193+
table_name = 'HA_Chassis_Group'
1194+
1195+
def __init__(self, api, name, chassis_priority, may_exist=False,
1196+
**columns):
1197+
super().__init__(api)
1198+
self.name = name
1199+
self.chassis_priority = copy.deepcopy(chassis_priority)
1200+
self.may_exist = may_exist
1201+
self.columns = columns
1202+
1203+
def run_idl(self, txn):
1204+
# HA_Chassis_Group register creation.
1205+
self.result = _sync_ha_chassis_group(
1206+
txn, self.api, self.name, self.chassis_priority,
1207+
may_exist=self.may_exist, table_name=self.table_name,
1208+
**self.columns)

0 commit comments

Comments
 (0)