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
|
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
|
||||||
|
12
etc/nsx.ini
12
etc/nsx.ini
@ -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 =
|
||||||
|
3
tox.ini
3
tox.ini
@ -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 =
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
28430956782d
|
279b70ac3ae8
|
||||||
393bf843b96
|
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.")),
|
"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')),
|
||||||
|
@ -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')
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
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
|
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
|
||||||
|
@ -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