Merge "DVR-HA: Unbinding a HA router from agent does not clear HA interface" into stable/ocata

This commit is contained in:
Zuul 2019-04-09 13:27:18 +00:00 committed by Gerrit Code Review
commit 4a29c37018
3 changed files with 141 additions and 20 deletions

View File

@ -321,13 +321,17 @@ class L3NATAgent(ha.AgentMixin,
kwargs['host'] = self.host
if router.get('distributed') and router.get('ha'):
# if the router does not contain information about the HA interface
# this means that this DVR+HA router needs to host only the edge
# side of it, typically because it's landing on a node that needs
# to provision a router namespace because of a DVR service port
# (e.g. DHCP).
if (self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT
and router.get(lib_const.HA_INTERFACE_KEY) is not None):
# Case 1: If the router contains information about the HA interface
# and if the requesting agent is a DVR_SNAT agent then go ahead
# and create a HA router.
# Case 2: If the router does not contain information about the HA
# interface this means that this DVR+HA router needs to host only
# the edge side of it, typically because it's landing on a node
# that needs to provision a router namespace because of a DVR
# service port (e.g. DHCP). So go ahead and create a regular DVR
# edge router.
if (self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT and
router.get(lib_const.HA_INTERFACE_KEY) is not None):
kwargs['state_change_callback'] = self.enqueue_state_change
return dvr_edge_ha_router.DvrEdgeHaRouter(*args, **kwargs)
@ -469,19 +473,38 @@ class L3NATAgent(ha.AgentMixin,
def _process_updated_router(self, router):
ri = self.router_info[router['id']]
is_dvr_only_agent = (
self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR)
is_ha_router = getattr(ri, 'ha_state', None) is not None
# For HA routers check that DB state matches actual state
if router.get('ha') and not is_dvr_only_agent and is_ha_router:
self.check_ha_state_for_router(
router['id'], router.get(l3_constants.HA_ROUTER_STATE_KEY))
ri.router = router
registry.notify(resources.ROUTER, events.BEFORE_UPDATE,
self, router=ri)
ri.process()
registry.notify(resources.ROUTER, events.AFTER_UPDATE, self, router=ri)
self.l3_ext_manager.update_router(self.context, router)
is_dvr_snat_agent = (self.conf.agent_mode ==
lib_const.L3_AGENT_MODE_DVR_SNAT)
is_dvr_only_agent = (self.conf.agent_mode ==
lib_const.L3_AGENT_MODE_DVR)
old_router_ha_interface = ri.router.get(lib_const.HA_INTERFACE_KEY)
current_router_ha_interface = router.get(lib_const.HA_INTERFACE_KEY)
ha_interface_change = ((old_router_ha_interface is None and
current_router_ha_interface is not None) or
(old_router_ha_interface is not None and
current_router_ha_interface is None))
is_dvr_ha_router = router.get('distributed') and router.get('ha')
if is_dvr_snat_agent and is_dvr_ha_router and ha_interface_change:
LOG.debug("Removing HA router %s, since it is not bound to "
"the current agent, and recreating regular DVR router "
"based on service port requirements.",
router['id'])
if self._safe_router_removed(router['id']):
self._process_added_router(router)
else:
is_ha_router = getattr(ri, 'ha_state', None) is not None
# For HA routers check that DB state matches actual state
if router.get('ha') and not is_dvr_only_agent and is_ha_router:
self.check_ha_state_for_router(
router['id'], router.get(l3_constants.HA_ROUTER_STATE_KEY))
ri.router = router
registry.notify(resources.ROUTER, events.BEFORE_UPDATE,
self, router=ri)
ri.process()
registry.notify(
resources.ROUTER, events.AFTER_UPDATE, self, router=ri)
self.l3_ext_manager.update_router(self.context, router)
def _resync_router(self, router_update,
priority=queue.PRIORITY_SYNC_ROUTERS_TASK):

View File

@ -1068,6 +1068,43 @@ class TestDvrRouter(framework.L3AgentTestFramework):
self._assert_no_ip_addresses_on_interface(namespace,
ex_gw_port_name)
def test_dvr_ha_router_unbound_from_agents(self):
self._setup_dvr_ha_agents()
self._setup_dvr_ha_bridges()
router1 = self._create_dvr_ha_router(
self.agent, enable_gw=True)
router2 = self._create_dvr_ha_router(
self.failover_agent, enable_gw=True)
utils.wait_until_true(lambda: router1.ha_state == 'master')
utils.wait_until_true(lambda: router2.ha_state == 'backup')
self._assert_ip_addresses_in_dvr_ha_snat_namespace(router1)
self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router2)
router1_ha_device = router1.get_ha_device_name()
router2_ha_device = router2.get_ha_device_name()
self.assertTrue(
ip_lib.device_exists(router1_ha_device, router1.ha_namespace))
self.assertTrue(
ip_lib.device_exists(router2_ha_device, router2.ha_namespace))
router1.router['_ha_interface'] = None
self.agent._process_updated_router(router1.router)
router_updated = self.agent.router_info[router1.router_id]
self.assertTrue(self._namespace_exists(router_updated.ns_name))
self._assert_snat_namespace_exists(router_updated)
snat_namespace_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
router_updated.router_id)
self.assertFalse(
ip_lib.device_exists(router1_ha_device, snat_namespace_name))
utils.wait_until_true(lambda: router2.ha_state == 'master')
self._assert_ip_addresses_in_dvr_ha_snat_namespace(router2)
self.assertTrue(
ip_lib.device_exists(router2_ha_device, router2.ha_namespace))
def _test_dvr_ha_router_failover(self, enable_gw):
self._setup_dvr_ha_agents()
self._setup_dvr_ha_bridges()

View File

@ -2272,6 +2272,67 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
def test_process_routers_update_router_deleted_error(self):
self._test_process_routers_update_router_deleted(True)
def test_process_dvr_routers_ha_on_update_when_router_unbound(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent.conf.agent_mode = 'dvr_snat'
router = mock.Mock()
router.id = '1234'
router.distributed = True
router.ha = True
router_info = mock.MagicMock()
agent.router_info[router.id] = router_info
updated_router = {'id': '1234',
'distributed': True,
'ha': True,
'external_gateway_info': {},
'routes': [],
'admin_state_up': True}
self.plugin_api.get_routers.return_value = [updated_router]
update = router_processing_queue.RouterUpdate(
updated_router['id'], router_processing_queue.PRIORITY_RPC,
router=updated_router)
agent._queue.add(update)
with mock.patch.object(agent,
"_safe_router_removed"
) as router_remove,\
mock.patch.object(agent,
"_process_added_router"
) as add_router:
agent._process_router_if_compatible(updated_router)
router_remove.assert_called_once_with(updated_router['id'])
add_router.assert_called_once_with(updated_router)
def test_process_dvr_routers_ha_on_update_without_ha_interface(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent.conf.agent_mode = 'dvr_snat'
router = mock.Mock()
router.id = '1234'
router.distributed = True
router._ha_interface = True
router.ha = True
router_info = mock.MagicMock()
agent.router_info[router.id] = router_info
updated_router = {'id': '1234',
'distributed': True, 'ha': True,
'external_gateway_info': {}, 'routes': [],
'admin_state_up': True}
self.plugin_api.get_routers.return_value = [updated_router]
update = router_processing_queue.RouterUpdate(
updated_router['id'], router_processing_queue.PRIORITY_RPC,
router=updated_router)
agent._queue.add(update)
with mock.patch.object(agent,
"_safe_router_removed"
) as router_remove,\
mock.patch.object(agent,
"_process_added_router"
) as add_router:
agent._process_router_if_compatible(updated_router)
router_remove.assert_called_once_with(updated_router['id'])
add_router.assert_called_once_with(updated_router)
def test_process_ha_dvr_router_if_compatible_no_ha_interface(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent.conf.agent_mode = 'dvr_snat'