--- /dev/null
+# 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)
# 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'
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
"%(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.
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(
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(
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,
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,
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
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)
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
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 "<NetworkBinding(%s,%s,%s,%s)>" % (self.network_id,
self.binding_type,
- self.tz_uuid,
+ self.phy_uuid,
self.vlan_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)
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)
{
%(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"
}
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):
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
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:
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'])
# limitations under the License.
import contextlib
-import logging
import os
import mock
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
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
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"
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.'
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)