From da9ed12baea45ae2f03a0ba3898feb9becc6a5d3 Mon Sep 17 00:00:00 2001 From: ronak Date: Thu, 15 May 2014 07:38:43 -0700 Subject: [PATCH] Extraroute extension support for nuage plugin 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 --- .../versions/10cd28e692e9_nuage_extraroute.py | 68 +++++++++++++++++++ .../alembic_migrations/versions/HEAD | 2 +- neutron/plugins/nuage/nuage_models.py | 10 +++ neutron/plugins/nuage/nuagedb.py | 21 ++++++ neutron/plugins/nuage/plugin.py | 64 ++++++++++++++++- neutron/tests/unit/nuage/fake_nuageclient.py | 6 ++ neutron/tests/unit/nuage/test_nuage_plugin.py | 31 +++++++++ 7 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/10cd28e692e9_nuage_extraroute.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 index 000000000..2949813bb --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/10cd28e692e9_nuage_extraroute.py @@ -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') diff --git a/neutron/db/migration/alembic_migrations/versions/HEAD b/neutron/db/migration/alembic_migrations/versions/HEAD index 38f74bb5b..7bf9d4cdf 100644 --- a/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -1b837a7125a9 +10cd28e692e9 \ No newline at end of file diff --git a/neutron/plugins/nuage/nuage_models.py b/neutron/plugins/nuage/nuage_models.py index 53df04ac8..2ac8e25ea 100644 --- a/neutron/plugins/nuage/nuage_models.py +++ b/neutron/plugins/nuage/nuage_models.py @@ -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)) diff --git a/neutron/plugins/nuage/nuagedb.py b/neutron/plugins/nuage/nuagedb.py index fedd13d75..a52b695c0 100644 --- a/neutron/plugins/nuage/nuagedb.py +++ b/neutron/plugins/nuage/nuagedb.py @@ -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 diff --git a/neutron/plugins/nuage/plugin.py b/neutron/plugins/nuage/plugin.py index ffac7cf6e..b27821895 100644 --- a/neutron/plugins/nuage/plugin.py +++ b/neutron/plugins/nuage/plugin.py @@ -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, diff --git a/neutron/tests/unit/nuage/fake_nuageclient.py b/neutron/tests/unit/nuage/fake_nuageclient.py index 47c9dc300..a9eabdb3e 100644 --- a/neutron/tests/unit/nuage/fake_nuageclient.py +++ b/neutron/tests/unit/nuage/fake_nuageclient.py @@ -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 diff --git a/neutron/tests/unit/nuage/test_nuage_plugin.py b/neutron/tests/unit/nuage/test_nuage_plugin.py index 259a3637d..1fd40e43a 100644 --- a/neutron/tests/unit/nuage/test_nuage_plugin.py +++ b/neutron/tests/unit/nuage/test_nuage_plugin.py @@ -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']) -- 2.45.2