Merge "NSXv3: Add backend driver for Layer 2 gateway"
This commit is contained in:
commit
12690ba02e
@ -109,6 +109,9 @@ function neutron_plugin_configure_service {
|
||||
else
|
||||
die $LINENO "The VMware NSX plugin needs at least an NSX controller."
|
||||
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 nsx_manager $NSX_MANAGER "The VMWare NSX plugin needs a NSX manager."
|
||||
_nsxv3_ini_set nsx_user $NSX_USER
|
||||
|
12
etc/nsx.ini
12
etc/nsx.ini
@ -225,6 +225,10 @@
|
||||
# "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]
|
||||
# Interval in seconds between runs of the status synchronization task.
|
||||
# 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
|
||||
# tier1 logical routers and configuring external network
|
||||
# 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 =
|
||||
|
3
tox.ini
3
tox.ini
@ -10,7 +10,8 @@ setenv = VIRTUAL_ENV={envdir}
|
||||
PYTHONHASHSEED=0
|
||||
usedevelop = True
|
||||
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
|
||||
whitelist_externals = sh
|
||||
commands =
|
||||
|
@ -1,2 +1,2 @@
|
||||
28430956782d
|
||||
279b70ac3ae8
|
||||
393bf843b96
|
||||
|
@ -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'),
|
||||
)
|
@ -183,6 +183,10 @@ nsx_v3_opts = [
|
||||
"starting Neutron with the NSX plugin.")),
|
||||
cfg.StrOpt('default_edge_cluster_uuid',
|
||||
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',
|
||||
default=10,
|
||||
help=_('Maximum number of times to retry API request')),
|
||||
|
@ -137,3 +137,7 @@ class ResourceNotFound(ManagerError):
|
||||
|
||||
class StaleRevision(ManagerError):
|
||||
pass
|
||||
|
||||
|
||||
class NsxL2GWConnectionMappingNotFound(n_exc.NotFound):
|
||||
message = _('Unable to find mapping for L2 gateway connection: %(conn)s')
|
||||
|
@ -41,3 +41,6 @@ ROUTER_TYPES = [ROUTER_TYPE_TIER0, ROUTER_TYPE_TIER1]
|
||||
|
||||
# L2 agent vif type
|
||||
VIF_TYPE_DVS = 'dvs'
|
||||
|
||||
# NSXv3 L2 Gateway constants
|
||||
BRIDGE_ENDPOINT = "BRIDGEENDPOINT"
|
||||
|
@ -21,6 +21,7 @@ from sqlalchemy.orm import exc
|
||||
|
||||
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
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -203,3 +204,23 @@ def is_multiprovider_network(session, network_id):
|
||||
return bool(
|
||||
session.query(nsx_models.MultiProviderNetworks).filter_by(
|
||||
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)
|
||||
|
@ -300,3 +300,17 @@ class NetworkQueueMapping(model_base.BASEV2):
|
||||
models_v2.Network,
|
||||
backref=orm.backref("qos_queue", uselist=False,
|
||||
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)
|
||||
|
@ -282,3 +282,30 @@ def get_qos_switching_profile(profile_id):
|
||||
def delete_qos_switching_profile(profile_id):
|
||||
resource = 'switching-profiles/%s' % profile_id
|
||||
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)
|
||||
|
@ -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 metadata_rpc
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.extensions import external_net as ext_net_extn
|
||||
from neutron.extensions import extra_dhcp_opt as edo_ext
|
||||
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.callbacks import events
|
||||
from neutron.callbacks import exceptions as callback_exc
|
||||
from neutron.callbacks import registry
|
||||
from neutron.callbacks import resources
|
||||
from neutron.common import constants as const
|
||||
from neutron.common import exceptions as n_exc
|
||||
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 portbindings_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.plugins.common import constants as plugin_const
|
||||
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 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.dbexts import db as nsx_db
|
||||
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)
|
||||
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)
|
||||
parent_name, tag = self._get_data_from_binding_profile(
|
||||
context, port_data)
|
||||
address_bindings = self._build_address_bindings(port_data)
|
||||
# FIXME(arosen): we might need to pull this out of the
|
||||
# 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(
|
||||
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'],
|
||||
address_bindings=address_bindings,
|
||||
attachment_type=attachment_type,
|
||||
parent_name=parent_name, parent_tag=tag)
|
||||
|
||||
# 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'])
|
||||
return result
|
||||
|
||||
def create_port(self, context, port):
|
||||
dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
|
||||
def create_port(self, context, port, l2gw_port_check=False):
|
||||
dhcp_opts = port['port'].get(ext_edo.EXTRADHCPOPTS, [])
|
||||
port_id = uuidutils.generate_uuid()
|
||||
port['port']['id'] = port_id
|
||||
|
||||
@ -468,7 +482,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
if not self._network_is_external(
|
||||
context, port['port']['network_id']):
|
||||
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,
|
||||
port['port'],
|
||||
neutron_db)
|
||||
@ -488,7 +502,27 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
context, lport['id'], [], sgids)
|
||||
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
|
||||
# a l3 router. If so, we should prevent deletion here
|
||||
if l3_port_check:
|
||||
|
266
vmware_nsx/neutron/services/l2gateway/nsx_v3_driver.py
Normal file
266
vmware_nsx/neutron/services/l2gateway/nsx_v3_driver.py
Normal 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)
|
@ -16,6 +16,8 @@
|
||||
|
||||
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 eventlet_client
|
||||
from vmware_nsx.neutron.plugins.vmware import extensions
|
||||
|
@ -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')
|
Loading…
Reference in New Issue
Block a user