715b16aca7
- Adds L3 routing/NAT service plugin - Removes L3 routing/NAT from ML2 plugin - Moves "router:external" attribute to new extension "External-net" - Introduces separate RPC topic for L3 callbacks from L3 agent Implements: blueprint quantum-l3-routing-plugin Change-Id: Id9af10c2910f9a1730b163203a68d101ffc3b282
487 lines
23 KiB
Python
487 lines
23 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
#
|
|
# Copyright 2013, Nachi Ueno, NTT MCL, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import contextlib
|
|
from oslo.config import cfg
|
|
from webob import exc
|
|
|
|
from neutron.common.test_lib import test_config
|
|
from neutron.db import extraroute_db
|
|
from neutron.extensions import extraroute
|
|
from neutron.extensions import l3
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.openstack.common.notifier import api as notifier_api
|
|
from neutron.openstack.common.notifier import test_notifier
|
|
from neutron.openstack.common import uuidutils
|
|
from neutron.tests.unit import test_api_v2
|
|
from neutron.tests.unit import test_l3_plugin as test_l3
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
_uuid = uuidutils.generate_uuid
|
|
_get_path = test_api_v2._get_path
|
|
|
|
|
|
class ExtraRouteTestExtensionManager(object):
|
|
|
|
def get_resources(self):
|
|
l3.RESOURCE_ATTRIBUTE_MAP['routers'].update(
|
|
extraroute.EXTENDED_ATTRIBUTES_2_0['routers'])
|
|
return l3.L3.get_resources()
|
|
|
|
def get_actions(self):
|
|
return []
|
|
|
|
def get_request_extensions(self):
|
|
return []
|
|
|
|
|
|
# This plugin class is for tests with plugin that integrates L3.
|
|
class TestExtraRouteIntPlugin(test_l3.TestL3NatIntPlugin,
|
|
extraroute_db.ExtraRoute_db_mixin):
|
|
supported_extension_aliases = ["external-net", "router", "extraroute"]
|
|
|
|
|
|
# A fake l3 service plugin class with extra route capability for
|
|
# plugins that delegate away L3 routing functionality
|
|
class TestExtraRouteL3NatServicePlugin(test_l3.TestL3NatServicePlugin,
|
|
extraroute_db.ExtraRoute_db_mixin):
|
|
supported_extension_aliases = ["router", "extraroute"]
|
|
|
|
|
|
class ExtraRouteDBTestCaseBase(object):
|
|
def _routes_update_prepare(self, router_id, subnet_id,
|
|
port_id, routes, skip_add=False):
|
|
if not skip_add:
|
|
self._router_interface_action('add', router_id, subnet_id, port_id)
|
|
self._update('routers', router_id, {'router': {'routes': routes}})
|
|
return self._show('routers', router_id)
|
|
|
|
def _routes_update_cleanup(self, port_id, subnet_id, router_id, routes):
|
|
self._update('routers', router_id, {'router': {'routes': routes}})
|
|
self._router_interface_action('remove', router_id, subnet_id, port_id)
|
|
|
|
def test_route_update_with_one_route(self):
|
|
routes = [{'destination': '135.207.0.0/16', 'nexthop': '10.0.1.3'}]
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
body = self._routes_update_prepare(r['router']['id'],
|
|
None, p['port']['id'],
|
|
routes)
|
|
self.assertEqual(body['router']['routes'], routes)
|
|
self._routes_update_cleanup(p['port']['id'],
|
|
None, r['router']['id'], [])
|
|
|
|
def test_route_clear_routes_with_None(self):
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'},
|
|
{'destination': '12.0.0.0/8',
|
|
'nexthop': '10.0.1.4'},
|
|
{'destination': '141.212.0.0/16',
|
|
'nexthop': '10.0.1.5'}]
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._routes_update_prepare(r['router']['id'],
|
|
None, p['port']['id'], routes)
|
|
body = self._update('routers', r['router']['id'],
|
|
{'router': {'routes': None}})
|
|
self.assertEqual(body['router']['routes'], [])
|
|
self._routes_update_cleanup(p['port']['id'],
|
|
None, r['router']['id'], [])
|
|
|
|
def test_router_interface_in_use_by_route(self):
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'}]
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
body = self._routes_update_prepare(r['router']['id'],
|
|
None, p['port']['id'],
|
|
routes)
|
|
self.assertEqual(body['router']['routes'], routes)
|
|
self._router_interface_action(
|
|
'remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'],
|
|
expected_code=exc.HTTPConflict.code)
|
|
|
|
self._routes_update_cleanup(p['port']['id'],
|
|
None, r['router']['id'], [])
|
|
|
|
def test_route_update_with_multi_routes(self):
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'},
|
|
{'destination': '12.0.0.0/8',
|
|
'nexthop': '10.0.1.4'},
|
|
{'destination': '141.212.0.0/16',
|
|
'nexthop': '10.0.1.5'}]
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
body = self._routes_update_prepare(r['router']['id'],
|
|
None, p['port']['id'],
|
|
routes)
|
|
self.assertEqual(sorted(body['router']['routes']),
|
|
sorted(routes))
|
|
self._routes_update_cleanup(p['port']['id'],
|
|
None, r['router']['id'], [])
|
|
|
|
def test_router_update_delete_routes(self):
|
|
routes_orig = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'},
|
|
{'destination': '12.0.0.0/8',
|
|
'nexthop': '10.0.1.4'},
|
|
{'destination': '141.212.0.0/16',
|
|
'nexthop': '10.0.1.5'}]
|
|
routes_left = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'},
|
|
{'destination': '141.212.0.0/16',
|
|
'nexthop': '10.0.1.5'}]
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
body = self._routes_update_prepare(r['router']['id'],
|
|
None, p['port']['id'],
|
|
routes_orig)
|
|
self.assertEqual(sorted(body['router']['routes']),
|
|
sorted(routes_orig))
|
|
body = self._routes_update_prepare(r['router']['id'],
|
|
None, p['port']['id'],
|
|
routes_left,
|
|
skip_add=True)
|
|
self.assertEqual(sorted(body['router']['routes']),
|
|
sorted(routes_left))
|
|
self._routes_update_cleanup(p['port']['id'],
|
|
None, r['router']['id'], [])
|
|
|
|
def _test_malformed_route(self, routes):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes': routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_no_destination_route(self):
|
|
self._test_malformed_route([{'nexthop': '10.0.1.6'}])
|
|
|
|
def test_no_nexthop_route(self):
|
|
self._test_malformed_route({'destination': '135.207.0.0/16'})
|
|
|
|
def test_none_destination(self):
|
|
self._test_malformed_route([{'destination': None,
|
|
'nexthop': '10.0.1.3'}])
|
|
|
|
def test_none_nexthop(self):
|
|
self._test_malformed_route([{'destination': '135.207.0.0/16',
|
|
'nexthop': None}])
|
|
|
|
def test_nexthop_is_port_ip(self):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
port_ip = p['port']['fixed_ips'][0]['ip_address']
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': port_ip}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_router_update_with_too_many_routes(self):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'},
|
|
{'destination': '12.0.0.0/8',
|
|
'nexthop': '10.0.1.4'},
|
|
{'destination': '141.212.0.0/16',
|
|
'nexthop': '10.0.1.5'},
|
|
{'destination': '192.168.0.0/16',
|
|
'nexthop': '10.0.1.6'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_router_update_with_dup_address(self):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'},
|
|
{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_router_update_with_invalid_ip_address(self):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
routes = [{'destination': '512.207.0.0/16',
|
|
'nexthop': '10.0.1.3'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
routes = [{'destination': '127.207.0.0/48',
|
|
'nexthop': '10.0.1.3'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
routes = [{'destination': 'invalid_ip_address',
|
|
'nexthop': '10.0.1.3'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_router_update_with_invalid_nexthop_ip(self):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
routes = [{'destination': '127.207.0.0/16',
|
|
'nexthop': ' 300.10.10.4'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_router_update_with_nexthop_is_outside_port_subnet(self):
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
with self.port(subnet=s, no_delete=True) as p:
|
|
self._router_interface_action('add',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
routes = [{'destination': '127.207.0.0/16',
|
|
'nexthop': ' 20.10.10.4'}]
|
|
|
|
self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}},
|
|
expected_code=exc.HTTPBadRequest.code)
|
|
|
|
# clean-up
|
|
self._router_interface_action('remove',
|
|
r['router']['id'],
|
|
None,
|
|
p['port']['id'])
|
|
|
|
def test_router_update_on_external_port(self):
|
|
DEVICE_OWNER_ROUTER_GW = "network:router_gateway"
|
|
with self.router() as r:
|
|
with self.subnet(cidr='10.0.1.0/24') as s:
|
|
self._set_net_external(s['subnet']['network_id'])
|
|
self._add_external_gateway_to_router(
|
|
r['router']['id'],
|
|
s['subnet']['network_id'])
|
|
body = self._show('routers', r['router']['id'])
|
|
net_id = body['router']['external_gateway_info']['network_id']
|
|
self.assertEqual(net_id, s['subnet']['network_id'])
|
|
port_res = self._list_ports('json',
|
|
200,
|
|
s['subnet']['network_id'],
|
|
tenant_id=r['router']['tenant_id'],
|
|
device_own=DEVICE_OWNER_ROUTER_GW)
|
|
port_list = self.deserialize('json', port_res)
|
|
self.assertEqual(len(port_list['ports']), 1)
|
|
|
|
routes = [{'destination': '135.207.0.0/16',
|
|
'nexthop': '10.0.1.3'}]
|
|
|
|
body = self._update('routers', r['router']['id'],
|
|
{'router': {'routes':
|
|
routes}})
|
|
|
|
body = self._show('routers', r['router']['id'])
|
|
self.assertEqual(body['router']['routes'],
|
|
routes)
|
|
|
|
self._remove_external_gateway_from_router(
|
|
r['router']['id'],
|
|
s['subnet']['network_id'])
|
|
body = self._show('routers', r['router']['id'])
|
|
gw_info = body['router']['external_gateway_info']
|
|
self.assertEqual(gw_info, None)
|
|
|
|
def test_router_list_with_sort(self):
|
|
with contextlib.nested(self.router(name='router1'),
|
|
self.router(name='router2'),
|
|
self.router(name='router3')
|
|
) as (router1, router2, router3):
|
|
self._test_list_with_sort('router', (router3, router2, router1),
|
|
[('name', 'desc')])
|
|
|
|
def test_router_list_with_pagination(self):
|
|
with contextlib.nested(self.router(name='router1'),
|
|
self.router(name='router2'),
|
|
self.router(name='router3')
|
|
) as (router1, router2, router3):
|
|
self._test_list_with_pagination('router',
|
|
(router1, router2, router3),
|
|
('name', 'asc'), 2, 2)
|
|
|
|
def test_router_list_with_pagination_reverse(self):
|
|
with contextlib.nested(self.router(name='router1'),
|
|
self.router(name='router2'),
|
|
self.router(name='router3')
|
|
) as (router1, router2, router3):
|
|
self._test_list_with_pagination_reverse('router',
|
|
(router1, router2,
|
|
router3),
|
|
('name', 'asc'), 2, 2)
|
|
|
|
|
|
class ExtraRouteDBIntTestCase(test_l3.L3NatDBIntTestCase,
|
|
ExtraRouteDBTestCaseBase):
|
|
|
|
def setUp(self, plugin=None):
|
|
if not plugin:
|
|
plugin = ('neutron.tests.unit.test_extension_extraroute.'
|
|
'TestExtraRouteIntPlugin')
|
|
test_config['plugin_name_v2'] = plugin
|
|
# for these tests we need to enable overlapping ips
|
|
cfg.CONF.set_default('allow_overlapping_ips', True)
|
|
cfg.CONF.set_default('max_routes', 3)
|
|
ext_mgr = ExtraRouteTestExtensionManager()
|
|
test_config['extension_manager'] = ext_mgr
|
|
# L3NatDBIntTestCase will overwrite plugin_name_v2,
|
|
# so we don't need to setUp on the class here
|
|
super(test_l3.L3BaseForIntTests, self).setUp()
|
|
|
|
# Set to None to reload the drivers
|
|
notifier_api._drivers = None
|
|
cfg.CONF.set_override("notification_driver", [test_notifier.__name__])
|
|
|
|
|
|
class ExtraRouteDBIntTestCaseXML(ExtraRouteDBIntTestCase):
|
|
fmt = 'xml'
|
|
|
|
|
|
class ExtraRouteDBSepTestCase(test_l3.L3NatDBSepTestCase,
|
|
ExtraRouteDBTestCaseBase):
|
|
def setUp(self):
|
|
# the plugin without L3 support
|
|
test_config['plugin_name_v2'] = (
|
|
'neutron.tests.unit.test_l3_plugin.TestNoL3NatPlugin')
|
|
# the L3 service plugin
|
|
l3_plugin = ('neutron.tests.unit.test_extension_extraroute.'
|
|
'TestExtraRouteL3NatServicePlugin')
|
|
service_plugins = {'l3_plugin_name': l3_plugin}
|
|
|
|
# for these tests we need to enable overlapping ips
|
|
cfg.CONF.set_default('allow_overlapping_ips', True)
|
|
cfg.CONF.set_default('max_routes', 3)
|
|
ext_mgr = ExtraRouteTestExtensionManager()
|
|
test_config['extension_manager'] = ext_mgr
|
|
# L3NatDBSepTestCase will overwrite plugin_name_v2,
|
|
# so we don't need to setUp on the class here
|
|
super(test_l3.L3BaseForSepTests, self).setUp(
|
|
service_plugins=service_plugins)
|
|
|
|
# Set to None to reload the drivers
|
|
notifier_api._drivers = None
|
|
cfg.CONF.set_override("notification_driver", [test_notifier.__name__])
|
|
|
|
|
|
class ExtraRouteDBSepTestCaseXML(ExtraRouteDBSepTestCase):
|
|
fmt = 'xml'
|