From 22cce1255e1d9bb7ea22ea4282aa11b3f1ca4b4e Mon Sep 17 00:00:00 2001 From: Divya ChanneGowda Date: Thu, 14 Aug 2014 12:03:13 -0700 Subject: [PATCH] Add support for provider-network extension in nuage Plugin This implements support for creating provider networks with Nuage plugin. Implements: blueprint providernet-ext-support-for-nuage-plugin Change-Id: Ibabc1561fc7b6bd5ea38617f145af1d0d4545a4f --- .../alembic_migrations/versions/HEAD | 2 +- .../aae5706a396_nuage_provider_networks.py | 45 ++++++++ neutron/plugins/nuage/common/exceptions.py | 4 + neutron/plugins/nuage/nuage_models.py | 14 ++- neutron/plugins/nuage/nuagedb.py | 18 ++- neutron/plugins/nuage/plugin.py | 107 ++++++++++++++++-- neutron/tests/unit/nuage/fake_nuageclient.py | 6 + neutron/tests/unit/nuage/test_nuage_plugin.py | 31 +++++ 8 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/aae5706a396_nuage_provider_networks.py diff --git a/neutron/db/migration/alembic_migrations/versions/HEAD b/neutron/db/migration/alembic_migrations/versions/HEAD index 7c2f9210a..b340f2714 100644 --- a/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -3b85b693a95f +aae5706a396 diff --git a/neutron/db/migration/alembic_migrations/versions/aae5706a396_nuage_provider_networks.py b/neutron/db/migration/alembic_migrations/versions/aae5706a396_nuage_provider_networks.py new file mode 100644 index 000000000..56d5d88b1 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/aae5706a396_nuage_provider_networks.py @@ -0,0 +1,45 @@ +# 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_provider_networks + +Revision ID: aae5706a396 +Revises: 3b85b693a95f +Create Date: 2014-08-18 16:00:21.898795 + +""" + +revision = 'aae5706a396' +down_revision = '3b85b693a95f' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(active_plugins=None, options=None): + op.create_table( + 'nuage_provider_net_bindings', + sa.Column('network_id', sa.String(length=36), nullable=False), + sa.Column('network_type', sa.String(length=32), nullable=False), + sa.Column('physical_network', sa.String(length=64), nullable=False), + sa.Column('vlan_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ['network_id'], ['networks.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('network_id') + ) + + +def downgrade(active_plugins=None, options=None): + op.drop_table('nuage_provider_net_bindings') diff --git a/neutron/plugins/nuage/common/exceptions.py b/neutron/plugins/nuage/common/exceptions.py index 2e1158896..0e1e5460a 100644 --- a/neutron/plugins/nuage/common/exceptions.py +++ b/neutron/plugins/nuage/common/exceptions.py @@ -22,3 +22,7 @@ from neutron.common import exceptions as n_exc class OperationNotSupported(n_exc.InvalidConfigurationOption): message = _("Nuage Plugin does not support this operation: %(msg)s") + + +class NuageBadRequest(n_exc.BadRequest): + message = _("Bad request: %(msg)s") diff --git a/neutron/plugins/nuage/nuage_models.py b/neutron/plugins/nuage/nuage_models.py index 4c901cc01..dbf7c0703 100644 --- a/neutron/plugins/nuage/nuage_models.py +++ b/neutron/plugins/nuage/nuage_models.py @@ -40,6 +40,18 @@ class NetPartitionRouter(model_base.BASEV2): nuage_router_id = sa.Column(sa.String(36)) +class ProviderNetBinding(model_base.BASEV2): + """Represents binding of virtual network to physical_network and vlan.""" + __tablename__ = 'nuage_provider_net_bindings' + + network_id = sa.Column(sa.String(36), + sa.ForeignKey('networks.id', ondelete="CASCADE"), + primary_key=True) + network_type = sa.Column(sa.String(32), nullable=False) + physical_network = sa.Column(sa.String(64), nullable=False) + vlan_id = sa.Column(sa.Integer, nullable=False) + + class SubnetL2Domain(model_base.BASEV2): __tablename__ = 'nuage_subnet_l2dom_mapping' subnet_id = sa.Column(sa.String(36), @@ -51,4 +63,4 @@ class SubnetL2Domain(model_base.BASEV2): nuage_subnet_id = sa.Column(sa.String(36)) nuage_l2dom_tmplt_id = sa.Column(sa.String(36)) nuage_user_id = sa.Column(sa.String(36)) - nuage_group_id = sa.Column(sa.String(36)) \ No newline at end of file + nuage_group_id = sa.Column(sa.String(36)) diff --git a/neutron/plugins/nuage/nuagedb.py b/neutron/plugins/nuage/nuagedb.py index 458fd4349..d7ef52f0e 100644 --- a/neutron/plugins/nuage/nuagedb.py +++ b/neutron/plugins/nuage/nuagedb.py @@ -98,4 +98,20 @@ def get_ent_rtr_mapping_by_entid(session, def get_ent_rtr_mapping_by_rtrid(session, rtrid): query = session.query(nuage_models.NetPartitionRouter) - return query.filter_by(router_id=rtrid).first() \ No newline at end of file + return query.filter_by(router_id=rtrid).first() + + +def add_network_binding(session, network_id, network_type, physical_network, + vlan_id): + binding = nuage_models.ProviderNetBinding( + network_id=network_id, + network_type=network_type, + physical_network=physical_network, + vlan_id=vlan_id) + session.add(binding) + + +def get_network_binding(session, network_id): + return (session.query(nuage_models.ProviderNetBinding). + filter_by(network_id=network_id). + first()) diff --git a/neutron/plugins/nuage/plugin.py b/neutron/plugins/nuage/plugin.py index 23119f2b0..565ec4502 100644 --- a/neutron/plugins/nuage/plugin.py +++ b/neutron/plugins/nuage/plugin.py @@ -36,6 +36,7 @@ from neutron.db import securitygroups_db as sg_db from neutron.extensions import external_net from neutron.extensions import l3 from neutron.extensions import portbindings +from neutron.extensions import providernet as pnet from neutron.extensions import securitygroup as ext_sg from neutron.openstack.common import excutils from neutron.openstack.common import importutils @@ -58,7 +59,7 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, """Class that implements Nuage Networks' plugin functionality.""" supported_extension_aliases = ["router", "binding", "external-net", "net-partition", "nuage-router", - "nuage-subnet", "quotas", + "nuage-subnet", "quotas", "provider", "extraroute", "security-group"] binding_view = "extension:port_binding:view" @@ -378,8 +379,49 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, subnets = self.get_subnets(context, filters=filters) return bool(routers or subnets) + def _extend_network_dict_provider(self, context, network): + binding = nuagedb.get_network_binding(context.session, network['id']) + if binding: + network[pnet.NETWORK_TYPE] = binding.network_type + network[pnet.PHYSICAL_NETWORK] = binding.physical_network + network[pnet.SEGMENTATION_ID] = binding.vlan_id + + def _process_provider_create(self, context, attrs): + network_type = attrs.get(pnet.NETWORK_TYPE) + physical_network = attrs.get(pnet.PHYSICAL_NETWORK) + segmentation_id = attrs.get(pnet.SEGMENTATION_ID) + + network_type_set = attributes.is_attr_set(network_type) + physical_network_set = attributes.is_attr_set(physical_network) + segmentation_id_set = attributes.is_attr_set(segmentation_id) + + if not (network_type_set or physical_network_set or + segmentation_id_set): + return None, None, None + if not network_type_set: + msg = _("provider:network_type required") + raise n_exc.InvalidInput(error_message=msg) + elif network_type != 'vlan': + msg = (_("provider:network_type %s not supported in VSP") + % network_type) + raise nuage_exc.NuageBadRequest(msg=msg) + if not physical_network_set: + msg = _("provider:physical_network required") + raise nuage_exc.NuageBadRequest(msg=msg) + if not segmentation_id_set: + msg = _("provider:segmentation_id required") + raise nuage_exc.NuageBadRequest(msg=msg) + + self.nuageclient.validate_provider_network(network_type, + physical_network, + segmentation_id) + + return network_type, physical_network, segmentation_id + def create_network(self, context, network): - net = network['network'] + (network_type, physical_network, + vlan_id) = self._process_provider_create(context, + network['network']) with context.session.begin(subtransactions=True): self._ensure_default_security_group( context, @@ -388,6 +430,11 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, net = super(NuagePlugin, self).create_network(context, network) self._process_l3_create(context, net, network['network']) + if network_type == 'vlan': + nuagedb.add_network_binding(context.session, net['id'], + network_type, + physical_network, vlan_id) + self._extend_network_dict_provider(context, net) return net def _validate_update_network(self, context, id, network): @@ -413,7 +460,25 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, raise n_exc.NetworkInUse(net_id=id) return (is_external_set, subnet) + def get_network(self, context, net_id, fields=None): + net = super(NuagePlugin, self).get_network(context, + net_id, + None) + self._extend_network_dict_provider(context, net) + return self._fields(net, fields) + + def get_networks(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, page_reverse=False): + nets = super(NuagePlugin, + self).get_networks(context, filters, None, sorts, + limit, marker, page_reverse) + for net in nets: + self._extend_network_dict_provider(context, net) + + return [self._fields(net, fields) for net in nets] + def update_network(self, context, id, network): + pnet._raise_if_updates_provider_attributes(network['network']) with context.session.begin(subtransactions=True): is_external_set, subnet = self._validate_update_network(context, id, @@ -477,6 +542,14 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, msg = "no-gateway option not supported with subnets" raise nuage_exc.OperationNotSupported(msg=msg) + def _validate_create_provider_subnet(self, context, net_id): + net_filter = {'network_id': [net_id]} + existing_subn = self.get_subnets(context, filters=net_filter) + if len(existing_subn) > 0: + msg = _('Only one subnet is allowed per ' + 'Provider network %s') % net_id + raise nuage_exc.OperationNotSupported(msg=msg) + def _delete_nuage_sharedresource(self, net_id): self.nuageclient.delete_nuage_sharedresource(net_id) @@ -508,14 +581,15 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, self._add_nuage_sharedresource(subn, net_id, type) return subn - def _create_nuage_subnet(self, context, neutron_subnet, - netpart_id, l2dom_template_id): + def _create_nuage_subnet(self, context, neutron_subnet, netpart_id, + l2dom_template_id, pnet_binding): net = netaddr.IPNetwork(neutron_subnet['cidr']) params = { 'netpart_id': netpart_id, 'tenant_id': neutron_subnet['tenant_id'], 'net': net, - 'l2dom_tmplt_id': l2dom_template_id + 'l2dom_tmplt_id': l2dom_template_id, + 'pnet_binding': pnet_binding } try: nuage_subnet = self.nuageclient.create_subnet(neutron_subnet, @@ -547,6 +621,9 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, if self._network_is_external(context, net_id): return self._create_nuage_sharedresource( context, subnet, constants.SR_TYPE_FLOATING) + pnet_binding = nuagedb.get_network_binding(context.session, net_id) + if pnet_binding: + self._validate_create_provider_subnet(context, net_id) self._validate_create_subnet(subn) @@ -554,7 +631,8 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, neutron_subnet = super(NuagePlugin, self).create_subnet(context, subnet) self._create_nuage_subnet(context, neutron_subnet, net_partition['id'], - subn['nuage_subnet_template']) + subn['nuage_subnet_template'], + pnet_binding) return neutron_subnet def delete_subnet(self, context, id): @@ -637,13 +715,17 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, self.nuageclient.delete_subnet(nuage_subnet_id, nuage_l2dom_tmplt_id) net = netaddr.IPNetwork(subn['cidr']) + pnet_binding = nuagedb.get_network_binding(context.session, + subn['network_id']) params = { 'net': net, 'zone_id': nuage_zone['nuage_zone_id'], - 'neutron_subnet_id': subnet_id + 'neutron_subnet_id': subnet_id, + 'pnet_binding': pnet_binding } if not attributes.is_attr_set(subn['gateway_ip']): subn['gateway_ip'] = str(netaddr.IPAddress(net.first + 1)) + try: nuage_subnet = self.nuageclient.create_domain_subnet(subn, params) @@ -723,14 +805,17 @@ class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2, net = netaddr.IPNetwork(neutron_subnet['cidr']) netpart_id = ent_rtr_mapping['net_partition_id'] + pnet_binding = nuagedb.get_network_binding( + context.session, neutron_subnet['network_id']) params = { 'tenant_id': neutron_subnet['tenant_id'], 'net': net, - 'netpart_id': netpart_id + 'netpart_id': netpart_id, + 'nuage_subn_id': nuage_subn_id, + 'neutron_subnet': neutron_subnet, + 'pnet_binding': pnet_binding } - nuage_subnet = self.nuageclient.create_subnet(neutron_subnet, - params) - self.nuageclient.delete_domain_subnet(nuage_subn_id) + nuage_subnet = self.nuageclient.remove_router_interface(params) info = super(NuagePlugin, self).remove_router_interface(context, router_id, interface_info) diff --git a/neutron/tests/unit/nuage/fake_nuageclient.py b/neutron/tests/unit/nuage/fake_nuageclient.py index bbfecd487..6ccb76244 100644 --- a/neutron/tests/unit/nuage/fake_nuageclient.py +++ b/neutron/tests/unit/nuage/fake_nuageclient.py @@ -185,3 +185,9 @@ class FakeNuageClient(object): def delete_port_security_group_bindings(self, params): pass + + def validate_provider_network(self, net_type, phy_net, vlan_id): + pass + + def remove_router_interface(self, params): + pass diff --git a/neutron/tests/unit/nuage/test_nuage_plugin.py b/neutron/tests/unit/nuage/test_nuage_plugin.py index d79933b13..cdb562396 100644 --- a/neutron/tests/unit/nuage/test_nuage_plugin.py +++ b/neutron/tests/unit/nuage/test_nuage_plugin.py @@ -22,9 +22,11 @@ import mock from oslo.config import cfg from webob import exc +from neutron import context from neutron.extensions import external_net from neutron.extensions import l3 from neutron.extensions import portbindings +from neutron.extensions import providernet as pnet from neutron.openstack.common import uuidutils from neutron.plugins.nuage import extensions from neutron.plugins.nuage.extensions import nuage_router @@ -348,3 +350,32 @@ class TestNuageExtrarouteTestCase(NuagePluginV2TestCase, class TestNuageSecurityGroupTestCase(NuagePluginV2TestCase, test_sg.TestSecurityGroups): pass + + +class TestNuageProviderNetTestCase(NuagePluginV2TestCase): + + def test_create_provider_network(self): + phy_net = uuidutils.generate_uuid() + data = {'network': {'name': 'pnet1', + 'tenant_id': 'admin', + pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: phy_net, + pnet.SEGMENTATION_ID: 123}} + network_req = self.new_create_request('networks', data, self.fmt) + net = self.deserialize(self.fmt, network_req.get_response(self.api)) + self.assertEqual('vlan', net['network'][pnet.NETWORK_TYPE]) + self.assertEqual(phy_net, net['network'][pnet.PHYSICAL_NETWORK]) + self.assertEqual(123, net['network'][pnet.SEGMENTATION_ID]) + + def test_create_provider_network_no_admin(self): + phy_net = uuidutils.generate_uuid() + data = {'network': {'name': 'pnet1', + 'tenant_id': 'no_admin', + pnet.NETWORK_TYPE: 'vlan', + pnet.PHYSICAL_NETWORK: phy_net, + pnet.SEGMENTATION_ID: 123}} + network_req = self.new_create_request('networks', data, self.fmt) + network_req.environ['neutron.context'] = context.Context( + '', 'no_admin', is_admin=False) + res = network_req.get_response(self.api) + self.assertEqual(exc.HTTPForbidden.code, res.status_int) -- 2.45.2