--- /dev/null
+# Copyright 2014 OpenStack Foundation
+#
+# 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.
+#
+
+"""nuage_extraroute
+
+Revision ID: 10cd28e692e9
+Revises: 1b837a7125a9
+Create Date: 2014-05-14 14:47:53.148132
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '10cd28e692e9'
+down_revision = '1b837a7125a9'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'neutron.plugins.nuage.plugin.NuagePlugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+
+
+def upgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.create_table(
+ 'routerroutes_mapping',
+ sa.Column('router_id', sa.String(length=36), nullable=False),
+ sa.Column('nuage_route_id', sa.String(length=36), nullable=True),
+ sa.ForeignKeyConstraint(['router_id'], ['routers.id'],
+ ondelete='CASCADE'),
+ )
+ 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_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.drop_table('routerroutes')
+ op.drop_table('routerroutes_mapping')
-1b837a7125a9
+10cd28e692e9
\ No newline at end of file
nuage_vport_id = Column(String(36))
nuage_vif_id = Column(String(36))
static_ip = Column(Boolean())
+
+
+class RouterRoutesMapping(model_base.BASEV2, models_v2.Route):
+ __tablename__ = 'routerroutes_mapping'
+ router_id = Column(String(36),
+ ForeignKey('routers.id',
+ ondelete="CASCADE"),
+ primary_key=True,
+ nullable=False)
+ nuage_route_id = Column(String(36))
nuage_models.NetPartition,
filters)
return query
+
+
+def delete_static_route(session, static_route):
+ session.delete(static_route)
+
+
+def get_router_route_mapping(session, id, route):
+ qry = session.query(nuage_models.RouterRoutesMapping)
+ return qry.filter_by(router_id=id,
+ destination=route['destination'],
+ nexthop=route['nexthop']).one()
+
+
+def add_static_route(session, router_id, nuage_rtr_id,
+ destination, nexthop):
+ staticrt = nuage_models.RouterRoutesMapping(router_id=router_id,
+ nuage_route_id=nuage_rtr_id,
+ destination=destination,
+ nexthop=nexthop)
+ session.add(staticrt)
+ return staticrt
from neutron.api.v2 import attributes
from neutron.common import constants as os_constants
from neutron.common import exceptions as n_exc
+from neutron.common import utils
from neutron.db import api as db
from neutron.db import db_base_plugin_v2
from neutron.db import external_net_db
+from neutron.db import extraroute_db
from neutron.db import l3_db
from neutron.db import models_v2
from neutron.db import quota_db # noqa
class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2,
external_net_db.External_net_db_mixin,
+ extraroute_db.ExtraRoute_db_mixin,
l3_db.L3_NAT_db_mixin,
netpartition.NetPartitionPluginBase):
"""Class that implements Nuage Networks' plugin functionality."""
supported_extension_aliases = ["router", "binding", "external-net",
"net-partition", "nuage-router",
- "nuage-subnet", "quotas"]
+ "nuage-subnet", "quotas", "extraroute"]
binding_view = "extension:port_binding:view"
nuage_group_id=group_id)
return neutron_router
+ def _validate_nuage_staticroutes(self, old_routes, added, removed):
+ cidrs = []
+ for old in old_routes:
+ if old not in removed:
+ ip = netaddr.IPNetwork(old['destination'])
+ cidrs.append(ip)
+ for route in added:
+ ip = netaddr.IPNetwork(route['destination'])
+ matching = netaddr.all_matching_cidrs(ip.ip, cidrs)
+ if matching:
+ msg = _('for same subnet, multiple static routes not allowed')
+ raise n_exc.BadRequest(resource='router', msg=msg)
+ cidrs.append(ip)
+
+ def update_router(self, context, id, router):
+ r = router['router']
+ with context.session.begin(subtransactions=True):
+ if 'routes' in r:
+ old_routes = self._get_extra_routes_by_router_id(context,
+ id)
+ added, removed = utils.diff_list_of_dict(old_routes,
+ r['routes'])
+ self._validate_nuage_staticroutes(old_routes, added, removed)
+ ent_rtr_mapping = nuagedb.get_ent_rtr_mapping_by_rtrid(
+ context.session, id)
+ if not ent_rtr_mapping:
+ msg = (_("Router %s does not hold net-partition "
+ "assoc on VSD. extra-route failed") % id)
+ raise n_exc.BadRequest(resource='router', msg=msg)
+ # Let it do internal checks first and verify it.
+ router_updated = super(NuagePlugin,
+ self).update_router(context,
+ id,
+ router)
+ for route in removed:
+ rtr_rt_mapping = nuagedb.get_router_route_mapping(
+ context.session, id, route)
+ if rtr_rt_mapping:
+ self.nuageclient.delete_nuage_staticroute(
+ rtr_rt_mapping['nuage_route_id'])
+ nuagedb.delete_static_route(context.session,
+ rtr_rt_mapping)
+ for route in added:
+ params = {
+ 'parent_id': ent_rtr_mapping['nuage_router_id'],
+ 'net': netaddr.IPNetwork(route['destination']),
+ 'nexthop': route['nexthop']
+ }
+ nuage_rt_id = self.nuageclient.create_nuage_staticroute(
+ params)
+ nuagedb.add_static_route(context.session,
+ id, nuage_rt_id,
+ route['destination'],
+ route['nexthop'])
+ else:
+ router_updated = super(NuagePlugin, self).update_router(
+ context, id, router)
+ return router_updated
+
def delete_router(self, context, id):
session = context.session
ent_rtr_mapping = nuagedb.get_ent_rtr_mapping_by_rtrid(session,
def delete_vms(self, params):
pass
+
+ def create_nuage_staticroute(self, params):
+ return str(uuid.uuid4())
+
+ def delete_nuage_staticroute(self, id):
+ pass
import mock
from oslo.config import cfg
+from webob import exc
from neutron.extensions import portbindings
from neutron.plugins.nuage import extensions
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit.nuage import fake_nuageclient
from neutron.tests.unit import test_db_plugin
+from neutron.tests.unit import test_extension_extraroute as extraroute_test
from neutron.tests.unit import test_l3_plugin
API_EXT_PATH = os.path.dirname(extensions.__file__)
class TestNuageL3NatTestCase(NuagePluginV2TestCase,
test_l3_plugin.L3NatDBIntTestCase):
pass
+
+
+class TestNuageExtrarouteTestCase(NuagePluginV2TestCase,
+ extraroute_test.ExtraRouteDBIntTestCase):
+
+ def test_router_update_with_dup_destination_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.5'}]
+
+ 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'])