From a2f07de43f250b2114c9b4b074b61b4b4d8e450f Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Tue, 24 Jan 2017 13:34:58 -0500 Subject: [PATCH] Add IPv6 Prefix Delegation support for DVR Unlike Legacy routers, DVR Edge Routers have their gateway interfaces in the SNAT namespace as opposed to the router namespace. Added a new method to the router_info class, get_gw_ns_name(), so callers can determine which namespace the gateway device lives in. This can then be over-ridden in the DVR Edge router class. The Prefix Delegation code will also now listen on the update_router event from the l3-agent and reset the namespace name if it changes. Conflicts: neutron/tests/unit/agent/linux/test_pd.py Closes-Bug: #1541406 Change-Id: If6ada5027d0483fac7fc3ff935fee1edfc6e2759 (cherry picked from commit 7b5f01c2e58cd21ab3d46676ee6a3288012fbb45) --- neutron/agent/l3/dvr_edge_router.py | 3 + neutron/agent/l3/router_info.py | 3 + neutron/agent/linux/pd.py | 21 +++++- neutron/tests/unit/agent/l3/test_agent.py | 4 +- .../unit/agent/l3/test_dvr_local_router.py | 4 ++ neutron/tests/unit/agent/linux/test_pd.py | 64 ++++++++++++++++++- 6 files changed, 94 insertions(+), 5 deletions(-) diff --git a/neutron/agent/l3/dvr_edge_router.py b/neutron/agent/l3/dvr_edge_router.py index 32db639e211..9fa22257dad 100644 --- a/neutron/agent/l3/dvr_edge_router.py +++ b/neutron/agent/l3/dvr_edge_router.py @@ -33,6 +33,9 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter): self.router_id, self.agent_conf, self.driver, self.use_ipv6) self.snat_iptables_manager = None + def get_gw_ns_name(self): + return self.snat_namespace.name + def external_gateway_added(self, ex_gw_port, interface_name): super(DvrEdgeRouter, self).external_gateway_added( ex_gw_port, interface_name) diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 09a82db93b2..d671d3f4f43 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -122,6 +122,9 @@ class RouterInfo(object): def get_external_device_interface_name(self, ex_gw_port): return self.get_external_device_name(ex_gw_port['id']) + def get_gw_ns_name(self): + return self.ns_name + def _update_routing_table(self, operation, route, namespace): cmd = ['ip', 'route', operation, 'to', route['destination'], 'via', route['nexthop']] diff --git a/neutron/agent/linux/pd.py b/neutron/agent/linux/pd.py index 98aadfd33d4..557e2825d14 100644 --- a/neutron/agent/linux/pd.py +++ b/neutron/agent/linux/pd.py @@ -24,7 +24,7 @@ from oslo_utils import netutils import six from stevedore import driver -from neutron._i18n import _ +from neutron._i18n import _, _LE from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources @@ -57,6 +57,9 @@ class PrefixDelegation(object): registry.subscribe(add_router, resources.ROUTER, events.BEFORE_CREATE) + registry.subscribe(update_router, + resources.ROUTER, + events.AFTER_UPDATE) registry.subscribe(remove_router, resources.ROUTER, events.AFTER_DELETE) @@ -320,12 +323,24 @@ def get_router_entry(ns_name): def add_router(resource, event, l3_agent, **kwargs): added_router = kwargs['router'] router = l3_agent.pd.routers.get(added_router.router_id) + gw_ns_name = added_router.get_gw_ns_name() if not router: l3_agent.pd.routers[added_router.router_id] = ( - get_router_entry(added_router.ns_name)) + get_router_entry(gw_ns_name)) else: # This will happen during l3 agent restart - router['ns_name'] = added_router.ns_name + router['ns_name'] = gw_ns_name + + +@utils.synchronized("l3-agent-pd") +def update_router(resource, event, l3_agent, **kwargs): + updated_router = kwargs['router'] + router = l3_agent.pd.routers.get(updated_router.router_id) + if not router: + LOG.exception(_LE("Router to be updated is not in internal routers " + "list: %s"), updated_router.router_id) + else: + router['ns_name'] = updated_router.get_gw_ns_name() class PDInfo(object): diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index e0412945814..c478032e28b 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -689,8 +689,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router, **self.ri_kwargs) # Make sure that ri.snat_namespace object is created when the - # router is initialized + # router is initialized, and that it's name matches the gw + # namespace name self.assertIsNotNone(ri.snat_namespace) + self.assertEqual(ri.snat_namespace.name, ri.get_gw_ns_name()) def test_ext_gw_updated_calling_snat_ns_delete_if_gw_port_host_none( self): diff --git a/neutron/tests/unit/agent/l3/test_dvr_local_router.py b/neutron/tests/unit/agent/l3/test_dvr_local_router.py index 781fb659a0e..4e46fd89f41 100644 --- a/neutron/tests/unit/agent/l3/test_dvr_local_router.py +++ b/neutron/tests/unit/agent/l3/test_dvr_local_router.py @@ -159,6 +159,10 @@ class TestDvrRouterOperations(base.BaseTestCase): mock.Mock(), **kwargs) + def test_gw_ns_name(self): + ri = self._create_router() + self.assertEqual(ri.ns_name, ri.get_gw_ns_name()) + def test_create_dvr_fip_interfaces_update(self): ri = self._create_router() fip_agent_port = {'subnets': []} diff --git a/neutron/tests/unit/agent/linux/test_pd.py b/neutron/tests/unit/agent/linux/test_pd.py index e12106727dd..13afa147783 100644 --- a/neutron/tests/unit/agent/linux/test_pd.py +++ b/neutron/tests/unit/agent/linux/test_pd.py @@ -12,6 +12,9 @@ import mock +from neutron.agent.l3 import dvr_edge_router +from neutron.agent.l3 import dvr_local_router +from neutron.agent.l3 import legacy_router from neutron.agent.linux import pd from neutron.tests import base as tests_base @@ -24,8 +27,67 @@ class FakeRouter(object): class TestPrefixDelegation(tests_base.DietTestCase): def test_remove_router(self): l3_agent = mock.Mock() - router_id = 1 + router_id = '1' l3_agent.pd.routers = {router_id: pd.get_router_entry(None)} pd.remove_router(None, None, l3_agent, router=FakeRouter(router_id)) self.assertTrue(l3_agent.pd.delete_router_pd.called) self.assertEqual({}, l3_agent.pd.routers) + + def _test_add_update_pd(self, l3_agent, router, ns_name): + # add entry + pd.add_router(None, None, l3_agent, router=router) + pd_router = l3_agent.pd.routers.get(router.router_id) + self.assertEqual(ns_name, pd_router.get('ns_name')) + + # clear namespace name, update entry + pd_router['ns_name'] = None + pd.update_router(None, None, l3_agent, router=router) + pd_router = l3_agent.pd.routers.get(router.router_id) + self.assertEqual(ns_name, pd_router.get('ns_name')) + + def test_add_update_dvr_edge_router(self): + l3_agent = mock.Mock() + l3_agent.pd.routers = {} + router_id = '1' + ri = dvr_edge_router.DvrEdgeRouter(l3_agent, + 'host', + router_id, + mock.Mock(), + mock.Mock(), + mock.Mock()) + ns_name = ri.snat_namespace.name + self._test_add_update_pd(l3_agent, ri, ns_name) + + def test_add_update_dvr_local_router(self): + l3_agent = mock.Mock() + l3_agent.pd.routers = {} + router_id = '1' + ri = dvr_local_router.DvrLocalRouter(l3_agent, + 'host', + router_id, + mock.Mock(), + mock.Mock(), + mock.Mock()) + ns_name = ri.ns_name + self._test_add_update_pd(l3_agent, ri, ns_name) + + def test_add_update_legacy_router(self): + l3_agent = mock.Mock() + l3_agent.pd.routers = {} + router_id = '1' + ri = legacy_router.LegacyRouter(router_id, + mock.Mock(), + mock.Mock(), + mock.Mock()) + ns_name = ri.ns_name + self._test_add_update_pd(l3_agent, ri, ns_name) + + def test_update_no_router_exception(self): + l3_agent = mock.Mock() + l3_agent.pd.routers = {} + router = mock.Mock() + router.router_id = '1' + + with mock.patch.object(pd.LOG, 'exception') as log: + pd.update_router(None, None, l3_agent, router=router) + self.assertTrue(log.called)