Merge "[OVN] Sync the LRP Gateway_Chassis with the network HCG"

This commit is contained in:
Zuul
2025-11-21 20:48:15 +00:00
committed by Gerrit Code Review
4 changed files with 117 additions and 0 deletions

View File

@@ -89,6 +89,15 @@ class ChassisEvent(row_event.RowEvent):
'HA_Chassis_Group').execute(check_error=True):
if not hcg.name.startswith(ovn_const.OVN_NAME_PREFIX):
continue
net_id = hcg.external_ids.get(ovn_const.OVN_NETWORK_ID_EXT_ID_KEY)
router_id = hcg.external_ids.get(
ovn_const.OVN_ROUTER_ID_EXT_ID_KEY)
if net_id and router_id:
# This HA_Chassis_Group is linked to a router, it will be
# updated matching the router Gateway_Chassis registers.
continue
# The filter() is to get rid of the empty string in
# the list that is returned because of split()
azs = {az for az in

View File

@@ -75,3 +75,32 @@ class LogicalRouterPortEvent(row_event.RowEvent):
else: # LRP gateway port.
self.l3_plugin._ovn_client.update_router_ha_chassis_group(
self.admin_context, router_id)
class LogicalRouterPortGatewayChassisEvent(row_event.RowEvent):
"""Logical_Router_Port Gateway_Chassis change event.
When the Gateway_Chassis list of a Logical_Router_Port changes, it is
needed to update the linked HA_Chassis_Group registers.
"""
def __init__(self, driver):
self.driver = driver
self.l3_plugin = directory.get_plugin(constants.L3)
self.admin_context = neutron_context.get_admin_context()
table = 'Logical_Router_Port'
events = (self.ROW_UPDATE, )
super().__init__(events, table, None)
def match_fn(self, event, row, old):
if hasattr(old, 'gateway_chassis'):
# NOTE: when a Gateway_Chassis register is deleted, is no longer
# present in the old.gateway_chassis list.
return True
return False
def run(self, event, row, old=None):
lr_name = row.external_ids.get(ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY)
router_id = utils.get_neutron_name(lr_name)
self.l3_plugin._ovn_client.update_router_ha_chassis_group(
self.admin_context, router_id)

View File

@@ -179,6 +179,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
# Register needed events.
self._nb_ovn.idl.notify_handler.watch_events([
ovsdb_monitor.LogicalRouterPortEvent(self),
ovsdb_monitor.LogicalRouterPortGatewayChassisEvent(self),
])
def _add_neutron_router_interface(self, context, router_id,

View File

@@ -18,6 +18,7 @@ from neutron_lib.api.definitions import external_net
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron.common.ovn import utils as ovn_utils
from neutron.common import utils as n_utils
from neutron.tests.functional import base
from neutron.tests.unit.api import test_extensions
@@ -101,3 +102,80 @@ class TestLogicalRouterPortEvent(
self._router_interface_action(
'remove', self.router_id, self.subnet_id, None)
n_utils.wait_until_true(is_called, timeout=10)
def test_delete_router(self):
# The ``Logical_Router`` deletion triggers the
# ``LogicalRouterPortEvent`` event, but nothing is executed/called.
def is_called():
try:
mock_update_router.assert_called_once_with(
mock.ANY, self.router_id)
return True
except AssertionError:
return False
with mock.patch.object(
self.l3_plugin._ovn_client,
'update_router_ha_chassis_group') as mock_update_router:
self._add_external_gateway_to_router(self.router_id,
self.net_ext_id)
n_utils.wait_until_true(is_called, timeout=10)
mock_update_router.reset_mock()
req = self.new_delete_request('routers', self.router_id)
req.get_response(self.api)
self.assertRaises(n_utils.WaitTimeout, n_utils.wait_until_true,
is_called, timeout=5)
class TestLogicalRouterPortGatewayChassisEvent(
base.TestOVNFunctionalBase,
test_l3.L3NatTestCaseMixin):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.chassis = self.add_fake_chassis('ovs-host1')
self.l3_plugin = directory.get_plugin(plugin_constants.L3)
self.l3_plugin._post_fork_initialize(mock.ANY, mock.ANY, mock.ANY)
self.ext_api = test_extensions.setup_extensions_middleware(
test_l3.L3TestExtensionManager())
kwargs = {'arg_list': (external_net.EXTERNAL,),
external_net.EXTERNAL: True}
self.net_ext = self._make_network(
self.fmt, 'net_ext', True, as_admin=True, **kwargs)
self.subnet = self._make_subnet(self.fmt, self.net_ext, '20.0.10.1',
'20.0.10.0/24')
self.router = self._make_router(self.fmt, self._tenant_id)
self.router_id = self.router['router']['id']
self.net_ext_id = self.net_ext['network']['id']
self.subnet_id = self.subnet['subnet']['id']
def test_add_and_remove_gateway_chassis(self):
def is_called():
try:
mock_update_router.assert_called_once_with(
mock.ANY, self.router_id)
return True
except AssertionError:
return False
ch_list = []
for idx in range(5):
ch_list.append(self.add_fake_chassis(f'host-{idx}'))
self._add_external_gateway_to_router(self.router_id, self.net_ext_id)
lr = self.l3_plugin._nb_ovn.lookup('Logical_Router',
ovn_utils.ovn_name(self.router_id))
lrp_gw = lr.ports[0]
with mock.patch.object(
self.l3_plugin._ovn_client,
'update_router_ha_chassis_group') as mock_update_router:
for ch_name in ch_list:
self.l3_plugin._nb_ovn.lrp_set_gateway_chassis(
lrp_gw.uuid, ch_name).execute(check_error=True)
n_utils.wait_until_true(is_called, timeout=10)
mock_update_router.reset_mock()
for ch_name in ch_list:
self.l3_plugin._nb_ovn.lrp_del_gateway_chassis(
lrp_gw.uuid, ch_name).execute(check_error=True)
n_utils.wait_until_true(is_called, timeout=10)
mock_update_router.reset_mock()