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.

Closes-Bug: #1541406
Change-Id: If6ada5027d0483fac7fc3ff935fee1edfc6e2759
This commit is contained in:
Brian Haley 2017-01-24 13:34:58 -05:00
parent eeeee06bf0
commit 7b5f01c2e5
6 changed files with 95 additions and 5 deletions

View File

@ -33,6 +33,9 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter):
self.router_id, self.agent_conf, self.driver, self.use_ipv6) self.router_id, self.agent_conf, self.driver, self.use_ipv6)
self.snat_iptables_manager = None 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): def external_gateway_added(self, ex_gw_port, interface_name):
super(DvrEdgeRouter, self).external_gateway_added( super(DvrEdgeRouter, self).external_gateway_added(
ex_gw_port, interface_name) ex_gw_port, interface_name)

View File

@ -122,6 +122,9 @@ class RouterInfo(object):
def get_external_device_interface_name(self, ex_gw_port): def get_external_device_interface_name(self, ex_gw_port):
return self.get_external_device_name(ex_gw_port['id']) 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): def _update_routing_table(self, operation, route, namespace):
cmd = ['ip', 'route', operation, 'to', route['destination'], cmd = ['ip', 'route', operation, 'to', route['destination'],
'via', route['nexthop']] 'via', route['nexthop']]

View File

@ -24,7 +24,7 @@ from oslo_utils import netutils
import six import six
from stevedore import driver from stevedore import driver
from neutron._i18n import _ from neutron._i18n import _, _LE
from neutron.callbacks import events from neutron.callbacks import events
from neutron.callbacks import registry from neutron.callbacks import registry
from neutron.callbacks import resources from neutron.callbacks import resources
@ -57,6 +57,9 @@ class PrefixDelegation(object):
registry.subscribe(add_router, registry.subscribe(add_router,
resources.ROUTER, resources.ROUTER,
events.BEFORE_CREATE) events.BEFORE_CREATE)
registry.subscribe(update_router,
resources.ROUTER,
events.AFTER_UPDATE)
registry.subscribe(remove_router, registry.subscribe(remove_router,
resources.ROUTER, resources.ROUTER,
events.AFTER_DELETE) events.AFTER_DELETE)
@ -329,12 +332,24 @@ def get_router_entry(ns_name):
def add_router(resource, event, l3_agent, **kwargs): def add_router(resource, event, l3_agent, **kwargs):
added_router = kwargs['router'] added_router = kwargs['router']
router = l3_agent.pd.routers.get(added_router.router_id) router = l3_agent.pd.routers.get(added_router.router_id)
gw_ns_name = added_router.get_gw_ns_name()
if not router: if not router:
l3_agent.pd.routers[added_router.router_id] = ( l3_agent.pd.routers[added_router.router_id] = (
get_router_entry(added_router.ns_name)) get_router_entry(gw_ns_name))
else: else:
# This will happen during l3 agent restart # 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): class PDInfo(object):

View File

@ -670,8 +670,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
self._set_ri_kwargs(mock.Mock(), router['id'], router) self._set_ri_kwargs(mock.Mock(), router['id'], router)
ri = dvr_router.DvrEdgeRouter(HOSTNAME, **self.ri_kwargs) ri = dvr_router.DvrEdgeRouter(HOSTNAME, **self.ri_kwargs)
# Make sure that ri.snat_namespace object is created when the # 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.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( def test_ext_gw_updated_calling_snat_ns_delete_if_gw_port_host_none(
self): self):

View File

@ -164,6 +164,10 @@ class TestDvrRouterOperations(base.BaseTestCase):
self.ri_kwargs['router_id'] = router_id self.ri_kwargs['router_id'] = router_id
self.ri_kwargs['router'] = router self.ri_kwargs['router'] = router
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): def test_create_dvr_fip_interfaces_update(self):
ri = self._create_router() ri = self._create_router()
fip_agent_port = {'subnets': []} fip_agent_port = {'subnets': []}

View File

@ -12,6 +12,9 @@
import mock 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.agent.linux import pd
from neutron.tests import base as tests_base from neutron.tests import base as tests_base
@ -24,8 +27,68 @@ class FakeRouter(object):
class TestPrefixDelegation(tests_base.DietTestCase): class TestPrefixDelegation(tests_base.DietTestCase):
def test_remove_router(self): def test_remove_router(self):
l3_agent = mock.Mock() l3_agent = mock.Mock()
router_id = 1 router_id = '1'
l3_agent.pd.routers = {router_id: pd.get_router_entry(None)} l3_agent.pd.routers = {router_id: pd.get_router_entry(None)}
pd.remove_router(None, None, l3_agent, router=FakeRouter(router_id)) pd.remove_router(None, None, l3_agent, router=FakeRouter(router_id))
self.assertTrue(l3_agent.pd.delete_router_pd.called) self.assertTrue(l3_agent.pd.delete_router_pd.called)
self.assertEqual({}, l3_agent.pd.routers) 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(l3_agent,
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)