@@ -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