Routing table configuration support on L3
Implements bp quantum-l3-routes -- Adding the extraroute extension -- Updating the routing table based on routes attribute on route -- Updated OVS plugin, linuxbridge plugin, metaplugin NEC plugin, Ryu plugin User can configure the routes through quantum client API by using the extension feature. sample quantum router-update <router_id> \ --routes type=dict list=true destination=40.0.1.0/24,nexthop=10.1.0.10 Change-Id: I2a11486709e55d3143373858254febaabb93cfe8
This commit is contained in:
parent
e1c0d2a8f7
commit
fcac32f04f
@ -35,6 +35,7 @@ from quantum.agent.linux import utils
|
||||
from quantum.agent import rpc as agent_rpc
|
||||
from quantum.common import constants as l3_constants
|
||||
from quantum.common import topics
|
||||
from quantum.common import utils as common_utils
|
||||
from quantum import context
|
||||
from quantum import manager
|
||||
from quantum.openstack.common import importutils
|
||||
@ -105,6 +106,8 @@ class RouterInfo(object):
|
||||
#FIXME(danwent): use_ipv6=True,
|
||||
namespace=self.ns_name())
|
||||
|
||||
self.routes = []
|
||||
|
||||
def ns_name(self):
|
||||
if self.use_namespaces:
|
||||
return NS_PREFIX + self.router_id
|
||||
@ -319,6 +322,8 @@ class L3NATAgent(manager.Manager):
|
||||
|
||||
ri.ex_gw_port = ex_gw_port
|
||||
|
||||
self.routes_updated(ri)
|
||||
|
||||
def process_router_floating_ips(self, ri, ex_gw_port):
|
||||
floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
|
||||
existing_floating_ip_ids = set([fip['id'] for fip in ri.floating_ips])
|
||||
@ -618,6 +623,36 @@ class L3NATAgent(manager.Manager):
|
||||
def after_start(self):
|
||||
LOG.info(_("L3 agent started"))
|
||||
|
||||
def _update_routing_table(self, ri, operation, route):
|
||||
cmd = ['ip', 'route', operation, 'to', route['destination'],
|
||||
'via', route['nexthop']]
|
||||
#TODO(nati) move this code to iplib
|
||||
if self.conf.use_namespaces:
|
||||
ip_wrapper = ip_lib.IPWrapper(self.conf.root_helper,
|
||||
namespace=ri.ns_name())
|
||||
ip_wrapper.netns.execute(cmd, check_exit_code=False)
|
||||
else:
|
||||
utils.execute(cmd, check_exit_code=False,
|
||||
root_helper=self.conf.root_helper)
|
||||
|
||||
def routes_updated(self, ri):
|
||||
new_routes = ri.router['routes']
|
||||
old_routes = ri.routes
|
||||
adds, removes = common_utils.diff_list_of_dict(old_routes,
|
||||
new_routes)
|
||||
for route in adds:
|
||||
LOG.debug(_("Added route entry is '%s'"), route)
|
||||
# remove replaced route from deleted route
|
||||
for del_route in removes:
|
||||
if route['destination'] == del_route['destination']:
|
||||
removes.remove(del_route)
|
||||
#replace success even if there is no existing route
|
||||
self._update_routing_table(ri, 'replace', route)
|
||||
for route in removes:
|
||||
LOG.debug(_("Removed route entry is '%s'"), route)
|
||||
self._update_routing_table(ri, 'delete', route)
|
||||
ri.routes = new_routes
|
||||
|
||||
|
||||
class L3NATAgentWithStateReport(L3NATAgent):
|
||||
|
||||
|
@ -162,3 +162,24 @@ def compare_elements(a, b):
|
||||
if b is None:
|
||||
b = []
|
||||
return set(a) == set(b)
|
||||
|
||||
|
||||
def dict2str(dic):
|
||||
return ','.join("%s=%s" % (key, val)
|
||||
for key, val in sorted(dic.iteritems()))
|
||||
|
||||
|
||||
def str2dict(string):
|
||||
res_dict = {}
|
||||
for keyvalue in string.split(',', 1):
|
||||
(key, value) = keyvalue.split('=', 1)
|
||||
res_dict[key] = value
|
||||
return res_dict
|
||||
|
||||
|
||||
def diff_list_of_dict(old_list, new_list):
|
||||
new_set = set([dict2str(l) for l in new_list])
|
||||
old_set = set([dict2str(l) for l in old_list])
|
||||
added = new_set - old_set
|
||||
removed = old_set - new_set
|
||||
return [str2dict(a) for a in added], [str2dict(r) for r in removed]
|
||||
|
@ -181,7 +181,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
return []
|
||||
|
||||
def _get_route_by_subnet(self, context, subnet_id):
|
||||
route_qry = context.session.query(models_v2.Route)
|
||||
route_qry = context.session.query(models_v2.SubnetRoute)
|
||||
return route_qry.filter_by(subnet_id=subnet_id).all()
|
||||
|
||||
def _get_subnets_by_network(self, context, network_id):
|
||||
@ -1085,9 +1085,10 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
|
||||
if s['host_routes'] is not attributes.ATTR_NOT_SPECIFIED:
|
||||
for rt in s['host_routes']:
|
||||
route = models_v2.Route(subnet_id=subnet.id,
|
||||
destination=rt['destination'],
|
||||
nexthop=rt['nexthop'])
|
||||
route = models_v2.SubnetRoute(
|
||||
subnet_id=subnet.id,
|
||||
destination=rt['destination'],
|
||||
nexthop=rt['nexthop'])
|
||||
context.session.add(route)
|
||||
|
||||
for pool in s['allocation_pools']:
|
||||
@ -1157,7 +1158,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
if _combine(route) == route_str:
|
||||
context.session.delete(route)
|
||||
for route_str in new_route_set - old_route_set:
|
||||
route = models_v2.Route(
|
||||
route = models_v2.SubnetRoute(
|
||||
destination=route_str.partition("_")[0],
|
||||
nexthop=route_str.partition("_")[2],
|
||||
subnet_id=id)
|
||||
|
174
quantum/db/extraroute_db.py
Normal file
174
quantum/db/extraroute_db.py
Normal file
@ -0,0 +1,174 @@
|
||||
# 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 netaddr
|
||||
from oslo.config import cfg
|
||||
import sqlalchemy as sa
|
||||
|
||||
from quantum.common import utils
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import model_base
|
||||
from quantum.db import models_v2
|
||||
from quantum.extensions import extraroute
|
||||
from quantum.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
extra_route_opts = [
|
||||
#TODO(nati): use quota framework when it support quota for attributes
|
||||
cfg.IntOpt('max_routes', default=30,
|
||||
help=_("Maximum number of routes")),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(extra_route_opts)
|
||||
|
||||
|
||||
class RouterRoute(model_base.BASEV2, models_v2.Route):
|
||||
router_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('routers.id',
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class ExtraRoute_db_mixin(l3_db.L3_NAT_db_mixin):
|
||||
""" Mixin class to support extra route configuration on router"""
|
||||
def update_router(self, context, id, router):
|
||||
r = router['router']
|
||||
with context.session.begin(subtransactions=True):
|
||||
#check if route exists and have permission to access
|
||||
router_db = self._get_router(context, id)
|
||||
if 'routes' in r:
|
||||
self._update_extra_routes(context,
|
||||
router_db,
|
||||
r['routes'])
|
||||
router_updated = super(ExtraRoute_db_mixin, self).update_router(
|
||||
context, id, router)
|
||||
router_updated['routes'] = self._get_extra_routes_by_router_id(
|
||||
context, id)
|
||||
|
||||
return router_updated
|
||||
|
||||
def _get_subnets_by_cidr(self, context, cidr):
|
||||
query_subnets = context.session.query(models_v2.Subnet)
|
||||
return query_subnets.filter_by(cidr=cidr).all()
|
||||
|
||||
def _validate_routes_nexthop(self, context, ports, routes, nexthop):
|
||||
#Note(nati): Nexthop should be connected,
|
||||
# so we need to check
|
||||
# nexthop belongs to one of cidrs of the router ports
|
||||
cidrs = []
|
||||
for port in ports:
|
||||
cidrs += [self._get_subnet(context,
|
||||
ip['subnet_id'])['cidr']
|
||||
for ip in port['fixed_ips']]
|
||||
if not netaddr.all_matching_cidrs(nexthop, cidrs):
|
||||
raise extraroute.InvalidRoutes(
|
||||
routes=routes,
|
||||
reason=_('the nexthop is not connected with router'))
|
||||
#Note(nati) nexthop should not be same as fixed_ips
|
||||
for port in ports:
|
||||
for ip in port['fixed_ips']:
|
||||
if nexthop == ip['ip_address']:
|
||||
raise extraroute.InvalidRoutes(
|
||||
routes=routes,
|
||||
reason=_('the nexthop is used by router'))
|
||||
|
||||
def _validate_routes(self, context,
|
||||
router_id, routes):
|
||||
if len(routes) > cfg.CONF.max_routes:
|
||||
raise extraroute.RoutesExhausted(
|
||||
router_id=router_id,
|
||||
quota=cfg.CONF.max_routes)
|
||||
|
||||
filters = {'device_id': [router_id]}
|
||||
ports = self.get_ports(context, filters)
|
||||
for route in routes:
|
||||
self._validate_routes_nexthop(
|
||||
context, ports, routes, route['nexthop'])
|
||||
|
||||
def _update_extra_routes(self, context, router, routes):
|
||||
self._validate_routes(context, router['id'],
|
||||
routes)
|
||||
old_routes = self._get_extra_routes_by_router_id(
|
||||
context, router['id'])
|
||||
added, removed = utils.diff_list_of_dict(old_routes,
|
||||
routes)
|
||||
LOG.debug('Added routes are %s' % added)
|
||||
for route in added:
|
||||
router_routes = RouterRoute(
|
||||
router_id=router['id'],
|
||||
destination=route['destination'],
|
||||
nexthop=route['nexthop'])
|
||||
context.session.add(router_routes)
|
||||
|
||||
LOG.debug('Removed routes are %s' % removed)
|
||||
for route in removed:
|
||||
del_context = context.session.query(RouterRoute)
|
||||
del_context.filter_by(router_id=router['id'],
|
||||
destination=route['destination'],
|
||||
nexthop=route['nexthop']).delete()
|
||||
|
||||
def _make_extra_route_list(self, extra_routes):
|
||||
return [{'destination': route['destination'],
|
||||
'nexthop': route['nexthop']}
|
||||
for route in extra_routes]
|
||||
|
||||
def _get_extra_routes_by_router_id(self, context, id):
|
||||
query = context.session.query(RouterRoute)
|
||||
query.filter(RouterRoute.router_id == id)
|
||||
extra_routes = query.all()
|
||||
return self._make_extra_route_list(extra_routes)
|
||||
|
||||
def get_router(self, context, id, fields=None):
|
||||
with context.session.begin(subtransactions=True):
|
||||
router = super(ExtraRoute_db_mixin, self).get_router(
|
||||
context, id, fields)
|
||||
router['routes'] = self._get_extra_routes_by_router_id(
|
||||
context, id)
|
||||
return router
|
||||
|
||||
def get_routers(self, context, filters=None, fields=None):
|
||||
with context.session.begin(subtransactions=True):
|
||||
routers = super(ExtraRoute_db_mixin, self).get_routers(
|
||||
context, filters, fields)
|
||||
for router in routers:
|
||||
router['routes'] = self._get_extra_routes_by_router_id(
|
||||
context, router['id'])
|
||||
return routers
|
||||
|
||||
def get_sync_data(self, context, router_ids=None):
|
||||
"""Query routers and their related floating_ips, interfaces."""
|
||||
with context.session.begin(subtransactions=True):
|
||||
routers = super(ExtraRoute_db_mixin,
|
||||
self).get_sync_data(context, router_ids)
|
||||
for router in routers:
|
||||
router['routes'] = self._get_extra_routes_by_router_id(
|
||||
context, router['id'])
|
||||
return routers
|
||||
|
||||
def _confirm_router_interface_not_in_use(self, context, router_id,
|
||||
subnet_id):
|
||||
super(ExtraRoute_db_mixin, self)._confirm_router_interface_not_in_use(
|
||||
context, router_id, subnet_id)
|
||||
subnet_db = self._get_subnet(context, subnet_id)
|
||||
subnet_cidr = netaddr.IPNetwork(subnet_db['cidr'])
|
||||
extra_routes = self._get_extra_routes_by_router_id(context, router_id)
|
||||
for route in extra_routes:
|
||||
if netaddr.all_matching_cidrs(route['nexthop'], [subnet_cidr]):
|
||||
raise extraroute.RouterInterfaceInUseByRoute(
|
||||
router_id=router_id, subnet_id=subnet_id)
|
@ -0,0 +1,74 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 OpenStack LLC
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Support routring table configration on Router
|
||||
|
||||
Revision ID: 1c33fa3cd1a1
|
||||
Revises: 1d76643bcec4
|
||||
Create Date: 2013-01-17 14:35:09.386975
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1c33fa3cd1a1'
|
||||
down_revision = '1d76643bcec4'
|
||||
|
||||
# Change to ['*'] if this migration applies to all plugins
|
||||
|
||||
migration_for_plugins = [
|
||||
'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2',
|
||||
'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2',
|
||||
'quantum.plugins.nec.nec_plugin.NECPluginV2',
|
||||
'quantum.plugins.ryu.ryu_quantum_plugin.RyuQuantumPluginV2',
|
||||
'quantum.plugins.metaplugin.meta_quantum_plugin.MetaPluginV2'
|
||||
]
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from quantum.db import migration
|
||||
|
||||
|
||||
def upgrade(active_plugin=None, options=None):
|
||||
if not migration.should_run(active_plugin, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.rename_table(
|
||||
'routes',
|
||||
'subnetroutes',
|
||||
)
|
||||
op.create_table(
|
||||
'routerroutes',
|
||||
sa.Column('destination', sa.String(length=64), nullable=False),
|
||||
sa.Column(
|
||||
'nexthop', sa.String(length=64), nullable=False),
|
||||
sa.Column('router_id', sa.String(length=36), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
['router_id'], ['routers.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('destination', 'nexthop', 'router_id')
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugin=None, options=None):
|
||||
if not migration.should_run(active_plugin, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.rename_table(
|
||||
'subnetroutes',
|
||||
'routes',
|
||||
)
|
||||
op.drop_table('routerroutes')
|
@ -92,6 +92,19 @@ class IPAllocation(model_base.BASEV2):
|
||||
expiration = sa.Column(sa.DateTime, nullable=True)
|
||||
|
||||
|
||||
class Route(object):
|
||||
"""mixin of a route."""
|
||||
destination = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||
nexthop = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||
|
||||
|
||||
class SubnetRoute(model_base.BASEV2, Route):
|
||||
subnet_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('subnets.id',
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class Port(model_base.BASEV2, HasId, HasTenant):
|
||||
"""Represents a port on a quantum v2 network."""
|
||||
name = sa.Column(sa.String(255))
|
||||
@ -114,16 +127,6 @@ class DNSNameServer(model_base.BASEV2):
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class Route(model_base.BASEV2):
|
||||
"""Represents a route for a subnet or port."""
|
||||
destination = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||
nexthop = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||
subnet_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('subnets.id',
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class Subnet(model_base.BASEV2, HasId, HasTenant):
|
||||
"""Represents a quantum subnet.
|
||||
|
||||
@ -143,7 +146,7 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
|
||||
dns_nameservers = orm.relationship(DNSNameServer,
|
||||
backref='subnet',
|
||||
cascade='delete')
|
||||
routes = orm.relationship(Route,
|
||||
routes = orm.relationship(SubnetRoute,
|
||||
backref='subnet',
|
||||
cascade='delete')
|
||||
shared = sa.Column(sa.Boolean)
|
||||
|
74
quantum/extensions/extraroute.py
Normal file
74
quantum/extensions/extraroute.py
Normal file
@ -0,0 +1,74 @@
|
||||
# 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.
|
||||
|
||||
|
||||
from quantum.api.v2 import attributes as attr
|
||||
from quantum.common import exceptions as qexception
|
||||
|
||||
|
||||
# Extra Routes Exceptions
|
||||
class InvalidRoutes(qexception.InvalidInput):
|
||||
message = _("Invalid format for routes: %(routes)s, %(reason)s")
|
||||
|
||||
|
||||
class RouterInterfaceInUseByRoute(qexception.InUse):
|
||||
message = _("Router interface for subnet %(subnet_id)s on router "
|
||||
"%(router_id)s cannot be deleted, as it is required "
|
||||
"by one or more routes.")
|
||||
|
||||
|
||||
class RoutesExhausted(qexception.BadRequest):
|
||||
message = _("Unable to complete operation for %(router_id)s. "
|
||||
"The number of routes exceeds the maximum %(quota)s.")
|
||||
|
||||
# Attribute Map
|
||||
EXTENDED_ATTRIBUTES_2_0 = {
|
||||
'routers': {
|
||||
'routes': {'allow_post': False, 'allow_put': True,
|
||||
'validate': {'type:hostroutes': None},
|
||||
'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Extraroute():
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "Quantum Extra Route"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return "extraroute"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Extra routes configuration for L3 router"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
return "http://docs.openstack.org/ext/quantum/extraroutes/api/v1.0"
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2013-02-01T10:00:00-00:00"
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return EXTENDED_ATTRIBUTES_2_0
|
||||
else:
|
||||
return {}
|
@ -28,7 +28,7 @@ from quantum.db import agents_db
|
||||
from quantum.db import api as db_api
|
||||
from quantum.db import db_base_plugin_v2
|
||||
from quantum.db import dhcp_rpc_base
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import extraroute_db
|
||||
from quantum.db import l3_rpc_base
|
||||
# NOTE: quota_db cannot be removed, it is for db model
|
||||
from quantum.db import quota_db
|
||||
@ -172,7 +172,7 @@ class AgentNotifierApi(proxy.RpcProxy,
|
||||
|
||||
|
||||
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
l3_db.L3_NAT_db_mixin,
|
||||
extraroute_db.ExtraRoute_db_mixin,
|
||||
sg_db_rpc.SecurityGroupServerRpcMixin,
|
||||
agents_db.AgentDbMixin):
|
||||
"""Implement the Quantum abstractions using Linux bridging.
|
||||
@ -197,7 +197,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
__native_bulk_support = True
|
||||
|
||||
supported_extension_aliases = ["provider", "router", "binding", "quotas",
|
||||
"security-group", "agent"]
|
||||
"security-group", "agent", "extraroute"]
|
||||
|
||||
network_view = "extension:provider_network:view"
|
||||
network_set = "extension:provider_network:set"
|
||||
|
@ -20,6 +20,7 @@ from oslo.config import cfg
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.db import api as db
|
||||
from quantum.db import db_base_plugin_v2
|
||||
from quantum.db import extraroute_db
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import models_v2
|
||||
from quantum.extensions.flavor import (FLAVOR_NETWORK, FLAVOR_ROUTER)
|
||||
@ -45,13 +46,13 @@ class FaildToAddFlavorBinding(exc.QuantumException):
|
||||
|
||||
|
||||
class MetaPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
l3_db.L3_NAT_db_mixin):
|
||||
extraroute_db.ExtraRoute_db_mixin):
|
||||
|
||||
def __init__(self, configfile=None):
|
||||
LOG.debug(_("Start initializing metaplugin"))
|
||||
self.supported_extension_aliases = \
|
||||
cfg.CONF.META.supported_extension_aliases.split(',')
|
||||
self.supported_extension_aliases += ['flavor', 'router']
|
||||
self.supported_extension_aliases += ['flavor', 'router', 'extraroute']
|
||||
|
||||
# Ignore config option overapping
|
||||
def _is_opt_registered(opts, opt):
|
||||
|
@ -22,7 +22,7 @@ from quantum.common import rpc as q_rpc
|
||||
from quantum.common import topics
|
||||
from quantum import context
|
||||
from quantum.db import dhcp_rpc_base
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import extraroute_db
|
||||
from quantum.db import l3_rpc_base
|
||||
#NOTE(amotoki): quota_db cannot be removed, it is for db model
|
||||
from quantum.db import quota_db
|
||||
@ -58,7 +58,7 @@ class OperationalStatus:
|
||||
|
||||
|
||||
class NECPluginV2(nec_plugin_base.NECPluginV2Base,
|
||||
l3_db.L3_NAT_db_mixin,
|
||||
extraroute_db.ExtraRoute_db_mixin,
|
||||
sg_db_rpc.SecurityGroupServerRpcMixin):
|
||||
"""NECPluginV2 controls an OpenFlow Controller.
|
||||
|
||||
@ -74,7 +74,7 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base,
|
||||
"""
|
||||
|
||||
supported_extension_aliases = ["router", "quotas", "binding",
|
||||
"security-group"]
|
||||
"security-group", "extraroute"]
|
||||
|
||||
binding_view = "extension:port_binding:view"
|
||||
binding_set = "extension:port_binding:set"
|
||||
|
@ -33,7 +33,7 @@ from quantum.common import topics
|
||||
from quantum.db import agents_db
|
||||
from quantum.db import db_base_plugin_v2
|
||||
from quantum.db import dhcp_rpc_base
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import extraroute_db
|
||||
from quantum.db import l3_rpc_base
|
||||
# NOTE: quota_db cannot be removed, it is for db model
|
||||
from quantum.db import quota_db
|
||||
@ -209,10 +209,9 @@ class AgentNotifierApi(proxy.RpcProxy,
|
||||
|
||||
|
||||
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
l3_db.L3_NAT_db_mixin,
|
||||
extraroute_db.ExtraRoute_db_mixin,
|
||||
sg_db_rpc.SecurityGroupServerRpcMixin,
|
||||
agents_db.AgentDbMixin):
|
||||
|
||||
"""Implement the Quantum abstractions using Open vSwitch.
|
||||
|
||||
Depending on whether tunneling is enabled, either a GRE tunnel or
|
||||
@ -236,7 +235,8 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
__native_bulk_support = True
|
||||
supported_extension_aliases = ["provider", "router",
|
||||
"binding", "quotas", "security-group",
|
||||
"agent"]
|
||||
"agent",
|
||||
"extraroute"]
|
||||
|
||||
network_view = "extension:provider_network:view"
|
||||
network_set = "extension:provider_network:set"
|
||||
|
@ -27,7 +27,7 @@ from quantum.common import topics
|
||||
from quantum.db import api as db
|
||||
from quantum.db import db_base_plugin_v2
|
||||
from quantum.db import dhcp_rpc_base
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import extraroute_db
|
||||
from quantum.db import l3_rpc_base
|
||||
from quantum.db import models_v2
|
||||
from quantum.openstack.common import log as logging
|
||||
@ -56,9 +56,9 @@ class RyuRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
|
||||
|
||||
|
||||
class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
l3_db.L3_NAT_db_mixin):
|
||||
extraroute_db.ExtraRoute_db_mixin):
|
||||
|
||||
supported_extension_aliases = ["router"]
|
||||
supported_extension_aliases = ["router", "extraroute"]
|
||||
|
||||
def __init__(self, configfile=None):
|
||||
db.configure_db()
|
||||
|
449
quantum/tests/unit/test_extension_extraroute.py
Normal file
449
quantum/tests/unit/test_extension_extraroute.py
Normal file
@ -0,0 +1,449 @@
|
||||
# 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.
|
||||
|
||||
from oslo.config import cfg
|
||||
from webob import exc
|
||||
|
||||
from quantum.common.test_lib import test_config
|
||||
from quantum.db import extraroute_db
|
||||
from quantum.extensions import extraroute
|
||||
from quantum.extensions import l3
|
||||
from quantum.openstack.common import log as logging
|
||||
from quantum.openstack.common.notifier import api as notifier_api
|
||||
from quantum.openstack.common.notifier import test_notifier
|
||||
from quantum.openstack.common import uuidutils
|
||||
from quantum.tests.unit import test_api_v2
|
||||
from quantum.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 just for testing
|
||||
class TestExtraRoutePlugin(test_l3.TestL3NatPlugin,
|
||||
extraroute_db.ExtraRoute_db_mixin):
|
||||
supported_extension_aliases = ["router", "extraroute"]
|
||||
|
||||
|
||||
class ExtraRouteDBTestCase(test_l3.L3NatDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
test_config['plugin_name_v2'] = (
|
||||
'quantum.tests.unit.'
|
||||
'test_extension_extraroute.TestExtraRoutePlugin')
|
||||
# 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
|
||||
#L3NatDBTestCase will overwrite plugin_name_v2,
|
||||
#so we don't need to setUp on the class here
|
||||
super(test_l3.L3NatTestCaseBase, self).setUp()
|
||||
|
||||
# Set to None to reload the drivers
|
||||
notifier_api._drivers = None
|
||||
cfg.CONF.set_override("notification_driver", [test_notifier.__name__])
|
||||
|
||||
def test_route_update_with_one_route(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:
|
||||
body = self._show('routers', r['router']['id'])
|
||||
body = self._router_interface_action('add',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'])
|
||||
|
||||
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.assertEquals(body['router']['routes'],
|
||||
routes)
|
||||
self._update('routers', r['router']['id'],
|
||||
{'router': {'routes': []}})
|
||||
# clean-up
|
||||
self._router_interface_action('remove',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'])
|
||||
|
||||
def test_router_interface_in_use_by_route(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:
|
||||
body = self._router_interface_action('add',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'])
|
||||
|
||||
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.assertEquals(body['router']['routes'],
|
||||
routes)
|
||||
|
||||
self._router_interface_action(
|
||||
'remove',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'],
|
||||
expected_code=exc.HTTPConflict.code)
|
||||
|
||||
self._update('routers', r['router']['id'],
|
||||
{'router': {'routes': []}})
|
||||
# clean-up
|
||||
self._router_interface_action('remove',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'])
|
||||
|
||||
def test_route_update_with_multi_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:
|
||||
body = 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'}]
|
||||
|
||||
body = self._update('routers', r['router']['id'],
|
||||
{'router': {'routes': routes}})
|
||||
|
||||
body = self._show('routers', r['router']['id'])
|
||||
self.assertItemsEqual(body['router']['routes'],
|
||||
routes)
|
||||
|
||||
# clean-up
|
||||
self._update('routers', r['router']['id'],
|
||||
{'router': {'routes': []}})
|
||||
self._router_interface_action('remove',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'])
|
||||
|
||||
def test_router_update_delete_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:
|
||||
body = self._router_interface_action('add',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['id'])
|
||||
|
||||
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'}]
|
||||
|
||||
body = self._update('routers', r['router']['id'],
|
||||
{'router': {'routes':
|
||||
routes_orig}})
|
||||
|
||||
body = self._show('routers', r['router']['id'])
|
||||
self.assertItemsEqual(body['router']['routes'],
|
||||
routes_orig)
|
||||
|
||||
routes_left = [{'destination': '135.207.0.0/16',
|
||||
'nexthop': '10.0.1.3'},
|
||||
{'destination': '141.212.0.0/16',
|
||||
'nexthop': '10.0.1.5'}]
|
||||
|
||||
body = self._update('routers', r['router']['id'],
|
||||
{'router': {'routes':
|
||||
routes_left}})
|
||||
|
||||
body = self._show('routers', r['router']['id'])
|
||||
self.assertItemsEqual(body['router']['routes'],
|
||||
routes_left)
|
||||
|
||||
body = self._update('routers', r['router']['id'],
|
||||
{'router': {'routes': []}})
|
||||
|
||||
body = self._show('routers', r['router']['id'])
|
||||
self.assertEqual(body['router']['routes'], [])
|
||||
|
||||
# clean-up
|
||||
self._update('routers', r['router']['id'],
|
||||
{'router': {'routes': []}})
|
||||
self._router_interface_action('remove',
|
||||
r['router']['id'],
|
||||
None,
|
||||
p['port']['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.assertEquals(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.assertEquals(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.assertEquals(gw_info, None)
|
@ -91,7 +91,7 @@ class TestBasicRouterOperations(unittest2.TestCase):
|
||||
self.assertTrue(ri.ns_name().endswith(id))
|
||||
|
||||
def testAgentCreate(self):
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
|
||||
def _test_internal_network_action(self, action):
|
||||
port_id = _uuid()
|
||||
@ -100,7 +100,6 @@ class TestBasicRouterOperations(unittest2.TestCase):
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces)
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
interface_name = agent.get_internal_device_name(port_id)
|
||||
cidr = '99.0.1.9/24'
|
||||
mac = 'ca:fe:de:ad:be:ef'
|
||||
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30'}]}
|
||||
@ -209,6 +208,105 @@ class TestBasicRouterOperations(unittest2.TestCase):
|
||||
def testAgentRemoveFloatingIP(self):
|
||||
self._test_floating_ip_action('remove')
|
||||
|
||||
def _check_agent_method_called(self, agent, calls, namespace):
|
||||
if namespace:
|
||||
self.mock_ip.netns.execute.assert_has_calls(
|
||||
[mock.call(call, check_exit_code=False) for call in calls],
|
||||
any_order=True)
|
||||
else:
|
||||
self.utils_exec.assert_has_calls([
|
||||
mock.call(call, root_helper='sudo',
|
||||
check_exit_code=False) for call in calls],
|
||||
any_order=True)
|
||||
|
||||
def _test_routing_table_update(self, namespace):
|
||||
if not namespace:
|
||||
self.conf.set_override('use_namespaces', False)
|
||||
|
||||
router_id = _uuid()
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces)
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
|
||||
fake_route1 = {'destination': '135.207.0.0/16',
|
||||
'nexthop': '1.2.3.4'}
|
||||
fake_route2 = {'destination': '135.207.111.111/32',
|
||||
'nexthop': '1.2.3.4'}
|
||||
|
||||
agent._update_routing_table(ri, 'replace', fake_route1)
|
||||
expected = [['ip', 'route', 'replace', 'to', '135.207.0.0/16',
|
||||
'via', '1.2.3.4']]
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
|
||||
agent._update_routing_table(ri, 'delete', fake_route1)
|
||||
expected = [['ip', 'route', 'delete', 'to', '135.207.0.0/16',
|
||||
'via', '1.2.3.4']]
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
|
||||
agent._update_routing_table(ri, 'replace', fake_route2)
|
||||
expected = [['ip', 'route', 'replace', 'to', '135.207.111.111/32',
|
||||
'via', '1.2.3.4']]
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
|
||||
agent._update_routing_table(ri, 'delete', fake_route2)
|
||||
expected = [['ip', 'route', 'delete', 'to', '135.207.111.111/32',
|
||||
'via', '1.2.3.4']]
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
|
||||
def testAgentRoutingTableUpdated(self):
|
||||
self._test_routing_table_update(namespace=True)
|
||||
|
||||
def testAgentRoutingTableUpdatedNoNameSpace(self):
|
||||
self._test_routing_table_update(namespace=False)
|
||||
|
||||
def testRoutesUpdated(self):
|
||||
self._test_routes_updated(namespace=True)
|
||||
|
||||
def testRoutesUpdatedNoNamespace(self):
|
||||
self._test_routes_updated(namespace=False)
|
||||
|
||||
def _test_routes_updated(self, namespace=True):
|
||||
if not namespace:
|
||||
self.conf.set_override('use_namespaces', False)
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
router_id = _uuid()
|
||||
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces)
|
||||
ri.router = {}
|
||||
|
||||
fake_old_routes = []
|
||||
fake_new_routes = [{'destination': "110.100.31.0/24",
|
||||
'nexthop': "10.100.10.30"},
|
||||
{'destination': "110.100.30.0/24",
|
||||
'nexthop': "10.100.10.30"}]
|
||||
ri.routes = fake_old_routes
|
||||
ri.router['routes'] = fake_new_routes
|
||||
agent.routes_updated(ri)
|
||||
|
||||
expected = [['ip', 'route', 'replace', 'to', '110.100.30.0/24',
|
||||
'via', '10.100.10.30'],
|
||||
['ip', 'route', 'replace', 'to', '110.100.31.0/24',
|
||||
'via', '10.100.10.30']]
|
||||
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
|
||||
fake_new_routes = [{'destination': "110.100.30.0/24",
|
||||
'nexthop': "10.100.10.30"}]
|
||||
ri.router['routes'] = fake_new_routes
|
||||
agent.routes_updated(ri)
|
||||
expected = [['ip', 'route', 'delete', 'to', '110.100.31.0/24',
|
||||
'via', '10.100.10.30']]
|
||||
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
fake_new_routes = []
|
||||
ri.router['routes'] = fake_new_routes
|
||||
agent.routes_updated(ri)
|
||||
|
||||
expected = [['ip', 'route', 'delete', 'to', '110.100.30.0/24',
|
||||
'via', '10.100.10.30']]
|
||||
self._check_agent_method_called(agent, expected, namespace)
|
||||
|
||||
def testProcessRouter(self):
|
||||
|
||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||
@ -233,10 +331,12 @@ class TestBasicRouterOperations(unittest2.TestCase):
|
||||
'floating_ip_address': '8.8.8.8',
|
||||
'fixed_ip_address': '7.7.7.7',
|
||||
'port_id': _uuid()}]}
|
||||
|
||||
router = {
|
||||
'id': router_id,
|
||||
l3_constants.FLOATINGIP_KEY: fake_floatingips1['floatingips'],
|
||||
l3_constants.INTERFACE_KEY: [internal_port],
|
||||
'routes': [],
|
||||
'gw_port': ex_gw_port}
|
||||
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
||||
self.conf.use_namespaces, router=router)
|
||||
@ -245,6 +345,7 @@ class TestBasicRouterOperations(unittest2.TestCase):
|
||||
# remap floating IP to a new fixed ip
|
||||
fake_floatingips2 = copy.deepcopy(fake_floatingips1)
|
||||
fake_floatingips2['floatingips'][0]['fixed_ip_address'] = '7.7.7.8'
|
||||
|
||||
router[l3_constants.FLOATINGIP_KEY] = fake_floatingips2['floatingips']
|
||||
agent.process_router(ri)
|
||||
|
||||
@ -274,6 +375,7 @@ class TestBasicRouterOperations(unittest2.TestCase):
|
||||
routers = [
|
||||
{'id': _uuid(),
|
||||
'admin_state_up': True,
|
||||
'routes': [],
|
||||
'external_gateway_info': {}}]
|
||||
agent._process_routers(routers)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user