]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Nuage Networks Plugin
authorronak <ronak.malav.shah@gmail.com>
Thu, 16 Jan 2014 18:26:01 +0000 (10:26 -0800)
committerThomas Goirand <thomas@goirand.fr>
Thu, 13 Mar 2014 07:20:37 +0000 (15:20 +0800)
Nuage networks’ openstack networking plugin enables integration
of openstack with Nuage Networks’ Virtual Service Platform (VSP)

Change-Id: If20b385b78a350cb9aae2c70b6a44888e74c23bc
Implements: blueprint nuage-networks-plugin

18 files changed:
etc/neutron/plugins/nuage/nuage_plugin.ini [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/e766b19a3bb_nuage_initial.py [new file with mode: 0644]
neutron/plugins/nuage/__init__.py [new file with mode: 0644]
neutron/plugins/nuage/common/__init__.py [new file with mode: 0644]
neutron/plugins/nuage/common/config.py [new file with mode: 0644]
neutron/plugins/nuage/common/constants.py [new file with mode: 0644]
neutron/plugins/nuage/common/exceptions.py [new file with mode: 0644]
neutron/plugins/nuage/extensions/__init__.py [new file with mode: 0644]
neutron/plugins/nuage/extensions/netpartition.py [new file with mode: 0644]
neutron/plugins/nuage/extensions/nuage_router.py [new file with mode: 0644]
neutron/plugins/nuage/extensions/nuage_subnet.py [new file with mode: 0644]
neutron/plugins/nuage/nuage_models.py [new file with mode: 0644]
neutron/plugins/nuage/nuagedb.py [new file with mode: 0644]
neutron/plugins/nuage/plugin.py [new file with mode: 0644]
neutron/tests/unit/nuage/__init__.py [new file with mode: 0644]
neutron/tests/unit/nuage/fake_nuageclient.py [new file with mode: 0644]
neutron/tests/unit/nuage/test_netpartition.py [new file with mode: 0644]
neutron/tests/unit/nuage/test_nuage_plugin.py [new file with mode: 0644]

diff --git a/etc/neutron/plugins/nuage/nuage_plugin.ini b/etc/neutron/plugins/nuage/nuage_plugin.ini
new file mode 100644 (file)
index 0000000..994d120
--- /dev/null
@@ -0,0 +1,10 @@
+# Please fill in the correct data for all the keys below and uncomment key-value pairs
+[restproxy]
+#default_net_partition_name = <default-net-partition-name>
+#auth_resource = /auth
+#server = ip:port
+#organization = org
+#serverauth = uname:pass
+#serverssl = True
+#base_uri = /base
+
diff --git a/neutron/db/migration/alembic_migrations/versions/e766b19a3bb_nuage_initial.py b/neutron/db/migration/alembic_migrations/versions/e766b19a3bb_nuage_initial.py
new file mode 100644 (file)
index 0000000..3562c7d
--- /dev/null
@@ -0,0 +1,120 @@
+# 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_initial
+
+Revision ID: e766b19a3bb
+Revises: 1b2580001654
+Create Date: 2014-02-14 18:03:14.841064
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'e766b19a3bb'
+down_revision = '1b2580001654'
+
+migration_for_plugins = [
+    'neutron.plugins.nuage.plugin.NuagePlugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+from neutron.db.migration.alembic_migrations import common_ext_ops
+
+
+def upgrade(active_plugins=None, options=None):
+    if not migration.should_run(active_plugins, migration_for_plugins):
+        return
+
+    common_ext_ops.upgrade_l3()
+
+    op.create_table(
+        'quotas',
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('tenant_id', sa.String(length=255), nullable=True),
+        sa.Column('resource', sa.String(length=255), nullable=True),
+        sa.Column('limit', sa.Integer(), nullable=True),
+        sa.PrimaryKeyConstraint('id'),
+    )
+    op.create_table(
+        'net_partitions',
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('name', sa.String(length=64), nullable=True),
+        sa.Column('l3dom_tmplt_id', sa.String(length=36), nullable=True),
+        sa.Column('l2dom_tmplt_id', sa.String(length=36), nullable=True),
+        sa.PrimaryKeyConstraint('id'),
+    )
+    op.create_table(
+        'port_mapping',
+        sa.Column('port_id', sa.String(length=36), nullable=False),
+        sa.Column('nuage_vport_id', sa.String(length=36), nullable=True),
+        sa.Column('nuage_vif_id', sa.String(length=36), nullable=True),
+        sa.Column('static_ip', sa.Boolean(), nullable=True),
+        sa.ForeignKeyConstraint(['port_id'], ['ports.id'],
+                                ondelete='CASCADE'),
+        sa.PrimaryKeyConstraint('port_id'),
+    )
+    op.create_table(
+        'subnet_l2dom_mapping',
+        sa.Column('subnet_id', sa.String(length=36), nullable=False),
+        sa.Column('net_partition_id', sa.String(length=36), nullable=True),
+        sa.Column('nuage_subnet_id', sa.String(length=36), nullable=True),
+        sa.Column('nuage_l2dom_tmplt_id', sa.String(length=36),
+                  nullable=True),
+        sa.Column('nuage_user_id', sa.String(length=36), nullable=True),
+        sa.Column('nuage_group_id', sa.String(length=36), nullable=True),
+        sa.ForeignKeyConstraint(['net_partition_id'], ['net_partitions.id'],
+                                ondelete='CASCADE'),
+        sa.ForeignKeyConstraint(['subnet_id'], ['subnets.id'],
+                                ondelete='CASCADE'),
+        sa.PrimaryKeyConstraint('subnet_id'),
+    )
+    op.create_table(
+        'net_partition_router_mapping',
+        sa.Column('net_partition_id', sa.String(length=36), nullable=False),
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.Column('nuage_router_id', sa.String(length=36), nullable=True),
+        sa.ForeignKeyConstraint(['net_partition_id'], ['net_partitions.id'],
+                                ondelete='CASCADE'),
+        sa.ForeignKeyConstraint(['router_id'], ['routers.id'],
+                                ondelete='CASCADE'),
+        sa.PrimaryKeyConstraint('router_id'),
+    )
+    op.create_table(
+        'router_zone_mapping',
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.Column('nuage_zone_id', sa.String(length=36), nullable=True),
+        sa.Column('nuage_user_id', sa.String(length=36), nullable=True),
+        sa.Column('nuage_group_id', sa.String(length=36), nullable=True),
+        sa.ForeignKeyConstraint(['router_id'], ['routers.id'],
+                                ondelete='CASCADE'),
+        sa.PrimaryKeyConstraint('router_id'),
+    )
+
+
+def downgrade(active_plugins=None, options=None):
+    if not migration.should_run(active_plugins, migration_for_plugins):
+        return
+
+    op.drop_table('router_zone_mapping')
+    op.drop_table('net_partition_router_mapping')
+    op.drop_table('subnet_l2dom_mapping')
+    op.drop_table('port_mapping')
+    op.drop_table('net_partitions')
+    op.drop_table('quotas')
+
+    common_ext_ops.downgrade_l3()
diff --git a/neutron/plugins/nuage/__init__.py b/neutron/plugins/nuage/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/nuage/common/__init__.py b/neutron/plugins/nuage/common/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/nuage/common/config.py b/neutron/plugins/nuage/common/config.py
new file mode 100644 (file)
index 0000000..cd5a8a8
--- /dev/null
@@ -0,0 +1,47 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+from oslo.config import cfg
+
+
+restproxy_opts = [
+    cfg.StrOpt('server', default='localhost:8800',
+               help=_("IP Address and Port of Nuage's VSD server")),
+    cfg.StrOpt('serverauth', default='username:password',
+               secret=True,
+               help=_("Username and password for authentication")),
+    cfg.BoolOpt('serverssl', default=False,
+                help=_("Boolean for SSL connection with VSD server")),
+    cfg.StrOpt('base_uri', default='/',
+               help=_("Nuage provided base uri to reach out to VSD")),
+    cfg.StrOpt('organization', default='system',
+               help=_("Organization name in which VSD will orchestrate "
+                      "network resources using openstack")),
+    cfg.StrOpt('auth_resource', default='',
+               help=_("Nuage provided uri for initial authorization to "
+                      "access VSD")),
+    cfg.StrOpt('default_net_partition_name',
+               default='OpenStackDefaultNetPartition',
+               help=_("Default Network partition in which VSD will "
+                      "orchestrate network resources using openstack")),
+    cfg.IntOpt('default_floatingip_quota',
+               default=254,
+               help=_("Per Net Partition quota of floating ips")),
+]
+
+
+def nuage_register_cfg_opts():
+    cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
diff --git a/neutron/plugins/nuage/common/constants.py b/neutron/plugins/nuage/common/constants.py
new file mode 100644 (file)
index 0000000..f354812
--- /dev/null
@@ -0,0 +1,26 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+from neutron.common import constants
+
+AUTO_CREATE_PORT_OWNERS = [
+    constants.DEVICE_OWNER_DHCP,
+    constants.DEVICE_OWNER_ROUTER_INTF,
+    constants.DEVICE_OWNER_ROUTER_GW,
+    constants.DEVICE_OWNER_FLOATINGIP
+]
+
+NOVA_PORT_OWNER_PREF = 'compute:'
diff --git a/neutron/plugins/nuage/common/exceptions.py b/neutron/plugins/nuage/common/exceptions.py
new file mode 100644 (file)
index 0000000..2e11588
--- /dev/null
@@ -0,0 +1,24 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+
+''' Nuage specific exceptions '''
+
+from neutron.common import exceptions as n_exc
+
+
+class OperationNotSupported(n_exc.InvalidConfigurationOption):
+    message = _("Nuage Plugin does not support this operation: %(msg)s")
diff --git a/neutron/plugins/nuage/extensions/__init__.py b/neutron/plugins/nuage/extensions/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/nuage/extensions/netpartition.py b/neutron/plugins/nuage/extensions/netpartition.py
new file mode 100644 (file)
index 0000000..ee228eb
--- /dev/null
@@ -0,0 +1,107 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+from abc import abstractmethod
+
+from neutron.api import extensions
+from neutron.api.v2 import base
+from neutron import manager
+from neutron import quota
+
+
+# Attribute Map
+RESOURCE_ATTRIBUTE_MAP = {
+    'net_partitions': {
+        'id': {'allow_post': False, 'allow_put': False,
+               'validate': {'type:uuid': None},
+               'is_visible': True},
+        'name': {'allow_post': True, 'allow_put': False,
+                 'is_visible': True, 'default': '',
+                 'validate': {'type:name_not_default': None}},
+        'description': {'allow_post': True, 'allow_put': False,
+                        'is_visible': True, 'default': '',
+                        'validate': {'type:string_or_none': None}},
+        'tenant_id': {'allow_post': True, 'allow_put': False,
+                      'required_by_policy': True,
+                      'is_visible': True},
+    },
+}
+
+
+class Netpartition(object):
+    """Extension class supporting net_partition.
+    """
+
+    @classmethod
+    def get_name(cls):
+        return "NetPartition"
+
+    @classmethod
+    def get_alias(cls):
+        return "net-partition"
+
+    @classmethod
+    def get_description(cls):
+        return "NetPartition"
+
+    @classmethod
+    def get_namespace(cls):
+        return "http://nuagenetworks.net/ext/net_partition/api/v1.0"
+
+    @classmethod
+    def get_updated(cls):
+        return "2014-01-01T10:00:00-00:00"
+
+    @classmethod
+    def get_resources(cls):
+        """Returns Ext Resources."""
+        exts = []
+        plugin = manager.NeutronManager.get_plugin()
+        resource_name = 'net_partition'
+        collection_name = resource_name.replace('_', '-') + "s"
+        params = RESOURCE_ATTRIBUTE_MAP.get(resource_name + "s", dict())
+        quota.QUOTAS.register_resource_by_name(resource_name)
+        controller = base.create_resource(collection_name,
+                                          resource_name,
+                                          plugin, params, allow_bulk=True)
+        ex = extensions.ResourceExtension(collection_name,
+                                          controller)
+        exts.append(ex)
+
+        return exts
+
+
+class NetPartitionPluginBase(object):
+
+    @abstractmethod
+    def create_net_partition(self, context, router):
+        pass
+
+    @abstractmethod
+    def update_net_partition(self, context, id, router):
+        pass
+
+    @abstractmethod
+    def get_net_partition(self, context, id, fields=None):
+        pass
+
+    @abstractmethod
+    def delete_net_partition(self, context, id):
+        pass
+
+    @abstractmethod
+    def get_net_partitions(self, context, filters=None, fields=None):
+        pass
diff --git a/neutron/plugins/nuage/extensions/nuage_router.py b/neutron/plugins/nuage/extensions/nuage_router.py
new file mode 100644 (file)
index 0000000..55d4e58
--- /dev/null
@@ -0,0 +1,73 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+
+EXTENDED_ATTRIBUTES_2_0 = {
+    'routers': {
+        'net_partition': {
+            'allow_post': True,
+            'allow_put': True,
+            'is_visible': True,
+            'default': None,
+            'validate': {'type:string_or_none': None}
+        },
+        'rd': {
+            'allow_post': True,
+            'allow_put': True,
+            'is_visible': True,
+            'default': None,
+            'validate': {'type:string_or_none': None}
+        },
+        'rt': {
+            'allow_post': True,
+            'allow_put': True,
+            'is_visible': True,
+            'default': None,
+            'validate': {'type:string_or_none': None}
+        },
+    },
+}
+
+
+class Nuage_router(object):
+    """Extension class supporting nuage router.
+    """
+
+    @classmethod
+    def get_name(cls):
+        return "Nuage router"
+
+    @classmethod
+    def get_alias(cls):
+        return "nuage-router"
+
+    @classmethod
+    def get_description(cls):
+        return "Nuage Router"
+
+    @classmethod
+    def get_namespace(cls):
+        return "http://nuagenetworks.net/ext/routers/api/v1.0"
+
+    @classmethod
+    def get_updated(cls):
+        return "2014-01-01T10:00:00-00:00"
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return EXTENDED_ATTRIBUTES_2_0
+        else:
+            return {}
diff --git a/neutron/plugins/nuage/extensions/nuage_subnet.py b/neutron/plugins/nuage/extensions/nuage_subnet.py
new file mode 100644 (file)
index 0000000..b3705d5
--- /dev/null
@@ -0,0 +1,59 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+
+EXTENDED_ATTRIBUTES_2_0 = {
+    'subnets': {
+        'net_partition': {
+            'allow_post': True,
+            'allow_put': True,
+            'is_visible': True,
+            'default': None,
+            'validate': {'type:string_or_none': None}
+        },
+    },
+}
+
+
+class Nuage_subnet(object):
+    """Extension class supporting Nuage subnet.
+    """
+
+    @classmethod
+    def get_name(cls):
+        return "Nuage subnet"
+
+    @classmethod
+    def get_alias(cls):
+        return "nuage-subnet"
+
+    @classmethod
+    def get_description(cls):
+        return "Nuage subnet"
+
+    @classmethod
+    def get_namespace(cls):
+        return "http://nuagenetworks.net/ext/subnets/api/v1.0"
+
+    @classmethod
+    def get_updated(cls):
+        return "2014-01-01T10:00:00-00:00"
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return EXTENDED_ATTRIBUTES_2_0
+        else:
+            return {}
diff --git a/neutron/plugins/nuage/nuage_models.py b/neutron/plugins/nuage/nuage_models.py
new file mode 100644 (file)
index 0000000..53df04a
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright 2014 Alcatel-Lucent USA 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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+from sqlalchemy import Boolean, Column, ForeignKey, String
+
+from neutron.db import model_base
+from neutron.db import models_v2
+
+
+class NetPartition(model_base.BASEV2, models_v2.HasId):
+    __tablename__ = 'net_partitions'
+    name = Column(String(64))
+    l3dom_tmplt_id = Column(String(36))
+    l2dom_tmplt_id = Column(String(36))
+
+
+class NetPartitionRouter(model_base.BASEV2):
+    __tablename__ = "net_partition_router_mapping"
+    net_partition_id = Column(String(36),
+                              ForeignKey('net_partitions.id',
+                                         ondelete="CASCADE"),
+                              primary_key=True)
+    router_id = Column(String(36),
+                       ForeignKey('routers.id', ondelete="CASCADE"),
+                       primary_key=True)
+    nuage_router_id = Column(String(36))
+
+
+class RouterZone(model_base.BASEV2):
+    __tablename__ = "router_zone_mapping"
+    router_id = Column(String(36),
+                       ForeignKey('routers.id', ondelete="CASCADE"),
+                       primary_key=True)
+    nuage_zone_id = Column(String(36))
+    nuage_user_id = Column(String(36))
+    nuage_group_id = Column(String(36))
+
+
+class SubnetL2Domain(model_base.BASEV2):
+    __tablename__ = 'subnet_l2dom_mapping'
+    subnet_id = Column(String(36),
+                       ForeignKey('subnets.id', ondelete="CASCADE"),
+                       primary_key=True)
+    net_partition_id = Column(String(36),
+                              ForeignKey('net_partitions.id',
+                                         ondelete="CASCADE"))
+    nuage_subnet_id = Column(String(36))
+    nuage_l2dom_tmplt_id = Column(String(36))
+    nuage_user_id = Column(String(36))
+    nuage_group_id = Column(String(36))
+
+
+class PortVPortMapping(model_base.BASEV2):
+    __tablename__ = 'port_mapping'
+    port_id = Column(String(36),
+                     ForeignKey('ports.id', ondelete="CASCADE"),
+                     primary_key=True)
+    nuage_vport_id = Column(String(36))
+    nuage_vif_id = Column(String(36))
+    static_ip = Column(Boolean())
diff --git a/neutron/plugins/nuage/nuagedb.py b/neutron/plugins/nuage/nuagedb.py
new file mode 100644 (file)
index 0000000..fedd13d
--- /dev/null
@@ -0,0 +1,133 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+from neutron.db import db_base_plugin_v2
+from neutron.plugins.nuage import nuage_models
+
+
+def add_entrouter_mapping(session, np_id,
+                          router_id,
+                          n_l3id):
+    ent_rtr_mapping = nuage_models.NetPartitionRouter(net_partition_id=np_id,
+                                                      router_id=router_id,
+                                                      nuage_router_id=n_l3id)
+    session.add(ent_rtr_mapping)
+
+
+def add_rtrzone_mapping(session, neutron_router_id,
+                        nuage_zone_id,
+                        nuage_user_id=None,
+                        nuage_group_id=None):
+    rtr_zone_mapping = nuage_models.RouterZone(router_id=neutron_router_id,
+                                               nuage_zone_id=nuage_zone_id,
+                                               nuage_user_id=nuage_user_id,
+                                               nuage_group_id=nuage_group_id)
+    session.add(rtr_zone_mapping)
+
+
+def add_subnetl2dom_mapping(session, neutron_subnet_id,
+                            nuage_sub_id,
+                            np_id,
+                            l2dom_id=None,
+                            nuage_user_id=None,
+                            nuage_group_id=None):
+    subnet_l2dom = nuage_models.SubnetL2Domain(subnet_id=neutron_subnet_id,
+                                               nuage_subnet_id=nuage_sub_id,
+                                               net_partition_id=np_id,
+                                               nuage_l2dom_tmplt_id=l2dom_id,
+                                               nuage_user_id=nuage_user_id,
+                                               nuage_group_id=nuage_group_id)
+    session.add(subnet_l2dom)
+
+
+def update_subnetl2dom_mapping(subnet_l2dom,
+                               new_dict):
+    subnet_l2dom.update(new_dict)
+
+
+def add_port_vport_mapping(session, port_id, nuage_vport_id,
+                           nuage_vif_id, static_ip):
+    port_mapping = nuage_models.PortVPortMapping(port_id=port_id,
+                                                 nuage_vport_id=nuage_vport_id,
+                                                 nuage_vif_id=nuage_vif_id,
+                                                 static_ip=static_ip)
+    session.add(port_mapping)
+    return port_mapping
+
+
+def update_port_vport_mapping(port_mapping,
+                              new_dict):
+    port_mapping.update(new_dict)
+
+
+def get_port_mapping_by_id(session, id):
+    query = session.query(nuage_models.PortVPortMapping)
+    return query.filter_by(port_id=id).first()
+
+
+def get_ent_rtr_mapping_by_rtrid(session, rtrid):
+    query = session.query(nuage_models.NetPartitionRouter)
+    return query.filter_by(router_id=rtrid).first()
+
+
+def get_rtr_zone_mapping(session, router_id):
+    query = session.query(nuage_models.RouterZone)
+    return query.filter_by(router_id=router_id).first()
+
+
+def get_subnet_l2dom_by_id(session, id):
+    query = session.query(nuage_models.SubnetL2Domain)
+    return query.filter_by(subnet_id=id).first()
+
+
+def add_net_partition(session, netpart_id,
+                      l3dom_id, l2dom_id,
+                      ent_name):
+    net_partitioninst = nuage_models.NetPartition(id=netpart_id,
+                                                  name=ent_name,
+                                                  l3dom_tmplt_id=l3dom_id,
+                                                  l2dom_tmplt_id=l2dom_id)
+    session.add(net_partitioninst)
+    return net_partitioninst
+
+
+def delete_net_partition(session, net_partition):
+    session.delete(net_partition)
+
+
+def get_ent_rtr_mapping_by_entid(session,
+                                 entid):
+    query = session.query(nuage_models.NetPartitionRouter)
+    return query.filter_by(net_partition_id=entid).all()
+
+
+def get_net_partition_by_name(session, name):
+    query = session.query(nuage_models.NetPartition)
+    return query.filter_by(name=name).first()
+
+
+def get_net_partition_by_id(session, id):
+    query = session.query(nuage_models.NetPartition)
+    return query.filter_by(id=id).first()
+
+
+def get_net_partitions(session, filters=None, fields=None):
+    query = session.query(nuage_models.NetPartition)
+    common_db = db_base_plugin_v2.CommonDbMixin()
+    query = common_db._apply_filters_to_query(query,
+                                              nuage_models.NetPartition,
+                                              filters)
+    return query
diff --git a/neutron/plugins/nuage/plugin.py b/neutron/plugins/nuage/plugin.py
new file mode 100644 (file)
index 0000000..df04f9b
--- /dev/null
@@ -0,0 +1,697 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+
+import re
+
+import netaddr
+from oslo.config import cfg
+from sqlalchemy.orm import exc
+
+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 q_exc
+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 l3_db
+from neutron.db import models_v2
+from neutron.db import quota_db  # noqa
+from neutron.extensions import portbindings
+from neutron.openstack.common import excutils
+from neutron.openstack.common import importutils
+from neutron.plugins.nuage.common import config
+from neutron.plugins.nuage.common import constants
+from neutron.plugins.nuage.common import exceptions as nuage_exc
+from neutron.plugins.nuage import extensions
+from neutron.plugins.nuage.extensions import netpartition
+from neutron.plugins.nuage import nuagedb
+from neutron import policy
+
+
+class NuagePlugin(db_base_plugin_v2.NeutronDbPluginV2,
+                  external_net_db.External_net_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"]
+
+    binding_view = "extension:port_binding:view"
+
+    def __init__(self):
+        super(NuagePlugin, self).__init__()
+        neutron_extensions.append_api_extensions_path(extensions.__path__)
+        config.nuage_register_cfg_opts()
+        self.nuageclient_init()
+        net_partition = cfg.CONF.RESTPROXY.default_net_partition_name
+        self._create_default_net_partition(net_partition)
+
+    def nuageclient_init(self):
+        server = cfg.CONF.RESTPROXY.server
+        serverauth = cfg.CONF.RESTPROXY.serverauth
+        serverssl = cfg.CONF.RESTPROXY.serverssl
+        base_uri = cfg.CONF.RESTPROXY.base_uri
+        auth_resource = cfg.CONF.RESTPROXY.auth_resource
+        organization = cfg.CONF.RESTPROXY.organization
+        nuageclient = importutils.import_module('nuagenetlib.nuageclient')
+        self.nuageclient = nuageclient.NuageClient(server, base_uri,
+                                                   serverssl, serverauth,
+                                                   auth_resource,
+                                                   organization)
+
+    def _resource_finder(self, context, for_resource, resource, user_req):
+        match = re.match(attributes.UUID_PATTERN, user_req[resource])
+        if match:
+            obj_lister = getattr(self, "get_%s" % resource)
+            found_resource = obj_lister(context, user_req[resource])
+            if not found_resource:
+                msg = (_("%(resource)s with id %(resource_id)s does not "
+                         "exist") % {'resource': resource,
+                                     'resource_id': user_req[resource]})
+                raise q_exc.BadRequest(resource=for_resource, msg=msg)
+        else:
+            filter = {'name': [user_req[resource]]}
+            obj_lister = getattr(self, "get_%ss" % resource)
+            found_resource = obj_lister(context, filters=filter)
+            if not found_resource:
+                msg = (_("Either %(resource)s %(req_resource)s not found "
+                         "or you dont have credential to access it")
+                       % {'resource': resource,
+                          'req_resource': user_req[resource]})
+                raise q_exc.BadRequest(resource=for_resource, msg=msg)
+            if len(found_resource) > 1:
+                msg = (_("More than one entry found for %(resource)s "
+                         "%(req_resource)s. Use id instead")
+                       % {'resource': resource,
+                          'req_resource': user_req[resource]})
+                raise q_exc.BadRequest(resource=for_resource, msg=msg)
+            found_resource = found_resource[0]
+        return found_resource
+
+    def _update_port_ip(self, context, port, new_ip):
+        subid = port['fixed_ips'][0]['subnet_id']
+        new_fixed_ips = {}
+        new_fixed_ips['subnet_id'] = subid
+        new_fixed_ips['ip_address'] = new_ip
+        ips, prev_ips = self._update_ips_for_port(context,
+                                                  port["network_id"],
+                                                  port['id'],
+                                                  port["fixed_ips"],
+                                                  [new_fixed_ips])
+
+        # Update ips if necessary
+        for ip in ips:
+            allocated = models_v2.IPAllocation(
+                network_id=port['network_id'], port_id=port['id'],
+                ip_address=ip['ip_address'], subnet_id=ip['subnet_id'])
+            context.session.add(allocated)
+
+    def _create_update_port(self, context, port,
+                            port_mapping, subnet_mapping):
+        filters = {'device_id': [port['device_id']]}
+        ports = self.get_ports(context, filters)
+        netpart_id = subnet_mapping['net_partition_id']
+        net_partition = nuagedb.get_net_partition_by_id(context.session,
+                                                        netpart_id)
+        params = {
+            'id': port['device_id'],
+            'mac': port['mac_address'],
+            'parent_id': subnet_mapping['nuage_subnet_id'],
+            'net_partition': net_partition,
+            'ip': None,
+            'no_of_ports': len(ports),
+            'tenant': port['tenant_id']
+        }
+        if port_mapping['static_ip']:
+            params['ip'] = port['fixed_ips'][0]['ip_address']
+
+        nuage_vm = self.nuageclient.create_vms(params)
+        if nuage_vm:
+            if port['fixed_ips'][0]['ip_address'] != str(nuage_vm['ip']):
+                self._update_port_ip(context, port, nuage_vm['ip'])
+            port_dict = {
+                'nuage_vport_id': nuage_vm['vport_id'],
+                'nuage_vif_id': nuage_vm['vif_id']
+            }
+            nuagedb.update_port_vport_mapping(port_mapping,
+                                              port_dict)
+
+    def create_port(self, context, port):
+        session = context.session
+        with session.begin(subtransactions=True):
+            p = port['port']
+            port = super(NuagePlugin, self).create_port(context, port)
+            device_owner = port.get('device_owner', None)
+            if (device_owner and
+                device_owner not in constants.AUTO_CREATE_PORT_OWNERS):
+                if 'fixed_ips' not in port or len(port['fixed_ips']) == 0:
+                    return self._extend_port_dict_binding(context, port)
+                subnet_id = port['fixed_ips'][0]['subnet_id']
+                subnet_mapping = nuagedb.get_subnet_l2dom_by_id(session,
+                                                                subnet_id)
+                if subnet_mapping:
+                    static_ip = False
+                    if (attributes.is_attr_set(p['fixed_ips']) and
+                        'ip_address' in p['fixed_ips'][0]):
+                        static_ip = True
+                    nuage_vport_id = None
+                    nuage_vif_id = None
+                    port_mapping = nuagedb.add_port_vport_mapping(
+                        session,
+                        port['id'],
+                        nuage_vport_id,
+                        nuage_vif_id,
+                        static_ip)
+                    port_prefix = constants.NOVA_PORT_OWNER_PREF
+                    if port['device_owner'].startswith(port_prefix):
+                        #This request is coming from nova
+                        try:
+                            self._create_update_port(context, port,
+                                                     port_mapping,
+                                                     subnet_mapping)
+                        except Exception:
+                            with excutils.save_and_reraise_exception():
+                                super(NuagePlugin, self).delete_port(
+                                    context,
+                                    port['id'])
+        return self._extend_port_dict_binding(context, port)
+
+    def update_port(self, context, id, port):
+        p = port['port']
+        if p.get('device_owner', '').startswith(
+            constants.NOVA_PORT_OWNER_PREF):
+            session = context.session
+            with session.begin(subtransactions=True):
+                port = self._get_port(context, id)
+                port.update(p)
+                if 'fixed_ips' not in port or len(port['fixed_ips']) == 0:
+                    return self._make_port_dict(port)
+                subnet_id = port['fixed_ips'][0]['subnet_id']
+                subnet_mapping = nuagedb.get_subnet_l2dom_by_id(session,
+                                                                subnet_id)
+                if not subnet_mapping:
+                    msg = (_("Subnet %s not found on VSD") % subnet_id)
+                    raise q_exc.BadRequest(resource='port', msg=msg)
+                port_mapping = nuagedb.get_port_mapping_by_id(session,
+                                                              id)
+                if not port_mapping:
+                    msg = (_("Port-Mapping for port %s not "
+                             " found on VSD") % id)
+                    raise q_exc.BadRequest(resource='port', msg=msg)
+                if not port_mapping['nuage_vport_id']:
+                    self._create_update_port(context, port,
+                                             port_mapping, subnet_mapping)
+                updated_port = self._make_port_dict(port)
+        else:
+            updated_port = super(NuagePlugin, self).update_port(context, id,
+                                                                port)
+        return updated_port
+
+    def delete_port(self, context, id, l3_port_check=True):
+        if l3_port_check:
+            self.prevent_l3_port_deletion(context, id)
+        port = self._get_port(context, id)
+        port_mapping = nuagedb.get_port_mapping_by_id(context.session,
+                                                      id)
+        # This is required for to pass ut test_floatingip_port_delete
+        self.disassociate_floatingips(context, id)
+        if not port['fixed_ips']:
+            return super(NuagePlugin, self).delete_port(context, id)
+
+        sub_id = port['fixed_ips'][0]['subnet_id']
+        subnet_mapping = nuagedb.get_subnet_l2dom_by_id(context.session,
+                                                        sub_id)
+        if not subnet_mapping:
+            return super(NuagePlugin, self).delete_port(context, id)
+
+        netpart_id = subnet_mapping['net_partition_id']
+        net_partition = nuagedb.get_net_partition_by_id(context.session,
+                                                        netpart_id)
+        # Need to call this explicitly to delete vport_vporttag_mapping
+        if constants.NOVA_PORT_OWNER_PREF in port['device_owner']:
+            # This was a VM Port
+            filters = {'device_id': [port['device_id']]}
+            ports = self.get_ports(context, filters)
+            params = {
+                'no_of_ports': len(ports),
+                'net_partition': net_partition,
+                'tenant': port['tenant_id'],
+                'mac': port['mac_address'],
+                'nuage_vif_id': port_mapping['nuage_vif_id'],
+                'id': port['device_id']
+            }
+            self.nuageclient.delete_vms(params)
+        super(NuagePlugin, self).delete_port(context, id)
+
+    def _check_view_auth(self, context, resource, action):
+        return policy.check(context, action, resource)
+
+    def _extend_port_dict_binding(self, context, port):
+        if self._check_view_auth(context, port, self.binding_view):
+            port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS
+            port[portbindings.VIF_DETAILS] = {
+                portbindings.CAP_PORT_FILTER: False
+            }
+        return port
+
+    def get_port(self, context, id, fields=None):
+        port = super(NuagePlugin, self).get_port(context, id, fields)
+        return self._fields(self._extend_port_dict_binding(context, port),
+                            fields)
+
+    def get_ports(self, context, filters=None, fields=None):
+        ports = super(NuagePlugin, self).get_ports(context, filters, fields)
+        return [self._fields(self._extend_port_dict_binding(context, port),
+                             fields) for port in ports]
+
+    def _check_router_subnet_for_tenant(self, context):
+        # Search router and subnet tables.
+        # If no entry left delete user and group from VSD
+        filters = {'tenant_id': [context.tenant]}
+        routers = self.get_routers(context, filters=filters)
+        subnets = self.get_subnets(context, filters=filters)
+        return bool(routers or subnets)
+
+    def create_network(self, context, network):
+        net = network['network']
+        with context.session.begin(subtransactions=True):
+            net = super(NuagePlugin, self).create_network(context,
+                                                          network)
+            self._process_l3_create(context, net, network['network'])
+        return net
+
+    def update_network(self, context, id, network):
+        with context.session.begin(subtransactions=True):
+            net = super(NuagePlugin, self).update_network(context, id,
+                                                          network)
+            self._process_l3_update(context, net, network['network'])
+        return net
+
+    def delete_network(self, context, id):
+        filter = {'network_id': [id]}
+        subnets = self.get_subnets(context, filters=filter)
+        for subnet in subnets:
+            self.delete_subnet(context, subnet['id'])
+        super(NuagePlugin, self).delete_network(context, id)
+
+    def _get_net_partition_for_subnet(self, context, subnet):
+        subn = subnet['subnet']
+        ent = subn.get('net_partition', None)
+        if not ent:
+            def_net_part = cfg.CONF.RESTPROXY.default_net_partition_name
+            net_partition = nuagedb.get_net_partition_by_name(context.session,
+                                                              def_net_part)
+        else:
+            net_partition = self._resource_finder(context, 'subnet',
+                                                  'net_partition', subn)
+        if not net_partition:
+            msg = _('Either net_partition is not provided with subnet OR '
+                    'default net_partition is not created at the start')
+            raise q_exc.BadRequest(resource='subnet', msg=msg)
+        return net_partition
+
+    def _validate_create_subnet(self, subnet):
+        if ('host_routes' in subnet and
+            attributes.is_attr_set(subnet['host_routes'])):
+            msg = 'host_routes extensions not supported for subnets'
+            raise nuage_exc.OperationNotSupported(msg=msg)
+        if subnet['gateway_ip'] is None:
+            msg = "no-gateway option not supported with subnets"
+            raise nuage_exc.OperationNotSupported(msg=msg)
+
+    def create_subnet(self, context, subnet):
+        subn = subnet['subnet']
+        net_id = subn['network_id']
+
+        if self._network_is_external(context, net_id):
+            return super(NuagePlugin, self).create_subnet(context, subnet)
+
+        self._validate_create_subnet(subn)
+
+        net_partition = self._get_net_partition_for_subnet(context, subnet)
+        neutron_subnet = super(NuagePlugin, self).create_subnet(context,
+                                                                subnet)
+        net = netaddr.IPNetwork(neutron_subnet['cidr'])
+        params = {
+            'net_partition': net_partition,
+            'tenant_id': neutron_subnet['tenant_id'],
+            'net': net
+        }
+        try:
+            nuage_subnet = self.nuageclient.create_subnet(neutron_subnet,
+                                                          params)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                super(NuagePlugin, self).delete_subnet(context,
+                                                       neutron_subnet['id'])
+
+        if nuage_subnet:
+            l2dom_id = str(nuage_subnet['nuage_l2template_id'])
+            user_id = nuage_subnet['nuage_userid']
+            group_id = nuage_subnet['nuage_groupid']
+            id = nuage_subnet['nuage_l2domain_id']
+            session = context.session
+            with session.begin(subtransactions=True):
+                nuagedb.add_subnetl2dom_mapping(session,
+                                                neutron_subnet['id'],
+                                                id,
+                                                net_partition['id'],
+                                                l2dom_id=l2dom_id,
+                                                nuage_user_id=user_id,
+                                                nuage_group_id=group_id)
+        return neutron_subnet
+
+    def delete_subnet(self, context, id):
+        subnet = self.get_subnet(context, id)
+        if self._network_is_external(context, subnet['network_id']):
+            return super(NuagePlugin, self).delete_subnet(context, id)
+
+        subnet_l2dom = nuagedb.get_subnet_l2dom_by_id(context.session, id)
+        if subnet_l2dom:
+            template_id = subnet_l2dom['nuage_l2dom_tmplt_id']
+            try:
+                self.nuageclient.delete_subnet(subnet_l2dom['nuage_subnet_id'],
+                                               template_id)
+            except Exception:
+                msg = (_('Unable to complete operation on subnet %s.'
+                         'One or more ports have an IP allocation '
+                         'from this subnet.') % id)
+                raise q_exc.BadRequest(resource='subnet', msg=msg)
+        super(NuagePlugin, self).delete_subnet(context, id)
+        if subnet_l2dom and not self._check_router_subnet_for_tenant(context):
+            self.nuageclient.delete_user(subnet_l2dom['nuage_user_id'])
+            self.nuageclient.delete_group(subnet_l2dom['nuage_group_id'])
+
+    def add_router_interface(self, context, router_id, interface_info):
+        session = context.session
+        with session.begin(subtransactions=True):
+            rtr_if_info = super(NuagePlugin,
+                                self).add_router_interface(context,
+                                                           router_id,
+                                                           interface_info)
+            subnet_id = rtr_if_info['subnet_id']
+            subn = self.get_subnet(context, subnet_id)
+
+            rtr_zone_mapping = nuagedb.get_rtr_zone_mapping(session,
+                                                            router_id)
+            ent_rtr_mapping = nuagedb.get_ent_rtr_mapping_by_rtrid(session,
+                                                                   router_id)
+            subnet_l2dom = nuagedb.get_subnet_l2dom_by_id(session,
+                                                          subnet_id)
+            if not rtr_zone_mapping or not ent_rtr_mapping:
+                super(NuagePlugin,
+                      self).remove_router_interface(context,
+                                                    router_id,
+                                                    interface_info)
+                msg = (_("Router %s does not hold default zone OR "
+                         "net_partition mapping. Router-IF add failed")
+                       % router_id)
+                raise q_exc.BadRequest(resource='router', msg=msg)
+
+            if not subnet_l2dom:
+                super(NuagePlugin,
+                      self).remove_router_interface(context,
+                                                    router_id,
+                                                    interface_info)
+                msg = (_("Subnet %s does not hold Nuage VSD reference. "
+                         "Router-IF add failed") % subnet_id)
+                raise q_exc.BadRequest(resource='subnet', msg=msg)
+
+            if (subnet_l2dom['net_partition_id'] !=
+                ent_rtr_mapping['net_partition_id']):
+                super(NuagePlugin,
+                      self).remove_router_interface(context,
+                                                    router_id,
+                                                    interface_info)
+                msg = (_("Subnet %(subnet)s and Router %(router)s belong to "
+                         "different net_partition Router-IF add "
+                         "not permitted") % {'subnet': subnet_id,
+                                             'router': router_id})
+                raise q_exc.BadRequest(resource='subnet', msg=msg)
+            nuage_subnet_id = subnet_l2dom['nuage_subnet_id']
+            nuage_l2dom_tmplt_id = subnet_l2dom['nuage_l2dom_tmplt_id']
+            if self.nuageclient.vms_on_l2domain(nuage_subnet_id):
+                super(NuagePlugin,
+                      self).remove_router_interface(context,
+                                                    router_id,
+                                                    interface_info)
+                msg = (_("Subnet %s has one or more active VMs "
+                       "Router-IF add not permitted") % subnet_id)
+                raise q_exc.BadRequest(resource='subnet', msg=msg)
+            self.nuageclient.delete_subnet(nuage_subnet_id,
+                                           nuage_l2dom_tmplt_id)
+            net = netaddr.IPNetwork(subn['cidr'])
+            params = {
+                'net': net,
+                'zone_id': rtr_zone_mapping['nuage_zone_id']
+            }
+            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)
+            except Exception:
+                with excutils.save_and_reraise_exception():
+                    super(NuagePlugin,
+                          self).remove_router_interface(context,
+                                                        router_id,
+                                                        interface_info)
+            if nuage_subnet:
+                ns_dict = {}
+                ns_dict['nuage_subnet_id'] = nuage_subnet['nuage_subnetid']
+                ns_dict['nuage_l2dom_tmplt_id'] = None
+                nuagedb.update_subnetl2dom_mapping(subnet_l2dom,
+                                                   ns_dict)
+        return rtr_if_info
+
+    def remove_router_interface(self, context, router_id, interface_info):
+        if 'subnet_id' in interface_info:
+            subnet_id = interface_info['subnet_id']
+            subnet = self.get_subnet(context, subnet_id)
+            found = False
+            try:
+                filters = {'device_id': [router_id],
+                           'device_owner':
+                           [os_constants.DEVICE_OWNER_ROUTER_INTF],
+                           'network_id': [subnet['network_id']]}
+                ports = self.get_ports(context, filters)
+
+                for p in ports:
+                    if p['fixed_ips'][0]['subnet_id'] == subnet_id:
+                        found = True
+                        break
+            except exc.NoResultFound:
+                msg = (_("No router interface found for Router %s. "
+                         "Router-IF delete failed") % router_id)
+                raise q_exc.BadRequest(resource='router', msg=msg)
+
+            if not found:
+                msg = (_("No router interface found for Router %s. "
+                         "Router-IF delete failed") % router_id)
+                raise q_exc.BadRequest(resource='router', msg=msg)
+        elif 'port_id' in interface_info:
+            port_db = self._get_port(context, interface_info['port_id'])
+            if not port_db:
+                msg = (_("No router interface found for Router %s. "
+                         "Router-IF delete failed") % router_id)
+                raise q_exc.BadRequest(resource='router', msg=msg)
+            subnet_id = port_db['fixed_ips'][0]['subnet_id']
+
+        session = context.session
+        with session.begin(subtransactions=True):
+            subnet_l2dom = nuagedb.get_subnet_l2dom_by_id(session,
+                                                          subnet_id)
+            nuage_subn_id = subnet_l2dom['nuage_subnet_id']
+            if self.nuageclient.vms_on_l2domain(nuage_subn_id):
+                msg = (_("Subnet %s has one or more active VMs "
+                         "Router-IF delete not permitted") % subnet_id)
+                raise q_exc.BadRequest(resource='subnet', msg=msg)
+
+            neutron_subnet = self.get_subnet(context, subnet_id)
+            ent_rtr_mapping = nuagedb.get_ent_rtr_mapping_by_rtrid(
+                context.session,
+                router_id)
+            if not ent_rtr_mapping:
+                msg = (_("Router %s does not hold net_partition "
+                         "assoc on Nuage VSD. Router-IF delete failed")
+                       % router_id)
+                raise q_exc.BadRequest(resource='router', msg=msg)
+            net = netaddr.IPNetwork(neutron_subnet['cidr'])
+            net_part_id = ent_rtr_mapping['net_partition_id']
+            net_partition = self.get_net_partition(context,
+                                                   net_part_id)
+            params = {
+                'net_partition': net_partition,
+                'tenant_id': neutron_subnet['tenant_id'],
+                'net': net
+            }
+            nuage_subnet = self.nuageclient.create_subnet(neutron_subnet,
+                                                          params)
+            self.nuageclient.delete_domain_subnet(nuage_subn_id)
+            info = super(NuagePlugin,
+                         self).remove_router_interface(context, router_id,
+                                                       interface_info)
+            if nuage_subnet:
+                tmplt_id = str(nuage_subnet['nuage_l2template_id'])
+                ns_dict = {}
+                ns_dict['nuage_subnet_id'] = nuage_subnet['nuage_l2domain_id']
+                ns_dict['nuage_l2dom_tmplt_id'] = tmplt_id
+                nuagedb.update_subnetl2dom_mapping(subnet_l2dom,
+                                                   ns_dict)
+        return info
+
+    def _get_net_partition_for_router(self, context, router):
+        rtr = router['router']
+        ent = rtr.get('net_partition', None)
+        if not ent:
+            def_net_part = cfg.CONF.RESTPROXY.default_net_partition_name
+            net_partition = nuagedb.get_net_partition_by_name(context.session,
+                                                              def_net_part)
+        else:
+            net_partition = self._resource_finder(context, 'router',
+                                                  'net_partition', rtr)
+        if not net_partition:
+            msg = _("Either net_partition is not provided with router OR "
+                    "default net_partition is not created at the start")
+            raise q_exc.BadRequest(resource='router', msg=msg)
+        return net_partition
+
+    def create_router(self, context, router):
+        net_partition = self._get_net_partition_for_router(context, router)
+        neutron_router = super(NuagePlugin, self).create_router(context,
+                                                                router)
+        params = {
+            'net_partition': net_partition,
+            'tenant_id': neutron_router['tenant_id']
+        }
+        try:
+            nuage_router = self.nuageclient.create_router(neutron_router,
+                                                          router['router'],
+                                                          params)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                super(NuagePlugin, self).delete_router(context,
+                                                       neutron_router['id'])
+        if nuage_router:
+            user_id = nuage_router['nuage_userid']
+            group_id = nuage_router['nuage_groupid']
+            with context.session.begin(subtransactions=True):
+                nuagedb.add_entrouter_mapping(context.session,
+                                              net_partition['id'],
+                                              neutron_router['id'],
+                                              nuage_router['nuage_domain_id'])
+                nuagedb.add_rtrzone_mapping(context.session,
+                                            neutron_router['id'],
+                                            nuage_router['nuage_def_zone_id'],
+                                            nuage_user_id=user_id,
+                                            nuage_group_id=group_id)
+        return neutron_router
+
+    def delete_router(self, context, id):
+        session = context.session
+        ent_rtr_mapping = nuagedb.get_ent_rtr_mapping_by_rtrid(session,
+                                                               id)
+        if ent_rtr_mapping:
+            nuage_router_id = ent_rtr_mapping['nuage_router_id']
+            self.nuageclient.delete_router(nuage_router_id)
+        router_zone = nuagedb.get_rtr_zone_mapping(session, id)
+        super(NuagePlugin, self).delete_router(context, id)
+        if router_zone and not self._check_router_subnet_for_tenant(context):
+            self.nuageclient.delete_user(router_zone['nuage_user_id'])
+            self.nuageclient.delete_group(router_zone['nuage_group_id'])
+
+    def _make_net_partition_dict(self, net_partition, fields=None):
+        res = {
+            'id': net_partition['id'],
+            'name': net_partition['name'],
+            'l3dom_tmplt_id': net_partition['l3dom_tmplt_id'],
+            'l2dom_tmplt_id': net_partition['l2dom_tmplt_id'],
+        }
+        return self._fields(res, fields)
+
+    def _create_net_partition(self, session, net_part_name):
+        fip_quota = cfg.CONF.RESTPROXY.default_floatingip_quota
+        params = {
+            "name": net_part_name,
+            "fp_quota": str(fip_quota)
+        }
+        nuage_net_partition = self.nuageclient.create_net_partition(params)
+        net_partitioninst = None
+        if nuage_net_partition:
+            nuage_entid = nuage_net_partition['nuage_entid']
+            l3dom_id = nuage_net_partition['l3dom_id']
+            l2dom_id = nuage_net_partition['l2dom_id']
+            with session.begin():
+                net_partitioninst = nuagedb.add_net_partition(session,
+                                                              nuage_entid,
+                                                              l3dom_id,
+                                                              l2dom_id,
+                                                              net_part_name)
+        if not net_partitioninst:
+            return {}
+        return self._make_net_partition_dict(net_partitioninst)
+
+    def _create_default_net_partition(self, default_net_part):
+        self.nuageclient.check_del_def_net_partition(default_net_part)
+        session = db.get_session()
+        net_partition = nuagedb.get_net_partition_by_name(session,
+                                                          default_net_part)
+        if net_partition:
+            with session.begin(subtransactions=True):
+                nuagedb.delete_net_partition(session, net_partition)
+
+        self._create_net_partition(session, default_net_part)
+
+    def create_net_partition(self, context, net_partition):
+        ent = net_partition['net_partition']
+        session = context.session
+        return self._create_net_partition(session, ent["name"])
+
+    def delete_net_partition(self, context, id):
+        ent_rtr_mapping = nuagedb.get_ent_rtr_mapping_by_entid(
+            context.session,
+            id)
+        if ent_rtr_mapping:
+            msg = (_("One or more router still attached to "
+                     "net_partition %s.") % id)
+            raise q_exc.BadRequest(resource='net_partition', msg=msg)
+        net_partition = nuagedb.get_net_partition_by_id(context.session, id)
+        if not net_partition:
+            msg = (_("NetPartition with %s does not exist") % id)
+            raise q_exc.BadRequest(resource='net_partition', msg=msg)
+        l3dom_tmplt_id = net_partition['l3dom_tmplt_id']
+        l2dom_tmplt_id = net_partition['l2dom_tmplt_id']
+        self.nuageclient.delete_net_partition(net_partition['id'],
+                                              l3dom_id=l3dom_tmplt_id,
+                                              l2dom_id=l2dom_tmplt_id)
+        with context.session.begin(subtransactions=True):
+            nuagedb.delete_net_partition(context.session,
+                                         net_partition)
+
+    def get_net_partition(self, context, id, fields=None):
+        net_partition = nuagedb.get_net_partition_by_id(context.session,
+                                                        id)
+        return self._make_net_partition_dict(net_partition)
+
+    def get_net_partitions(self, context, filters=None, fields=None):
+        net_partitions = nuagedb.get_net_partitions(context.session,
+                                                    filters=filters,
+                                                    fields=fields)
+        return [self._make_net_partition_dict(net_partition, fields)
+                for net_partition in net_partitions]
diff --git a/neutron/tests/unit/nuage/__init__.py b/neutron/tests/unit/nuage/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/nuage/fake_nuageclient.py b/neutron/tests/unit/nuage/fake_nuageclient.py
new file mode 100644 (file)
index 0000000..47c9dc3
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Aniket Dandekar, Nuage Networks, Alcatel-Lucent USA Inc.
+
+import uuid
+
+
+class FakeNuageClient(object):
+    def __init__(self, server, base_uri, serverssl,
+                 serverauth, auth_resource, organization):
+        pass
+
+    def rest_call(self, action, resource, data, extra_headers=None):
+        pass
+
+    def vms_on_l2domain(self, l2dom_id):
+        pass
+
+    def create_subnet(self, neutron_subnet, params):
+        nuage_subnet = {
+            'nuage_l2template_id': str(uuid.uuid4()),
+            'nuage_userid': str(uuid.uuid4()),
+            'nuage_groupid': str(uuid.uuid4()),
+            'nuage_l2domain_id': str(uuid.uuid4())
+        }
+        return nuage_subnet
+
+    def delete_subnet(self, id, template_id):
+        pass
+
+    def create_router(self, neutron_router, router, params):
+        nuage_router = {
+            'nuage_userid': str(uuid.uuid4()),
+            'nuage_groupid': str(uuid.uuid4()),
+            'nuage_domain_id': str(uuid.uuid4()),
+            'nuage_def_zone_id': str(uuid.uuid4()),
+        }
+        return nuage_router
+
+    def delete_router(self, id):
+        pass
+
+    def delete_user(self, id):
+        pass
+
+    def delete_group(self, id):
+        pass
+
+    def create_domain_subnet(self, neutron_subnet, params):
+        pass
+
+    def delete_domain_subnet(self, id):
+        pass
+
+    def create_net_partition(self, params):
+        fake_net_partition = {
+            'nuage_entid': str(uuid.uuid4()),
+            'l3dom_id': str(uuid.uuid4()),
+            'l2dom_id': str(uuid.uuid4()),
+        }
+        return fake_net_partition
+
+    def delete_net_partition(self, id, l3dom_id=None, l2dom_id=None):
+        pass
+
+    def check_del_def_net_partition(self, ent_name):
+        pass
+
+    def create_vms(self, params):
+        pass
+
+    def delete_vms(self, params):
+        pass
diff --git a/neutron/tests/unit/nuage/test_netpartition.py b/neutron/tests/unit/nuage/test_netpartition.py
new file mode 100644 (file)
index 0000000..da4aa38
--- /dev/null
@@ -0,0 +1,93 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
+
+import contextlib
+import uuid
+import webob.exc
+
+from neutron.plugins.nuage.extensions import netpartition as netpart_ext
+from neutron.tests.unit.nuage import test_nuage_plugin
+from neutron.tests.unit import test_extensions
+
+
+class NetPartitionTestExtensionManager(object):
+    def get_resources(self):
+        return netpart_ext.Netpartition.get_resources()
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+
+class NetPartitionTestCase(test_nuage_plugin.NuagePluginV2TestCase):
+    def setUp(self):
+        ext_mgr = NetPartitionTestExtensionManager()
+        super(NetPartitionTestCase, self).setUp()
+        self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
+
+    def _make_netpartition(self, fmt, name):
+        data = {
+            'net_partition': {
+                'name': name,
+                'tenant_id': uuid.uuid4()
+            }
+        }
+        netpart_req = self.new_create_request('net-partitions', data, fmt)
+        resp = netpart_req.get_response(self.ext_api)
+        if resp.status_int >= webob.exc.HTTPClientError.code:
+            raise webob.exc.HTTPClientError(code=resp.status_int)
+        return self.deserialize(fmt, resp)
+
+    def _del_netpartition(self, id):
+        self._delete('net-partitions', id)
+
+    @contextlib.contextmanager
+    def netpartition(self, name='netpartition1',
+                     do_delete=True,
+                     fmt=None,
+                     **kwargs):
+        netpart = self._make_netpartition(fmt or self.fmt, name)
+
+        try:
+            yield netpart
+        finally:
+            if do_delete:
+                self._del_netpartition(netpart['net_partition']['id'])
+
+    def test_create_netpartition(self):
+        name = 'netpart1'
+        keys = [('name', name)]
+        with self.netpartition(name=name) as netpart:
+            for k, v in keys:
+                self.assertEqual(netpart['net_partition'][k], v)
+
+    def test_delete_netpartition(self):
+        name = 'netpart1'
+        netpart = self._make_netpartition(self.fmt, name)
+        req = self.new_delete_request('net-partitions',
+                                      netpart['net_partition']['id'])
+        res = req.get_response(self.ext_api)
+        self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code)
+
+    def test_show_netpartition(self):
+        with self.netpartition(name='netpart1') as npart:
+            req = self.new_show_request('net-partitions',
+                                        npart['net_partition']['id'])
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            self.assertEqual(res['net_partition']['name'],
+                             npart['net_partition']['name'])
diff --git a/neutron/tests/unit/nuage/test_nuage_plugin.py b/neutron/tests/unit/nuage/test_nuage_plugin.py
new file mode 100644 (file)
index 0000000..259a363
--- /dev/null
@@ -0,0 +1,162 @@
+# Copyright 2014 Alcatel-Lucent USA Inc.
+#
+#    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.
+#
+# @author: Ronak Shah, Aniket Dandekar, Nuage Networks, Alcatel-Lucent USA Inc.
+
+import os
+
+import mock
+from oslo.config import cfg
+
+from neutron.extensions import portbindings
+from neutron.plugins.nuage import extensions
+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_l3_plugin
+
+API_EXT_PATH = os.path.dirname(extensions.__file__)
+FAKE_DEFAULT_ENT = 'default'
+NUAGE_PLUGIN_PATH = 'neutron.plugins.nuage.plugin'
+FAKE_SERVER = '1.1.1.1'
+FAKE_SERVER_AUTH = 'user:pass'
+FAKE_SERVER_SSL = False
+FAKE_BASE_URI = '/base/'
+FAKE_AUTH_RESOURCE = '/auth'
+FAKE_ORGANIZATION = 'fake_org'
+
+_plugin_name = ('%s.NuagePlugin' % NUAGE_PLUGIN_PATH)
+
+
+class NuagePluginV2TestCase(test_db_plugin.NeutronDbPluginV2TestCase):
+    def setUp(self, plugin=_plugin_name,
+              ext_mgr=None, service_plugins=None):
+        def mock_nuageClient_init(self):
+            server = FAKE_SERVER
+            serverauth = FAKE_SERVER_AUTH
+            serverssl = FAKE_SERVER_SSL
+            base_uri = FAKE_BASE_URI
+            auth_resource = FAKE_AUTH_RESOURCE
+            organization = FAKE_ORGANIZATION
+            self.nuageclient = None
+            self.nuageclient = fake_nuageclient.FakeNuageClient(server,
+                                                                base_uri,
+                                                                serverssl,
+                                                                serverauth,
+                                                                auth_resource,
+                                                                organization)
+
+        with mock.patch.object(nuage_plugin.NuagePlugin,
+                               'nuageclient_init', new=mock_nuageClient_init):
+            cfg.CONF.set_override('api_extensions_path',
+                                  API_EXT_PATH)
+            super(NuagePluginV2TestCase, self).setUp(plugin=plugin,
+                                                     ext_mgr=ext_mgr)
+
+
+class TestNuageBasicGet(NuagePluginV2TestCase,
+                        test_db_plugin.TestBasicGet):
+    pass
+
+
+class TestNuageV2HTTPResponse(NuagePluginV2TestCase,
+                              test_db_plugin.TestV2HTTPResponse):
+    pass
+
+
+class TestNuageNetworksV2(NuagePluginV2TestCase,
+                          test_db_plugin.TestNetworksV2):
+    pass
+
+
+class TestNuageSubnetsV2(NuagePluginV2TestCase,
+                         test_db_plugin.TestSubnetsV2):
+    def test_create_subnet_bad_hostroutes(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_create_subnet_inconsistent_ipv4_hostroute_dst_v6(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_create_subnet_inconsistent_ipv4_hostroute_np_v6(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_update_subnet_adding_additional_host_routes_and_dns(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_update_subnet_inconsistent_ipv6_hostroute_dst_v4(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_update_subnet_inconsistent_ipv6_hostroute_np_v4(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_create_subnet_with_one_host_route(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_create_subnet_with_two_host_routes(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_create_subnet_with_too_many_routes(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_update_subnet_route(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_update_subnet_route_to_None(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_update_subnet_route_with_too_many_entries(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_delete_subnet_with_route(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_delete_subnet_with_dns_and_route(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_validate_subnet_host_routes_exhausted(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_validate_subnet_dns_nameservers_exhausted(self):
+        self.skipTest("Plugin does not support Neutron Subnet host-routes")
+
+    def test_create_subnet_with_none_gateway(self):
+        self.skipTest("Plugin does not support "
+                      "Neutron Subnet no-gateway option")
+
+    def test_create_subnet_with_none_gateway_fully_allocated(self):
+        self.skipTest("Plugin does not support Neutron "
+                      "Subnet no-gateway option")
+
+    def test_create_subnet_with_none_gateway_allocation_pool(self):
+        self.skipTest("Plugin does not support Neutron "
+                      "Subnet no-gateway option")
+
+
+class TestNuagePluginPortBinding(NuagePluginV2TestCase,
+                                 test_bindings.PortBindingsTestCase):
+    VIF_TYPE = portbindings.VIF_TYPE_OVS
+
+    def setUp(self):
+        super(TestNuagePluginPortBinding, self).setUp()
+
+
+class TestNuagePortsV2(NuagePluginV2TestCase,
+                       test_db_plugin.TestPortsV2):
+    pass
+
+
+class TestNuageL3NatTestCase(NuagePluginV2TestCase,
+                             test_l3_plugin.L3NatDBIntTestCase):
+    pass