From: Salvatore Orlando Date: Mon, 25 Feb 2013 14:41:06 +0000 (+0100) Subject: Enable multiple L3 GW services on NVP plugin X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=7d736867a34f6db592dc67813dececf6e94294d0;p=openstack-build%2Fneutron-build.git Enable multiple L3 GW services on NVP plugin Bug 1130211 This patch allows for using multiple layer-3 gateways leveraging the provider networks extension. Change-Id: I293920c2565f4670e9be9b22dc1b60431b62f7e7 --- diff --git a/quantum/db/migration/alembic_migrations/versions/1341ed32cc1e_nvp_netbinding_update.py b/quantum/db/migration/alembic_migrations/versions/1341ed32cc1e_nvp_netbinding_update.py new file mode 100644 index 000000000..c1f83a3e6 --- /dev/null +++ b/quantum/db/migration/alembic_migrations/versions/1341ed32cc1e_nvp_netbinding_update.py @@ -0,0 +1,66 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 OpenStack LLC +# +# 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_net_binding + +Revision ID: 1341ed32cc1e +Revises: 3b54bf9e29f7 +Create Date: 2013-02-26 01:28:29.182195 + +""" + +# revision identifiers, used by Alembic. +revision = '1341ed32cc1e' +down_revision = '3b54bf9e29f7' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2' +] + +from alembic import op +import sqlalchemy as sa + + +from quantum.db import migration + + +def upgrade(active_plugin=None, options=None): + if not migration.should_run(active_plugin, migration_for_plugins): + return + op.alter_column('nvp_network_bindings', 'tz_uuid', + name='phy_uuid', + existing_type=sa.String(36), + existing_nullable=True) + op.alter_column('nvp_network_bindings', 'binding_type', + type_=sa.Enum('flat', 'vlan', 'stt', 'gre', 'l3_ext', + name='nvp_network_bindings_binding_type'), + existing_nullable=True) + + +def downgrade(active_plugin=None, options=None): + if not migration.should_run(active_plugin, migration_for_plugins): + return + op.alter_column('nvp_network_bindings', 'phy_uuid', + name='tz_uuid', + existing_type=sa.String(36), + existing_nullable=True) + op.alter_column('nvp_network_bindings', 'binding_type', + type_=sa.Enum('flat', 'vlan', 'stt', 'gre', + name='nvp_network_bindings_binding_type'), + existing_nullable=True) diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py b/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py index 3ec2ddda8..218f531e6 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py @@ -78,6 +78,7 @@ NVP_EXTGW_NAT_RULES_ORDER = 255 # Provider network extension - allowed network types for the NVP Plugin class NetworkTypes: """ Allowed provider network types for the NVP Plugin """ + L3_EXT = 'l3_ext' STT = 'stt' GRE = 'gre' FLAT = 'flat' @@ -330,6 +331,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, def _create_and_attach_router_port(self, cluster, context, router_id, port_data, attachment_type, attachment, + attachment_vlan=None, subnet_ids=None): # Use a fake IP address if gateway port is not 'real' ip_addresses = (port_data.get('fake_ext_gw') and @@ -352,33 +354,43 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, "%(router_id)s"), {'port_id': port_data.get('id'), 'router_id': router_id}) + self._update_router_port_attachment(cluster, context, router_id, + port_data, attachment_type, + attachment, attachment_vlan, + lrouter_port['uuid']) + return lrouter_port + + def _update_router_port_attachment(self, cluster, context, + router_id, port_data, + attachment_type, attachment, + attachment_vlan=None, + nvp_router_port_id=None): + if not nvp_router_port_id: + nvp_router_port_id = self._find_router_gw_port(context, port_data) try: - # Add a L3 gateway attachment - # TODO(Salvatore-Orlando): Allow per router specification of - # l3 gw service uuid as well as per-tenant specification nvplib.plug_router_port_attachment(cluster, router_id, - lrouter_port['uuid'], + nvp_router_port_id, attachment, - attachment_type) + attachment_type, + attachment_vlan) LOG.debug(_("Attached %(att)s to NVP router port %(port)s"), - {'att': attachment, 'port': lrouter_port['uuid']}) + {'att': attachment, 'port': nvp_router_port_id}) except NvpApiClient.NvpApiException: # Must remove NVP logical port nvplib.delete_router_lport(cluster, router_id, - lrouter_port['uuid']) + nvp_router_port_id) LOG.exception(_("Unable to plug attachment in NVP logical " "router port %(r_port_id)s, associated with " "Quantum %(q_port_id)s"), - {'r_port_id': lrouter_port['uuid'], + {'r_port_id': nvp_router_port_id, 'q_port_id': port_data.get('id')}) raise nvp_exc.NvpPluginException( err_msg=(_("Unable to plug attachment in router port " "%(r_port_id)s for quantum port id %(q_port_id)s " "on router %(router_id)s") % - {'r_port_id': lrouter_port['uuid'], + {'r_port_id': nvp_router_port_id, 'q_port_id': port_data.get('id'), 'router_id': router_id})) - return lrouter_port def _get_port_by_device_id(self, context, device_id, device_owner): """ Retrieve ports associated with a specific device id. @@ -596,6 +608,15 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, port_data['name'], True, ip_addresses) + ext_network = self.get_network(context, port_data['network_id']) + if ext_network.get(pnet.NETWORK_TYPE) == NetworkTypes.L3_EXT: + # Update attachment + self._update_router_port_attachment( + cluster, context, router_id, port_data, + "L3GatewayAttachment", + ext_network[pnet.PHYSICAL_NETWORK], + ext_network[pnet.SEGMENTATION_ID], + lr_port['uuid']) # Set the SNAT rule for each subnet (only first IP) for cidr in self._find_router_subnets_cidrs(context, router_id): nvplib.create_lrouter_snat_rule( @@ -635,6 +656,12 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, cluster, router_id, "SourceNatRule", max_num_expected=1, min_num_expected=1, source_ip_addresses=cidr) + # Reset attachment + self._update_router_port_attachment( + cluster, context, router_id, port_data, + "L3GatewayAttachment", + self.default_cluster.default_l3_gw_service_uuid, + nvp_router_port_id=lr_port['uuid']) except NvpApiClient.ResourceNotFound: raise nvp_exc.NvpPluginException( @@ -780,6 +807,10 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, if binding: raise q_exc.VlanIdInUse(vlan_id=segmentation_id, physical_network=physical_network) + elif network_type == NetworkTypes.L3_EXT: + if (segmentation_id_set and + (segmentation_id < 1 or segmentation_id > 4094)): + err_msg = _("%s out of range (1 to 4094)") % segmentation_id else: err_msg = _("%(net_type_param)s %(net_type_value)s not " "supported") % {'net_type_param': pnet.NETWORK_TYPE, @@ -796,10 +827,10 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, network['id']) # With NVP plugin 'normal' overlay networks will have no binding # TODO(salvatore-orlando) make sure users can specify a distinct - # tz_uuid as 'provider network' for STT net type + # phy_uuid as 'provider network' for STT net type if binding: network[pnet.NETWORK_TYPE] = binding.binding_type - network[pnet.PHYSICAL_NETWORK] = binding.tz_uuid + network[pnet.PHYSICAL_NETWORK] = binding.phy_uuid network[pnet.SEGMENTATION_ID] = binding.vlan_id def _handle_lswitch_selection(self, cluster, network, @@ -829,7 +860,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, cluster, network.tenant_id, "%s-ext-%s" % (network.name, len(lswitches)), network_binding.binding_type, - network_binding.tz_uuid, + network_binding.phy_uuid, network_binding.vlan_id, network.id) return selected_lswitch @@ -907,7 +938,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, context.session, new_net['id'], net_data.get(pnet.NETWORK_TYPE), net_data.get(pnet.PHYSICAL_NETWORK), - net_data.get(pnet.SEGMENTATION_ID)) + net_data.get(pnet.SEGMENTATION_ID, 0)) self._extend_network_dict_provider(context, new_net, net_binding) self._extend_network_port_security_dict(context, new_net) diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py b/quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py index 821a24bee..a40e0d7c4 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py @@ -47,10 +47,22 @@ def get_network_binding_by_vlanid(session, vlan_id): return -def add_network_binding(session, network_id, binding_type, tz_uuid, vlan_id): +def get_network_binding_by_vlanid_and_phynet(session, vlan_id, + physical_network): + session = session or db.get_session() + try: + binding = (session.query(nicira_models.NvpNetworkBinding). + filter_by(vlan_id=vlan_id, phy_uuid=physical_network). + one()) + return binding + except exc.NoResultFound: + return + + +def add_network_binding(session, network_id, binding_type, phy_uuid, vlan_id): with session.begin(subtransactions=True): binding = nicira_models.NvpNetworkBinding(network_id, binding_type, - tz_uuid, vlan_id) + phy_uuid, vlan_id) session.add(binding) return binding diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py b/quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py index 0e81eb556..1c879c9f5 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py @@ -33,22 +33,22 @@ class NvpNetworkBinding(model_base.BASEV2): ForeignKey('networks.id', ondelete="CASCADE"), primary_key=True) # 'flat', 'vlan', stt' or 'gre' - binding_type = Column(Enum('flat', 'vlan', 'stt', 'gre', + binding_type = Column(Enum('flat', 'vlan', 'stt', 'gre', 'l3_ext', name='nvp_network_bindings_binding_type'), nullable=False) - tz_uuid = Column(String(36)) + phy_uuid = Column(String(36)) vlan_id = Column(Integer) - def __init__(self, network_id, binding_type, tz_uuid, vlan_id): + def __init__(self, network_id, binding_type, phy_uuid, vlan_id): self.network_id = network_id self.binding_type = binding_type - self.tz_uuid = tz_uuid + self.phy_uuid = phy_uuid self.vlan_id = vlan_id def __repr__(self): return "" % (self.network_id, self.binding_type, - self.tz_uuid, + self.phy_uuid, self.vlan_id) diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py b/quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py index 0bbabd598..3af0c3455 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py @@ -869,18 +869,22 @@ def find_router_gw_port(context, cluster, router_id): # TODO(salvatore-orlando): Consider storing it in Quantum DB results = query_lrouter_lports( cluster, router_id, - filters={'attachment_gwsvc_uuid': cluster.default_l3_gw_service_uuid}) - if len(results): - # Return logical router port - return results[0] + relations="LogicalPortAttachment") + for lport in results: + if '_relations' in lport: + attachment = lport['_relations'].get('LogicalPortAttachment') + if attachment and attachment.get('type') == 'L3GatewayAttachment': + return lport def plug_router_port_attachment(cluster, router_id, port_id, - attachment_uuid, nvp_attachment_type): + attachment_uuid, nvp_attachment_type, + attachment_vlan=None): """Attach a router port to the given attachment. Current attachment types: - PatchAttachment [-> logical switch port uuid] - L3GatewayAttachment [-> L3GatewayService uuid] + For the latter attachment type a VLAN ID can be specified as well """ uri = _build_uri_path(LROUTERPORT_RESOURCE, port_id, router_id, is_attachment=True) @@ -890,6 +894,8 @@ def plug_router_port_attachment(cluster, router_id, port_id, attach_obj["peer_port_uuid"] = attachment_uuid elif nvp_attachment_type == "L3GatewayAttachment": attach_obj["l3_gateway_service_uuid"] = attachment_uuid + if attachment_vlan: + attach_obj['vlan_id'] = attachment_vlan else: raise Exception(_("Invalid NVP attachment type '%s'"), nvp_attachment_type) diff --git a/quantum/tests/unit/nicira/etc/fake_get_lrouter_lport_att.json b/quantum/tests/unit/nicira/etc/fake_get_lrouter_lport_att.json index 9ba94d3e4..bc5723d11 100644 --- a/quantum/tests/unit/nicira/etc/fake_get_lrouter_lport_att.json +++ b/quantum/tests/unit/nicira/etc/fake_get_lrouter_lport_att.json @@ -3,6 +3,8 @@ { %(peer_port_href_field)s %(peer_port_uuid_field)s + %(l3_gateway_service_uuid_field)s + %(vlan_id)s "type": "%(type)s", "schema": "/ws.v1/schema/%(type)s" } diff --git a/quantum/tests/unit/nicira/fake_nvpapiclient.py b/quantum/tests/unit/nicira/fake_nvpapiclient.py index 039e22930..772343c56 100644 --- a/quantum/tests/unit/nicira/fake_nvpapiclient.py +++ b/quantum/tests/unit/nicira/fake_nvpapiclient.py @@ -245,10 +245,9 @@ class FakeClient: if not '_relations' in src or not src['_relations'].get(relation): return # Item does not have relation relation_data = src['_relations'].get(relation) - dst_relations = dst.get('_relations') - if not dst_relations: - dst_relations = {} + dst_relations = dst.get('_relations', {}) dst_relations[relation] = relation_data + dst['_relations'] = dst_relations def _fill_attachment(self, att_data, ls_uuid=None, lr_uuid=None, lp_uuid=None): @@ -266,7 +265,8 @@ class FakeClient: else: new_data['%s_field' % field_name] = "" - for field in ['vif_uuid', 'peer_port_href', 'peer_port_uuid']: + for field in ['vif_uuid', 'peer_port_href', 'vlan_id', + 'peer_port_uuid', 'l3_gateway_service_uuid']: populate_field(field) return new_data @@ -460,13 +460,11 @@ class FakeClient: if not is_attachment: resource.update(json.loads(body)) else: - relations = resource.get("_relations") - if not relations: - relations = {} - relations['LogicalPortAttachment'] = json.loads(body) - resource['_relations'] = relations + relations = resource.get("_relations", {}) body_2 = json.loads(body) resource['att_type'] = body_2['type'] + relations['LogicalPortAttachment'] = body_2 + resource['_relations'] = relations if body_2['type'] == "PatchAttachment": # We need to do a trick here if self.LROUTER_RESOURCE in res_type: @@ -486,6 +484,7 @@ class FakeClient: elif body_2['type'] == "L3GatewayAttachment": resource['attachment_gwsvc_uuid'] = ( body_2['l3_gateway_service_uuid']) + resource['vlan_id'] = body_2.get('vlan_id') elif body_2['type'] == "L2GatewayAttachment": resource['attachment_gwsvc_uuid'] = ( body_2['l2_gateway_service_uuid']) diff --git a/quantum/tests/unit/nicira/test_nicira_plugin.py b/quantum/tests/unit/nicira/test_nicira_plugin.py index f4c8d9786..34d5749f8 100644 --- a/quantum/tests/unit/nicira/test_nicira_plugin.py +++ b/quantum/tests/unit/nicira/test_nicira_plugin.py @@ -14,7 +14,6 @@ # limitations under the License. import contextlib -import logging import os import mock @@ -26,6 +25,7 @@ import webob.exc from quantum.common import constants import quantum.common.test_lib as test_lib from quantum import context +from quantum.extensions import l3 from quantum.extensions import providernet as pnet from quantum.extensions import securitygroup as secgrp from quantum import manager @@ -34,6 +34,7 @@ from quantum.plugins.nicira.nicira_nvp_plugin.extensions import nvp_networkgw from quantum.plugins.nicira.nicira_nvp_plugin.extensions import (nvp_qos as ext_qos) from quantum.plugins.nicira.nicira_nvp_plugin import nvplib +from quantum.plugins.nicira.nicira_nvp_plugin import QuantumPlugin from quantum.tests.unit.nicira import fake_nvpapiclient import quantum.tests.unit.nicira.test_networkgw as test_l2_gw import quantum.tests.unit.test_db_plugin as test_plugin @@ -42,7 +43,6 @@ import quantum.tests.unit.test_extension_security_group as ext_sg from quantum.tests.unit import test_extensions import quantum.tests.unit.test_l3_plugin as test_l3_plugin -LOG = logging.getLogger(__name__) NICIRA_PKG_PATH = nvp_plugin.__name__ NICIRA_EXT_PATH = "../../plugins/nicira/nicira_nvp_plugin/extensions" @@ -261,6 +261,115 @@ class TestNiciraSecurityGroup(ext_sg.TestSecurityGroups, class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase, NiciraPluginV2TestCase): + def _create_l3_ext_network(self, vlan_id=None): + name = 'l3_ext_net' + net_type = QuantumPlugin.NetworkTypes.L3_EXT + providernet_args = {pnet.NETWORK_TYPE: net_type, + pnet.PHYSICAL_NETWORK: 'l3_gw_uuid'} + if vlan_id: + providernet_args[pnet.SEGMENTATION_ID] = vlan_id + return self.network(name=name, + router__external=True, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID)) + + def _test_create_l3_ext_network(self, vlan_id=None): + name = 'l3_ext_net' + net_type = QuantumPlugin.NetworkTypes.L3_EXT + expected = [('subnets', []), ('name', name), ('admin_state_up', True), + ('status', 'ACTIVE'), ('shared', False), + (l3.EXTERNAL, True), + (pnet.NETWORK_TYPE, net_type), + (pnet.PHYSICAL_NETWORK, 'l3_gw_uuid'), + (pnet.SEGMENTATION_ID, vlan_id)] + with self._create_l3_ext_network(vlan_id) as net: + for k, v in expected: + self.assertEqual(net['network'][k], v) + + def _nvp_validate_ext_gw(self, router_id, l3_gw_uuid, vlan_id): + """ Verify data on fake NVP API client in order to validate + plugin did set them properly""" + ports = [port for port in self.fc._fake_lrouter_lport_dict.values() + if (port['lr_uuid'] == router_id and + port['att_type'] == "L3GatewayAttachment")] + self.assertEqual(len(ports), 1) + self.assertEqual(ports[0]['attachment_gwsvc_uuid'], l3_gw_uuid) + self.assertEqual(ports[0].get('vlan_id'), vlan_id) + + 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): + with self._create_l3_ext_network(vlan_id) as net: + with self.subnet(network=net) as s: + data = {'router': {'tenant_id': 'whatever'}} + data['router']['name'] = 'router1' + data['router']['external_gateway_info'] = { + 'network_id': s['subnet']['network_id']} + router_req = self.new_create_request('routers', data, + self.fmt) + try: + res = router_req.get_response(self.ext_api) + router = self.deserialize(self.fmt, res) + self.assertEqual( + s['subnet']['network_id'], + (router['router']['external_gateway_info'] + ['network_id'])) + self._nvp_validate_ext_gw(router['router']['id'], + 'l3_gw_uuid', vlan_id) + finally: + self._delete('routers', router['router']['id']) + + def test_router_create_with_gwinfo_and_l3_ext_net(self): + self._test_router_create_with_gwinfo_and_l3_ext_net() + + 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_update_gateway_on_l3_ext_net(self, vlan_id=None): + with self.router() as r: + with self.subnet() as s1: + with self._create_l3_ext_network(vlan_id) as net: + with self.subnet(network=net) as s2: + self._set_net_external(s1['subnet']['network_id']) + try: + self._add_external_gateway_to_router( + r['router']['id'], + s1['subnet']['network_id']) + body = self._show('routers', r['router']['id']) + net_id = (body['router'] + ['external_gateway_info']['network_id']) + self.assertEqual(net_id, + s1['subnet']['network_id']) + # Plug network with external mapping + self._set_net_external(s2['subnet']['network_id']) + self._add_external_gateway_to_router( + r['router']['id'], + s2['subnet']['network_id']) + body = self._show('routers', r['router']['id']) + net_id = (body['router'] + ['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) + finally: + # Cleanup + self._remove_external_gateway_from_router( + r['router']['id'], + s2['subnet']['network_id']) + + def test_router_update_gateway_on_l3_ext_net(self): + self._test_router_update_gateway_on_l3_ext_net() + + def test_router_update_gateway_on_l3_ext_net_with_vlan(self): + self._test_router_update_gateway_on_l3_ext_net(444) + + def test_create_l3_ext_network_with_vlan(self): + self._test_create_l3_ext_network(666) + def test_floatingip_with_assoc_fails(self): self._test_floatingip_with_assoc_fails( 'quantum.plugins.nicira.nicira_nvp_plugin.' diff --git a/quantum/tests/unit/test_l3_plugin.py b/quantum/tests/unit/test_l3_plugin.py index 557353e04..3e0e7b0d5 100644 --- a/quantum/tests/unit/test_l3_plugin.py +++ b/quantum/tests/unit/test_l3_plugin.py @@ -333,7 +333,7 @@ class L3NatTestCaseBase(test_db_plugin.QuantumDbPluginV2TestCase): new_args = dict(itertools.izip(map(lambda x: x.replace('__', ':'), kwargs), kwargs.values())) - arg_list = (l3.EXTERNAL,) + arg_list = new_args.pop('arg_list', ()) + (l3.EXTERNAL,) return super(L3NatTestCaseBase, self)._create_network( fmt, name, admin_state_up, arg_list=arg_list, **new_args)