Merge "NSXv3: Add backend driver for Layer 2 gateway"

This commit is contained in:
Jenkins 2015-09-14 15:50:46 +00:00 committed by Gerrit Code Review
commit 12690ba02e
17 changed files with 523 additions and 15 deletions

View File

@ -109,6 +109,9 @@ function neutron_plugin_configure_service {
else else
die $LINENO "The VMware NSX plugin needs at least an NSX controller." die $LINENO "The VMware NSX plugin needs at least an NSX controller."
fi fi
if [[ "$NSX_L2GW_DRIVER" != "" ]]; then
iniset /$Q_PLUGIN_CONF_FILE nsx nsx_l2gw_driver $NSX_L2GW_DRIVER
fi
_nsxv3_ini_set default_tier0_router_uuid $DEFAULT_TIER0_ROUTER_UUID _nsxv3_ini_set default_tier0_router_uuid $DEFAULT_TIER0_ROUTER_UUID
_nsxv3_ini_set nsx_manager $NSX_MANAGER "The VMWare NSX plugin needs a NSX manager." _nsxv3_ini_set nsx_manager $NSX_MANAGER "The VMWare NSX plugin needs a NSX manager."
_nsxv3_ini_set nsx_user $NSX_USER _nsxv3_ini_set nsx_user $NSX_USER

View File

@ -225,6 +225,10 @@
# "service". # "service".
# replication_mode = service # replication_mode = service
# Specify the class path for the Layer 2 gateway backend driver(i.e. NSXv3/NSX-V).
# This field will be used when a L2 Gateway service plugin is configured.
# nsx_l2gw_driver = vmware_nsx.neutron.services.l2gateway.nsx_v3_driver.NsxV3Driver
[nsx_sync] [nsx_sync]
# Interval in seconds between runs of the status synchronization task. # Interval in seconds between runs of the status synchronization task.
# The plugin will aim at resynchronizing operational status for all # The plugin will aim at resynchronizing operational status for all
@ -327,3 +331,11 @@
# UUID of the default tier0 router that will be used for connecting to # UUID of the default tier0 router that will be used for connecting to
# tier1 logical routers and configuring external network # tier1 logical routers and configuring external network
# default_tier0_router_uuid = 412983fd-9016-45e5-93f2-48ba2a931225 # default_tier0_router_uuid = 412983fd-9016-45e5-93f2-48ba2a931225
# UUID of the default NSX bridge cluster that will be used to perform
# L2 gateway bridging between VXLAN and VLAN networks. It is an optional
# field. If default bridge cluster UUID is not specified, admin will have to
# manually create a L2 gateway corresponding to a NSX Bridge Cluster using
# L2 gateway APIs.
# This field must be specified on one of the active neutron servers only.
# default_bridge_cluster_uuid =

View File

@ -10,7 +10,8 @@ setenv = VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0 PYTHONHASHSEED=0
usedevelop = True usedevelop = True
install_command = {toxinidir}/tools/tox_install.sh {opts} {packages} install_command = {toxinidir}/tools/tox_install.sh {opts} {packages}
deps = -r{toxinidir}/requirements.txt deps = -egit+https://git.openstack.org/openstack/networking-l2gw#egg=networking-l2gw
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt -r{toxinidir}/test-requirements.txt
whitelist_externals = sh whitelist_externals = sh
commands = commands =

View File

@ -1,2 +1,2 @@
28430956782d 279b70ac3ae8
393bf843b96 393bf843b96

View File

@ -0,0 +1,43 @@
# Copyright 2015 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.
#
"""NSXv3 Add l2gwconnection table
Revision ID: 279b70ac3ae8
Revises: 28430956782d
Create Date: 2015-08-14 02:04:09.807926
"""
# revision identifiers, used by Alembic.
revision = '279b70ac3ae8'
down_revision = '28430956782d'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'nsx_l2gw_connection_mappings',
sa.Column('connection_id', sa.String(length=36), nullable=False),
sa.Column('port_id', sa.String(length=36), nullable=False),
sa.Column('bridge_endpoint_id', sa.String(length=36), nullable=False),
sa.ForeignKeyConstraint(['connection_id'],
['l2gatewayconnections.id'],
ondelete='CASCADE'),
sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('connection_id'),
)

View File

@ -183,6 +183,10 @@ nsx_v3_opts = [
"starting Neutron with the NSX plugin.")), "starting Neutron with the NSX plugin.")),
cfg.StrOpt('default_edge_cluster_uuid', cfg.StrOpt('default_edge_cluster_uuid',
help=_("Default edge cluster identifier")), help=_("Default edge cluster identifier")),
cfg.StrOpt('default_bridge_cluster_uuid',
help=_("Default bridge cluster identifier for L2 gateway. "
"This needs to be created in NSX before using the L2 "
"gateway service plugin.")),
cfg.IntOpt('retries', cfg.IntOpt('retries',
default=10, default=10,
help=_('Maximum number of times to retry API request')), help=_('Maximum number of times to retry API request')),

View File

@ -137,3 +137,7 @@ class ResourceNotFound(ManagerError):
class StaleRevision(ManagerError): class StaleRevision(ManagerError):
pass pass
class NsxL2GWConnectionMappingNotFound(n_exc.NotFound):
message = _('Unable to find mapping for L2 gateway connection: %(conn)s')

View File

@ -41,3 +41,6 @@ ROUTER_TYPES = [ROUTER_TYPE_TIER0, ROUTER_TYPE_TIER1]
# L2 agent vif type # L2 agent vif type
VIF_TYPE_DVS = 'dvs' VIF_TYPE_DVS = 'dvs'
# NSXv3 L2 Gateway constants
BRIDGE_ENDPOINT = "BRIDGEENDPOINT"

View File

@ -21,6 +21,7 @@ from sqlalchemy.orm import exc
import neutron.db.api as db import neutron.db.api as db
from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc
from vmware_nsx.neutron.plugins.vmware.dbexts import nsx_models from vmware_nsx.neutron.plugins.vmware.dbexts import nsx_models
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -203,3 +204,23 @@ def is_multiprovider_network(session, network_id):
return bool( return bool(
session.query(nsx_models.MultiProviderNetworks).filter_by( session.query(nsx_models.MultiProviderNetworks).filter_by(
network_id=network_id).first()) network_id=network_id).first())
# NSXv3 L2 Gateway DB methods.
def add_l2gw_connection_mapping(session, connection_id, bridge_endpoint_id,
port_id):
with session.begin(subtransactions=True):
mapping = nsx_models.NsxL2GWConnectionMapping(
connection_id=connection_id,
port_id=port_id,
bridge_endpoint_id=bridge_endpoint_id)
session.add(mapping)
return mapping
def get_l2gw_connection_mapping(session, connection_id):
try:
return (session.query(nsx_models.NsxL2GWConnectionMapping).
filter_by(connection_id=connection_id).one())
except exc.NoResultFound:
raise nsx_exc.NsxL2GWConnectionMappingNotFound(conn=connection_id)

View File

@ -300,3 +300,17 @@ class NetworkQueueMapping(model_base.BASEV2):
models_v2.Network, models_v2.Network,
backref=orm.backref("qos_queue", uselist=False, backref=orm.backref("qos_queue", uselist=False,
cascade='delete', lazy='joined')) cascade='delete', lazy='joined'))
class NsxL2GWConnectionMapping(model_base.BASEV2):
"""Define a mapping between L2 gateway connection and bridge endpoint."""
__tablename__ = 'nsx_l2gw_connection_mappings'
connection_id = sa.Column(sa.String(36),
sa.ForeignKey("l2gatewayconnections.id",
ondelete="CASCADE"),
nullable=False,
primary_key=True)
port_id = sa.Column(sa.String(36),
sa.ForeignKey("ports.id", ondelete="CASCADE"),
nullable=False)
bridge_endpoint_id = sa.Column(sa.String(36), nullable=False)

View File

@ -282,3 +282,30 @@ def get_qos_switching_profile(profile_id):
def delete_qos_switching_profile(profile_id): def delete_qos_switching_profile(profile_id):
resource = 'switching-profiles/%s' % profile_id resource = 'switching-profiles/%s' % profile_id
client.delete_resource(resource) client.delete_resource(resource)
def create_bridge_endpoint(device_name, seg_id, tags):
"""Create a bridge endpoint on the backend.
Create a bridge endpoint resource on a bridge cluster for the L2 gateway
network connection.
:param device_name: device_name actually refers to the bridge cluster's
UUID.
:param seg_id: integer representing the VLAN segmentation ID.
:param tags: nsx backend specific tags.
"""
resource = 'bridge-endpoints'
body = {'bridge_cluster_id': device_name,
'tags': tags,
'vlan': seg_id}
return client.create_resource(resource, body)
def delete_bridge_endpoint(bridge_endpoint_id):
"""Delete a bridge endpoint on the backend.
:param bridge_endpoint_id: string representing the UUID of the bridge
endpoint to be deleted.
"""
resource = 'bridge-endpoints/%s' % bridge_endpoint_id
client.delete_resource(resource)

View File

@ -27,13 +27,10 @@ from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.handlers import dhcp_rpc from neutron.api.rpc.handlers import dhcp_rpc
from neutron.api.rpc.handlers import metadata_rpc from neutron.api.rpc.handlers import metadata_rpc
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.extensions import external_net as ext_net_extn from neutron.callbacks import events
from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.callbacks import exceptions as callback_exc
from neutron.extensions import l3 from neutron.callbacks import registry
from neutron.extensions import portbindings as pbin from neutron.callbacks import resources
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as ext_sg
from neutron.common import constants as const from neutron.common import constants as const
from neutron.common import exceptions as n_exc from neutron.common import exceptions as n_exc
from neutron.common import rpc as n_rpc from neutron.common import rpc as n_rpc
@ -49,12 +46,19 @@ from neutron.db import l3_gwmode_db
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import portbindings_db from neutron.db import portbindings_db
from neutron.db import securitygroups_db from neutron.db import securitygroups_db
from neutron.extensions import external_net as ext_net_extn
from neutron.extensions import extra_dhcp_opt as ext_edo
from neutron.extensions import l3
from neutron.extensions import portbindings as pbin
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as ext_sg
from neutron.i18n import _LE, _LI, _LW from neutron.i18n import _LE, _LI, _LW
from neutron.plugins.common import constants as plugin_const from neutron.plugins.common import constants as plugin_const
from neutron.plugins.common import utils as n_utils from neutron.plugins.common import utils as n_utils
from vmware_nsx.neutron.plugins.vmware.common import config # noqa from vmware_nsx.neutron.plugins.vmware.common import config # noqa
from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc
from vmware_nsx.neutron.plugins.vmware.common import nsx_constants
from vmware_nsx.neutron.plugins.vmware.common import utils from vmware_nsx.neutron.plugins.vmware.common import utils
from vmware_nsx.neutron.plugins.vmware.dbexts import db as nsx_db from vmware_nsx.neutron.plugins.vmware.dbexts import db as nsx_db
from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib
@ -429,18 +433,28 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# self.get_port(context, parent_name) # self.get_port(context, parent_name)
return parent_name, tag return parent_name, tag
def _create_port_at_the_backend(self, context, neutron_db, port_data): def _create_port_at_the_backend(self, context, neutron_db,
port_data, l2gw_port_check):
tags = utils.build_v3_tags_payload(port_data) tags = utils.build_v3_tags_payload(port_data)
parent_name, tag = self._get_data_from_binding_profile( parent_name, tag = self._get_data_from_binding_profile(
context, port_data) context, port_data)
address_bindings = self._build_address_bindings(port_data) address_bindings = self._build_address_bindings(port_data)
# FIXME(arosen): we might need to pull this out of the # FIXME(arosen): we might need to pull this out of the
# transaction here later. # transaction here later.
vif_uuid = port_data['id']
attachment_type = nsx_constants.ATTACHMENT_VIF
# Change the attachment type for L2 gateway owned ports.
if l2gw_port_check:
# NSX backend requires the vif id be set to bridge endpoint id
# for ports plugged into a Bridge Endpoint.
vif_uuid = port_data.get('device_id')
attachment_type = port_data.get('device_owner')
result = nsxlib.create_logical_port( result = nsxlib.create_logical_port(
lswitch_id=port_data['network_id'], lswitch_id=port_data['network_id'],
vif_uuid=port_data['id'], name=port_data['name'], tags=tags, vif_uuid=vif_uuid, name=port_data['name'], tags=tags,
admin_state=port_data['admin_state_up'], admin_state=port_data['admin_state_up'],
address_bindings=address_bindings, address_bindings=address_bindings,
attachment_type=attachment_type,
parent_name=parent_name, parent_tag=tag) parent_name=parent_name, parent_tag=tag)
# TODO(salv-orlando): The logical switch identifier in the # TODO(salv-orlando): The logical switch identifier in the
@ -450,8 +464,8 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
neutron_db['network_id'], result['id']) neutron_db['network_id'], result['id'])
return result return result
def create_port(self, context, port): def create_port(self, context, port, l2gw_port_check=False):
dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) dhcp_opts = port['port'].get(ext_edo.EXTRADHCPOPTS, [])
port_id = uuidutils.generate_uuid() port_id = uuidutils.generate_uuid()
port['port']['id'] = port_id port['port']['id'] = port_id
@ -468,7 +482,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
if not self._network_is_external( if not self._network_is_external(
context, port['port']['network_id']): context, port['port']['network_id']):
lport = self._create_port_at_the_backend( lport = self._create_port_at_the_backend(
context, neutron_db, port['port']) context, neutron_db, port['port'], l2gw_port_check)
self._process_portbindings_create_and_update(context, self._process_portbindings_create_and_update(context,
port['port'], port['port'],
neutron_db) neutron_db)
@ -488,7 +502,27 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
context, lport['id'], [], sgids) context, lport['id'], [], sgids)
return neutron_db return neutron_db
def delete_port(self, context, port_id, l3_port_check=True): def _pre_delete_port_check(self, context, port_id, l2gw_port_check):
"""Perform checks prior to deleting a port."""
try:
kwargs = {
'context': context,
'port_check': l2gw_port_check,
'port_id': port_id,
}
# Send delete port notification to any interested service plugin
registry.notify(
resources.PORT, events.BEFORE_DELETE, self, **kwargs)
except callback_exc.CallbackFailure as e:
if len(e.errors) == 1:
raise e.errors[0].error
raise n_exc.ServicePortInUse(port_id=port_id, reason=e)
def delete_port(self, context, port_id,
l3_port_check=True, l2gw_port_check=True):
# if needed, check to see if this is a port owned by
# a l2 gateway. If so, we should prevent deletion here
self._pre_delete_port_check(context, port_id, l2gw_port_check)
# if needed, check to see if this is a port owned by # if needed, check to see if this is a port owned by
# a l3 router. If so, we should prevent deletion here # a l3 router. If so, we should prevent deletion here
if l3_port_check: if l3_port_check:

View File

@ -0,0 +1,266 @@
# Copyright 2015 VMware, Inc.
#
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from networking_l2gw.db.l2gateway import l2gateway_db
from networking_l2gw.services.l2gateway.common import constants as l2gw_const
from networking_l2gw.services.l2gateway import exceptions as l2gw_exc
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import uuidutils
from neutron.api.v2 import attributes
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import exceptions as n_exc
from neutron import context
from neutron.extensions import providernet
from neutron.i18n import _LE, _LI
from neutron import manager
from neutron.plugins.common import utils as n_utils
from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc
from vmware_nsx.neutron.plugins.vmware.common import nsx_constants
from vmware_nsx.neutron.plugins.vmware.common import utils as nsx_utils
from vmware_nsx.neutron.plugins.vmware.dbexts import db as nsx_db
from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib
LOG = logging.getLogger(__name__)
class NsxV3Driver(l2gateway_db.L2GatewayMixin):
"""Class to handle API calls for L2 gateway and NSXv3 backend."""
gateway_resource = l2gw_const.GATEWAY_RESOURCE_NAME
def __init__(self):
# Create a default L2 gateway if default_bridge_cluster_uuid is
# provided in nsx.ini
self._ensure_default_l2_gateway()
self.subscribe_callback_notifications()
LOG.debug("Initialization complete for NSXv3 driver for "
"L2 gateway service plugin.")
@property
def _core_plugin(self):
return manager.NeutronManager.get_plugin()
def subscribe_callback_notifications(self):
registry.subscribe(self._prevent_l2gw_port_delete, resources.PORT,
events.BEFORE_DELETE)
def _ensure_default_l2_gateway(self):
"""
Create a default logical L2 gateway.
Create a logical L2 gateway in the neutron database if the
default_bridge_cluster_uuid config parameter is set and if it is
not previously created. If not set, return.
"""
def_l2gw_uuid = cfg.CONF.nsx_v3.default_bridge_cluster_uuid
# Return if no default_bridge_cluster_uuid set in config
if not def_l2gw_uuid:
LOG.info(_LI("NSX: Default bridge cluster UUID not configured "
"in nsx.ini. No default L2 gateway created."))
return
admin_ctx = context.get_admin_context()
# Optimistically create the default L2 gateway in neutron DB
device = {'device_name': def_l2gw_uuid,
'interfaces': [{'name': 'default-bridge-cluster'}]}
def_l2gw = {'name': 'default-l2gw',
'devices': [device]}
l2gw_dict = {self.gateway_resource: def_l2gw}
l2_gateway = self.create_l2_gateway(admin_ctx, l2gw_dict)
# Verify that only one default L2 gateway is created
def_l2gw_exists = False
l2gateways = self._get_l2_gateways(admin_ctx)
for l2gateway in l2gateways:
# Since we ensure L2 gateway is created with only 1 device, we use
# the first device in the list.
if l2gateway['devices'][0]['device_name'] == def_l2gw_uuid:
if def_l2gw_exists:
LOG.info(_LI("Default L2 gateway is already created."))
try:
# Try deleting this duplicate default L2 gateway
self.delete_l2_gateway(admin_ctx, l2gateway['id'])
except l2gw_exc.L2GatewayInUse:
# If the L2 gateway we are trying to delete is in
# use then we should delete the L2 gateway which
# we just created ensuring there is only one
# default L2 gateway in the database.
self.delete_l2_gateway(admin_ctx, l2_gateway['id'])
else:
def_l2gw_exists = True
return l2_gateway
def _prevent_l2gw_port_delete(self, resource, event, trigger, **kwargs):
context = kwargs.get('context')
port_id = kwargs.get('port_id')
port_check = kwargs.get('port_check')
if port_check:
self.prevent_l2gw_port_deletion(context, port_id)
def _validate_device_list(self, devices):
# In NSXv3, one L2 gateway is mapped to one bridge cluster.
# So we expect only one device to be configured as part of
# a L2 gateway resource. The name of the device must be the bridge
# cluster's UUID.
if len(devices) != 1:
msg = _("Only a single device is supported for one L2 gateway")
raise n_exc.InvalidInput(error_message=msg)
if not uuidutils.is_uuid_like(devices[0]['device_name']):
msg = _("Device name must be configured with a UUID")
raise n_exc.InvalidInput(error_message=msg)
def create_l2_gateway(self, context, l2_gateway):
"""Create a logical L2 gateway."""
gw = l2_gateway[self.gateway_resource]
devices = gw['devices']
self._validate_device_list(devices)
return super(NsxV3Driver, self).create_l2_gateway(context,
l2_gateway)
def _validate_network(self, context, network_id):
network = self._core_plugin.get_network(context, network_id)
network_type = network.get(providernet.NETWORK_TYPE)
# If network is a provider network, verify whether it is of type VXLAN
if network_type and network_type != nsx_utils.NsxV3NetworkTypes.VXLAN:
msg = (_("Unsupported network type %s for L2 gateway "
"connection. Only VXLAN network type supported") %
network_type)
raise n_exc.InvalidInput(error_message=msg)
def _validate_segment_id(self, seg_id):
if not seg_id:
raise l2gw_exc.L2GatewaySegmentationRequired
return n_utils.is_valid_vlan_tag(seg_id)
def create_l2_gateway_connection(self, context, l2_gateway_connection):
"""Create a L2 gateway connection."""
#TODO(abhiraut): Move backend logic in a separate method
gw_connection = l2_gateway_connection.get(l2gw_const.
CONNECTION_RESOURCE_NAME)
network_id = gw_connection.get(l2gw_const.NETWORK_ID)
self._validate_network(context, network_id)
l2gw_connection = super(
NsxV3Driver, self).create_l2_gateway_connection(
context, l2_gateway_connection)
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
devices = self._get_l2_gateway_devices(context, l2gw_id)
# In NSXv3, there will be only one device configured per L2 gateway.
# The name of the device shall carry the backend bridge cluster's UUID.
device_name = devices[0].get('device_name')
# The seg-id will be provided either during gateway create or gateway
# connection create. l2gateway_db_mixin makes sure that it is
# configured one way or the other.
seg_id = gw_connection.get(l2gw_const.SEG_ID)
if seg_id is None:
seg_id = devices[0]['interfaces'][0].get('segmentation_id')
self._validate_segment_id(seg_id)
try:
tags = nsx_utils.build_v3_tags_payload(gw_connection)
bridge_endpoint = nsxlib.create_bridge_endpoint(
device_name=device_name,
seg_id=seg_id,
tags=tags)
except nsx_exc.ManagerError:
LOG.exception(_LE("Unable to update NSX backend, rolling back "
"changes on neutron"))
with excutils.save_and_reraise_exception():
super(NsxV3Driver,
self).delete_l2_gateway_connection(context,
l2gw_connection['id'])
# Create a logical port and connect it to the bridge endpoint.
tenant_id = self._core_plugin._get_tenant_id_for_create(context,
gw_connection)
# _get_tenant_id_for_create might return None in some cases.
# This is not acceptable for the NSX plugin
if context.is_admin and not tenant_id:
tenant_id = context.tenant_id
#TODO(abhiraut): Consider specifying the name of the port
port_dict = {'port': {
'tenant_id': tenant_id,
'network_id': network_id,
'mac_address': attributes.ATTR_NOT_SPECIFIED,
'admin_state_up': True,
'fixed_ips': [],
'device_id': bridge_endpoint['id'],
'device_owner': nsx_constants.BRIDGE_ENDPOINT,
'name': '', }}
try:
port = self._core_plugin.create_port(context, port_dict)
# Deallocate IP address from the port.
for fixed_ip in port.get('fixed_ips', []):
self._core_plugin._delete_ip_allocation(context, network_id,
fixed_ip['subnet_id'],
fixed_ip['ip_address'])
LOG.debug("IP addresses deallocated on port %s", port['id'])
except (nsx_exc.ManagerError,
n_exc.NeutronException):
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Unable to create L2 gateway port, "
"rolling back changes on neutron"))
nsxlib.delete_bridge_endpoint(bridge_endpoint['id'])
super(NsxV3Driver,
self).delete_l2_gateway_connection(context,
l2gw_connection['id'])
try:
# Update neutron's database with the mappings.
nsx_db.add_l2gw_connection_mapping(
session=context.session,
connection_id=l2gw_connection['id'],
bridge_endpoint_id=bridge_endpoint['id'],
port_id=port['id'])
except db_exc.DBError:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Unable to add L2 gateway connection "
"mappings, rolling back changes on neutron"))
nsxlib.delete_bridge_endpoint(bridge_endpoint['id'])
super(NsxV3Driver,
self).delete_l2_gateway_connection(context,
l2gw_connection['id'])
return l2gw_connection
def delete_l2_gateway_connection(self, context, l2_gateway_connection):
"""Delete a L2 gateway connection."""
conn_mapping = nsx_db.get_l2gw_connection_mapping(
session=context.session,
connection_id=l2_gateway_connection)
bridge_endpoint_id = conn_mapping.get('bridge_endpoint_id')
# Delete the logical port from the bridge endpoint.
self._core_plugin.delete_port(context=context,
port_id=conn_mapping.get('port_id'),
l2gw_port_check=False)
try:
nsxlib.delete_bridge_endpoint(bridge_endpoint_id)
except nsx_exc.ManagerError:
LOG.exception(_LE("Unable to delete bridge endpoint %s on the "
"backend.") % bridge_endpoint_id)
return (super(NsxV3Driver, self).
delete_l2_gateway_connection(context,
l2_gateway_connection))
def prevent_l2gw_port_deletion(self, context, port_id):
"""Prevent core plugin from deleting L2 gateway port."""
try:
port = self._core_plugin.get_port(context, port_id)
except n_exc.PortNotFound:
return
if port['device_owner'] == nsx_constants.BRIDGE_ENDPOINT:
reason = _("has device owner %s") % port['device_owner']
raise n_exc.ServicePortInUse(port_id=port_id, reason=reason)

View File

@ -16,6 +16,8 @@
import os import os
from networking_l2gw.db.l2gateway import l2gateway_models # noqa
from vmware_nsx.neutron.plugins.vmware.api_client import client as nsx_client from vmware_nsx.neutron.plugins.vmware.api_client import client as nsx_client
from vmware_nsx.neutron.plugins.vmware.api_client import eventlet_client from vmware_nsx.neutron.plugins.vmware.api_client import eventlet_client
from vmware_nsx.neutron.plugins.vmware import extensions from vmware_nsx.neutron.plugins.vmware import extensions

View File

@ -0,0 +1,74 @@
# Copyright (c) 2015 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from neutron import context
from neutron.tests import base
from networking_l2gw.tests.unit.db import test_l2gw_db
from vmware_nsx.neutron.services.l2gateway import nsx_v3_driver
from vmware_nsx.neutron.services.l2gateway import plugin as l2gw_plugin
from oslo_config import cfg
from oslo_utils import uuidutils
NSX_V3_DRIVER_CLASS_PATH = ('vmware_nsx.neutron.services.l2gateway.'
'nsx_v3_driver.NsxV3Driver')
class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
base.BaseTestCase):
def setUp(self):
super(TestNsxV3L2GatewayDriver, self).setUp()
cfg.CONF.set_override("nsx_l2gw_driver",
NSX_V3_DRIVER_CLASS_PATH, 'NSX')
self.l2gw_plugin = l2gw_plugin.NsxL2GatewayPlugin()
self.driver = nsx_v3_driver.NsxV3Driver()
self.context = context.get_admin_context()
def test_nsxl2gw_driver_init(self):
with mock.patch.object(nsx_v3_driver.NsxV3Driver,
'_ensure_default_l2_gateway') as def_gw:
with mock.patch.object(nsx_v3_driver.NsxV3Driver,
'subscribe_callback_notifications') as sub:
with mock.patch.object(nsx_v3_driver.LOG,
'debug') as debug:
l2gw_plugin.NsxL2GatewayPlugin()
self.assertTrue(def_gw.called)
self.assertTrue(sub.called)
self.assertTrue(debug.called)
def test_create_default_l2_gateway(self):
def_bridge_cluster_id = uuidutils.generate_uuid()
with mock.patch.object(nsx_v3_driver.NsxV3Driver,
'subscribe_callback_notifications'):
cfg.CONF.set_override("default_bridge_cluster_uuid",
def_bridge_cluster_id,
"nsx_v3")
l2gw_plugin.NsxL2GatewayPlugin()
l2gws = self.driver._get_l2_gateways(self.context)
def_l2gw = None
for l2gw in l2gws:
for device in l2gw['devices']:
if device['device_name'] == def_bridge_cluster_id:
def_l2gw = l2gw
self.assertIsNotNone(def_l2gw)
self.assertTrue(def_l2gw.devices[0].device_name,
def_bridge_cluster_id)
self.assertTrue(def_l2gw.devices[0].interfaces[0].interface_name,
'default-bridge-cluster')