Skip to content

Commit 83bdc54

Browse files
authored
Merge pull request #2150 from rackerlabs/backport-ovn-ha-chassis-fix
fix(neutron-understack): ignore stale chassis when linking vxlan network HCG
2 parents f60f4dd + 5024652 commit 83bdc54

2 files changed

Lines changed: 61 additions & 9 deletions

File tree

python/neutron-understack/neutron_understack/routers.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,11 @@ def link_vxlan_network_ha_chassis_group(_resource, _event, _trigger, payload) ->
220220
We do what link_network_ha_chassis_group would have done: populate the unified
221221
network HCG with sync_ha_chassis_group_network_unified, then anchor the internal
222222
router-interface LRP to that same HCG. The gateway chassis is sourced from the
223-
global HA_Chassis table (all records must share one chassis_name) so the fix
224-
fires even before the external gateway port is attached.
223+
global HA_Chassis table (all *live* records must share one chassis_name) so the
224+
fix fires even before the external gateway port is attached. Rows pointing at a
225+
chassis no longer present in the Southbound DB (e.g. left behind by a
226+
decommissioned/replaced host) are excluded before checking for uniqueness, so a
227+
single stale row elsewhere in the fleet doesn't block every vxlan network.
225228
226229
For VLAN/FLAT networks neutron's handler already populates the network HCG
227230
correctly; we detect that and return early.
@@ -239,6 +242,7 @@ def link_vxlan_network_ha_chassis_group(_resource, _event, _trigger, payload) ->
239242
if not client:
240243
return
241244
nb_idl = client._nb_idl
245+
sb_idl = client._sb_idl
242246

243247
# Skip if the per-network HCG is already populated — neutron handled it
244248
# (VLAN/FLAT gateways). For vxlan the HCG is empty due to the neutron bug.
@@ -248,15 +252,22 @@ def link_vxlan_network_ha_chassis_group(_resource, _event, _trigger, payload) ->
248252
if network_hcg and network_hcg.ha_chassis:
249253
return
250254

251-
# Derive the gateway chassis from every HA_Chassis row in the NB database.
252-
# If exactly one distinct chassis_name exists, that is our gateway chassis.
253-
# This avoids requiring the per-router HCG to exist first.
255+
# Derive the gateway chassis from every *live* HA_Chassis row in the NB
256+
# database. If exactly one distinct chassis_name exists, that is our
257+
# gateway chassis. This avoids requiring the per-router HCG to exist yet.
258+
# Rows referencing a chassis no longer registered in the Southbound DB
259+
# (e.g. a decommissioned/replaced host) are excluded so they don't block
260+
# this inference for every network in the fleet.
254261
all_ha_chassis = nb_idl.db_list_rows("HA_Chassis").execute(check_error=True)
255-
chassis_names = {row.chassis_name for row in all_ha_chassis}
262+
chassis_names = {
263+
row.chassis_name
264+
for row in all_ha_chassis
265+
if sb_idl.lookup("Chassis", row.chassis_name, default=None) is not None
266+
}
256267
if len(chassis_names) != 1:
257268
LOG.debug(
258269
"Cannot determine unique gateway chassis for network %(net)s "
259-
"(router %(router)s): found %(n)d distinct chassis name(s)",
270+
"(router %(router)s): found %(n)d distinct live chassis name(s)",
260271
{"net": network_id, "router": router_id, "n": len(chassis_names)},
261272
)
262273
return

python/neutron-understack/neutron_understack/tests/test_routers.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ def _payload(mocker, router_id="router-1", port_id="port-1", network_id="net-1")
407407
)
408408

409409
@staticmethod
410-
def _client(mocker, ha_chassis_rows, lrp, network_hcg=None):
410+
def _client(mocker, ha_chassis_rows, lrp, network_hcg=None, live_chassis=None):
411411
nb_idl = mocker.MagicMock()
412412
nb_idl.db_list_rows.return_value.execute.return_value = ha_chassis_rows
413413

@@ -419,7 +419,22 @@ def lookup(table, _name, default=None):
419419
return default
420420

421421
nb_idl.lookup.side_effect = lookup
422-
client = mocker.Mock(_nb_idl=nb_idl)
422+
423+
# By default every provided HA_Chassis row is treated as live, so
424+
# existing callers don't need to know about the SB liveness check.
425+
if live_chassis is None:
426+
live_chassis = {row.chassis_name for row in ha_chassis_rows}
427+
428+
sb_idl = mocker.MagicMock()
429+
430+
def sb_lookup(table, name, default=None):
431+
if table == "Chassis" and name in live_chassis:
432+
return mocker.Mock(name=name)
433+
return default
434+
435+
sb_idl.lookup.side_effect = sb_lookup
436+
437+
client = mocker.Mock(_nb_idl=nb_idl, _sb_idl=sb_idl)
423438
return client, nb_idl
424439

425440
def _patch_sync(self, mocker, hcg="net-hcg-uuid"):
@@ -474,6 +489,32 @@ def test_multiple_chassis_names(self, mocker):
474489
sync.assert_not_called()
475490
nb_idl.db_set.assert_not_called()
476491

492+
def test_stale_chassis_excluded_from_uniqueness_check(self, mocker):
493+
# chassis-2 no longer exists in the Southbound DB (e.g. decommissioned
494+
# host) but a stale HA_Chassis row for it still lingers in NB. It must
495+
# not block inference of the one live chassis.
496+
hc_live = mocker.Mock(chassis_name="chassis-1")
497+
hc_dead = mocker.Mock(chassis_name="chassis-2")
498+
lrp = mocker.Mock(ha_chassis_group=[])
499+
client, nb_idl = self._client(
500+
mocker,
501+
ha_chassis_rows=[hc_live, hc_dead],
502+
lrp=lrp,
503+
live_chassis={"chassis-1"},
504+
)
505+
sync = self._patch_sync(mocker)
506+
mocker.patch("neutron_understack.routers.ovn_client", return_value=client)
507+
508+
link_vxlan_network_ha_chassis_group(None, None, None, self._payload(mocker))
509+
510+
sync.assert_called_once()
511+
assert sync.call_args.args[5] == {"chassis-1": 32767} # chassis_prio
512+
nb_idl.db_set.assert_called_once_with(
513+
"Logical_Router_Port",
514+
"lrp-port-1",
515+
("ha_chassis_group", "net-hcg-uuid"),
516+
)
517+
477518
def test_network_hcg_already_populated(self, mocker):
478519
# VLAN/FLAT: neutron already populated the per-network HCG — we skip.
479520
existing_hcg = mocker.Mock(ha_chassis=[mocker.Mock(chassis_name="chassis-1")])

0 commit comments

Comments
 (0)