[NSX-v3] Use bridge endpoint profiles for L2 gateways

Replace NSX bridge cluster with bridge endpoint profiles as the
backend resource used to implement L2 gateways.

The logic for creating a gateway connection is not changed,
with the only exception that bridge endpoints now have a reference
to a bridge endpoint profile.

Connections created using bridge clusters can be safely removed,
while creation of new connection on gateways leveraging bridge
clusters will fail.

Change-Id: I29cd9a2501ab4b7dd226729f33ab962bbba2dfff
This commit is contained in:
Salvatore Orlando 2019-03-11 03:50:49 -07:00 committed by Adit Sarfaty
parent 0be608d8b0
commit 0a952ce786
7 changed files with 360 additions and 182 deletions

View File

@ -417,6 +417,7 @@ nsx_v3_opts = nsx_v3_and_p + [
"Neutron networks, if no physical network has been "
"specified")),
cfg.StrOpt('default_bridge_cluster',
deprecated_for_removal=True,
help=_("(Optional) Name or UUID of the default NSX bridge "
"cluster that will be used to perform L2 gateway "
"bridging between VXLAN and VLAN networks. If default "
@ -425,6 +426,13 @@ nsx_v3_opts = nsx_v3_and_p + [
"NSX Bridge Cluster using L2 gateway APIs. This field "
"must be specified on one of the active neutron "
"servers only.")),
cfg.StrOpt('default_bridge_endpoint_profile',
help=_("(Optional) Name or UUID of the default NSX bridge "
"endpoint profile that will be used to perform L2 "
"bridging between networks in the NSX fabric and "
"VLANs external to NSX. If not specified, operators "
"will need to explictly create a layer-2 gateway in "
"Neutron using the L2 gateway APIs.")),
cfg.StrOpt('default_tier0_router',
help=_("Name or UUID of the default tier0 router that will be "
"used for connecting to tier1 logical routers and "

View File

@ -76,6 +76,11 @@ class L2GatewayAlreadyInUse(n_exc.Conflict):
message = _("Gateway Service %(gateway)s is already in use")
class BridgeEndpointAttachmentInUse(n_exc.Conflict):
message = _("The NSX backend only allow a single L2 gateway connection "
"for network %(network_id)s")
class InvalidTransportType(NsxPluginException):
message = _("The transport type %(transport_type)s is not recognized "
"by the backend")

View File

@ -391,11 +391,6 @@ def get_l2gw_connection_mapping(session, connection_id):
pass
def get_l2gw_connection_mappings_by_bridge(session, bridge_endpoint_id):
return (session.query(nsx_models.NsxL2GWConnectionMapping).
filter_by(bridge_endpoint_id=bridge_endpoint_id).all())
# NSXv3 QoS policy id <-> switch Id mapping
def add_qos_policy_profile_mapping(session, qos_policy_id, switch_profile_id):
with session.begin(subtransactions=True):

View File

@ -1409,6 +1409,9 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
# (for example - transport zone with KVM)
LOG.exception("Unable to create port on the backend: %s",
inst)
if inst.error_code == 8407:
raise nsx_exc.BridgeEndpointAttachmentInUse(
network_id=port_data['network_id'])
msg = _("Unable to create port on the backend")
raise nsx_exc.NsxPluginException(err_msg=msg)

View File

@ -40,18 +40,22 @@ from vmware_nsx.db import db as nsx_db
from vmware_nsx.extensions import projectpluginmap
from vmware_nsxlib.v3 import exceptions as nsxlib_exc
from vmware_nsxlib.v3 import nsx_constants
from vmware_nsxlib.v3 import utils as nsxlib_utils
LOG = logging.getLogger(__name__)
class _NotUniqueL2GW(Exception):
"""Raised if validation of default L2 GW uniqueness fails."""
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, plugin):
# Create a default L2 gateway if default_bridge_cluster is
# provided in nsx.ini
super(NsxV3Driver, self).__init__()
self._plugin = plugin
LOG.debug("Starting service plugin for NSX L2Gateway")
@ -75,62 +79,142 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
registry.subscribe(self._ensure_default_l2_gateway, resources.PROCESS,
events.BEFORE_SPAWN)
def _find_default_l2_gateway(self, admin_ctx, def_device_id):
for l2gateway in self._get_l2_gateways(admin_ctx):
if l2gateway['devices'][0]['device_name'] == def_device_id:
return l2gateway
@nsxlib_utils.retry_random_upon_exception(_NotUniqueL2GW, max_attempts=10)
def _create_default_l2_gateway(self, admin_ctx, l2gw_dict, def_device_id):
LOG.debug("Creating default layer-2 gateway with: %s", l2gw_dict)
def_l2gw = super(NsxV3Driver, self).create_l2_gateway(admin_ctx,
l2gw_dict)
# Verify that no other instance of neutron-server had the same
# brilliant idea...
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_device_id:
if l2gateway['id'] == def_l2gw['id']:
# Nothing to worry about, that's our gateway
continue
LOG.info("Default L2 gateway is already created with "
"id %s, deleting L2 gateway with id %s",
l2gateway['id'], def_l2gw['id'])
# Commit suicide!
self.validate_l2_gateway_for_delete(
admin_ctx, def_l2gw)
# We can be sure the gateway is not in use...
super(NsxV3Driver, self).delete_l2_gateway(
admin_ctx, def_l2gw['id'])
# The operation should be retried to avoid the situation where
# every instance deletes the gateway it created
raise _NotUniqueL2GW
return def_l2gw
def _get_bridge_vlan_tz_id(self, bep_data):
nsxlib = self._core_plugin.nsxlib
# Edge cluster Id is mandatory, do not fear KeyError
edge_cluster_id = bep_data['edge_cluster_id']
member_indexes = bep_data.get('edge_cluster_member_indexes', [])
# NSX should not allow bridge endpoint profiles attached to
# non-existing edge clusters
edge_cluster = nsxlib.edge_cluster.get(edge_cluster_id)
member_map = dict((member['member_index'],
member['transport_node_id'])
for member in edge_cluster['members'])
# By default consider all transport nodes in the cluster for
# retrieving the VLAN transprtzone
tn_ids = member_map.values()
if member_indexes:
try:
tn_ids = [member_map[idx] for idx in member_indexes]
except KeyError:
LOG.warning("Invalid member indexes specified in bridge "
"endpoint profile: %(bep_id)s: %(indexes)s",
{'bep_id': bep_data['id'],
'indexes': member_indexes})
# Retrieve VLAN transport zones
vlan_transport_zones = nsxlib.search_all_resource_by_attributes(
nsxlib.transport_zone.resource_type,
transport_type='VLAN')
vlan_tz_map = dict((vlan_tz['id'], [])
for vlan_tz in vlan_transport_zones)
for tn_id in tn_ids:
tn_data = nsxlib.transport_node.get(tn_id)
for tz_endpoint in tn_data.get('transport_zone_endpoints', []):
tz_id = tz_endpoint['transport_zone_id']
if tz_id in vlan_tz_map:
vlan_tz_map[tz_id].append(tn_id)
# Find the VLAN transport zone that is used by all transport nodes
results = []
for (tz_id, nodes) in vlan_tz_map.items():
if set(nodes) != set(tn_ids):
continue
results.append(tz_id)
return results
def _ensure_default_l2_gateway(self, resource, event,
trigger, payload=None):
"""
Create a default logical L2 gateway.
Create a logical L2 gateway in the neutron database if the
default_bridge_cluster config parameter is set and if it is
not previously created. If not set, return.
Create a logical L2 gateway in the neutron database for the
default bridge endpoint profile, if specified in the configuration.
Ensure only one gateway is configured in presence of multiple
neutron servers.
"""
def_l2gw_name = cfg.CONF.nsx_v3.default_bridge_cluster
# Return if no default_bridge_cluster set in config
if not def_l2gw_name:
LOG.info("NSX: Default bridge cluster not configured "
"in nsx.ini. No default L2 gateway created.")
if cfg.CONF.nsx_v3.default_bridge_cluster:
LOG.warning("Attention! The default_bridge_cluster option is "
"still set to %s. This option won't have any effect "
"as L2 gateways based on bridge clusters are not "
"implemented anymore",
cfg.CONF.nsx_v3.default_bridge_cluster)
def_bep = cfg.CONF.nsx_v3.default_bridge_endpoint_profile
# Return if no default_endpoint_profile set in config
if not def_bep:
LOG.info("NSX Default bridge endpoint profile not set. "
"Default L2 gateway will not be processed.")
return
admin_ctx = context.get_admin_context()
def_l2gw_uuid = (
self._core_plugin.nsxlib.bridge_cluster.get_id_by_name_or_id(
def_l2gw_name))
# Optimistically create the default L2 gateway in neutron DB
device = {'device_name': def_l2gw_uuid,
'interfaces': [{'name': 'default-bridge-cluster'}]}
nsx_bep_client = self._core_plugin.nsxlib.bridge_endpoint_profile
bep_id = nsx_bep_client.get_id_by_name_or_id(def_bep)
def_l2gw = self._find_default_l2_gateway(admin_ctx, bep_id)
# If there is already an existing gateway, use that one
if def_l2gw:
LOG.info("A default L2 gateway for bridge endpoint profile "
"%(bep_id)s already exists. Reusing L2 gateway "
"%(def_l2gw_id)s)",
{'bep_id': bep_id, 'def_l2gw_id': def_l2gw['id']})
return def_l2gw
bep_data = nsx_bep_client.get(bep_id)
vlan_tzs = self._get_bridge_vlan_tz_id(bep_data)
if not vlan_tzs:
LOG.info("No NSX VLAN transport zone could be used for bridge "
"endpoint profile: %s. Default L2 gateway will not "
"be processed", bep_id)
return
# TODO(salv-orlando): Implement support for multiple VLAN TZ
vlan_tz = vlan_tzs[0]
if len(vlan_tzs) > 1:
LOG.info("The NSX L2 gateway driver currenly supports a single "
"VLAN transport zone for bridging, but %(num_tz)d "
"were specified. Transport zone %(tz)s will be used "
"for L2 gateways",
{'num_tz': len(vlan_tzs), 'tz': vlan_tz})
device = {'device_name': bep_id,
'interfaces': [{'name': vlan_tz}]}
# TODO(asarfaty): Add a default v3 tenant-id to allow TVD filtering
def_l2gw = {'name': 'default-l2gw',
'devices': [device]}
l2gw_dict = {self.gateway_resource: def_l2gw}
self.create_l2_gateway(admin_ctx, l2gw_dict)
l2_gateway = super(NsxV3Driver, 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("Default L2 gateway is already created.")
try:
# Try deleting this duplicate default L2 gateway
self.validate_l2_gateway_for_delete(
admin_ctx, l2gateway['id'])
super(NsxV3Driver, 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.
super(NsxV3Driver, self).delete_l2_gateway(
admin_ctx, l2_gateway['id'])
else:
def_l2gw_exists = True
return l2_gateway
l2gw_dict = {self.gateway_resource: {
'name': 'default-nsxedge-l2gw',
'devices': [device]}}
self._create_default_l2_gateway(admin_ctx, l2gw_dict, bep_id)
return def_l2gw
def _prevent_l2gw_port_delete(self, resource, event,
trigger, payload=None):
@ -140,30 +224,34 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
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.
def _validate_device_list(self, devices, check_backend=True):
# In NSXv3, one L2 gateway is mapped to one bridge endpoint profle.
# 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.
# endpoint profile UUID.
if len(devices) != 1:
msg = _("Only a single device is supported for one L2 gateway")
msg = _("Only a single device is supported by the NSX L2"
"gateway driver")
raise n_exc.InvalidInput(error_message=msg)
if not uuidutils.is_uuid_like(devices[0]['device_name']):
dev_name = devices[0]['device_name']
if not uuidutils.is_uuid_like(dev_name):
msg = _("Device name must be configured with a UUID")
raise n_exc.InvalidInput(error_message=msg)
# Make sure the L2GW device ID exists as Bridge Cluster on NSX.
# Ensure the L2GW device is a valid bridge endpoint profile in NSX
if check_backend:
try:
self._core_plugin.nsxlib.bridge_cluster.get(
devices[0]['device_name'])
self._core_plugin.nsxlib.bridge_endpoint_profile.get(
dev_name)
except nsxlib_exc.ResourceNotFound:
msg = _("Could not find Bridge Cluster for L2 gateway device "
"%s on NSX backend") % devices[0]['device_name']
msg = _("Could not find Bridge Endpoint Profile for L2 "
"gateway device %s on NSX backend") % dev_name
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
# One L2 gateway must have only one interface defined.
interfaces = devices[0].get(l2gw_const.IFACE_NAME_ATTR)
if len(interfaces) > 1:
msg = _("Maximum of one interface is supported for one L2 gateway")
msg = _("Maximum of one interface is supported by the NSX L2 "
"gateway driver")
raise n_exc.InvalidInput(error_message=msg)
def create_l2_gateway(self, context, l2_gateway):
@ -196,10 +284,9 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
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") %
# If network is a provider network, verify whether it is of type GENEVE
if network_type and network_type != nsx_utils.NsxV3NetworkTypes.GENEVE:
msg = (_("Unsupported network type %s for L2 gateway connection") %
network_type)
raise n_exc.InvalidInput(error_message=msg)
@ -213,33 +300,67 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
network_id = gw_connection.get(l2gw_const.NETWORK_ID)
self._validate_network(context, network_id)
def _get_bridge_cluster(self, context, l2gw_id):
def _get_bep(self, 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.
# The name of the device shall carry the bridge endpoint profile id.
devices = self._get_l2_gateway_devices(context, l2gw_id)
return devices[0].get('device_name')
def _get_conn_seg_id(self, context, gw_connection):
def _get_conn_parameters(self, context, gw_connection):
"""Return interface and segmenantion id for a connection. """
if not gw_connection:
return
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
seg_id = gw_connection.get(l2gw_const.SEG_ID)
devices = self._get_l2_gateway_devices(context, l2gw_id)
# TODO(salv-orlando): support more than a single interface
interface = self._get_l2_gw_interfaces(context, devices[0]['id'])[0]
if not seg_id:
# Seg-id was not passed as part of connection-create. Retrieve
# seg-id from L2 gateway's interface.
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
devices = self._get_l2_gateway_devices(context, l2gw_id)
interface = self._get_l2_gw_interfaces(context, devices[0]['id'])
seg_id = interface[0].get(l2gw_const.SEG_ID)
return seg_id
seg_id = interface.get('segmentation_id')
return interface['interface_name'], seg_id
def create_l2_gateway_connection_precommit(self, context, gw_connection):
"""Validate the L2 gateway connection
Do not allow another connection with the same bride cluster and seg_id
"""
admin_ctx = context.elevated()
nsxlib = self._core_plugin.nsxlib
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
seg_id = self._get_conn_seg_id(admin_ctx, gw_connection)
bridge_cluster = self._get_bridge_cluster(admin_ctx, l2gw_id)
devices = self._get_l2_gateway_devices(context, l2gw_id)
bep_id = devices[0].get('device_name')
# Check for bridge endpoint profile existence
# if bridge endpoint profile is not found, this is likely an old
# connection, fail with error.
try:
nsxlib.bridge_endpoint_profile.get_id_by_name_or_id(bep_id)
except nsxlib_exc.ManagerError as e:
msg = (_("Error while retrieving bridge endpoint profile "
"%(bep_id)s from NSX backend. Check that the profile "
"exits and there are not multiple profiles with "
"the given name. Exception: %(exc)s") %
{'bep_id': bep_id, 'exc': e})
raise n_exc.InvalidInput(error_message=msg)
interface_name, seg_id = self._get_conn_parameters(
admin_ctx, gw_connection)
try:
# Use search API for listing bridge endpoints on NSX for provided
# VLAN id, transport zone id, and Bridge endpoint profile
endpoints = nsxlib.search_all_resource_by_attributes(
nsxlib.bridge_endpoint.resource_type,
bridge_endpoint_profile_id=bep_id,
vlan_transport_zone_id=interface_name,
vlan=seg_id)
endpoint_map = dict((endpoint['id'],
endpoint['bridge_endpoint_profile_id'])
for endpoint in endpoints)
except nsxlib_exc.ManagerError as e:
msg = (_("Error while retrieving endpoints for bridge endpoint "
"profile %(bep_id)s s from NSX backend. "
"Exception: %(exc)s") % {'bep_id': bep_id, 'exc': e})
raise n_exc.InvalidInput(error_message=msg)
# get all bridge endpoint ports
with db_api.CONTEXT_WRITER.using(admin_ctx):
@ -247,65 +368,55 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
ports = self._core_plugin.get_ports(
admin_ctx, filters=port_filters)
for port in ports:
# get the nsx mapping by bridge endpoint
if port.get('device_id'):
mappings = nsx_db.get_l2gw_connection_mappings_by_bridge(
admin_ctx.session, port['device_id'])
for mapping in mappings:
conn_id = mapping.connection_id
# get the matching GW connection
conn = self._get_l2_gateway_connection(
admin_ctx, conn_id)
con_seg_id = self._get_conn_seg_id(admin_ctx, conn)
if (conn and con_seg_id and
int(con_seg_id) == int(seg_id)):
# compare the bridge cluster
conn_bridge_cluster = self._get_bridge_cluster(
admin_ctx, conn.l2_gateway_id)
if conn_bridge_cluster == bridge_cluster:
msg = (_("Cannot create multiple connections "
"with the same segmentation id "
"%(seg_id)s for bridge cluster "
"%(bridge)s") % {
'seg_id': seg_id,
'bridge': bridge_cluster})
device_id = port.get('device_id')
if endpoint_map.get(device_id) == bep_id:
# This device is using the same vlan id and bridge endpoint
# profile as the one requested. Not ok.
msg = (_("Cannot create multiple connections with the "
"same segmentation id %(seg_id)s for bridge "
"endpoint profile %(bep_id)s") %
{'seg_id': seg_id,
'bep_id': bep_id})
raise n_exc.InvalidInput(error_message=msg)
def create_l2_gateway_connection_postcommit(self, context, gw_connection):
"""Create a L2 gateway connection."""
"""Create a L2 gateway connection on the backend"""
nsxlib = self._core_plugin.nsxlib
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
network_id = gw_connection.get(l2gw_const.NETWORK_ID)
device_name = self._get_bridge_cluster(context, l2gw_id)
seg_id = self._get_conn_seg_id(context, gw_connection)
device_name = self._get_bep(context, l2gw_id)
interface_name, seg_id = self._get_conn_parameters(
context, gw_connection)
self._validate_segment_id(seg_id)
tenant_id = gw_connection['tenant_id']
if context.is_admin and not tenant_id:
tenant_id = context.tenant_id
gw_connection['tenant_id'] = tenant_id
try:
tags = self._core_plugin.nsxlib.build_v3_tags_payload(
tags = nsxlib.build_v3_tags_payload(
gw_connection, resource_type='os-neutron-l2gw-id',
project_name=context.tenant_name)
bridge_endpoint = self._core_plugin.nsxlib.bridge_endpoint.create(
bridge_endpoint = nsxlib.bridge_endpoint.create(
device_name=device_name,
seg_id=seg_id,
vlan_transport_zone_id=interface_name,
vlan_id=seg_id,
tags=tags)
except nsxlib_exc.ManagerError as e:
LOG.exception("Unable to create bridge endpoint, rolling back "
"changes on neutron. Exception is %s", e)
LOG.exception("Unable to create bridge endpoint. "
"Exception is %s", e)
raise l2gw_exc.L2GatewayServiceDriverError(
method='create_l2_gateway_connection_postcommit')
#TODO(abhiraut): Consider specifying the name of the port
# Create a logical port and connect it to the bridge endpoint.
port_dict = {'port': {
'name': 'l2gw-conn-%s-%s' % (
l2gw_id, seg_id),
'tenant_id': tenant_id,
'network_id': network_id,
'mac_address': constants.ATTR_NOT_SPECIFIED,
'admin_state_up': True,
'fixed_ips': [],
'device_id': bridge_endpoint['id'],
'device_owner': nsx_constants.BRIDGE_ENDPOINT,
'name': '', }}
'device_owner': nsx_constants.BRIDGE_ENDPOINT}}
try:
#TODO(abhiraut): Consider adding UT for port check once UTs are
# refactored
@ -319,12 +430,16 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
LOG.debug("IP addresses deallocated on port %s", port['id'])
except (nsxlib_exc.ManagerError,
n_exc.NeutronException) as e:
with excutils.save_and_reraise_exception():
LOG.exception("Unable to create L2 gateway port, "
"rolling back changes on neutron: %s", e)
"rolling back changes on backend: %s", e)
self._core_plugin.nsxlib.bridge_endpoint.delete(
bridge_endpoint['id'])
raise l2gw_exc.L2GatewayServiceDriverError(
method='create_l2_gateway_connection_postcommit')
super(NsxV3Driver,
self).delete_l2_gateway_connection(
context,
gw_connection['id'])
try:
# Update neutron's database with the mappings.
nsx_db.add_l2gw_connection_mapping(
@ -335,7 +450,10 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin):
except db_exc.DBError:
with excutils.save_and_reraise_exception():
LOG.exception("Unable to add L2 gateway connection "
"mappings, rolling back changes on neutron")
"mappings for %(conn_id)s on network "
"%(net_id)s. rolling back changes.",
{'conn_id': gw_connection['id'],
'net_id': network_id})
self._core_plugin.nsxlib.bridge_endpoint.delete(
bridge_endpoint['id'])
super(NsxV3Driver,

View File

@ -121,11 +121,6 @@ def _mock_nsx_backend_calls():
"create_port_mirror_profile",
side_effect=_return_id_key).start()
mock.patch(
"vmware_nsxlib.v3.core_resources.NsxLibBridgeCluster."
"get_id_by_name_or_id",
return_value=uuidutils.generate_uuid()).start()
mock.patch(
"vmware_nsxlib.v3.core_resources.NsxLibBridgeEndpoint.create",
side_effect=_return_id_key).start()

View File

@ -25,21 +25,23 @@ from oslo_utils import importutils
from oslo_utils import uuidutils
from neutron.tests import base
from neutron_lib.api.definitions import provider_net as providernet
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import context
from neutron_lib import exceptions as n_exc
from vmware_nsx.common import utils as nsx_utils
from vmware_nsx.services.l2gateway.nsx_v3 import driver as nsx_v3_driver
from vmware_nsx.tests.unit.nsx_v3 import test_plugin as test_nsx_v3_plugin
from vmware_nsxlib.tests.unit.v3 import mocks as nsx_v3_mocks
from vmware_nsxlib.v3 import nsx_constants
NSX_V3_PLUGIN_CLASS = ('vmware_nsx.plugins.nsx_v3.plugin.NsxV3Plugin')
NSX_V3_L2GW_DRIVER_CLASS_PATH = ('vmware_nsx.services.l2gateway.'
'nsx_v3.driver.NsxV3Driver')
NSX_DEFAULT_BEP_NAME = "default-bridge-endpoint-profile"
class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
@ -58,12 +60,29 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
mock.patch.object(l2gateway_db, 'subscribe')
mock.patch('neutron.db.servicetype_db.ServiceTypeManager.get_instance',
return_value=mock.MagicMock()).start()
mock_default_bep_uuid = uuidutils.generate_uuid()
mock.patch('vmware_nsxlib.v3.core_resources.'
'NsxLibBridgeEndpointProfile.get_id_by_name_or_id',
return_value=mock_default_bep_uuid).start()
mock.patch('vmware_nsxlib.v3.core_resources.'
'NsxLibBridgeEndpointProfile.get',
return_value={'id': mock_default_bep_uuid,
'edge_cluster_id': 'meh'}).start()
mock.patch('vmware_nsxlib.v3.core_resources.'
'NsxLibTransportZone.get_transport_type',
return_value="VLAN").start()
self.l2gw_plugin = core_l2gw_plugin.L2GatewayPlugin()
self.context = context.get_admin_context()
def _get_nw_data(self):
def _get_nw_data(self, provider=False):
net_data = super(TestNsxV3L2GatewayDriver, self)._get_nw_data()
net_data['network']['port_security_enabled'] = True
net_spec = net_data['network']
net_spec['port_security_enabled'] = True
if provider:
net_spec[providernet.NETWORK_TYPE] = (
nsx_utils.NsxV3NetworkTypes.VLAN)
net_spec[providernet.SEGMENTATION_ID] = 666
return net_data
def test_nsxl2gw_driver_init(self):
@ -76,34 +95,39 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
self.assertTrue(debug.called)
def test_create_default_l2_gateway(self):
def_bridge_cluster_name = nsx_v3_mocks.NSX_BRIDGE_CLUSTER_NAME
cfg.CONF.set_override("default_bridge_cluster",
def_bridge_cluster_name,
"nsx_v3")
def_bep_name = NSX_DEFAULT_BEP_NAME
cfg.CONF.set_override("default_bridge_endpoint_profile",
def_bep_name, "nsx_v3")
with mock.patch.object(nsx_v3_driver.NsxV3Driver,
'_get_bridge_vlan_tz_id',
return_value=['some_tz_id']) as mock_get_tz:
nsx_v3_driver.NsxV3Driver(mock.MagicMock())
def_bep_id = (
self.nsxlib.bridge_endpoint_profile.get_id_by_name_or_id(
def_bep_name))
# fake the callback invoked after init
registry.publish(resources.PROCESS, events.BEFORE_SPAWN,
mock.MagicMock())
l2gws = self.driver._get_l2_gateways(self.context)
def_bridge_cluster_id = (
self.nsxlib.bridge_cluster.get_id_by_name_or_id(
def_bridge_cluster_name))
def_l2gw = None
for l2gw in l2gws:
for device in l2gw['devices']:
if device['device_name'] == def_bridge_cluster_id:
if device['device_name'] == def_bep_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].device_name, def_bep_id)
self.assertTrue(def_l2gw.devices[0].interfaces[0].interface_name,
'default-bridge-cluster')
'some_tz_id')
mock_get_tz.assert_called_once_with({'id': def_bep_id,
'edge_cluster_id': 'meh'})
def test_create_duplicate_default_l2_gateway_noop(self):
def_bridge_cluster_name = nsx_v3_mocks.NSX_BRIDGE_CLUSTER_NAME
cfg.CONF.set_override("default_bridge_cluster",
def_bridge_cluster_name,
"nsx_v3")
def_bep_name = NSX_DEFAULT_BEP_NAME
cfg.CONF.set_override("default_bridge_endpoint_profile",
def_bep_name, "nsx_v3")
with mock.patch.object(nsx_v3_driver.NsxV3Driver,
'_get_bridge_vlan_tz_id',
return_value=['some_tz_id']):
for i in range(0, 2):
nsx_v3_driver.NsxV3Driver(mock.MagicMock())
# fake the callback invoked after init
@ -118,8 +142,8 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
'subscribe_callback_notifications'):
nsx_v3_driver.NsxV3Driver(mock.MagicMock())
l2gws = self.driver._get_l2_gateways(self.context)
# Verify no default L2 gateway is created if bridge cluster id is
# not configured in nsx.ini
# Verify no default L2 gateway is created if bridge endpoint
# profile id is not configured in nsx.ini
self.assertEqual([], l2gws)
def test_create_l2_gateway_multiple_devices_fail(self):
@ -193,24 +217,37 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
def test_create_l2_gateway_connections_same_params(self):
type(self.driver)._core_plugin = self.core_plugin
bc_uuid = uuidutils.generate_uuid()
l2gw_data1 = self._get_l2_gateway_data(name='def-l2gw1',
device_name=bc_uuid)
be_uuid = uuidutils.generate_uuid()
bep_uuid = uuidutils.generate_uuid()
l2gw_data1 = self._get_l2_gateway_data_without_seg_id(
name='def-l2gw1', device_name=bep_uuid)
l2gw1 = self._create_l2gateway(l2gw_data1)
l2gw_data2 = self._get_l2_gateway_data(name='def-l2gw2',
device_name=bc_uuid)
l2gw_data2 = self._get_l2_gateway_data_without_seg_id(
name='def-l2gw2', device_name=bep_uuid)
l2gw2 = self._create_l2gateway(l2gw_data2)
net_data = self._get_nw_data()
net = self.core_plugin.create_network(self.context, net_data)
l2gw_conn_data1 = {constants.CONNECTION_RESOURCE_NAME: {
'l2_gateway_id': l2gw1['id'],
'tenant_id': 'fake_tenant_id',
'segmentation_id': '666',
'network_id': net['id']}}
# Override "global" mock to return a known id
with mock.patch('vmware_nsxlib.v3.core_resources.'
'NsxLibBridgeEndpoint.create',
return_value={'id': be_uuid}):
self.l2gw_plugin.create_l2_gateway_connection(
self.context, l2gw_conn_data1)
fake_be = {'id': be_uuid,
'vlan': 666,
'bridge_endpoint_profile_id': bep_uuid}
with mock.patch('vmware_nsxlib.v3.NsxLib.'
'search_all_resource_by_attributes',
return_value=[fake_be]):
l2gw_conn_data2 = {constants.CONNECTION_RESOURCE_NAME: {
'l2_gateway_id': l2gw2['id'],
'tenant_id': 'fake_tenant_id',
'segmentation_id': 666,
'network_id': net['id']}}
self.assertRaises(n_exc.InvalidInput,
self.l2gw_plugin.create_l2_gateway_connection,
@ -241,6 +278,23 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase,
self.l2gw_plugin.create_l2_gateway_connection(
self.context, l2gw_conn_data2)
def test_create_l2_gateway_connection_invalid_network_type_fails(self):
type(self.driver)._core_plugin = self.core_plugin
bep_uuid = uuidutils.generate_uuid()
l2gw_data = self._get_l2_gateway_data(name='def-l2gw',
device_name=bep_uuid)
l2gw = self._create_l2gateway(l2gw_data)
net_data = self._get_nw_data(provider=True)
net = self.core_plugin.create_network(self.context, net_data)
l2gw_conn_data = {constants.CONNECTION_RESOURCE_NAME: {
'l2_gateway_id': l2gw['id'],
'tenant_id': 'fake_tenant_id',
'network_id': net['id']}}
self.assertRaises(n_exc.InvalidInput,
self.l2gw_plugin.create_l2_gateway_connection,
self.context,
l2gw_conn_data)
def test_delete_l2_gateway_connection(self):
type(self.driver)._core_plugin = self.core_plugin
bc_uuid = uuidutils.generate_uuid()