]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Support for NVP distributed router
authorSalvatore Orlando <salv.orlando@gmail.com>
Wed, 3 Jul 2013 20:07:09 +0000 (22:07 +0200)
committerSalvatore Orlando <salv.orlando@gmail.com>
Thu, 29 Aug 2013 16:56:08 +0000 (09:56 -0700)
Blueprint nvp-distributed-router

This patch adds support for NVP distributed logical routers
adding a simple attribute extension.
The default router type can be controlled used the default_router_type
option in the nvp section of neutron configuration.
In order to ensure backward compatibility, pre-existing routers
will be treated as centralized routers.

Change-Id: Iaab9ffb6071c93990be711ebb56c212230544a7a

16 files changed:
neutron/db/l3_db.py
neutron/db/l3_gwmode_db.py
neutron/db/migration/alembic_migrations/versions/40dffbf4b549_nvp_dist_router.py [new file with mode: 0644]
neutron/plugins/nicira/NeutronPlugin.py
neutron/plugins/nicira/dbexts/distributedrouter.py [new file with mode: 0644]
neutron/plugins/nicira/dbexts/nicira_models.py
neutron/plugins/nicira/extensions/distributedrouter.py [new file with mode: 0644]
neutron/plugins/nicira/nvplib.py
neutron/tests/unit/metaplugin/test_basic.py
neutron/tests/unit/nicira/etc/fake_get_lrouter.json
neutron/tests/unit/nicira/etc/fake_post_lrouter.json
neutron/tests/unit/nicira/fake_nvpapiclient.py
neutron/tests/unit/nicira/test_nicira_plugin.py
neutron/tests/unit/nicira/test_nvplib.py
neutron/tests/unit/test_extension_ext_gw_mode.py
neutron/tests/unit/test_l3_plugin.py

index 32371769cf2480d1ade31168f2eac53c59d43dcf..071d74f507fa5fd62ae13d1a52bedf9c90c39ae5 100644 (file)
@@ -167,7 +167,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
             context.session.add(router_db)
             if has_gw_info:
                 self._update_router_gw_info(context, router_db['id'], gw_info)
-        return self._make_router_dict(router_db)
+        return self._make_router_dict(router_db, process_extensions=False)
 
     def update_router(self, context, id, router):
         r = router['router']
index c2b49b4c719494029ad6380da4f2a4371a595471..a9866992a9b0c20b8290eb7d62b18dc6f6532ec3 100644 (file)
@@ -35,8 +35,9 @@ setattr(l3_db.Router, 'enable_snat',
 class L3_NAT_db_mixin(l3_db.L3_NAT_db_mixin):
     """Mixin class to add configurable gateway modes."""
 
-    def _make_router_dict(self, router, fields=None):
-        res = super(L3_NAT_db_mixin, self)._make_router_dict(router)
+    def _make_router_dict(self, router, fields=None, process_extensions=True):
+        res = super(L3_NAT_db_mixin, self)._make_router_dict(
+            router, process_extensions=process_extensions)
         if router['gw_port_id']:
             nw_id = router.gw_port['network_id']
             res[EXTERNAL_GW_INFO] = {'network_id': nw_id,
diff --git a/neutron/db/migration/alembic_migrations/versions/40dffbf4b549_nvp_dist_router.py b/neutron/db/migration/alembic_migrations/versions/40dffbf4b549_nvp_dist_router.py
new file mode 100644 (file)
index 0000000..523d9ab
--- /dev/null
@@ -0,0 +1,60 @@
+# 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.
+#
+
+"""nvp_dist_router
+
+Revision ID: 40dffbf4b549
+Revises: 63afba73813
+Create Date: 2013-08-21 18:00:26.214923
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '40dffbf4b549'
+down_revision = '63afba73813'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+    'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2'
+]
+
+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(
+        'nsxrouterextattributess',
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.Column('distributed', sa.Boolean(), nullable=False),
+        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('nsxrouterextattributess')
index 131f856c057fe6fbe2b2094ca7600067221db178..46c6fbf868ee93a38d222bb948a6dec52ddd4dee 100644 (file)
@@ -64,6 +64,7 @@ from neutron.plugins.nicira.common import config  # noqa
 from neutron.plugins.nicira.common import exceptions as nvp_exc
 from neutron.plugins.nicira.common import metadata_access as nvp_meta
 from neutron.plugins.nicira.common import securitygroups as nvp_sec
+from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr
 from neutron.plugins.nicira.dbexts import maclearning as mac_db
 from neutron.plugins.nicira.dbexts import nicira_db
 from neutron.plugins.nicira.dbexts import nicira_networkgw_db as networkgw_db
@@ -134,6 +135,7 @@ class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
 class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                   extraroute_db.ExtraRoute_db_mixin,
                   l3_gwmode_db.L3_NAT_db_mixin,
+                  dist_rtr.DistributedRouter_mixin,
                   portbindings_db.PortBindingMixin,
                   portsecurity_db.PortSecurityDbMixin,
                   securitygroups_db.SecurityGroupDbMixin,
@@ -152,6 +154,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
     supported_extension_aliases = ["agent",
                                    "binding",
                                    "dhcp_agent_scheduler",
+                                   "dist-router",
                                    "ext-gw-mode",
                                    "extraroute",
                                    "mac-learning",
@@ -162,7 +165,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                                    "provider",
                                    "quotas",
                                    "router",
-                                   "security-group"]
+                                   "security-group"]
 
     __native_bulk_support = True
 
@@ -171,6 +174,8 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
 
     def __init__(self):
 
+        # TODO(salv-orlando): Replace These dicts with
+        # collections.defaultdict for better handling of default values
         # Routines for managing logical ports in NVP
         self._port_drivers = {
             'create': {l3_db.DEVICE_OWNER_ROUTER_GW:
@@ -1624,11 +1629,16 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                     if ext_net.subnets:
                         ext_subnet = ext_net.subnets[0]
                         nexthop = ext_subnet.gateway_ip
-            lrouter = nvplib.create_lrouter(self.cluster, tenant_id,
-                                            router['router']['name'],
-                                            nexthop)
+            distributed = r.get('distributed')
+            lrouter = nvplib.create_lrouter(
+                self.cluster, tenant_id, router['router']['name'], nexthop,
+                distributed=attr.is_attr_set(distributed) and distributed)
             # Use NVP identfier for Neutron resource
-            router['router']['id'] = lrouter['uuid']
+            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
+            r['distributed'] = lrouter['distributed']
         except NvpApiClient.NvpApiException:
             raise nvp_exc.NvpPluginException(
                 err_msg=_("Unable to create logical router on NVP Platform"))
@@ -1652,15 +1662,20 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                 err_msg=_("Unable to create router %s") % r['name'])
 
         with context.session.begin(subtransactions=True):
-            router_db = l3_db.Router(id=lrouter['uuid'],
-                                     tenant_id=tenant_id,
-                                     name=r['name'],
-                                     admin_state_up=r['admin_state_up'],
-                                     status="ACTIVE")
-            context.session.add(router_db)
+            # Transaction nesting is needed to avoid foreign key violations
+            # when processing the service provider binding
+            with context.session.begin(subtransactions=True):
+                router_db = l3_db.Router(id=lrouter['uuid'],
+                                         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)
+                context.session.add(router_db)
             if has_gw_info:
                 self._update_router_gw_info(context, router_db['id'], gw_info)
-        return self._make_router_dict(router_db)
+        router = self._make_router_dict(router_db)
+        return router
 
     def update_router(self, context, router_id, router):
         # Either nexthop is updated or should be kept as it was before
@@ -1816,9 +1831,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
             LOG.warning(_("Found %s logical routers not bound "
                           "to Neutron routers. Neutron and NVP are "
                           "potentially out of sync"), len(nvp_lrouters))
-
-        return [self._make_router_dict(router, fields)
-                for router in routers]
+        return [self._make_router_dict(router, fields) for router in routers]
 
     def add_router_interface(self, context, router_id, interface_info):
         # When adding interface by port_id we need to create the
diff --git a/neutron/plugins/nicira/dbexts/distributedrouter.py b/neutron/plugins/nicira/dbexts/distributedrouter.py
new file mode 100644 (file)
index 0000000..1d82847
--- /dev/null
@@ -0,0 +1,69 @@
+# 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
+from neutron.plugins.nicira.extensions import distributedrouter as dist_rtr
+
+LOG = logging.getLogger(__name__)
+
+
+class DistributedRouter_mixin(object):
+    """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])
index 976f673a872487a898e6cd06d4fa055adfde6cc5..d6a21880c9163927f785c0de96d303aa5498013d 100644 (file)
 #    under the License.
 
 
-from sqlalchemy import Column, Enum, ForeignKey, Integer, String
+from sqlalchemy import Boolean, Column, Enum, ForeignKey, Integer, String
+from sqlalchemy import orm
 
+from neutron.db import l3_db
 from neutron.db.models_v2 import model_base
 
 
@@ -79,3 +81,17 @@ class MultiProviderNetworks(model_base.BASEV2):
 
     def __init__(self, network_id):
         self.network_id = network_id
+
+
+class NSXRouterExtAttributes(model_base.BASEV2):
+    """Router attributes managed by Nicira plugin extensions."""
+    router_id = Column(String(36),
+                       ForeignKey('routers.id', ondelete="CASCADE"),
+                       primary_key=True)
+    distributed = 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(
+        l3_db.Router,
+        backref=orm.backref("nsx_attributes", lazy='joined',
+                            uselist=False, cascade='delete'))
diff --git a/neutron/plugins/nicira/extensions/distributedrouter.py b/neutron/plugins/nicira/extensions/distributedrouter.py
new file mode 100644 (file)
index 0000000..0573b3c
--- /dev/null
@@ -0,0 +1,73 @@
+# 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.
+#
+
+from neutron.api.v2 import attributes
+
+
+def convert_to_boolean_if_not_none(data):
+    if data is not None:
+        return attributes.convert_to_boolean(data)
+    return data
+
+
+DISTRIBUTED = 'distributed'
+EXTENDED_ATTRIBUTES_2_0 = {
+    'routers': {
+        DISTRIBUTED: {'allow_post': True, 'allow_put': False,
+                      'convert_to': convert_to_boolean_if_not_none,
+                      'default': attributes.ATTR_NOT_SPECIFIED,
+                      'is_visible': True},
+    }
+}
+
+
+class Distributedrouter(object):
+    """Extension class supporting distributed router."""
+
+    @classmethod
+    def get_name(cls):
+        return "Distributed Router"
+
+    @classmethod
+    def get_alias(cls):
+        return "dist-router"
+
+    @classmethod
+    def get_description(cls):
+        return "Enables configuration of NSX Distributed routers"
+
+    @classmethod
+    def get_namespace(cls):
+        return "http://docs.openstack.org/ext/dist-router/api/v1.0"
+
+    @classmethod
+    def get_updated(cls):
+        return "2013-08-1T10:00:00-00:00"
+
+    def get_required_extensions(self):
+        return ["router"]
+
+    @classmethod
+    def get_resources(cls):
+        """Returns Ext Resources."""
+        return []
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return EXTENDED_ATTRIBUTES_2_0
+        else:
+            return {}
index c4594f4d2f62469f1d318069f133a94366b70461..58ff0edbed5f819d2fedc2c6b4a05db17f1d08f3 100644 (file)
@@ -274,7 +274,8 @@ def create_l2_gw_service(cluster, tenant_id, display_name, devices):
         json.dumps(gwservice_obj), cluster=cluster)
 
 
-def _prepare_lrouter_body(name, tenant_id, router_type, **kwargs):
+def _prepare_lrouter_body(name, tenant_id, router_type,
+                          distributed=None, **kwargs):
     body = {
         "display_name": _check_and_truncate_name(name),
         "tags": [{"tag": tenant_id, "scope": "os_tid"},
@@ -284,22 +285,17 @@ def _prepare_lrouter_body(name, tenant_id, router_type, **kwargs):
         },
         "type": "LogicalRouterConfig"
     }
+    # add the distributed key only if not None (ie: True or False)
+    if distributed is not None:
+        body['distributed'] = distributed
     if kwargs:
         body["routing_config"].update(kwargs)
     return body
 
 
-def create_implicit_routing_lrouter(cluster, tenant_id, display_name, nexthop):
-    """Create a NVP logical router on the specified cluster.
-
-        :param cluster: The target NVP cluster
-        :param tenant_id: Identifier of the Openstack tenant for which
-        the logical router is being created
-        :param display_name: Descriptive name of this logical router
-        :param nexthop: External gateway IP address for the logical router
-        :raise NvpApiException: if there is a problem while communicating
-        with the NVP controller
-    """
+def _create_implicit_routing_lrouter(cluster, tenant_id,
+                                     display_name, nexthop,
+                                     distributed=None):
     implicit_routing_config = {
         "default_route_next_hop": {
             "gateway_ip_address": nexthop,
@@ -309,15 +305,52 @@ def create_implicit_routing_lrouter(cluster, tenant_id, display_name, nexthop):
     lrouter_obj = _prepare_lrouter_body(
         display_name, tenant_id,
         "SingleDefaultRouteImplicitRoutingConfig",
+        distributed=distributed,
         **implicit_routing_config)
     return do_request(HTTP_POST, _build_uri_path(LROUTER_RESOURCE),
                       json.dumps(lrouter_obj), cluster=cluster)
 
 
-def create_explicit_routing_lrouter(cluster, tenant_id,
+def create_implicit_routing_lrouter(cluster, tenant_id,
                                     display_name, nexthop):
+    """Create a NVP logical router on the specified cluster.
+
+        :param cluster: The target NVP cluster
+        :param tenant_id: Identifier of the Openstack tenant for which
+        the logical router is being created
+        :param display_name: Descriptive name of this logical router
+        :param nexthop: External gateway IP address for the logical router
+        :raise NvpApiException: if there is a problem while communicating
+        with the NVP controller
+    """
+    return _create_implicit_routing_lrouter(
+        cluster, tenant_id, display_name, nexthop)
+
+
+def create_implicit_routing_lrouter_with_distribution(
+    cluster, tenant_id, display_name, nexthop, distributed=None):
+    """Create a NVP logical router on the specified cluster.
+
+    This function also allows for creating distributed lrouters
+    :param cluster: The target NVP cluster
+    :param tenant_id: Identifier of the Openstack tenant for which
+    the logical router is being created
+    :param display_name: Descriptive name of this logical router
+    :param nexthop: External gateway IP address for the logical router
+    :param distributed: True for distributed logical routers
+    :raise NvpApiException: if there is a problem while communicating
+    with the NVP controller
+    """
+    return _create_implicit_routing_lrouter(
+        cluster, tenant_id, display_name, nexthop, distributed)
+
+
+def create_explicit_routing_lrouter(cluster, tenant_id,
+                                    display_name, nexthop,
+                                    distributed=None):
     lrouter_obj = _prepare_lrouter_body(
-        display_name, tenant_id, "RoutingTableRoutingConfig")
+        display_name, tenant_id, "RoutingTableRoutingConfig",
+        distributed=distributed)
     router = do_request(HTTP_POST, _build_uri_path(LROUTER_RESOURCE),
                         json.dumps(lrouter_obj), cluster=cluster)
     default_gw = {'prefix': '0.0.0.0/0', 'next_hop_ip': nexthop}
@@ -1191,6 +1224,7 @@ NVPLIB_FUNC_DICT = {
     'create_lrouter': {
         2: {DEFAULT: create_implicit_routing_lrouter, },
         3: {DEFAULT: create_implicit_routing_lrouter,
+            1: create_implicit_routing_lrouter_with_distribution,
             2: create_explicit_routing_lrouter, }, },
     'update_lrouter': {
         2: {DEFAULT: update_implicit_routing_lrouter, },
index 9b4b31867a338816a4af138787d212c67fb2e16b..0445e50a19e2a6fb8bd1ac4ee34368ba9481e3cd 100644 (file)
@@ -13,7 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from neutron.common.test_lib import test_config
 from neutron.tests.unit.metaplugin.test_metaplugin import setup_metaplugin_conf
 from neutron.tests.unit import test_db_plugin as test_plugin
 from neutron.tests.unit import test_l3_plugin
@@ -24,11 +23,15 @@ class MetaPluginV2DBTestCase(test_plugin.NeutronDbPluginV2TestCase):
     _plugin_name = ('neutron.plugins.metaplugin.'
                     'meta_neutron_plugin.MetaPluginV2')
 
-    def setUp(self):
+    def setUp(self, plugin=None, ext_mgr=None):
+        # NOTE(salv-orlando): The plugin keyword argument is ignored,
+        # as this class will always invoke super with self._plugin_name.
+        # These keyword parameters ensure setUp methods always have the
+        # same signature.
         setup_metaplugin_conf()
-        ext_mgr = test_l3_plugin.L3TestExtensionManager()
-        test_config['extension_manager'] = ext_mgr
-        super(MetaPluginV2DBTestCase, self).setUp(self._plugin_name)
+        ext_mgr = ext_mgr or test_l3_plugin.L3TestExtensionManager()
+        super(MetaPluginV2DBTestCase, self).setUp(
+            plugin=self._plugin_name, ext_mgr=ext_mgr)
 
 
 class TestMetaBasicGet(test_plugin.TestBasicGet,
index 30898e914c7a5baf78cab9d860fc632a68b2bf64..9bda5b476402647770ecfe0501da7d7d77d27598 100644 (file)
@@ -1,5 +1,6 @@
 {
   "display_name": "%(display_name)s",
+  %(distributed_json)s
   "uuid": "%(uuid)s",
   "tags": %(tags_json)s,
   "routing_config": {
index 083824627abae95d72aeb8e74e2a1e46f291002f..dbe2811b0067bfb3013a3f13a3595362eac9aa9f 100644 (file)
@@ -1,5 +1,6 @@
 {
   "display_name": "%(display_name)s",
+  %(distributed_json)s
   "uuid": "%(uuid)s",
   "tags": [
     {
index 2b73d4dc0adfde188195e6101121ac41cacd1381..1133b0e7e09b4625922241f86cd77a0334d99440 100644 (file)
@@ -168,6 +168,14 @@ class FakeClient:
             'default_route_next_hop')
         fake_lrouter['default_next_hop'] = default_nexthop.get(
             'gateway_ip_address', '0.0.0.0')
+        # NOTE(salv-orlando): We won't make the Fake NVP API client
+        # aware of NVP version. The long term plan is to replace it
+        # with behavioral mocking of NVP API requests
+        if 'distributed' not in fake_lrouter:
+            fake_lrouter['distributed'] = False
+        distributed_json = ('"distributed": %s,' %
+                            str(fake_lrouter['distributed']).lower())
+        fake_lrouter['distributed_json'] = distributed_json
         return fake_lrouter
 
     def _add_lrouter(self, body):
index 7b0d3b8c65122cf1b163baa83c90d0ffc21517ea..7d737683487ec72c4ced597dd1d8e8dbf6a30e5b 100644 (file)
@@ -20,11 +20,13 @@ import netaddr
 from oslo.config import cfg
 import webob.exc
 
+from neutron.api.v2 import attributes
 from neutron.common import constants
 from neutron.common import exceptions as ntn_exc
 import neutron.common.test_lib as test_lib
 from neutron import context
 from neutron.extensions import l3
+from neutron.extensions import l3_ext_gw_mode
 from neutron.extensions import multiprovidernet as mpnet
 from neutron.extensions import portbindings
 from neutron.extensions import providernet as pnet
@@ -34,6 +36,7 @@ from neutron.openstack.common import uuidutils
 from neutron.plugins.nicira.common import exceptions as nvp_exc
 from neutron.plugins.nicira.dbexts import nicira_db
 from neutron.plugins.nicira.dbexts import nicira_qos_db as qos_db
+from neutron.plugins.nicira.extensions import distributedrouter as dist_router
 from neutron.plugins.nicira.extensions import nvp_networkgw
 from neutron.plugins.nicira.extensions import nvp_qos as ext_qos
 from neutron.plugins.nicira import NeutronPlugin
@@ -57,6 +60,10 @@ import neutron.tests.unit.test_l3_plugin as test_l3_plugin
 from neutron.tests.unit import testlib_api
 
 
+from neutron.openstack.common import log
+LOG = log.getLogger(__name__)
+
+
 class NiciraPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
 
     def _create_network(self, fmt, name, admin_state_up,
@@ -64,9 +71,9 @@ class NiciraPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
         data = {'network': {'name': name,
                             'admin_state_up': admin_state_up,
                             'tenant_id': self._tenant_id}}
-        attributes = kwargs
+        attrs = kwargs
         if providernet_args:
-            attributes.update(providernet_args)
+            attrs.update(providernet_args)
         for arg in (('admin_state_up', 'tenant_id', 'shared') +
                     (arg_list or ())):
             # Arg must be present and not empty
@@ -79,20 +86,23 @@ class NiciraPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
                 '', kwargs['tenant_id'])
         return network_req.get_response(self.api)
 
-    def setUp(self):
+    def setUp(self, plugin=None, ext_mgr=None):
         test_lib.test_config['config_files'] = [get_fake_conf('nvp.ini.test')]
         # mock nvp api client
         self.fc = fake_nvpapiclient.FakeClient(STUBS_PATH)
         self.mock_nvpapi = mock.patch(NVPAPI_NAME, autospec=True)
-        instance = self.mock_nvpapi.start()
+        self.mock_instance = self.mock_nvpapi.start()
 
         def _fake_request(*args, **kwargs):
             return self.fc.fake_request(*args, **kwargs)
 
         # Emulate tests against NVP 2.x
-        instance.return_value.get_nvp_version.return_value = NVPVersion("2.9")
-        instance.return_value.request.side_effect = _fake_request
-        super(NiciraPluginV2TestCase, self).setUp(plugin=PLUGIN_NAME)
+        self.mock_instance.return_value.get_nvp_version.return_value = (
+            NVPVersion("2.9"))
+        self.mock_instance.return_value.request.side_effect = _fake_request
+        plugin = plugin or PLUGIN_NAME
+        super(NiciraPluginV2TestCase, self).setUp(plugin=plugin,
+                                                  ext_mgr=ext_mgr)
         cfg.CONF.set_override('metadata_mode', None, 'NVP')
         self.addCleanup(self.fc.reset_all)
         self.addCleanup(self.mock_nvpapi.stop)
@@ -361,9 +371,47 @@ class TestNiciraSecurityGroup(ext_sg.TestSecurityGroups,
             self.assertEqual(sg['security_group']['name'], name)
 
 
+class TestNiciraL3ExtensionManager(object):
+
+    def get_resources(self):
+        # Simulate extension of L3 attribute map
+        # First apply attribute extensions
+        for key in l3.RESOURCE_ATTRIBUTE_MAP.keys():
+            l3.RESOURCE_ATTRIBUTE_MAP[key].update(
+                l3_ext_gw_mode.EXTENDED_ATTRIBUTES_2_0.get(key, {}))
+            l3.RESOURCE_ATTRIBUTE_MAP[key].update(
+                dist_router.EXTENDED_ATTRIBUTES_2_0.get(key, {}))
+        # Finally add l3 resources to the global attribute map
+        attributes.RESOURCE_ATTRIBUTE_MAP.update(
+            l3.RESOURCE_ATTRIBUTE_MAP)
+        return l3.L3.get_resources()
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+
 class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                               NiciraPluginV2TestCase):
 
+    def _restore_l3_attribute_map(self):
+        l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk
+
+    def setUp(self):
+        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)
+        super(TestNiciraL3NatTestCase, self).setUp(
+            ext_mgr=TestNiciraL3ExtensionManager())
+
+    def tearDown(self):
+        super(TestNiciraL3NatTestCase, self).tearDown()
+
     def _create_l3_ext_network(self, vlan_id=None):
         name = 'l3_ext_net'
         net_type = NeutronPlugin.NetworkTypes.L3_EXT
@@ -432,6 +480,33 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
     def test_router_create_with_gwinfo_and_l3_ext_net_with_vlan(self):
         self._test_router_create_with_gwinfo_and_l3_ext_net(444)
 
+    def _test_router_create_with_distributed(self, dist_input, dist_expected):
+        self.mock_instance.return_value.get_nvp_version.return_value = (
+            NvpApiClient.NVPVersion('3.1'))
+
+        data = {'tenant_id': 'whatever'}
+        data['name'] = 'router1'
+        data['distributed'] = dist_input
+        router_req = self.new_create_request(
+            'routers', {'router': data}, self.fmt)
+        try:
+            res = router_req.get_response(self.ext_api)
+            router = self.deserialize(self.fmt, res)
+            self.assertIn('distributed', router['router'])
+            self.assertEqual(dist_expected,
+                             router['router']['distributed'])
+        finally:
+            self._delete('routers', router['router']['id'])
+
+    def test_router_create_distributed(self):
+        self._test_router_create_with_distributed(True, True)
+
+    def test_router_create_not_distributed(self):
+        self._test_router_create_with_distributed(False, False)
+
+    def test_router_create_distributed_unspecified(self):
+        self._test_router_create_with_distributed(None, False)
+
     def test_router_create_nvp_error_returns_500(self, vlan_id=None):
         with mock.patch.object(nvplib,
                                'create_router_lport',
@@ -447,6 +522,16 @@ class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
                     res = router_req.get_response(self.ext_api)
                     self.assertEqual(500, res.status_int)
 
+    def test_router_add_gateway_invalid_network_returns_404(self):
+        # NOTE(salv-orlando): This unit test has been overriden
+        # as the nicira plugin support the ext_gw_mode extension
+        # which mandates a uuid for the external network identifier
+        with self.router() as r:
+            self._add_external_gateway_to_router(
+                r['router']['id'],
+                uuidutils.generate_uuid(),
+                expected_code=webob.exc.HTTPNotFound.code)
+
     def _test_router_update_gateway_on_l3_ext_net(self, vlan_id=None):
         with self.router() as r:
             with self.subnet() as s1:
@@ -1004,8 +1089,11 @@ class NiciraExtGwModeTestCase(NiciraPluginV2TestCase,
     pass
 
 
-class NiciraNeutronNVPOutOfSync(test_l3_plugin.L3NatTestCaseBase,
-                                NiciraPluginV2TestCase):
+class NiciraNeutronNVPOutOfSync(NiciraPluginV2TestCase,
+                                test_l3_plugin.L3NatTestCaseBase):
+
+    def setUp(self):
+        super(NiciraNeutronNVPOutOfSync, self).setUp()
 
     def test_delete_network_not_in_nvp(self):
         res = self._create_network('json', 'net1', True)
index 7e416502ce976a8b6ed0c27e653f1ffc1070b72c..d7b815ae21c202dde9747c54301d0c4133bb1649 100644 (file)
@@ -42,7 +42,7 @@ class NvplibTestCase(base.BaseTestCase):
         self.mock_nvpapi = mock.patch(NVPAPI_NAME, autospec=True)
         instance = self.mock_nvpapi.start()
         instance.return_value.login.return_value = "the_cookie"
-        fake_version = getattr(self, 'fake_version', "2.9")
+        fake_version = getattr(self, 'fake_version', "3.0")
         instance.return_value.get_nvp_version.return_value = (
             NvpApiClient.NVPVersion(fake_version))
 
@@ -106,10 +106,11 @@ class NvplibNegativeTests(base.BaseTestCase):
         self.mock_nvpapi = mock.patch(NVPAPI_NAME, autospec=True)
         instance = self.mock_nvpapi.start()
         instance.return_value.login.return_value = "the_cookie"
-        # Choose 2.9, but the version is irrelevant for the aim of
+        # Choose 3.0, but the version is irrelevant for the aim of
         # these tests as calls are throwing up errors anyway
-        self.fake_version = NvpApiClient.NVPVersion('2.9')
-        instance.return_value.get_nvp_version.return_value = self.fake_version
+        fake_version = getattr(self, 'fake_version', "3.0")
+        instance.return_value.get_nvp_version.return_value = (
+            NvpApiClient.NVPVersion(fake_version))
 
         def _faulty_request(*args, **kwargs):
             raise nvplib.NvpApiClient.NvpApiException
@@ -572,7 +573,8 @@ class TestNvplibLogicalRouters(NvplibTestCase):
                         expected_uuid,
                         expected_display_name,
                         expected_nexthop,
-                        expected_tenant_id):
+                        expected_tenant_id,
+                        expected_distributed=None):
         self.assertEqual(res_lrouter['uuid'], expected_uuid)
         nexthop = (res_lrouter['routing_config']
                    ['default_route_next_hop']['gateway_ip_address'])
@@ -581,6 +583,9 @@ class TestNvplibLogicalRouters(NvplibTestCase):
         self.assertIn('os_tid', router_tags)
         self.assertEqual(res_lrouter['display_name'], expected_display_name)
         self.assertEqual(expected_tenant_id, router_tags['os_tid'])
+        if expected_distributed is not None:
+            self.assertEqual(expected_distributed,
+                             res_lrouter['distributed'])
 
     def test_get_lrouters(self):
         lrouter_uuids = [nvplib.create_lrouter(
@@ -590,16 +595,33 @@ class TestNvplibLogicalRouters(NvplibTestCase):
         for router in routers:
             self.assertIn(router['uuid'], lrouter_uuids)
 
-    def test_create_and_get_lrouter(self):
-        lrouter = nvplib.create_lrouter(self.fake_cluster,
-                                        'pippo',
-                                        'fake-lrouter',
-                                        '10.0.0.1')
-        res_lrouter = nvplib.get_lrouter(self.fake_cluster,
-                                         lrouter['uuid'])
-        self._verify_lrouter(res_lrouter, lrouter['uuid'],
+    def _create_lrouter(self, version, distributed=None):
+        with mock.patch.object(
+            self.fake_cluster.api_client, 'get_nvp_version',
+            return_value=NvpApiClient.NVPVersion(version)):
+                lrouter = nvplib.create_lrouter(
+                    self.fake_cluster, 'pippo', 'fake-lrouter',
+                    '10.0.0.1', distributed=distributed)
+                return nvplib.get_lrouter(self.fake_cluster,
+                                          lrouter['uuid'])
+
+    def test_create_and_get_lrouter_v30(self):
+        res_lrouter = self._create_lrouter('3.0')
+        self._verify_lrouter(res_lrouter, res_lrouter['uuid'],
                              'fake-lrouter', '10.0.0.1', 'pippo')
 
+    def test_create_and_get_lrouter_v31_centralized(self):
+        res_lrouter = self._create_lrouter('3.1', distributed=False)
+        self._verify_lrouter(res_lrouter, res_lrouter['uuid'],
+                             'fake-lrouter', '10.0.0.1', 'pippo',
+                             expected_distributed=False)
+
+    def test_create_and_get_lrouter_v31_distributed(self):
+        res_lrouter = self._create_lrouter('3.1', distributed=True)
+        self._verify_lrouter(res_lrouter, res_lrouter['uuid'],
+                             'fake-lrouter', '10.0.0.1', 'pippo',
+                             expected_distributed=True)
+
     def test_create_and_get_lrouter_name_exceeds_40chars(self):
         display_name = '*' * 50
         lrouter = nvplib.create_lrouter(self.fake_cluster,
index 8732debbc4aceaf081cd3265714bf24c53507d18..2a7fc6b9d0415ebd3c9cce934375f236c5d4d77d 100644 (file)
@@ -300,7 +300,7 @@ class TestL3GwModeMixin(base.BaseTestCase):
 class ExtGwModeTestCase(test_db_plugin.NeutronDbPluginV2TestCase,
                         test_l3_plugin.L3NatTestCaseMixin):
 
-    def setUp(self, plugin=None):
+    def setUp(self, plugin=None, ext_mgr=None):
         # Store l3 resource attribute map as it will be updated
         self._l3_attribute_map_bk = {}
         for item in l3.RESOURCE_ATTRIBUTE_MAP:
@@ -310,8 +310,9 @@ class ExtGwModeTestCase(test_db_plugin.NeutronDbPluginV2TestCase,
             'neutron.tests.unit.test_extension_ext_gw_mode.TestDbPlugin')
         # for these tests we need to enable overlapping ips
         cfg.CONF.set_default('allow_overlapping_ips', True)
+        ext_mgr = ext_mgr or TestExtensionManager()
         super(ExtGwModeTestCase, self).setUp(plugin=plugin,
-                                             ext_mgr=TestExtensionManager())
+                                             ext_mgr=ext_mgr)
         self.addCleanup(self.restore_l3_attribute_map)
 
     def restore_l3_attribute_map(self):
index 14c9b221244ac4b72e4d107f3c3911621dbe2d28..d85b22caa7559a05fd57d648a05b8a12482f0961 100644 (file)
@@ -481,14 +481,14 @@ class L3NatTestCaseMixin(object):
 class L3NatTestCaseBase(L3NatTestCaseMixin,
                         test_db_plugin.NeutronDbPluginV2TestCase):
 
-    def setUp(self):
+    def setUp(self, plugin=None, ext_mgr=None):
         test_config['plugin_name_v2'] = (
             'neutron.tests.unit.test_l3_plugin.TestL3NatPlugin')
         # for these tests we need to enable overlapping ips
         cfg.CONF.set_default('allow_overlapping_ips', True)
-        ext_mgr = L3TestExtensionManager()
-        test_config['extension_manager'] = ext_mgr
-        super(L3NatTestCaseBase, self).setUp()
+        ext_mgr = ext_mgr or L3TestExtensionManager()
+        super(L3NatTestCaseBase, self).setUp(
+            plugin=plugin, ext_mgr=ext_mgr)
 
         # Set to None to reload the drivers
         notifier_api._drivers = None