]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Extraroute extension support for nuage plugin
authorronak <ronak.malav.shah@gmail.com>
Thu, 15 May 2014 14:38:43 +0000 (07:38 -0700)
committerronak <ronak.malav.shah@gmail.com>
Fri, 23 May 2014 00:35:55 +0000 (17:35 -0700)
Nuage's VSP supports adding static-route to L3 Domain
which fits nicely with extraroute extension supported
by openstack's neutron. This set of change enables that.

Change-Id: Icd4d95b9077056c33c7509fe45966ff2a04cd923
Implements: blueprint extraroute-ext-support-for-nuage-plugin

neutron/db/migration/alembic_migrations/versions/10cd28e692e9_nuage_extraroute.py [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/HEAD
neutron/plugins/nuage/nuage_models.py
neutron/plugins/nuage/nuagedb.py
neutron/plugins/nuage/plugin.py
neutron/tests/unit/nuage/fake_nuageclient.py
neutron/tests/unit/nuage/test_nuage_plugin.py

diff --git a/neutron/db/migration/alembic_migrations/versions/10cd28e692e9_nuage_extraroute.py b/neutron/db/migration/alembic_migrations/versions/10cd28e692e9_nuage_extraroute.py
new file mode 100644 (file)
index 0000000..2949813
--- /dev/null
@@ -0,0 +1,68 @@
+# 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')
index 38f74bb5b42cd9ec10f92ff135d70b34ebf8879c..7bf9d4cdf0daf17bc8bb671d3420487cbc76887f 100644 (file)
@@ -1 +1 @@
-1b837a7125a9
+10cd28e692e9
\ No newline at end of file
index 53df04ac84184dab4e6cc6df11f5449900780fb2..2ac8e25eaea8b7f5fe182d6eea5835d14ab7de7a 100644 (file)
@@ -72,3 +72,13 @@ class PortVPortMapping(model_base.BASEV2):
     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))
index fedd13d75a62f7d3a8871950696844c7f11d450b..a52b695c04b72b5191e9cc14ea2cc371ee4036fc 100644 (file)
@@ -131,3 +131,24 @@ def get_net_partitions(session, filters=None, fields=None):
                                               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
index ffac7cf6e16db10976f5a4e35f55298e151a535a..b27821895d0c88453c1fa69fe2a9792292d448f8 100644 (file)
@@ -25,9 +25,11 @@ from neutron.api import extensions as neutron_extensions
 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
@@ -46,12 +48,13 @@ from neutron import policy
 
 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"
 
@@ -606,6 +609,65 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2,
                                             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,
index 47c9dc300e7f3ad8dec0b608de02d7985507739b..a9eabdb3e2f19d20889d165341ab77db832cb648 100644 (file)
@@ -83,3 +83,9 @@ class FakeNuageClient(object):
 
     def delete_vms(self, params):
         pass
+
+    def create_nuage_staticroute(self, params):
+        return str(uuid.uuid4())
+
+    def delete_nuage_staticroute(self, id):
+        pass
index 259a3637d1dd65eb756ba889846adf18d72a279c..1fd40e43ad080edad8963003080525752d016085 100644 (file)
@@ -18,6 +18,7 @@ import os
 
 import mock
 from oslo.config import cfg
+from webob import exc
 
 from neutron.extensions import portbindings
 from neutron.plugins.nuage import extensions
@@ -25,6 +26,7 @@ from neutron.plugins.nuage import plugin as nuage_plugin
 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__)
@@ -160,3 +162,32 @@ class TestNuagePortsV2(NuagePluginV2TestCase,
 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'])