86118f6692
This patch refactors the current codebase to be more modular and testable. The main changes are: - pull out all of the profile setup logic from the __init__ method to it's own method _init_nsx_profiles method to make testing the code easier. - refactors the nsxlib.v3 code to break out all neutron related exceptions and cleans up the interface so we can make nsxlib.v3 it's own standalone library eventually. To improve: - Currently we have nsxlib.v3.dfw_api and nsxlib.v3.firewall, we should refactor this code and merge them into one file. - refactor nsxlib to section of each api component to it's own subclass. For example, nsxlib().port.create() rather than nsxlib().create_port(). I think this would be most useful for the security group/firewall integration as there are many methods there that are needed to interface with nsx as the security group feature is requires the most orchestration with nsx. Breaking them into a sub class will make things more easy to understand. Change-Id: If2fe1e014b78703ff0a9cdff1e4e8d45f3a4a16d
316 lines
14 KiB
Python
316 lines
14 KiB
Python
# 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.callbacks import events
|
|
from neutron.callbacks import registry
|
|
from neutron.callbacks import resources
|
|
from neutron import context
|
|
from neutron.extensions import providernet
|
|
from neutron import manager
|
|
from neutron.plugins.common import utils as n_utils
|
|
from neutron_lib import constants
|
|
from neutron_lib import exceptions as n_exc
|
|
|
|
from vmware_nsx._i18n import _, _LE, _LI
|
|
from vmware_nsx.common import nsx_constants
|
|
from vmware_nsx.common import utils as nsx_utils
|
|
from vmware_nsx.db import db as nsx_db
|
|
from vmware_nsx.nsxlib.v3 import exceptions as nsxlib_exc
|
|
|
|
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, 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")
|
|
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 config parameter is set and if it is
|
|
not previously created. If not set, return.
|
|
"""
|
|
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(_LI("NSX: Default bridge cluster not configured "
|
|
"in nsx.ini. No default L2 gateway created."))
|
|
return
|
|
admin_ctx = context.get_admin_context()
|
|
|
|
def_l2gw_uuid = (
|
|
self._core_plugin.nsxlib.get_bridge_cluster_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'}]}
|
|
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(_LI("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
|
|
|
|
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)
|
|
# 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")
|
|
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)
|
|
|
|
def create_l2_gateway_precommit(self, context, l2_gateway):
|
|
pass
|
|
|
|
def create_l2_gateway_postcommit(self, context, l2_gateway):
|
|
pass
|
|
|
|
def update_l2_gateway_precommit(self, context, l2_gateway):
|
|
pass
|
|
|
|
def update_l2_gateway_postcommit(self, context, l2_gateway):
|
|
pass
|
|
|
|
def delete_l2_gateway(self, context, l2_gateway_id):
|
|
pass
|
|
|
|
def delete_l2_gateway_precommit(self, context, l2_gateway_id):
|
|
pass
|
|
|
|
def delete_l2_gateway_postcommit(self, context, l2_gateway_id):
|
|
pass
|
|
|
|
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):
|
|
gw_connection = l2_gateway_connection.get(self.connection_resource)
|
|
network_id = gw_connection.get(l2gw_const.NETWORK_ID)
|
|
self._validate_network(context, network_id)
|
|
|
|
def create_l2_gateway_connection_precommit(self, context, gw_connection):
|
|
pass
|
|
|
|
def create_l2_gateway_connection_postcommit(self, context, gw_connection):
|
|
"""Create a L2 gateway connection."""
|
|
l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID)
|
|
network_id = gw_connection.get(l2gw_const.NETWORK_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 not seg_id:
|
|
# Seg-id was not passed as part of connection-create. Retrieve
|
|
# seg-id from L2 gateway's interface.
|
|
interface = self._get_l2_gw_interfaces(context, devices[0]['id'])
|
|
seg_id = interface[0].get(l2gw_const.SEG_ID)
|
|
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 = nsx_utils.build_v3_tags_payload(
|
|
gw_connection, resource_type='os-neutron-l2gw-id',
|
|
project_name=context.tenant_name)
|
|
bridge_endpoint = self._core_plugin.nsxlib.create_bridge_endpoint(
|
|
device_name=device_name,
|
|
seg_id=seg_id,
|
|
tags=tags)
|
|
except nsxlib_exc.ManagerError as e:
|
|
LOG.exception(_LE("Unable to create bridge endpoint, rolling back "
|
|
"changes on neutron. 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': {
|
|
'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': '', }}
|
|
try:
|
|
#TODO(abhiraut): Consider adding UT for port check once UTs are
|
|
# refactored
|
|
port = self._core_plugin.create_port(context, port_dict,
|
|
l2gw_port_check=True)
|
|
# 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 (nsxlib_exc.ManagerError,
|
|
n_exc.NeutronException):
|
|
LOG.exception(_LE("Unable to create L2 gateway port, "
|
|
"rolling back changes on neutron"))
|
|
self._core_plugin.nsxlib.delete_bridge_endpoint(
|
|
bridge_endpoint['id'])
|
|
raise l2gw_exc.L2GatewayServiceDriverError(
|
|
method='create_l2_gateway_connection_postcommit')
|
|
try:
|
|
# Update neutron's database with the mappings.
|
|
nsx_db.add_l2gw_connection_mapping(
|
|
session=context.session,
|
|
connection_id=gw_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"))
|
|
self._core_plugin.nsxlib.delete_bridge_endpoint(
|
|
bridge_endpoint['id'])
|
|
super(NsxV3Driver,
|
|
self).delete_l2_gateway_connection(
|
|
context,
|
|
gw_connection['id'])
|
|
return gw_connection
|
|
|
|
def delete_l2_gateway_connection(self, context, gw_connection):
|
|
pass
|
|
|
|
def delete_l2_gateway_connection_precommit(self, context, gw_connection):
|
|
pass
|
|
|
|
def delete_l2_gateway_connection_postcommit(self, context, gw_connection):
|
|
"""Delete a L2 gateway connection."""
|
|
conn_mapping = nsx_db.get_l2gw_connection_mapping(
|
|
session=context.session,
|
|
connection_id=gw_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:
|
|
self._core_plugin.nsxlib.delete_bridge_endpoint(bridge_endpoint_id)
|
|
except nsxlib_exc.ManagerError as e:
|
|
LOG.exception(_LE("Unable to delete bridge endpoint %(id)s on the "
|
|
"backend due to exc: %(exc)s"),
|
|
{'id': bridge_endpoint_id, 'exc': e})
|
|
raise l2gw_exc.L2GatewayServiceDriverError(
|
|
method='delete_l2_gateway_connection_postcommit')
|
|
|
|
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)
|