]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Support for NVP advanced service router
authorKaiwei Fan <fank@vmware.com>
Fri, 23 Aug 2013 06:25:52 +0000 (23:25 -0700)
committerKaiwei Fan <fank@vmware.com>
Wed, 11 Sep 2013 07:58:57 +0000 (00:58 -0700)
When creating an LR:
    - deploy an Edge asynchronously
    - create a L2 switch for connecting LR and Edge
    - attach a router port to the L2 switch.
    - assign ip address 169.254.2.1/28 and nexthop 169.254.2.3 to LR
When set external gateway:
    - configure Edge interface and default gateway
    - Add static routes to Edge for all logic networks attached to LR via nexthop 169.254.2.1
    - configure SNAT rules for all logic networks attached to LR
When add router interface:
    - Add static route/SNAT rule for the network attached to LR
When associate floating IP address:
    - configure DNAT rule for the floating ip and the port

Tests being done:
    - Verified Edge is deployed asynchronously and LR is attached to the internal created L2 switch
    - Manually attach Edge's vNic to the L2 switch and Edge is able to ping 169.254.2.1
    - Verified router-delete deletes Edge asynchronously and remove the internal L2 switch
    - Verified SNAT/DNAT/static-routes rules are configured on Edge in correct order
    - Verified external vnic ip address/netmask and default gateway is configured

Implements: blueprint nvp-service-router
Change-Id: If9eff53df4d65cf4e318dedbfaafc742f6c6ab7f

15 files changed:
neutron/db/migration/alembic_migrations/versions/4a666eb208c2_service_router.py [new file with mode: 0644]
neutron/plugins/nicira/NeutronPlugin.py
neutron/plugins/nicira/NeutronServicePlugin.py [new file with mode: 0644]
neutron/plugins/nicira/dbexts/distributedrouter.py
neutron/plugins/nicira/dbexts/nicira_models.py
neutron/plugins/nicira/dbexts/nsxrouter.py [new file with mode: 0644]
neutron/plugins/nicira/dbexts/servicerouter.py [new file with mode: 0644]
neutron/plugins/nicira/dbexts/vcns_db.py [new file with mode: 0644]
neutron/plugins/nicira/dbexts/vcns_models.py [new file with mode: 0644]
neutron/plugins/nicira/extensions/servicerouter.py [new file with mode: 0644]
neutron/plugins/nicira/vshield/edge_appliance_driver.py
neutron/plugins/nicira/vshield/vcns.py
neutron/tests/unit/nicira/__init__.py
neutron/tests/unit/nicira/test_edge_router.py [new file with mode: 0644]
neutron/tests/unit/nicira/test_nicira_plugin.py

diff --git a/neutron/db/migration/alembic_migrations/versions/4a666eb208c2_service_router.py b/neutron/db/migration/alembic_migrations/versions/4a666eb208c2_service_router.py
new file mode 100644 (file)
index 0000000..9d4a7f3
--- /dev/null
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 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.
+#
+
+"""service router
+
+Revision ID: 4a666eb208c2
+Revises: 38fc1f6789f8
+Create Date: 2013-09-03 01:55:57.799217
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '4a666eb208c2'
+down_revision = '38fc1f6789f8'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+    'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+
+
+def upgrade(active_plugins=None, options=None):
+    if not migration.should_run(active_plugins, migration_for_plugins):
+        return
+
+    op.create_table(
+        'vcns_router_bindings',
+        sa.Column('status', sa.String(length=16), nullable=False),
+        sa.Column('status_description', sa.String(length=255), nullable=True),
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.Column('edge_id', sa.String(length=16), nullable=True),
+        sa.Column('lswitch_id', sa.String(length=36), nullable=False),
+        sa.PrimaryKeyConstraint('router_id'),
+        mysql_engine='InnoDB'
+    )
+    op.add_column(
+        u'nsxrouterextattributess',
+        sa.Column('service_router',
+        sa.Boolean(),
+        nullable=False))
+    op.execute("UPDATE nsxrouterextattributess set service_router=False")
+
+
+def downgrade(active_plugins=None, options=None):
+    if not migration.should_run(active_plugins, migration_for_plugins):
+        return
+
+    op.drop_column(u'nsxrouterextattributess', 'service_router')
+    op.drop_table('vcns_router_bindings')
index cf49b4c69ad6389f45e0d3112108cb65f572c29d..242abc464428332a7732147ccf56c53f46553ee5 100644 (file)
@@ -54,6 +54,7 @@ from neutron.extensions import portsecurity as psec
 from neutron.extensions import providernet as pnet
 from neutron.extensions import securitygroup as ext_sg
 from neutron.openstack.common import excutils
+from neutron.plugins.common import constants as plugin_const
 from neutron.plugins.nicira.common import config
 from neutron.plugins.nicira.common import exceptions as nvp_exc
 from neutron.plugins.nicira.common import securitygroups as nvp_sec
@@ -78,6 +79,7 @@ NVP_NOSNAT_RULES_ORDER = 10
 NVP_FLOATINGIP_NAT_RULES_ORDER = 224
 NVP_EXTGW_NAT_RULES_ORDER = 255
 NVP_EXT_PATH = os.path.join(os.path.dirname(__file__), 'extensions')
+NVP_DEFAULT_NEXTHOP = '1.1.1.1'
 
 
 # Provider network extension - allowed network types for the NVP Plugin
@@ -1381,48 +1383,14 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         else:
             return super(NvpPluginV2, self).get_router(context, id, fields)
 
-    def create_router(self, context, router):
-        # NOTE(salvatore-orlando): We completely override this method in
-        # order to be able to use the NVP ID as Neutron ID
-        # TODO(salvatore-orlando): Propose upstream patch for allowing
-        # 3rd parties to specify IDs as we do with l2 plugin
-        r = router['router']
-        has_gw_info = False
-        tenant_id = self._get_tenant_id_for_create(context, r)
-        # default value to set - nvp wants it (even if we don't have it)
-        nexthop = '1.1.1.1'
+    def _create_lrouter(self, context, router, nexthop):
+        tenant_id = self._get_tenant_id_for_create(context, router)
+        name = router['name']
+        distributed = router.get('distributed')
         try:
-            # if external gateway info are set, then configure nexthop to
-            # default external gateway
-            if 'external_gateway_info' in r and r.get('external_gateway_info'):
-                has_gw_info = True
-                gw_info = r['external_gateway_info']
-                del r['external_gateway_info']
-                # The following DB read will be performed again when updating
-                # gateway info. This is not great, but still better than
-                # creating NVP router here and updating it later
-                network_id = (gw_info.get('network_id', None) if gw_info
-                              else None)
-                if network_id:
-                    ext_net = self._get_network(context, network_id)
-                    if not ext_net.external:
-                        msg = (_("Network '%s' is not a valid external "
-                                 "network") % network_id)
-                        raise q_exc.BadRequest(resource='router', msg=msg)
-                    if ext_net.subnets:
-                        ext_subnet = ext_net.subnets[0]
-                        nexthop = ext_subnet.gateway_ip
-            distributed = r.get('distributed')
             lrouter = nvplib.create_lrouter(
-                self.cluster, tenant_id, router['router']['name'], nexthop,
+                self.cluster, tenant_id, name, nexthop,
                 distributed=attr.is_attr_set(distributed) and distributed)
-            # Use NVP identfier for Neutron resource
-            r['id'] = lrouter['uuid']
-            # Update 'distributed' with value returned from NVP
-            # This will be useful for setting the value if the API request
-            # did not specify any value for the 'distributed' attribute.
-            # Platforms older than 3.x do not support the attribute
-            r['distributed'] = lrouter.get('distributed', False)
         except nvp_exc.NvpInvalidVersion:
             msg = _("Cannot create a distributed router with the NVP "
                     "platform currently in execution. Please, try "
@@ -1432,6 +1400,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         except NvpApiClient.NvpApiException:
             raise nvp_exc.NvpPluginException(
                 err_msg=_("Unable to create logical router on NVP Platform"))
+
         # Create the port here - and update it later if we have gw_info
         try:
             self._create_and_attach_router_port(
@@ -1449,7 +1418,48 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
             nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
             # Return user a 500 with an apter message
             raise nvp_exc.NvpPluginException(
-                err_msg=_("Unable to create router %s") % r['name'])
+                err_msg=_("Unable to create router %s") % router['name'])
+        lrouter['status'] = plugin_const.ACTIVE
+        return lrouter
+
+    def create_router(self, context, router):
+        # NOTE(salvatore-orlando): We completely override this method in
+        # order to be able to use the NVP ID as Neutron ID
+        # TODO(salvatore-orlando): Propose upstream patch for allowing
+        # 3rd parties to specify IDs as we do with l2 plugin
+        r = router['router']
+        has_gw_info = False
+        tenant_id = self._get_tenant_id_for_create(context, r)
+        # default value to set - nvp wants it (even if we don't have it)
+        nexthop = NVP_DEFAULT_NEXTHOP
+        # if external gateway info are set, then configure nexthop to
+        # default external gateway
+        if 'external_gateway_info' in r and r.get('external_gateway_info'):
+            has_gw_info = True
+            gw_info = r['external_gateway_info']
+            del r['external_gateway_info']
+            # The following DB read will be performed again when updating
+            # gateway info. This is not great, but still better than
+            # creating NVP router here and updating it later
+            network_id = (gw_info.get('network_id', None) if gw_info
+                          else None)
+            if network_id:
+                ext_net = self._get_network(context, network_id)
+                if not ext_net.external:
+                    msg = (_("Network '%s' is not a valid external "
+                             "network") % network_id)
+                    raise q_exc.BadRequest(resource='router', msg=msg)
+                if ext_net.subnets:
+                    ext_subnet = ext_net.subnets[0]
+                    nexthop = ext_subnet.gateway_ip
+        lrouter = self._create_lrouter(context, r, nexthop)
+        # Use NVP identfier for Neutron resource
+        r['id'] = lrouter['uuid']
+        # Update 'distributed' with value returned from NVP
+        # This will be useful for setting the value if the API request
+        # did not specify any value for the 'distributed' attribute
+        # Platforms older than 3.x do not support the attribute
+        r['distributed'] = lrouter.get('distributed', False)
 
         with context.session.begin(subtransactions=True):
             # Transaction nesting is needed to avoid foreign key violations
@@ -1459,14 +1469,23 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                                          tenant_id=tenant_id,
                                          name=r['name'],
                                          admin_state_up=r['admin_state_up'],
-                                         status="ACTIVE")
-                self._process_distributed_router_create(context, router_db, r)
+                                         status=lrouter['status'])
+                self._process_nsx_router_create(context, router_db, r)
                 context.session.add(router_db)
             if has_gw_info:
                 self._update_router_gw_info(context, router_db['id'], gw_info)
         router = self._make_router_dict(router_db)
         return router
 
+    def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
+        return nvplib.update_lrouter(
+            self.cluster, router_id, name,
+            nexthop, routes=routes)
+
+    def _update_lrouter_routes(self, router_id, routes):
+        nvplib.update_explicit_routes_lrouter(
+            self.cluster, router_id, routes)
+
     def update_router(self, context, router_id, router):
         # Either nexthop is updated or should be kept as it was before
         r = router['router']
@@ -1494,8 +1513,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                             "this must be updated through the default "
                             "gateway attribute")
                     raise q_exc.BadRequest(resource='router', msg=msg)
-            previous_routes = nvplib.update_lrouter(
-                self.cluster, router_id, r.get('name'),
+            previous_routes = self._update_lrouter(
+                context, router_id, r.get('name'),
                 nexthop, routes=r.get('routes'))
         # NOTE(salv-orlando): The exception handling below is not correct, but
         # unfortunately nvplib raises a neutron notfound exception when an
@@ -1525,8 +1544,11 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                 extraroute.RoutesExhausted):
             with excutils.save_and_reraise_exception():
                 # revert changes made to NVP
-                nvplib.update_explicit_routes_lrouter(
-                    self.cluster, router_id, previous_routes)
+                self._update_lrouter_routes(
+                    router_id, previous_routes)
+
+    def _delete_lrouter(self, context, id):
+        nvplib.delete_lrouter(self.cluster, id)
 
     def delete_router(self, context, router_id):
         with context.session.begin(subtransactions=True):
@@ -1544,7 +1566,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
             # allow an extra field for storing the cluster information
             # together with the resource
             try:
-                nvplib.delete_lrouter(self.cluster, router_id)
+                self._delete_lrouter(context, router_id)
             except q_exc.NotFound:
                 LOG.warning(_("Logical router '%s' not found "
                               "on NVP Platform"), router_id)
@@ -1553,6 +1575,27 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                     err_msg=(_("Unable to delete logical router '%s' "
                                "on NVP Platform") % router_id))
 
+    def _add_subnet_snat_rule(self, router, subnet):
+        gw_port = router.gw_port
+        if gw_port and router.enable_snat:
+            # There is a change gw_port might have multiple IPs
+            # In that case we will consider only the first one
+            if gw_port.get('fixed_ips'):
+                snat_ip = gw_port['fixed_ips'][0]['ip_address']
+                cidr_prefix = int(subnet['cidr'].split('/')[1])
+                nvplib.create_lrouter_snat_rule(
+                    self.cluster, router['id'], snat_ip, snat_ip,
+                    order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix,
+                    match_criteria={'source_ip_addresses': subnet['cidr']})
+
+    def _delete_subnet_snat_rule(self, router, subnet):
+        # Remove SNAT rule if external gateway is configured
+        if router.gw_port:
+            nvplib.delete_nat_rules_by_match(
+                self.cluster, router['id'], "SourceNatRule",
+                max_num_expected=1, min_num_expected=1,
+                source_ip_addresses=subnet['cidr'])
+
     def add_router_interface(self, context, router_id, interface_info):
         # When adding interface by port_id we need to create the
         # peer port on the nvp logical router in this routine
@@ -1587,17 +1630,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         # If there is an external gateway we need to configure the SNAT rule.
         # Fetch router from DB
         router = self._get_router(context, router_id)
-        gw_port = router.gw_port
-        if gw_port and router.enable_snat:
-            # There is a change gw_port might have multiple IPs
-            # In that case we will consider only the first one
-            if gw_port.get('fixed_ips'):
-                snat_ip = gw_port['fixed_ips'][0]['ip_address']
-                cidr_prefix = int(subnet['cidr'].split('/')[1])
-                nvplib.create_lrouter_snat_rule(
-                    self.cluster, router_id, snat_ip, snat_ip,
-                    order=NVP_EXTGW_NAT_RULES_ORDER - cidr_prefix,
-                    match_criteria={'source_ip_addresses': subnet['cidr']})
+        self._add_subnet_snat_rule(router, subnet)
         nvplib.create_lrouter_nosnat_rule(
             self.cluster, router_id,
             order=NVP_NOSNAT_RULES_ORDER,
@@ -1655,12 +1688,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
             if not subnet:
                 subnet = self._get_subnet(context, subnet_id)
             router = self._get_router(context, router_id)
-            # Remove SNAT rule if external gateway is configured
-            if router.gw_port:
-                nvplib.delete_nat_rules_by_match(
-                    self.cluster, router_id, "SourceNatRule",
-                    max_num_expected=1, min_num_expected=1,
-                    source_ip_addresses=subnet['cidr'])
+            self._delete_subnet_snat_rule(router, subnet)
             # Relax the minimum expected number as the nosnat rules
             # do not exist in 2.x deployments
             nvplib.delete_nat_rules_by_match(
@@ -1677,7 +1705,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                            "on NVP Platform")))
         return info
 
-    def _retrieve_and_delete_nat_rules(self, floating_ip_address,
+    def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
                                        internal_ip, router_id,
                                        min_num_rules_expected=0):
         try:
@@ -1720,12 +1748,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                                        ips_to_add=[],
                                        ips_to_remove=nvp_floating_ips)
 
-    def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
-        """Update floating IP association data.
-
-        Overrides method from base class.
-        The method is augmented for creating NAT rules in the process.
-        """
+    def _get_fip_assoc_data(self, context, fip, floatingip_db):
         if (('fixed_ip_address' in fip and fip['fixed_ip_address']) and
             not ('port_id' in fip and fip['port_id'])):
             msg = _("fixed_ip_address cannot be specified without a port_id")
@@ -1748,7 +1771,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                 fip,
                 floatingip_db['floating_network_id'])
 
-        floating_ip = floatingip_db['floating_ip_address']
         # Retrieve and delete existing NAT rules, if any
         if not router_id and floatingip_db.get('fixed_port_id'):
             # This happens if we're disassociating. Need to explicitly
@@ -1757,9 +1779,22 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
             tmp_fip['port_id'] = floatingip_db['fixed_port_id']
             _pid, internal_ip, router_id = self.get_assoc_data(
                 context, tmp_fip, floatingip_db['floating_network_id'])
+
+        return (port_id, internal_ip, router_id)
+
+    def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
+        """Update floating IP association data.
+
+        Overrides method from base class.
+        The method is augmented for creating NAT rules in the process.
+        """
+        port_id, internal_ip, router_id = self._get_fip_assoc_data(
+            context, fip, floatingip_db)
+        floating_ip = floatingip_db['floating_ip_address']
         # If there's no association router_id will be None
         if router_id:
-            self._retrieve_and_delete_nat_rules(floating_ip,
+            self._retrieve_and_delete_nat_rules(context,
+                                                floating_ip,
                                                 internal_ip,
                                                 router_id)
             # Fetch logical port of router's external gateway
@@ -1797,6 +1832,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                                     "internal ip:%(internal_ip)s"),
                                   {'floating_ip': floating_ip,
                                    'internal_ip': internal_ip})
+                    msg = _("Failed to update NAT rules for floatingip update")
                     raise nvp_exc.NvpPluginException(err_msg=msg)
             elif floatingip_db['fixed_port_id']:
                 # This is a disassociation.
@@ -1816,7 +1852,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         fip_db = self._get_floatingip(context, id)
         # Check whether the floating ip is associated or not
         if fip_db.fixed_port_id:
-            self._retrieve_and_delete_nat_rules(fip_db.floating_ip_address,
+            self._retrieve_and_delete_nat_rules(context,
+                                                fip_db.floating_ip_address,
                                                 fip_db.fixed_ip_address,
                                                 fip_db.router_id,
                                                 min_num_rules_expected=1)
@@ -1828,7 +1865,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
         try:
             fip_qry = context.session.query(l3_db.FloatingIP)
             fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
-            self._retrieve_and_delete_nat_rules(fip_db.floating_ip_address,
+            self._retrieve_and_delete_nat_rules(context,
+                                                fip_db.floating_ip_address,
                                                 fip_db.fixed_ip_address,
                                                 fip_db.router_id,
                                                 min_num_rules_expected=1)
diff --git a/neutron/plugins/nicira/NeutronServicePlugin.py b/neutron/plugins/nicira/NeutronServicePlugin.py
new file mode 100644 (file)
index 0000000..211be71
--- /dev/null
@@ -0,0 +1,808 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware, 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.
+#
+
+import netaddr
+from oslo.config import cfg
+from sqlalchemy.orm import exc as sa_exc
+
+from neutron.common import exceptions as q_exc
+from neutron.db import l3_db
+from neutron.openstack.common import log as logging
+from neutron.plugins.common import constants as service_constants
+from neutron.plugins.nicira.common import config  # noqa
+from neutron.plugins.nicira.dbexts import servicerouter as sr_db
+from neutron.plugins.nicira.dbexts import vcns_db
+from neutron.plugins.nicira.dbexts import vcns_models
+from neutron.plugins.nicira.extensions import servicerouter as sr
+from neutron.plugins.nicira import NeutronPlugin
+from neutron.plugins.nicira import NvpApiClient
+from neutron.plugins.nicira import nvplib
+from neutron.plugins.nicira.vshield.common import (
+    constants as vcns_const)
+from neutron.plugins.nicira.vshield.common.constants import RouterStatus
+from neutron.plugins.nicira.vshield.common import exceptions
+from neutron.plugins.nicira.vshield.tasks.constants import TaskStatus
+from neutron.plugins.nicira.vshield import vcns_driver
+
+LOG = logging.getLogger(__name__)
+
+ROUTER_TYPE_BASIC = 1
+ROUTER_TYPE_ADVANCED = 2
+
+ROUTER_STATUS = [
+    service_constants.ACTIVE,
+    service_constants.DOWN,
+    service_constants.PENDING_CREATE,
+    service_constants.PENDING_DELETE,
+    service_constants.ERROR
+]
+
+ROUTER_STATUS_LEVEL = {
+    service_constants.ACTIVE: RouterStatus.ROUTER_STATUS_ACTIVE,
+    service_constants.DOWN: RouterStatus.ROUTER_STATUS_DOWN,
+    service_constants.PENDING_CREATE: (
+        RouterStatus.ROUTER_STATUS_PENDING_CREATE
+    ),
+    service_constants.PENDING_DELETE: (
+        RouterStatus.ROUTER_STATUS_PENDING_DELETE
+    ),
+    service_constants.ERROR: RouterStatus.ROUTER_STATUS_ERROR
+}
+
+
+class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin,
+                        NeutronPlugin.NvpPluginV2):
+
+    supported_extension_aliases = (
+        NeutronPlugin.NvpPluginV2.supported_extension_aliases + [
+            'service-router'
+        ])
+
+    def __init__(self):
+        super(NvpAdvancedPlugin, self).__init__()
+
+        self._super_create_ext_gw_port = (
+            self._port_drivers['create'][l3_db.DEVICE_OWNER_ROUTER_GW])
+        self._super_delete_ext_gw_port = (
+            self._port_drivers['delete'][l3_db.DEVICE_OWNER_ROUTER_GW])
+
+        self._port_drivers['create'][l3_db.DEVICE_OWNER_ROUTER_GW] = (
+            self._vcns_create_ext_gw_port)
+        self._port_drivers['delete'][l3_db.DEVICE_OWNER_ROUTER_GW] = (
+            self._vcns_delete_ext_gw_port)
+
+        # cache router type based on router id
+        self._router_type = {}
+        self.callbacks = VcnsCallbacks(self)
+
+        # load the vCNS driver
+        self._load_vcns_drivers()
+
+    def _load_vcns_drivers(self):
+        self.vcns_driver = vcns_driver.VcnsDriver(self.callbacks)
+
+    def _set_router_type(self, router_id, router_type):
+        self._router_type[router_id] = router_type
+
+    def _get_router_type(self, context=None, router_id=None, router=None):
+        if not router:
+            if router_id in self._router_type:
+                return self._router_type[router_id]
+            router = self._get_router(context, router_id)
+
+        LOG.debug(_("EDGE: router = %s"), router)
+        if router['nsx_attributes']['service_router']:
+            router_type = ROUTER_TYPE_ADVANCED
+        else:
+            router_type = ROUTER_TYPE_BASIC
+        self._set_router_type(router['id'], router_type)
+        return router_type
+
+    def _find_router_type(self, router):
+        is_service_router = router.get(sr.SERVICE_ROUTER, False)
+        if is_service_router:
+            return ROUTER_TYPE_ADVANCED
+        else:
+            return ROUTER_TYPE_BASIC
+
+    def _is_advanced_service_router(self, context=None, router_id=None,
+                                    router=None):
+        if router:
+            router_type = self._get_router_type(router=router)
+        else:
+            router_type = self._get_router_type(context, router_id)
+        return (router_type == ROUTER_TYPE_ADVANCED)
+
+    def _vcns_create_ext_gw_port(self, context, port_data):
+        router_id = port_data['device_id']
+        if not self._is_advanced_service_router(context, router_id):
+            self._super_create_ext_gw_port(context, port_data)
+            return
+
+        # NOP for Edge because currently the port will be create internally
+        # by VSM
+        LOG.debug(_("EDGE: _vcns_create_ext_gw_port"))
+
+    def _vcns_delete_ext_gw_port(self, context, port_data):
+        router_id = port_data['device_id']
+        if not self._is_advanced_service_router(context, router_id):
+            self._super_delete_ext_gw_port(context, port_data)
+            return
+
+        # NOP for Edge
+        LOG.debug(_("EDGE: _vcns_delete_ext_gw_port"))
+
+    def _get_external_attachment_info(self, context, router):
+        gw_port = router.gw_port
+        ipaddress = None
+        netmask = None
+        nexthop = None
+
+        if gw_port:
+            # gw_port may have multiple IPs, only configure the first one
+            if gw_port.get('fixed_ips'):
+                ipaddress = gw_port['fixed_ips'][0]['ip_address']
+
+            network_id = gw_port.get('network_id')
+            if network_id:
+                ext_net = self._get_network(context, network_id)
+                if not ext_net.external:
+                    msg = (_("Network '%s' is not a valid external "
+                             "network") % network_id)
+                    raise q_exc.BadRequest(resource='router', msg=msg)
+                if ext_net.subnets:
+                    ext_subnet = ext_net.subnets[0]
+                    netmask = str(netaddr.IPNetwork(ext_subnet.cidr).netmask)
+                    nexthop = ext_subnet.gateway_ip
+
+        return (ipaddress, netmask, nexthop)
+
+    def _get_external_gateway_address(self, context, router):
+        ipaddress, netmask, nexthop = self._get_external_attachment_info(
+            context, router)
+        return nexthop
+
+    def _vcns_update_static_routes(self, context, **kwargs):
+        router = kwargs.get('router')
+        if router is None:
+            router = self._get_router(context, kwargs['router_id'])
+
+        edge_id = kwargs.get('edge_id')
+        if edge_id is None:
+            binding = vcns_db.get_vcns_router_binding(context.session,
+                                                      router['id'])
+            edge_id = binding['edge_id']
+
+        skippable = True
+        if 'nexthop' in kwargs:
+            nexthop = kwargs['nexthop']
+            # The default gateway and vnic config has dependencies, if we
+            # explicitly specify nexthop to change, tell the driver not to
+            # skip this route update
+            skippable = False
+        else:
+            nexthop = self._get_external_gateway_address(context,
+                                                         router)
+
+        if 'subnets' in kwargs:
+            subnets = kwargs['subnets']
+        else:
+            subnets = self._find_router_subnets_cidrs(context.elevated(),
+                                                      router['id'])
+
+        routes = []
+        for subnet in subnets:
+            routes.append({
+                'cidr': subnet,
+                'nexthop': vcns_const.INTEGRATION_LR_IPADDRESS.split('/')[0]
+            })
+        self.vcns_driver.update_routes(router['id'], edge_id, nexthop, routes,
+                                       skippable)
+
+    def _get_nat_rules(self, context, router):
+        fip_qry = context.session.query(l3_db.FloatingIP)
+        fip_db = fip_qry.filter_by(router_id=router['id']).all()
+
+        dnat = []
+        snat = []
+        for fip in fip_db:
+            if fip.fixed_port_id:
+                dnat.append({
+                    'dst': fip.floating_ip_address,
+                    'translated': fip.fixed_ip_address
+                })
+
+        gw_port = router.gw_port
+        if gw_port and router.enable_snat:
+            if gw_port.get('fixed_ips'):
+                snat_ip = gw_port['fixed_ips'][0]['ip_address']
+                subnets = self._find_router_subnets_cidrs(context.elevated(),
+                                                          router['id'])
+                for subnet in subnets:
+                    snat.append({
+                        'src': subnet,
+                        'translated': snat_ip
+                    })
+
+        return (snat, dnat)
+
+    def _update_nat_rules(self, context, router):
+        snat, dnat = self._get_nat_rules(context, router)
+        binding = vcns_db.get_vcns_router_binding(context.session,
+                                                  router['id'])
+        self.vcns_driver.update_nat_rules(router['id'],
+                                          binding['edge_id'],
+                                          snat, dnat)
+
+    def _update_interface(self, context, router):
+        addr, mask, nexthop = self._get_external_attachment_info(
+            context, router)
+
+        secondary = []
+        fip_qry = context.session.query(l3_db.FloatingIP)
+        fip_db = fip_qry.filter_by(router_id=router['id']).all()
+        for fip in fip_db:
+            if fip.fixed_port_id:
+                secondary.append(fip.floating_ip_address)
+
+        binding = vcns_db.get_vcns_router_binding(context.session,
+                                                  router['id'])
+        self.vcns_driver.update_interface(
+            router['id'], binding['edge_id'],
+            vcns_const.EXTERNAL_VNIC_INDEX,
+            self.vcns_driver.external_network,
+            addr, mask, secondary=secondary)
+
+    def _update_router_gw_info(self, context, router_id, info):
+        if not self._is_advanced_service_router(context, router_id):
+            super(NvpAdvancedPlugin, self)._update_router_gw_info(
+                context, router_id, info)
+            return
+
+        # get original gw_port config
+        router = self._get_router(context, router_id)
+        org_ext_net_id = router.gw_port_id and router.gw_port.network_id
+        org_enable_snat = router.enable_snat
+        orgaddr, orgmask, orgnexthop = self._get_external_attachment_info(
+            context, router)
+
+        super(NeutronPlugin.NvpPluginV2, self)._update_router_gw_info(
+            context, router_id, info, router=router)
+
+        new_ext_net_id = router.gw_port_id and router.gw_port.network_id
+        new_enable_snat = router.enable_snat
+        newaddr, newmask, newnexthop = self._get_external_attachment_info(
+            context, router)
+
+        binding = vcns_db.get_vcns_router_binding(context.session, router_id)
+
+        if new_ext_net_id != org_ext_net_id and orgnexthop:
+            # network changed, need to remove default gateway before vnic
+            # can be configured
+            LOG.debug(_("VCNS: delete default gateway %s"), orgnexthop)
+            self._vcns_update_static_routes(context,
+                                            router=router,
+                                            edge_id=binding['edge_id'],
+                                            nexthop=None)
+
+        if orgaddr != newaddr or orgmask != newmask:
+            self.vcns_driver.update_interface(
+                router_id, binding['edge_id'],
+                vcns_const.EXTERNAL_VNIC_INDEX,
+                self.vcns_driver.external_network,
+                newaddr, newmask)
+
+        if orgnexthop != newnexthop:
+            self._vcns_update_static_routes(context,
+                                            router=router,
+                                            edge_id=binding['edge_id'],
+                                            nexthop=newnexthop)
+
+        if (new_ext_net_id == org_ext_net_id and
+            org_enable_snat == new_enable_snat):
+            return
+
+        self._update_nat_rules(context, router)
+
+    def _add_subnet_snat_rule(self, router, subnet):
+        # NOP for service router
+        if not self._is_advanced_service_router(router=router):
+            super(NvpAdvancedPlugin, self)._add_subnet_snat_rule(
+                router, subnet)
+
+    def _delete_subnet_snat_rule(self, router, subnet):
+        # NOP for service router
+        if not self._is_advanced_service_router(router=router):
+            super(NvpAdvancedPlugin, self)._delete_subnet_snat_rule(
+                router, subnet)
+
+    def _remove_floatingip_address(self, context, fip_db):
+        # NOP for service router
+        router_id = fip_db.router_id
+        if not self._is_advanced_service_router(context, router_id):
+            super(NvpAdvancedPlugin, self)._remove_floatingip_address(
+                context, fip_db)
+
+    def _create_advanced_service_router(self, context, name, lrouter, lswitch):
+
+        # store binding
+        binding = vcns_db.add_vcns_router_binding(
+            context.session, lrouter['uuid'], None, lswitch['uuid'],
+            service_constants.PENDING_CREATE)
+
+        # deploy edge
+        jobdata = {
+            'lrouter': lrouter,
+            'lswitch': lswitch,
+            'context': context
+        }
+
+        # deploy and wait until the deploy requeste has been requested
+        # so we will have edge_id ready. The wait here should be fine
+        # as we're not in a database transaction now
+        self.vcns_driver.deploy_edge(
+            lrouter['uuid'], name, lswitch['uuid'], jobdata=jobdata,
+            wait_for_exec=True)
+
+        return binding
+
+    def _create_integration_lswitch(self, tenant_id, name):
+        # use defautl transport zone
+        transport_zone_config = [{
+            "zone_uuid": self.cluster.default_tz_uuid,
+            "transport_type": cfg.CONF.NVP.default_transport_type
+        }]
+        return self.vcns_driver.create_lswitch(name, transport_zone_config)
+
+    def _add_router_integration_interface(self, tenant_id, name,
+                                          lrouter, lswitch):
+        # create logic switch port
+        try:
+            ls_port = nvplib.create_lport(
+                self.cluster, lswitch['uuid'], tenant_id,
+                '', '', lrouter['uuid'], True)
+        except NvpApiClient.NvpApiException:
+            msg = (_("An exception occured while creating a port "
+                     "on lswitch %s") % lswitch['uuid'])
+            LOG.exception(msg)
+            raise q_exc.NeutronException(message=msg)
+
+        # create logic router port
+        try:
+            neutron_port_id = ''
+            pname = name[:36] + '-lp'
+            admin_status_enabled = True
+            lr_port = nvplib.create_router_lport(
+                self.cluster, lrouter['uuid'], tenant_id,
+                neutron_port_id, pname, admin_status_enabled,
+                [vcns_const.INTEGRATION_LR_IPADDRESS])
+        except NvpApiClient.NvpApiException:
+            msg = (_("Unable to create port on NVP logical router %s") % name)
+            LOG.exception(msg)
+            nvplib.delete_port(self.cluster, lswitch['uuid'], ls_port['uuid'])
+            raise q_exc.NeutronException(message=msg)
+
+        # attach logic router port to switch port
+        try:
+            self._update_router_port_attachment(
+                self.cluster, None, lrouter['uuid'], {}, lr_port['uuid'],
+                'PatchAttachment', ls_port['uuid'], None)
+        except NvpApiClient.NvpApiException as e:
+            # lr_port should have been deleted
+            nvplib.delete_port(self.cluster, lswitch['uuid'], ls_port['uuid'])
+            raise e
+
+    def _create_lrouter(self, context, router, nexthop):
+        lrouter = super(NvpAdvancedPlugin, self)._create_lrouter(
+            context, router, vcns_const.INTEGRATION_EDGE_IPADDRESS)
+
+        router_type = self._find_router_type(router)
+        self._set_router_type(lrouter['uuid'], router_type)
+        if router_type == ROUTER_TYPE_BASIC:
+            return lrouter
+
+        tenant_id = self._get_tenant_id_for_create(context, router)
+        name = router['name']
+        try:
+            lsname = name[:36] + '-ls'
+            lswitch = self._create_integration_lswitch(
+                tenant_id, lsname)
+        except Exception:
+            msg = _("Unable to create integration logic switch "
+                    "for router %s") % name
+            LOG.exception(msg)
+            nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
+            raise q_exc.NeutronException(message=msg)
+
+        try:
+            self._add_router_integration_interface(tenant_id, name,
+                                                   lrouter, lswitch)
+        except Exception:
+            msg = _("Unable to add router interface to integration lswitch "
+                    "for router %s") % name
+            LOG.exception(msg)
+            nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
+            raise q_exc.NeutronException(message=msg)
+
+        try:
+            self._create_advanced_service_router(
+                context, name, lrouter, lswitch)
+        except Exception:
+            msg = (_("Unable to create advance service router for %s") % name)
+            LOG.exception(msg)
+            self.vcns_driver.delete_lswitch(lswitch('uuid'))
+            nvplib.delete_lrouter(self.cluster, lrouter['uuid'])
+            raise q_exc.NeutronException(message=msg)
+
+        lrouter['status'] = service_constants.PENDING_CREATE
+        return lrouter
+
+    def _delete_lrouter(self, context, id):
+        if not self._is_advanced_service_router(context, id):
+            super(NvpAdvancedPlugin, self)._delete_lrouter(context, id)
+            if id in self._router_type:
+                del self._router_type[id]
+            return
+
+        binding = vcns_db.get_vcns_router_binding(context.session, id)
+        if binding:
+            vcns_db.update_vcns_router_binding(
+                context.session, id, status=service_constants.PENDING_DELETE)
+
+            lswitch_id = binding['lswitch_id']
+            edge_id = binding['edge_id']
+
+            # delete lswitch
+            try:
+                self.vcns_driver.delete_lswitch(lswitch_id)
+            except exceptions.ResourceNotFound:
+                LOG.warning(_("Did not found lswitch %s in NVP"), lswitch_id)
+
+            # delete edge
+            jobdata = {
+                'context': context
+            }
+            self.vcns_driver.delete_edge(id, edge_id, jobdata=jobdata)
+
+        # delete LR
+        nvplib.delete_lrouter(self.cluster, id)
+        if id in self._router_type:
+            del self._router_type[id]
+
+    def _update_lrouter(self, context, router_id, name, nexthop, routes=None):
+        if not self._is_advanced_service_router(context, router_id):
+            return super(NvpAdvancedPlugin, self)._update_lrouter(
+                context, router_id, name, nexthop, routes=routes)
+
+        previous_routes = super(NvpAdvancedPlugin, self)._update_lrouter(
+            context, router_id, name,
+            vcns_const.INTEGRATION_EDGE_IPADDRESS, routes=routes)
+
+        # TODO(fank): Theoretically users can specify extra routes for
+        # physical network, and routes for phyiscal network needs to be
+        # configured on Edge. This can be done by checking if nexthop is in
+        # external network. But for now we only handle routes for logic
+        # space and leave it for future enhancement.
+
+        # Let _update_router_gw_info handle nexthop change
+        #self._vcns_update_static_routes(context, router_id=router_id)
+
+        return previous_routes
+
+    def _retrieve_and_delete_nat_rules(self, context, floating_ip_address,
+                                       internal_ip, router_id,
+                                       min_num_rules_expected=0):
+        # NOP for advanced service router
+        if not self._is_advanced_service_router(context, router_id):
+            super(NvpAdvancedPlugin, self)._retrieve_and_delete_nat_rules(
+                context, floating_ip_address, internal_ip, router_id,
+                min_num_rules_expected=min_num_rules_expected)
+
+    def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
+        # Update DB model only  for advanced service router
+        router_id = self._get_fip_assoc_data(context, fip, floatingip_db)[2]
+        if (router_id and
+            not self._is_advanced_service_router(context, router_id)):
+            super(NvpAdvancedPlugin, self)._update_fip_assoc(
+                context, fip, floatingip_db, external_port)
+        else:
+            super(NeutronPlugin.NvpPluginV2, self)._update_fip_assoc(
+                context, fip, floatingip_db, external_port)
+
+    def _get_nvp_lrouter_status(self, id):
+        try:
+            lrouter = nvplib.get_lrouter(self.cluster, id)
+            lr_status = lrouter["_relations"]["LogicalRouterStatus"]
+            if lr_status["fabric_status"]:
+                nvp_status = RouterStatus.ROUTER_STATUS_ACTIVE
+            else:
+                nvp_status = RouterStatus.ROUTER_STATUS_DOWN
+        except q_exc.NotFound:
+            nvp_status = RouterStatus.ROUTER_STATUS_ERROR
+
+        return nvp_status
+
+    def _get_vse_status(self, context, id):
+        binding = vcns_db.get_vcns_router_binding(context.session, id)
+
+        edge_status_level = self.vcns_driver.get_edge_status(
+            binding['edge_id'])
+        edge_db_status_level = ROUTER_STATUS_LEVEL[binding.status]
+
+        if edge_status_level > edge_db_status_level:
+            return edge_status_level
+        else:
+            return edge_db_status_level
+
+    def _get_all_nvp_lrouters_statuses(self, tenant_id, fields):
+        # get nvp lrouters status
+        nvp_lrouters = nvplib.get_lrouters(self.cluster,
+                                           tenant_id,
+                                           fields)
+
+        nvp_status = {}
+        for nvp_lrouter in nvp_lrouters:
+            if (nvp_lrouter["_relations"]["LogicalRouterStatus"]
+                ["fabric_status"]):
+                nvp_status[nvp_lrouter['uuid']] = (
+                    RouterStatus.ROUTER_STATUS_ACTIVE
+                )
+            else:
+                nvp_status[nvp_lrouter['uuid']] = (
+                    RouterStatus.ROUTER_STATUS_DOWN
+                )
+
+        return nvp_status
+
+    def _get_all_vse_statuses(self, context):
+        bindings = self._model_query(
+            context, vcns_models.VcnsRouterBinding)
+
+        vse_db_status_level = {}
+        edge_id_to_router_id = {}
+        router_ids = []
+        for binding in bindings:
+            if not binding['edge_id']:
+                continue
+            router_id = binding['router_id']
+            router_ids.append(router_id)
+            edge_id_to_router_id[binding['edge_id']] = router_id
+            vse_db_status_level[router_id] = (
+                ROUTER_STATUS_LEVEL[binding['status']])
+
+        if not vse_db_status_level:
+            # no advanced service router, no need to query
+            return {}
+
+        vse_status_level = {}
+        edges_status_level = self.vcns_driver.get_edges_statuses()
+        for edge_id, status_level in edges_status_level.iteritems():
+            if edge_id in edge_id_to_router_id:
+                router_id = edge_id_to_router_id[edge_id]
+                db_status_level = vse_db_status_level[router_id]
+                if status_level > db_status_level:
+                    vse_status_level[router_id] = status_level
+                else:
+                    vse_status_level[router_id] = db_status_level
+
+        return vse_status_level
+
+    def get_router(self, context, id, fields=None):
+        if fields and 'status' not in fields:
+            return super(NvpAdvancedPlugin, self).get_router(
+                context, id, fields=fields)
+
+        router = super(NvpAdvancedPlugin, self).get_router(context, id)
+
+        router_type = self._find_router_type(router)
+        if router_type == ROUTER_TYPE_ADVANCED:
+            vse_status_level = self._get_vse_status(context, id)
+            if vse_status_level > ROUTER_STATUS_LEVEL[router['status']]:
+                router['status'] = ROUTER_STATUS[vse_status_level]
+
+        return self._fields(router, fields)
+
+    def get_routers(self, context, filters=None, fields=None, **kwargs):
+        routers = super(NvpAdvancedPlugin, self).get_routers(
+            context, filters=filters, **kwargs)
+
+        if fields and 'status' not in fields:
+            # no status checking, just return regular get_routers
+            return [self._fields(router, fields) for router in routers]
+
+        for router in routers:
+            router_type = self._find_router_type(router)
+            if router_type == ROUTER_TYPE_ADVANCED:
+                break
+        else:
+            # no advanced service router, return here
+            return [self._fields(router, fields) for router in routers]
+
+        vse_status_all = self._get_all_vse_statuses(context)
+        for router in routers:
+            router_type = self._find_router_type(router)
+            if router_type == ROUTER_TYPE_ADVANCED:
+                vse_status_level = vse_status_all.get(router['id'])
+                if vse_status_level is None:
+                    vse_status_level = RouterStatus.ROUTER_STATUS_ERROR
+                if vse_status_level > ROUTER_STATUS_LEVEL[router['status']]:
+                    router['status'] = ROUTER_STATUS[vse_status_level]
+
+        return [self._fields(router, fields) for router in routers]
+
+    def add_router_interface(self, context, router_id, interface_info):
+        info = super(NvpAdvancedPlugin, self).add_router_interface(
+            context, router_id, interface_info)
+        if self._is_advanced_service_router(context, router_id):
+            router = self._get_router(context, router_id)
+            if router.enable_snat:
+                self._update_nat_rules(context, router)
+            # TODO(fank): do rollback if error, or have a dedicated thread
+            # do sync work (rollback, re-configure, or make router down)
+            self._vcns_update_static_routes(context, router=router)
+        return info
+
+    def remove_router_interface(self, context, router_id, interface_info):
+        info = super(NvpAdvancedPlugin, self).remove_router_interface(
+            context, router_id, interface_info)
+        if self._is_advanced_service_router(context, router_id):
+            router = self._get_router(context, router_id)
+            if router.enable_snat:
+                self._update_nat_rules(context, router)
+            # TODO(fank): do rollback if error, or have a dedicated thread
+            # do sync work (rollback, re-configure, or make router down)
+            self._vcns_update_static_routes(context, router=router)
+        return info
+
+    def create_floatingip(self, context, floatingip):
+        fip = super(NvpAdvancedPlugin, self).create_floatingip(
+            context, floatingip)
+        router_id = fip.get('router_id')
+        if router_id and self._is_advanced_service_router(context, router_id):
+            router = self._get_router(context, router_id)
+            # TODO(fank): do rollback if error, or have a dedicated thread
+            # do sync work (rollback, re-configure, or make router down)
+            self._update_interface(context, router)
+            self._update_nat_rules(context, router)
+        return fip
+
+    def update_floatingip(self, context, id, floatingip):
+        fip = super(NvpAdvancedPlugin, self).update_floatingip(
+            context, id, floatingip)
+        router_id = fip.get('router_id')
+        if router_id and self._is_advanced_service_router(context, router_id):
+            router = self._get_router(context, router_id)
+            # TODO(fank): do rollback if error, or have a dedicated thread
+            # do sync work (rollback, re-configure, or make router down)
+            self._update_interface(context, router)
+            self._update_nat_rules(context, router)
+        return fip
+
+    def delete_floatingip(self, context, id):
+        fip_db = self._get_floatingip(context, id)
+        router_id = None
+        if fip_db.fixed_port_id:
+            router_id = fip_db.router_id
+        super(NvpAdvancedPlugin, self).delete_floatingip(context, id)
+        if router_id and self._is_advanced_service_router(context, router_id):
+            router = self._get_router(context, router_id)
+            # TODO(fank): do rollback if error, or have a dedicated thread
+            # do sync work (rollback, re-configure, or make router down)
+            self._update_interface(context, router)
+            self._update_nat_rules(context, router)
+
+    def disassociate_floatingips(self, context, port_id):
+        try:
+            fip_qry = context.session.query(l3_db.FloatingIP)
+            fip_db = fip_qry.filter_by(fixed_port_id=port_id).one()
+            router_id = fip_db.router_id
+        except sa_exc.NoResultFound:
+            router_id = None
+        super(NvpAdvancedPlugin, self).disassociate_floatingips(context,
+                                                                port_id)
+        if router_id and self._is_advanced_service_router(context, router_id):
+            router = self._get_router(context, router_id)
+            # TODO(fank): do rollback if error, or have a dedicated thread
+            # do sync work (rollback, re-configure, or make router down)
+            self._update_interface(context, router)
+            self._update_nat_rules(context, router)
+
+
+class VcnsCallbacks(object):
+    """Edge callback implementation
+
+    Callback functions for asynchronous tasks
+    """
+    def __init__(self, plugin):
+        self.plugin = plugin
+
+    def edge_deploy_started(self, task):
+        """callback when deployment task started."""
+        jobdata = task.userdata['jobdata']
+        lrouter = jobdata['lrouter']
+        context = jobdata['context']
+        edge_id = task.userdata.get('edge_id')
+        name = task.userdata['router_name']
+        if edge_id:
+            LOG.debug(_("Start deploying %(edge_id)s for router %(name)s"), {
+                'edge_id': edge_id,
+                'name': name})
+            vcns_db.update_vcns_router_binding(
+                context.session, lrouter['uuid'], edge_id=edge_id)
+        else:
+                LOG.debug(_("Failed to deploy Edge for router %s"), name)
+                vcns_db.update_vcns_router_binding(
+                    context.session, lrouter['uuid'],
+                    status=service_constants.ERROR)
+
+    def edge_deploy_result(self, task):
+        """callback when deployment task finished."""
+        jobdata = task.userdata['jobdata']
+        lrouter = jobdata['lrouter']
+        context = jobdata['context']
+        name = task.userdata['router_name']
+        router_db = self.plugin._get_router(context, lrouter['uuid'])
+        if task.status == TaskStatus.COMPLETED:
+            LOG.debug(_("Successfully deployed %(edge_id)s for "
+                        "router %(name)s"), {
+                            'edge_id': task.userdata['edge_id'],
+                            'name': name})
+            if router_db['status'] == service_constants.PENDING_CREATE:
+                router_db['status'] = service_constants.ACTIVE
+                binding = vcns_db.get_vcns_router_binding(
+                    context.session, lrouter['uuid'])
+                # only update status to active if its status is pending create
+                if binding['status'] == service_constants.PENDING_CREATE:
+                    vcns_db.update_vcns_router_binding(
+                        context.session, lrouter['uuid'],
+                        status=service_constants.ACTIVE)
+        else:
+            LOG.debug(_("Failed to deploy Edge for router %s"), name)
+            router_db['status'] = service_constants.ERROR
+            vcns_db.update_vcns_router_binding(
+                context.session, lrouter['uuid'],
+                status=service_constants.ERROR)
+
+    def edge_delete_result(self, task):
+        jobdata = task.userdata['jobdata']
+        router_id = task.userdata['router_id']
+        context = jobdata['context']
+        if task.status == TaskStatus.COMPLETED:
+            vcns_db.delete_vcns_router_binding(context.session,
+                                               router_id)
+
+    def interface_update_result(self, task):
+        LOG.debug(_("interface_update_result %d"), task.status)
+
+    def snat_create_result(self, task):
+        LOG.debug(_("snat_create_result %d"), task.status)
+
+    def snat_delete_result(self, task):
+        LOG.debug(_("snat_delete_result %d"), task.status)
+
+    def dnat_create_result(self, task):
+        LOG.debug(_("dnat_create_result %d"), task.status)
+
+    def dnat_delete_result(self, task):
+        LOG.debug(_("dnat_delete_result %d"), task.status)
+
+    def routes_update_result(self, task):
+        LOG.debug(_("routes_update_result %d"), task.status)
+
+    def nat_update_result(self, task):
+        LOG.debug(_("nat_update_result %d"), task.status)
index 6856e8b355237b432a38e63316f0ce743e2eecca..c1d3bfbc4aada0be14b3e4a4b73305d976c84099 100644 (file)
 # @author: Salvatore Orlando, Nicira, Inc
 #
 
-from neutron.db import db_base_plugin_v2
-from neutron.extensions import l3
-from neutron.openstack.common import log as logging
-from neutron.plugins.nicira.dbexts import nicira_models
+from neutron.plugins.nicira.dbexts import nsxrouter
 from neutron.plugins.nicira.extensions import distributedrouter as dist_rtr
 
-LOG = logging.getLogger(__name__)
 
-
-class DistributedRouter_mixin(object):
+class DistributedRouter_mixin(nsxrouter.NsxRouterMixin):
     """Mixin class to enable distributed router support."""
 
-    def _extend_router_dict_distributed(self, router_res, router_db):
-        # Avoid setting attribute to None for routers already existing before
-        # the data model was extended with the distributed attribute
-        nsx_attrs = router_db['nsx_attributes']
-        # Return False if nsx attributes are not definied for this
-        # neutron router
-        router_res[dist_rtr.DISTRIBUTED] = (
-            nsx_attrs and nsx_attrs['distributed'] or False)
-
-    def _process_distributed_router_create(
-        self, context, router_db, router_req):
-        """Ensures persistency for the 'distributed' attribute.
-
-        Either creates or fetches the nicira extended attributes
-        record for this router and stores the 'distributed'
-        attribute value.
-        This method should be called from within a transaction, as
-        it does not start a new one.
-        """
-        if not router_db['nsx_attributes']:
-            nsx_attributes = nicira_models.NSXRouterExtAttributes(
-                router_id=router_db['id'],
-                distributed=router_req['distributed'])
-            context.session.add(nsx_attributes)
-            router_db['nsx_attributes'] = nsx_attributes
-        else:
-            # The situation where the record already exists will
-            # be likely once the NSXRouterExtAttributes model
-            # will allow for defining several attributes pertaining
-            # to different extensions
-            router_db['nsx_attributes']['distributed'] = (
-                router_req['distributed'])
-        LOG.debug(_("Distributed router extension successfully processed "
-                    "for router:%s"), router_db['id'])
-
-    # Register dict extend functions for ports
-    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
-        l3.ROUTERS, ['_extend_router_dict_distributed'])
+    nsx_attributes = (
+        nsxrouter.NsxRouterMixin.nsx_attributes + [{
+            'name': dist_rtr.DISTRIBUTED,
+            'default': False
+        }])
index d6a21880c9163927f785c0de96d303aa5498013d..ef7b01d9ca5856e1f4be0c231f67b487f1d65254 100644 (file)
@@ -89,6 +89,7 @@ class NSXRouterExtAttributes(model_base.BASEV2):
                        ForeignKey('routers.id', ondelete="CASCADE"),
                        primary_key=True)
     distributed = Column(Boolean, default=False, nullable=False)
+    service_router = Column(Boolean, default=False, nullable=False)
     # Add a relationship to the Router model in order to instruct
     # SQLAlchemy to eagerly load this association
     router = orm.relationship(
diff --git a/neutron/plugins/nicira/dbexts/nsxrouter.py b/neutron/plugins/nicira/dbexts/nsxrouter.py
new file mode 100644 (file)
index 0000000..de00ec9
--- /dev/null
@@ -0,0 +1,70 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira Networks, 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: Salvatore Orlando, Nicira, Inc
+#
+
+from neutron.db import db_base_plugin_v2
+from neutron.extensions import l3
+from neutron.openstack.common import log as logging
+from neutron.plugins.nicira.dbexts import nicira_models
+
+LOG = logging.getLogger(__name__)
+
+
+class NsxRouterMixin(object):
+    """Mixin class to enable nsx router support."""
+
+    nsx_attributes = []
+
+    def _extend_nsx_router_dict(self, router_res, router_db):
+        nsx_attrs = router_db['nsx_attributes']
+        # Return False if nsx attributes are not definied for this
+        # neutron router
+        for attr in self.nsx_attributes:
+            name = attr['name']
+            default = attr['default']
+            router_res[name] = (
+                nsx_attrs and nsx_attrs[name] or default)
+
+    def _process_nsx_router_create(
+        self, context, router_db, router_req):
+        if not router_db['nsx_attributes']:
+            kwargs = {}
+            for attr in self.nsx_attributes:
+                name = attr['name']
+                default = attr['default']
+                kwargs[name] = router_req.get(name, default)
+            nsx_attributes = nicira_models.NSXRouterExtAttributes(
+                router_id=router_db['id'], **kwargs)
+            context.session.add(nsx_attributes)
+            router_db['nsx_attributes'] = nsx_attributes
+        else:
+            # The situation where the record already exists will
+            # be likely once the NSXRouterExtAttributes model
+            # will allow for defining several attributes pertaining
+            # to different extensions
+            for attr in self.nsx_attributes:
+                name = attr['name']
+                default = attr['default']
+                router_db['nsx_attributes'][name] = router_req.get(
+                    name, default)
+        LOG.debug(_("Nsx router extension successfully processed "
+                    "for router:%s"), router_db['id'])
+
+    # Register dict extend functions for ports
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        l3.ROUTERS, ['_extend_nsx_router_dict'])
diff --git a/neutron/plugins/nicira/dbexts/servicerouter.py b/neutron/plugins/nicira/dbexts/servicerouter.py
new file mode 100644 (file)
index 0000000..9cba253
--- /dev/null
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware, 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.
+#
+
+from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr
+from neutron.plugins.nicira.extensions import servicerouter
+
+
+class ServiceRouter_mixin(dist_rtr.DistributedRouter_mixin):
+    """Mixin class to enable service router support."""
+
+    nsx_attributes = (
+        dist_rtr.DistributedRouter_mixin.nsx_attributes + [{
+            'name': servicerouter.SERVICE_ROUTER,
+            'default': False
+        }])
diff --git a/neutron/plugins/nicira/dbexts/vcns_db.py b/neutron/plugins/nicira/dbexts/vcns_db.py
new file mode 100644 (file)
index 0000000..9d7daaa
--- /dev/null
@@ -0,0 +1,50 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira, 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.
+
+from neutron.plugins.nicira.dbexts import vcns_models
+
+
+def add_vcns_router_binding(session, router_id, vse_id, lswitch_id, status):
+    with session.begin(subtransactions=True):
+        binding = vcns_models.VcnsRouterBinding(
+            router_id=router_id,
+            edge_id=vse_id,
+            lswitch_id=lswitch_id,
+            status=status)
+        session.add(binding)
+        return binding
+
+
+def get_vcns_router_binding(session, router_id):
+    with session.begin(subtransactions=True):
+        return (session.query(vcns_models.VcnsRouterBinding).
+                filter_by(router_id=router_id).first())
+
+
+def update_vcns_router_binding(session, router_id, **kwargs):
+    with session.begin(subtransactions=True):
+        binding = (session.query(vcns_models.VcnsRouterBinding).
+                   filter_by(router_id=router_id).one())
+        for key, value in kwargs.iteritems():
+            binding[key] = value
+
+
+def delete_vcns_router_binding(session, router_id):
+    with session.begin(subtransactions=True):
+        binding = (session.query(vcns_models.VcnsRouterBinding).
+                   filter_by(router_id=router_id).one())
+        session.delete(binding)
diff --git a/neutron/plugins/nicira/dbexts/vcns_models.py b/neutron/plugins/nicira/dbexts/vcns_models.py
new file mode 100644 (file)
index 0000000..fe96d24
--- /dev/null
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira, 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.
+
+
+import sqlalchemy as sa
+
+from neutron.db import model_base
+from neutron.db import models_v2
+
+
+class VcnsRouterBinding(model_base.BASEV2, models_v2.HasStatusDescription):
+    """Represents the mapping between neutron router and vShield Edge."""
+
+    __tablename__ = 'vcns_router_bindings'
+
+    # no ForeignKey to routers.id because for now, a router can be removed
+    # from routers when delete_router is executed, but the binding is only
+    # removed after the Edge is deleted
+    router_id = sa.Column(sa.String(36),
+                          primary_key=True)
+    edge_id = sa.Column(sa.String(16),
+                        nullable=True)
+    lswitch_id = sa.Column(sa.String(36),
+                           nullable=False)
diff --git a/neutron/plugins/nicira/extensions/servicerouter.py b/neutron/plugins/nicira/extensions/servicerouter.py
new file mode 100644 (file)
index 0000000..ed863f0
--- /dev/null
@@ -0,0 +1,61 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 VMware, 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: Kaiwei Fan, VMware, Inc
+
+
+from neutron.api import extensions
+from neutron.api.v2 import attributes
+
+
+SERVICE_ROUTER = 'service_router'
+EXTENDED_ATTRIBUTES_2_0 = {
+    'routers': {
+        SERVICE_ROUTER: {'allow_post': True, 'allow_put': False,
+                         'convert_to': attributes.convert_to_boolean,
+                         'default': False, 'is_visible': True},
+    }
+}
+
+
+class Servicerouter(extensions.ExtensionDescriptor):
+    """Extension class supporting advanced service router."""
+
+    @classmethod
+    def get_name(cls):
+        return "Service Router"
+
+    @classmethod
+    def get_alias(cls):
+        return "service-router"
+
+    @classmethod
+    def get_description(cls):
+        return "Provides service router"
+
+    @classmethod
+    def get_namespace(cls):
+        return ""
+
+    @classmethod
+    def get_updated(cls):
+        return "2013-08-08T00:00:00-00:00"
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return EXTENDED_ATTRIBUTES_2_0
+        else:
+            return {}
index 026744a8b56bdae0af8155e4378a7cb2838320bd..246cba5873781cc481b0c6dc2efeda7a3b1475fc 100644 (file)
@@ -506,6 +506,7 @@ class EdgeApplianceDriver(object):
         LOG.debug(_("VCNS: start updating nat rules: %s"), rules)
 
         nat = {
+            'featureType': 'nat',
             'rules': {
                 'natRulesDtos': rules
             }
@@ -565,20 +566,18 @@ class EdgeApplianceDriver(object):
         static_routes = []
         for route in routes:
             static_routes.append({
-                "route": {
-                    "description": "",
-                    "vnic": vcns_const.INTERNAL_VNIC_INDEX,
-                    "network": route['cidr'],
-                    "nextHop": route['nexthop']
-                }
+                "description": "",
+                "vnic": vcns_const.INTERNAL_VNIC_INDEX,
+                "network": route['cidr'],
+                "nextHop": route['nexthop']
             })
         request = {
-            "staticRouting": {
-                "staticRoutes": static_routes,
+            "staticRoutes": {
+                "staticRoutes": static_routes
             }
         }
         if gateway:
-            request["staticRouting"]["defaultRoute"] = {
+            request["defaultRoute"] = {
                 "description": "default-gateway",
                 "gatewayAddress": gateway,
                 "vnic": vcns_const.EXTERNAL_VNIC_INDEX
index 72e5e858bc88ce455f75f0afb809e66aa78e7f67..b02a97c2c9bbc182720324ba13522bb3f8583c28 100644 (file)
@@ -37,9 +37,6 @@ class Vcns(object):
         self.password = password
         self.jsonapi_client = VcnsApiClient.VcnsApiHelper(address, user,
                                                           password, 'json')
-        # TODO(fank): remove this after json syntax is fixed on VSM
-        self.xmlapi_client = VcnsApiClient.VcnsApiHelper(address, user,
-                                                         password, 'xml')
 
     def do_request(self, method, uri, params=None, format='json', **kwargs):
         LOG.debug(_("VcnsApiHelper('%(method)s', '%(uri)s', '%(body)s')"), {
@@ -100,7 +97,7 @@ class Vcns(object):
 
     def update_routes(self, edge_id, routes):
         uri = "%s/%s/routing/config/static" % (URI_PREFIX, edge_id)
-        return self.do_request(HTTP_PUT, uri, routes, format='xml')
+        return self.do_request(HTTP_PUT, uri, routes)
 
     def create_lswitch(self, lsconfig):
         uri = "/api/ws.v1/lswitch"
index c4a742471b96044f1081d24def2af1115eba78e5..62b27ecf98ed208eb3e499cd5ff9e745c2a985fa 100644 (file)
@@ -20,10 +20,12 @@ import os
 import neutron.plugins.nicira.api_client.client_eventlet as client
 from neutron.plugins.nicira import extensions
 import neutron.plugins.nicira.NeutronPlugin as plugin
+import neutron.plugins.nicira.NeutronServicePlugin as service_plugin
 import neutron.plugins.nicira.NvpApiClient as nvpapi
 from neutron.plugins.nicira.vshield import vcns
 
 nvp_plugin = plugin.NvpPluginV2
+nvp_service_plugin = service_plugin.NvpAdvancedPlugin
 api_helper = nvpapi.NVPApiHelper
 nvp_client = client.NvpApiClientEventlet
 vcns_class = vcns.Vcns
@@ -32,6 +34,8 @@ STUBS_PATH = os.path.join(os.path.dirname(__file__), 'etc')
 NVPEXT_PATH = os.path.dirname(extensions.__file__)
 NVPAPI_NAME = '%s.%s' % (api_helper.__module__, api_helper.__name__)
 PLUGIN_NAME = '%s.%s' % (nvp_plugin.__module__, nvp_plugin.__name__)
+SERVICE_PLUGIN_NAME = '%s.%s' % (nvp_service_plugin.__module__,
+                                 nvp_service_plugin.__name__)
 CLIENT_NAME = '%s.%s' % (nvp_client.__module__, nvp_client.__name__)
 VCNS_NAME = '%s.%s' % (vcns_class.__module__, vcns_class.__name__)
 
diff --git a/neutron/tests/unit/nicira/test_edge_router.py b/neutron/tests/unit/nicira/test_edge_router.py
new file mode 100644 (file)
index 0000000..fba4c28
--- /dev/null
@@ -0,0 +1,205 @@
+# Copyright (c) 2013 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.
+
+import copy
+
+from eventlet import greenthread
+import mock
+from oslo.config import cfg
+
+from neutron.api.v2 import attributes
+from neutron import context
+from neutron.extensions import l3
+from neutron.manager import NeutronManager
+from neutron.openstack.common import uuidutils
+from neutron.tests.unit.nicira import NVPEXT_PATH
+from neutron.tests.unit.nicira import SERVICE_PLUGIN_NAME
+from neutron.tests.unit.nicira import test_nicira_plugin
+from neutron.tests.unit.nicira import VCNS_NAME
+from neutron.tests.unit.nicira.vshield import fake_vcns
+
+_uuid = uuidutils.generate_uuid
+
+
+class ServiceRouterTestExtensionManager(object):
+
+    def get_resources(self):
+        # If l3 resources have been loaded and updated by main API
+        # router, update the map in the l3 extension so it will load
+        # the same attributes as the API router
+        l3_attr_map = copy.deepcopy(l3.RESOURCE_ATTRIBUTE_MAP)
+        for res in l3.RESOURCE_ATTRIBUTE_MAP.keys():
+            attr_info = attributes.RESOURCE_ATTRIBUTE_MAP.get(res)
+            if attr_info:
+                l3.RESOURCE_ATTRIBUTE_MAP[res] = attr_info
+        resources = l3.L3.get_resources()
+        # restore the original resources once the controllers are created
+        l3.RESOURCE_ATTRIBUTE_MAP = l3_attr_map
+
+        return resources
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+
+class NvpRouterTestCase(test_nicira_plugin.TestNiciraL3NatTestCase):
+
+    def setUp(self, plugin=None, ext_mgr=None):
+        plugin = plugin or SERVICE_PLUGIN_NAME
+        super(NvpRouterTestCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr)
+
+
+class ServiceRouterTestCase(NvpRouterTestCase):
+
+    def vcns_patch(self):
+        instance = self.mock_vcns.start()
+        instance.return_value.deploy_edge.side_effect = self.fc2.deploy_edge
+        instance.return_value.get_edge_id.side_effect = self.fc2.get_edge_id
+        instance.return_value.get_edge_deploy_status.side_effect = (
+            self.fc2.get_edge_deploy_status)
+        instance.return_value.delete_edge.side_effect = self.fc2.delete_edge
+        instance.return_value.update_interface.side_effect = (
+            self.fc2.update_interface)
+        instance.return_value.get_nat_config.side_effect = (
+            self.fc2.get_nat_config)
+        instance.return_value.update_nat_config.side_effect = (
+            self.fc2.update_nat_config)
+        instance.return_value.delete_nat_rule.side_effect = (
+            self.fc2.delete_nat_rule)
+        instance.return_value.get_edge_status.side_effect = (
+            self.fc2.get_edge_status)
+        instance.return_value.get_edges.side_effect = self.fc2.get_edges
+        instance.return_value.update_routes.side_effect = (
+            self.fc2.update_routes)
+        instance.return_value.create_lswitch.side_effect = (
+            self.fc2.create_lswitch)
+        instance.return_value.delete_lswitch.side_effect = (
+            self.fc2.delete_lswitch)
+
+    def setUp(self):
+        cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
+        cfg.CONF.set_override('task_status_check_interval', 100, group="vcns")
+
+        # vcns does not support duplicated router name, ignore router name
+        # validation for unit-test cases
+        self.fc2 = fake_vcns.FakeVcns(unique_router_name=False)
+        self.mock_vcns = mock.patch(VCNS_NAME, autospec=True)
+        self.vcns_patch()
+
+        super(ServiceRouterTestCase, self).setUp(
+            ext_mgr=ServiceRouterTestExtensionManager())
+
+        self.fc2.set_fake_nvpapi(self.fc)
+        self.addCleanup(self.fc2.reset_all)
+        self.addCleanup(self.mock_vcns.stop)
+
+    def tearDown(self):
+        plugin = NeutronManager.get_plugin()
+        manager = plugin.vcns_driver.task_manager
+        for i in range(20):
+            if not manager.has_pending_task():
+                break
+            greenthread.sleep(0.1)
+        if manager.has_pending_task():
+            manager.show_pending_tasks()
+            raise Exception(_("Tasks not completed"))
+        manager.stop()
+
+        super(ServiceRouterTestCase, self).tearDown()
+
+    def _create_router(self, fmt, tenant_id, name=None,
+                       admin_state_up=None, set_context=False,
+                       arg_list=None, **kwargs):
+        data = {'router': {'tenant_id': tenant_id}}
+        if name:
+            data['router']['name'] = name
+        if admin_state_up:
+            data['router']['admin_state_up'] = admin_state_up
+        for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())):
+            # Arg must be present and not empty
+            if arg in kwargs and kwargs[arg]:
+                data['router'][arg] = kwargs[arg]
+        data['router']['service_router'] = True
+        router_req = self.new_create_request('routers', data, fmt)
+        if set_context and tenant_id:
+            # create a specific auth context for this request
+            router_req.environ['neutron.context'] = context.Context(
+                '', tenant_id)
+
+        return router_req.get_response(self.ext_api)
+
+    def test_router_create(self):
+        name = 'router1'
+        tenant_id = _uuid()
+        expected_value = [('name', name), ('tenant_id', tenant_id),
+                          ('admin_state_up', True),
+                          ('external_gateway_info', None),
+                          ('service_router', True)]
+        with self.router(name='router1', admin_state_up=True,
+                         tenant_id=tenant_id) as router:
+            expected_value_1 = expected_value + [('status', 'PENDING_CREATE')]
+            for k, v in expected_value_1:
+                self.assertEqual(router['router'][k], v)
+
+            # wait ~1 seconds for router status update
+            for i in range(2):
+                greenthread.sleep(0.5)
+                res = self._show('routers', router['router']['id'])
+                if res['router']['status'] == 'ACTIVE':
+                    break
+            expected_value_2 = expected_value + [('status', 'ACTIVE')]
+            for k, v in expected_value_2:
+                self.assertEqual(res['router'][k], v)
+
+            # check an integration lswitch is created
+            lswitch_name = "%s-ls" % name
+            for lswitch_id, lswitch in self.fc2._lswitches.iteritems():
+                if lswitch['display_name'] == lswitch_name:
+                    break
+            else:
+                self.fail("Integration lswitch not found")
+
+        # check an integration lswitch is deleted
+        lswitch_name = "%s-ls" % name
+        for lswitch_id, lswitch in self.fc2._lswitches.iteritems():
+            if lswitch['display_name'] == lswitch_name:
+                self.fail("Integration switch is not deleted")
+
+    def test_router_show(self):
+        name = 'router1'
+        tenant_id = _uuid()
+        expected_value = [('name', name), ('tenant_id', tenant_id),
+                          ('admin_state_up', True),
+                          ('status', 'PENDING_CREATE'),
+                          ('external_gateway_info', None),
+                          ('service_router', True)]
+        with self.router(name='router1', admin_state_up=True,
+                         tenant_id=tenant_id) as router:
+            res = self._show('routers', router['router']['id'])
+            for k, v in expected_value:
+                self.assertEqual(res['router'][k], v)
+
+    def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None):
+        super(ServiceRouterTestCase,
+              self)._test_router_create_with_gwinfo_and_l3_ext_net(
+                  vlan_id, validate_ext_gw=False)
+
+    def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
+        super(ServiceRouterTestCase,
+              self)._test_router_update_gateway_on_l3_ext_net(
+                  vlan_id, validate_ext_gw=False)
index 467eb6a84f98b22d5758ee983827993c425d317c..e0be1521be5185d090df0a7c036dfaf62794e2ec 100644 (file)
@@ -32,6 +32,7 @@ from neutron.extensions import portbindings
 from neutron.extensions import providernet as pnet
 from neutron.extensions import securitygroup as secgrp
 from neutron import manager
+from neutron.manager import NeutronManager
 from neutron.openstack.common import uuidutils
 from neutron.plugins.nicira.common import exceptions as nvp_exc
 from neutron.plugins.nicira.common import sync
@@ -433,15 +434,21 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
     def _restore_l3_attribute_map(self):
         l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
 
-    def setUp(self):
+    def setUp(self, plugin=None, ext_mgr=None):
         self._l3_attribute_map_bk = {}
         for item in l3.RESOURCE_ATTRIBUTE_MAP:
             self._l3_attribute_map_bk[item] = (
                 l3.RESOURCE_ATTRIBUTE_MAP[item].copy())
         cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH)
         self.addCleanup(self._restore_l3_attribute_map)
+        ext_mgr = ext_mgr or TestNiciraL3ExtensionManager()
         super(TestNiciraL3NatTestCase, self).setUp(
-            ext_mgr=TestNiciraL3ExtensionManager())
+            plugin=plugin, ext_mgr=ext_mgr)
+        plugin_instance = NeutronManager.get_plugin()
+        self._plugin_name = "%s.%s" % (
+            plugin_instance.__module__,
+            plugin_instance.__class__.__name__)
+        self._plugin_class = plugin_instance.__class__
 
     def tearDown(self):
         super(TestNiciraL3NatTestCase, self).tearDown()
@@ -487,7 +494,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
     def test_create_l3_ext_network_without_vlan(self):
         self._test_create_l3_ext_network()
 
-    def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None):
+    def _test_router_create_with_gwinfo_and_l3_ext_net(self, vlan_id=None,
+                                                       validate_ext_gw=True):
         with self._create_l3_ext_network(vlan_id) as net:
             with self.subnet(network=net) as s:
                 data = {'router': {'tenant_id': 'whatever'}}
@@ -503,8 +511,9 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                         s['subnet']['network_id'],
                         (router['router']['external_gateway_info']
                          ['network_id']))
-                    self._nvp_validate_ext_gw(router['router']['id'],
-                                              'l3_gw_uuid', vlan_id)
+                    if validate_ext_gw:
+                        self._nvp_validate_ext_gw(router['router']['id'],
+                                                  'l3_gw_uuid', vlan_id)
                 finally:
                     self._delete('routers', router['router']['id'])
 
@@ -584,7 +593,8 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                 uuidutils.generate_uuid(),
                 expected_code=webob.exc.HTTPNotFound.code)
 
-    def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
+    def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None,
+                                                  validate_ext_gw=True):
         with self.router() as r:
             with self.subnet() as s1:
                 with self._create_l3_ext_network(vlan_id) as net:
@@ -609,8 +619,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                                       ['external_gateway_info']['network_id'])
                             self.assertEqual(net_id,
                                              s2['subnet']['network_id'])
-                            self._nvp_validate_ext_gw(body['router']['id'],
-                                                      'l3_gw_uuid', vlan_id)
+                            if validate_ext_gw:
+                                self._nvp_validate_ext_gw(
+                                    body['router']['id'],
+                                    'l3_gw_uuid', vlan_id)
                         finally:
                             # Cleanup
                             self._remove_external_gateway_from_router(
@@ -635,14 +647,10 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
         self._test_create_l3_ext_network(666)
 
     def test_floatingip_with_assoc_fails(self):
-        self._test_floatingip_with_assoc_fails(
-            'neutron.plugins.nicira.'
-            'NeutronPlugin.NvpPluginV2')
+        self._test_floatingip_with_assoc_fails(self._plugin_name)
 
     def test_floatingip_with_invalid_create_port(self):
-        self._test_floatingip_with_invalid_create_port(
-            'neutron.plugins.nicira.'
-            'NeutronPlugin.NvpPluginV2')
+        self._test_floatingip_with_invalid_create_port(self._plugin_name)
 
     def _nvp_metadata_setup(self):
         cfg.CONF.set_override('metadata_mode', 'access_network', 'NVP')
@@ -724,7 +732,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
         with self.router() as r:
             with self.subnet() as s:
                 # Raise a NeutronException (eg: NotFound)
-                with mock.patch.object(NeutronPlugin.NvpPluginV2,
+                with mock.patch.object(self._plugin_class,
                                        'create_subnet',
                                        side_effect=ntn_exc.NotFound):
                     self._router_interface_action(
@@ -746,7 +754,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                 # Raise a NeutronException when adding metadata subnet
                 # to router
                 # save function being mocked
-                real_func = NeutronPlugin.NvpPluginV2.add_router_interface
+                real_func = self._plugin_class.add_router_interface
                 plugin_instance = manager.NeutronManager.get_plugin()
 
                 def side_effect(*args):
@@ -756,7 +764,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                     # otherwise raise
                     raise NvpApiClient.NvpApiException()
 
-                with mock.patch.object(NeutronPlugin.NvpPluginV2,
+                with mock.patch.object(self._plugin_class,
                                        'add_router_interface',
                                        side_effect=side_effect):
                     self._router_interface_action(
@@ -817,7 +825,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                 # Raise a NeutronException when removing
                 # metadata subnet from router
                 # save function being mocked
-                real_func = NeutronPlugin.NvpPluginV2.remove_router_interface
+                real_func = self._plugin_class.remove_router_interface
                 plugin_instance = manager.NeutronManager.get_plugin()
 
                 def side_effect(*args):
@@ -827,7 +835,7 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                     # otherwise raise
                     raise NvpApiClient.NvpApiException()
 
-                with mock.patch.object(NeutronPlugin.NvpPluginV2,
+                with mock.patch.object(self._plugin_class,
                                        'remove_router_interface',
                                        side_effect=side_effect):
                     self._router_interface_action('remove', r['router']['id'],