From bf409330992be3ae91249fefd36dc04bf1177808 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 3 Jul 2013 22:07:09 +0200 Subject: [PATCH] Support for NVP distributed router 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 --- neutron/db/l3_db.py | 2 +- neutron/db/l3_gwmode_db.py | 5 +- .../versions/40dffbf4b549_nvp_dist_router.py | 60 ++++++++++ neutron/plugins/nicira/NeutronPlugin.py | 43 ++++--- .../nicira/dbexts/distributedrouter.py | 69 ++++++++++++ .../plugins/nicira/dbexts/nicira_models.py | 18 ++- .../nicira/extensions/distributedrouter.py | 73 ++++++++++++ neutron/plugins/nicira/nvplib.py | 66 ++++++++--- neutron/tests/unit/metaplugin/test_basic.py | 13 ++- .../unit/nicira/etc/fake_get_lrouter.json | 1 + .../unit/nicira/etc/fake_post_lrouter.json | 1 + .../tests/unit/nicira/fake_nvpapiclient.py | 8 ++ .../tests/unit/nicira/test_nicira_plugin.py | 106 ++++++++++++++++-- neutron/tests/unit/nicira/test_nvplib.py | 48 +++++--- .../tests/unit/test_extension_ext_gw_mode.py | 5 +- neutron/tests/unit/test_l3_plugin.py | 8 +- 16 files changed, 458 insertions(+), 68 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/40dffbf4b549_nvp_dist_router.py create mode 100644 neutron/plugins/nicira/dbexts/distributedrouter.py create mode 100644 neutron/plugins/nicira/extensions/distributedrouter.py diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 32371769cf..071d74f507 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -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'] diff --git a/neutron/db/l3_gwmode_db.py b/neutron/db/l3_gwmode_db.py index c2b49b4c71..a9866992a9 100644 --- a/neutron/db/l3_gwmode_db.py +++ b/neutron/db/l3_gwmode_db.py @@ -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 index 0000000000..523d9aba3e --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/40dffbf4b549_nvp_dist_router.py @@ -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') diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index 131f856c05..46c6fbf868 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -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 index 0000000000..1d82847e6b --- /dev/null +++ b/neutron/plugins/nicira/dbexts/distributedrouter.py @@ -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]) diff --git a/neutron/plugins/nicira/dbexts/nicira_models.py b/neutron/plugins/nicira/dbexts/nicira_models.py index 976f673a87..d6a21880c9 100644 --- a/neutron/plugins/nicira/dbexts/nicira_models.py +++ b/neutron/plugins/nicira/dbexts/nicira_models.py @@ -16,8 +16,10 @@ # 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 index 0000000000..0573b3c1d4 --- /dev/null +++ b/neutron/plugins/nicira/extensions/distributedrouter.py @@ -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 {} diff --git a/neutron/plugins/nicira/nvplib.py b/neutron/plugins/nicira/nvplib.py index c4594f4d2f..58ff0edbed 100644 --- a/neutron/plugins/nicira/nvplib.py +++ b/neutron/plugins/nicira/nvplib.py @@ -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,12 +285,34 @@ 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): +def _create_implicit_routing_lrouter(cluster, tenant_id, + display_name, nexthop, + distributed=None): + implicit_routing_config = { + "default_route_next_hop": { + "gateway_ip_address": nexthop, + "type": "RouterNextHop" + }, + } + 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_implicit_routing_lrouter(cluster, tenant_id, + display_name, nexthop): """Create a NVP logical router on the specified cluster. :param cluster: The target NVP cluster @@ -300,24 +323,34 @@ def create_implicit_routing_lrouter(cluster, tenant_id, display_name, nexthop): :raise NvpApiException: if there is a problem while communicating with the NVP controller """ - implicit_routing_config = { - "default_route_next_hop": { - "gateway_ip_address": nexthop, - "type": "RouterNextHop" - }, - } - lrouter_obj = _prepare_lrouter_body( - display_name, tenant_id, - "SingleDefaultRouteImplicitRoutingConfig", - **implicit_routing_config) - return do_request(HTTP_POST, _build_uri_path(LROUTER_RESOURCE), - json.dumps(lrouter_obj), cluster=cluster) + 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): + 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, }, diff --git a/neutron/tests/unit/metaplugin/test_basic.py b/neutron/tests/unit/metaplugin/test_basic.py index 9b4b31867a..0445e50a19 100644 --- a/neutron/tests/unit/metaplugin/test_basic.py +++ b/neutron/tests/unit/metaplugin/test_basic.py @@ -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, diff --git a/neutron/tests/unit/nicira/etc/fake_get_lrouter.json b/neutron/tests/unit/nicira/etc/fake_get_lrouter.json index 30898e914c..9bda5b4764 100644 --- a/neutron/tests/unit/nicira/etc/fake_get_lrouter.json +++ b/neutron/tests/unit/nicira/etc/fake_get_lrouter.json @@ -1,5 +1,6 @@ { "display_name": "%(display_name)s", + %(distributed_json)s "uuid": "%(uuid)s", "tags": %(tags_json)s, "routing_config": { diff --git a/neutron/tests/unit/nicira/etc/fake_post_lrouter.json b/neutron/tests/unit/nicira/etc/fake_post_lrouter.json index 083824627a..dbe2811b00 100644 --- a/neutron/tests/unit/nicira/etc/fake_post_lrouter.json +++ b/neutron/tests/unit/nicira/etc/fake_post_lrouter.json @@ -1,5 +1,6 @@ { "display_name": "%(display_name)s", + %(distributed_json)s "uuid": "%(uuid)s", "tags": [ { diff --git a/neutron/tests/unit/nicira/fake_nvpapiclient.py b/neutron/tests/unit/nicira/fake_nvpapiclient.py index 2b73d4dc0a..1133b0e7e0 100644 --- a/neutron/tests/unit/nicira/fake_nvpapiclient.py +++ b/neutron/tests/unit/nicira/fake_nvpapiclient.py @@ -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): diff --git a/neutron/tests/unit/nicira/test_nicira_plugin.py b/neutron/tests/unit/nicira/test_nicira_plugin.py index 7b0d3b8c65..7d73768348 100644 --- a/neutron/tests/unit/nicira/test_nicira_plugin.py +++ b/neutron/tests/unit/nicira/test_nicira_plugin.py @@ -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) diff --git a/neutron/tests/unit/nicira/test_nvplib.py b/neutron/tests/unit/nicira/test_nvplib.py index 7e416502ce..d7b815ae21 100644 --- a/neutron/tests/unit/nicira/test_nvplib.py +++ b/neutron/tests/unit/nicira/test_nvplib.py @@ -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, diff --git a/neutron/tests/unit/test_extension_ext_gw_mode.py b/neutron/tests/unit/test_extension_ext_gw_mode.py index 8732debbc4..2a7fc6b9d0 100644 --- a/neutron/tests/unit/test_extension_ext_gw_mode.py +++ b/neutron/tests/unit/test_extension_ext_gw_mode.py @@ -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): diff --git a/neutron/tests/unit/test_l3_plugin.py b/neutron/tests/unit/test_l3_plugin.py index 14c9b22124..d85b22caa7 100644 --- a/neutron/tests/unit/test_l3_plugin.py +++ b/neutron/tests/unit/test_l3_plugin.py @@ -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