Static routes not added to qrouter namespace for DVR
Today static routes are added to the SNAT namespace for DVR routers. But they are not added to the qrouter namespace. Also while configuring the static routes to SNAT namespace, the router is not checked for the existence of the gateway. When routes are added to a router without a gateway the routes are only configured in the router namespace, but when a gateway is set later, those routes have to be populated in the snat_namespace as well. This patch addresses the above mentioned issues. Closes-Bug: #1499785 Closes-Bug: #1499787 Change-Id: I37e0d0d723fcc727faa09028045b776957c75a82
This commit is contained in:
parent
b600129553
commit
158f9eabe2
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from neutron._i18n import _LE
|
||||||
from neutron.agent.l3 import dvr_local_router
|
from neutron.agent.l3 import dvr_local_router
|
||||||
from neutron.agent.l3 import dvr_snat_ns
|
from neutron.agent.l3 import dvr_snat_ns
|
||||||
from neutron.agent.l3 import router_info as router
|
from neutron.agent.l3 import router_info as router
|
||||||
|
@ -35,6 +36,11 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter):
|
||||||
ex_gw_port, interface_name)
|
ex_gw_port, interface_name)
|
||||||
if self._is_this_snat_host():
|
if self._is_this_snat_host():
|
||||||
self._create_dvr_gateway(ex_gw_port, interface_name)
|
self._create_dvr_gateway(ex_gw_port, interface_name)
|
||||||
|
# NOTE: When a router is created without a gateway the routes get
|
||||||
|
# added to the router namespace, but if we wanted to populate
|
||||||
|
# the same routes to the snat namespace after the gateway port
|
||||||
|
# is added, we need to call routes_updated here.
|
||||||
|
self.routes_updated([], self.router['routes'])
|
||||||
|
|
||||||
def external_gateway_updated(self, ex_gw_port, interface_name):
|
def external_gateway_updated(self, ex_gw_port, interface_name):
|
||||||
if not self._is_this_snat_host():
|
if not self._is_this_snat_host():
|
||||||
|
@ -182,10 +188,20 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter):
|
||||||
self._add_snat_rules(ex_gw_port, self.snat_iptables_manager,
|
self._add_snat_rules(ex_gw_port, self.snat_iptables_manager,
|
||||||
interface_name)
|
interface_name)
|
||||||
|
|
||||||
def update_routing_table(self, operation, route, namespace=None):
|
def update_routing_table(self, operation, route):
|
||||||
ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(self.router['id'])
|
if self.get_ex_gw_port() and self._is_this_snat_host():
|
||||||
super(DvrEdgeRouter, self).update_routing_table(operation, route,
|
ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
|
||||||
namespace=ns_name)
|
self.router['id'])
|
||||||
|
# NOTE: For now let us apply the static routes both in SNAT
|
||||||
|
# namespace and Router Namespace, to reduce the complexity.
|
||||||
|
ip_wrapper = ip_lib.IPWrapper(namespace=ns_name)
|
||||||
|
if ip_wrapper.netns.exists(ns_name):
|
||||||
|
super(DvrEdgeRouter, self)._update_routing_table(
|
||||||
|
operation, route, namespace=ns_name)
|
||||||
|
else:
|
||||||
|
LOG.error(_LE("The SNAT namespace %s does not exist for "
|
||||||
|
"the router."), ns_name)
|
||||||
|
super(DvrEdgeRouter, self).update_routing_table(operation, route)
|
||||||
|
|
||||||
def delete(self, agent):
|
def delete(self, agent):
|
||||||
super(DvrEdgeRouter, self).delete(agent)
|
super(DvrEdgeRouter, self).delete(agent)
|
||||||
|
|
|
@ -179,15 +179,12 @@ class HaRouter(router.RouterInfo):
|
||||||
def get_router_cidrs(self, device):
|
def get_router_cidrs(self, device):
|
||||||
return set(self._get_cidrs_from_keepalived(device.name))
|
return set(self._get_cidrs_from_keepalived(device.name))
|
||||||
|
|
||||||
def routes_updated(self):
|
def routes_updated(self, old_routes, new_routes):
|
||||||
new_routes = self.router['routes']
|
|
||||||
|
|
||||||
instance = self._get_keepalived_instance()
|
instance = self._get_keepalived_instance()
|
||||||
instance.virtual_routes.extra_routes = [
|
instance.virtual_routes.extra_routes = [
|
||||||
keepalived.KeepalivedVirtualRoute(
|
keepalived.KeepalivedVirtualRoute(
|
||||||
route['destination'], route['nexthop'])
|
route['destination'], route['nexthop'])
|
||||||
for route in new_routes]
|
for route in new_routes]
|
||||||
self.routes = new_routes
|
|
||||||
|
|
||||||
def _add_default_gw_virtual_route(self, ex_gw_port, interface_name):
|
def _add_default_gw_virtual_route(self, ex_gw_port, interface_name):
|
||||||
gateway_ips = self._get_external_gw_ips(ex_gw_port)
|
gateway_ips = self._get_external_gw_ips(ex_gw_port)
|
||||||
|
|
|
@ -107,15 +107,10 @@ class RouterInfo(object):
|
||||||
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
|
||||||
ip_wrapper.netns.execute(cmd, check_exit_code=False)
|
ip_wrapper.netns.execute(cmd, check_exit_code=False)
|
||||||
|
|
||||||
def update_routing_table(self, operation, route, namespace=None):
|
def update_routing_table(self, operation, route):
|
||||||
if namespace is None:
|
self._update_routing_table(operation, route, self.ns_name)
|
||||||
namespace = self.ns_name
|
|
||||||
self._update_routing_table(operation, route, namespace)
|
|
||||||
|
|
||||||
def routes_updated(self):
|
def routes_updated(self, old_routes, new_routes):
|
||||||
new_routes = self.router['routes']
|
|
||||||
|
|
||||||
old_routes = self.routes
|
|
||||||
adds, removes = common_utils.diff_list_of_dict(old_routes,
|
adds, removes = common_utils.diff_list_of_dict(old_routes,
|
||||||
new_routes)
|
new_routes)
|
||||||
for route in adds:
|
for route in adds:
|
||||||
|
@ -129,7 +124,6 @@ class RouterInfo(object):
|
||||||
for route in removes:
|
for route in removes:
|
||||||
LOG.debug("Removed route entry is '%s'", route)
|
LOG.debug("Removed route entry is '%s'", route)
|
||||||
self.update_routing_table('delete', route)
|
self.update_routing_table('delete', route)
|
||||||
self.routes = new_routes
|
|
||||||
|
|
||||||
def get_ex_gw_port(self):
|
def get_ex_gw_port(self):
|
||||||
return self.router.get('gw_port')
|
return self.router.get('gw_port')
|
||||||
|
@ -707,7 +701,8 @@ class RouterInfo(object):
|
||||||
agent.pd.sync_router(self.router['id'])
|
agent.pd.sync_router(self.router['id'])
|
||||||
self.process_external(agent, delete)
|
self.process_external(agent, delete)
|
||||||
# Process static routes for router
|
# Process static routes for router
|
||||||
self.routes_updated()
|
self.routes_updated(self.routes, self.router['routes'])
|
||||||
|
self.routes = self.router['routes']
|
||||||
|
|
||||||
# Update ex_gw_port and enable_snat on the router info cache
|
# Update ex_gw_port and enable_snat on the router info cache
|
||||||
self.ex_gw_port = self.get_ex_gw_port()
|
self.ex_gw_port = self.get_ex_gw_port()
|
||||||
|
|
|
@ -423,8 +423,10 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
||||||
self.assertTrue(self.device_exists_with_ips_and_mac(
|
self.assertTrue(self.device_exists_with_ips_and_mac(
|
||||||
device, router.get_internal_device_name, router.ns_name))
|
device, router.get_internal_device_name, router.ns_name))
|
||||||
|
|
||||||
def _assert_extra_routes(self, router):
|
def _assert_extra_routes(self, router, namespace=None):
|
||||||
routes = ip_lib.get_routing_table(4, namespace=router.ns_name)
|
if namespace is None:
|
||||||
|
namespace = router.ns_name
|
||||||
|
routes = ip_lib.get_routing_table(4, namespace=namespace)
|
||||||
routes = [{'nexthop': route['nexthop'],
|
routes = [{'nexthop': route['nexthop'],
|
||||||
'destination': route['destination']} for route in routes]
|
'destination': route['destination']} for route in routes]
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
|
||||||
|
|
||||||
# We get the router info particular to a dvr router
|
# We get the router info particular to a dvr router
|
||||||
router_info = self.generate_dvr_router_info(
|
router_info = self.generate_dvr_router_info(
|
||||||
enable_ha, enable_snat)
|
enable_ha, enable_snat, extra_routes=True)
|
||||||
|
|
||||||
# We need to mock the get_agent_gateway_port return value
|
# We need to mock the get_agent_gateway_port return value
|
||||||
# because the whole L3PluginApi is mocked and we need the port
|
# because the whole L3PluginApi is mocked and we need the port
|
||||||
|
@ -164,7 +164,6 @@ class TestDvrRouter(framework.L3AgentTestFramework):
|
||||||
self._assert_snat_chains(router)
|
self._assert_snat_chains(router)
|
||||||
self._assert_floating_ip_chains(router)
|
self._assert_floating_ip_chains(router)
|
||||||
self._assert_metadata_chains(router)
|
self._assert_metadata_chains(router)
|
||||||
self._assert_extra_routes(router)
|
|
||||||
self._assert_rfp_fpr_mtu(router, custom_mtu)
|
self._assert_rfp_fpr_mtu(router, custom_mtu)
|
||||||
if enable_snat:
|
if enable_snat:
|
||||||
ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4]
|
ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4]
|
||||||
|
@ -172,7 +171,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
|
||||||
router.router_id)
|
router.router_id)
|
||||||
self._assert_onlink_subnet_routes(
|
self._assert_onlink_subnet_routes(
|
||||||
router, ip_versions, snat_ns_name)
|
router, ip_versions, snat_ns_name)
|
||||||
|
self._assert_extra_routes(router, namespace=snat_ns_name)
|
||||||
self._delete_router(self.agent, router.router_id)
|
self._delete_router(self.agent, router.router_id)
|
||||||
self._assert_fip_namespace_deleted(ext_gateway_port)
|
self._assert_fip_namespace_deleted(ext_gateway_port)
|
||||||
self._assert_router_does_not_exist(router)
|
self._assert_router_does_not_exist(router)
|
||||||
|
@ -182,6 +181,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
|
||||||
enable_ha=False,
|
enable_ha=False,
|
||||||
enable_snat=False,
|
enable_snat=False,
|
||||||
agent=None,
|
agent=None,
|
||||||
|
extra_routes=False,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
if not agent:
|
if not agent:
|
||||||
agent = self.agent
|
agent = self.agent
|
||||||
|
@ -189,6 +189,8 @@ class TestDvrRouter(framework.L3AgentTestFramework):
|
||||||
enable_snat=enable_snat,
|
enable_snat=enable_snat,
|
||||||
enable_floating_ip=True,
|
enable_floating_ip=True,
|
||||||
enable_ha=enable_ha,
|
enable_ha=enable_ha,
|
||||||
|
extra_routes=extra_routes,
|
||||||
|
num_internal_ports=2,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
internal_ports = router.get(l3_constants.INTERFACE_KEY, [])
|
internal_ports = router.get(l3_constants.INTERFACE_KEY, [])
|
||||||
router['distributed'] = True
|
router['distributed'] = True
|
||||||
|
@ -644,6 +646,24 @@ class TestDvrRouter(framework.L3AgentTestFramework):
|
||||||
self._assert_ip_addresses_in_dvr_ha_snat_namespace(router2)
|
self._assert_ip_addresses_in_dvr_ha_snat_namespace(router2)
|
||||||
self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router1)
|
self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router1)
|
||||||
|
|
||||||
|
def test_dvr_router_static_routes(self):
|
||||||
|
"""Test to validate the extra routes on dvr routers."""
|
||||||
|
self.agent.conf.agent_mode = 'dvr_snat'
|
||||||
|
router_info = self.generate_dvr_router_info(enable_snat=True)
|
||||||
|
router1 = self.manage_router(self.agent, router_info)
|
||||||
|
self.assertTrue(self._namespace_exists(router1.ns_name))
|
||||||
|
self._assert_snat_namespace_exists(router1)
|
||||||
|
snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
|
||||||
|
router1.router_id)
|
||||||
|
# Now try to add routes that are suitable for both the
|
||||||
|
# router namespace and the snat namespace.
|
||||||
|
router1.router['routes'] = [{'destination': '8.8.4.0/24',
|
||||||
|
'nexthop': '35.4.0.20'}]
|
||||||
|
self.agent._process_updated_router(router1.router)
|
||||||
|
router_updated = self.agent.router_info[router_info['id']]
|
||||||
|
self._assert_extra_routes(router_updated, namespace=snat_ns_name)
|
||||||
|
self._assert_extra_routes(router_updated)
|
||||||
|
|
||||||
def _assert_fip_namespace_deleted(self, ext_gateway_port):
|
def _assert_fip_namespace_deleted(self, ext_gateway_port):
|
||||||
ext_net_id = ext_gateway_port['network_id']
|
ext_net_id = ext_gateway_port['network_id']
|
||||||
self.agent.fipnamespace_delete_on_ext_net(
|
self.agent.fipnamespace_delete_on_ext_net(
|
||||||
|
|
|
@ -1096,14 +1096,14 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
||||||
router)
|
router)
|
||||||
self.assertEqual(1, self.send_adv_notif.call_count)
|
self.assertEqual(1, self.send_adv_notif.call_count)
|
||||||
|
|
||||||
def test_update_routing_table(self):
|
def _test_update_routing_table(self, is_snat_host=True):
|
||||||
# Just verify the correct namespace was used in the call
|
|
||||||
router = l3_test_common.prepare_router_data()
|
router = l3_test_common.prepare_router_data()
|
||||||
uuid = router['id']
|
uuid = router['id']
|
||||||
netns = 'snat-' + uuid
|
s_netns = 'snat-' + uuid
|
||||||
|
q_netns = 'qrouter-' + uuid
|
||||||
fake_route1 = {'destination': '135.207.0.0/16',
|
fake_route1 = {'destination': '135.207.0.0/16',
|
||||||
'nexthop': '1.2.3.4'}
|
'nexthop': '19.4.4.200'}
|
||||||
|
calls = [mock.call('replace', fake_route1, q_netns)]
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
ri = dvr_router.DvrEdgeRouter(
|
ri = dvr_router.DvrEdgeRouter(
|
||||||
agent,
|
agent,
|
||||||
|
@ -1113,10 +1113,19 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
||||||
**self.ri_kwargs)
|
**self.ri_kwargs)
|
||||||
ri._update_routing_table = mock.Mock()
|
ri._update_routing_table = mock.Mock()
|
||||||
|
|
||||||
ri.update_routing_table('replace', fake_route1)
|
with mock.patch.object(ri, '_is_this_snat_host') as snat_host:
|
||||||
ri._update_routing_table.assert_called_once_with('replace',
|
snat_host.return_value = is_snat_host
|
||||||
fake_route1,
|
ri.update_routing_table('replace', fake_route1)
|
||||||
netns)
|
if is_snat_host:
|
||||||
|
ri._update_routing_table('replace', fake_route1, s_netns)
|
||||||
|
calls += [mock.call('replace', fake_route1, s_netns)]
|
||||||
|
ri._update_routing_table.assert_has_calls(calls, any_order=True)
|
||||||
|
|
||||||
|
def test_process_update_snat_routing_table(self):
|
||||||
|
self._test_update_routing_table()
|
||||||
|
|
||||||
|
def test_process_not_update_snat_routing_table(self):
|
||||||
|
self._test_update_routing_table(is_snat_host=False)
|
||||||
|
|
||||||
def test_process_router_interface_added(self):
|
def test_process_router_interface_added(self):
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
|
|
|
@ -97,7 +97,7 @@ class TestRouterInfo(base.BaseTestCase):
|
||||||
'nexthop': "10.100.10.30"}]
|
'nexthop': "10.100.10.30"}]
|
||||||
ri.routes = fake_old_routes
|
ri.routes = fake_old_routes
|
||||||
ri.router['routes'] = fake_new_routes
|
ri.router['routes'] = fake_new_routes
|
||||||
ri.routes_updated()
|
ri.routes_updated(fake_old_routes, fake_new_routes)
|
||||||
|
|
||||||
expected = [['ip', 'route', 'replace', 'to', '110.100.30.0/24',
|
expected = [['ip', 'route', 'replace', 'to', '110.100.30.0/24',
|
||||||
'via', '10.100.10.30'],
|
'via', '10.100.10.30'],
|
||||||
|
@ -105,18 +105,18 @@ class TestRouterInfo(base.BaseTestCase):
|
||||||
'via', '10.100.10.30']]
|
'via', '10.100.10.30']]
|
||||||
|
|
||||||
self._check_agent_method_called(expected)
|
self._check_agent_method_called(expected)
|
||||||
|
ri.routes = fake_new_routes
|
||||||
fake_new_routes = [{'destination': "110.100.30.0/24",
|
fake_new_routes = [{'destination': "110.100.30.0/24",
|
||||||
'nexthop': "10.100.10.30"}]
|
'nexthop': "10.100.10.30"}]
|
||||||
ri.router['routes'] = fake_new_routes
|
ri.router['routes'] = fake_new_routes
|
||||||
ri.routes_updated()
|
ri.routes_updated(ri.routes, fake_new_routes)
|
||||||
expected = [['ip', 'route', 'delete', 'to', '110.100.31.0/24',
|
expected = [['ip', 'route', 'delete', 'to', '110.100.31.0/24',
|
||||||
'via', '10.100.10.30']]
|
'via', '10.100.10.30']]
|
||||||
|
|
||||||
self._check_agent_method_called(expected)
|
self._check_agent_method_called(expected)
|
||||||
fake_new_routes = []
|
fake_new_routes = []
|
||||||
ri.router['routes'] = fake_new_routes
|
ri.router['routes'] = fake_new_routes
|
||||||
ri.routes_updated()
|
ri.routes_updated(ri.routes, fake_new_routes)
|
||||||
|
|
||||||
expected = [['ip', 'route', 'delete', 'to', '110.100.30.0/24',
|
expected = [['ip', 'route', 'delete', 'to', '110.100.30.0/24',
|
||||||
'via', '10.100.10.30']]
|
'via', '10.100.10.30']]
|
||||||
|
|
Loading…
Reference in New Issue