# 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 utils as nsx_utils from vmware_nsx.db import db as nsx_db from vmware_nsx.nsxlib.v3 import exceptions as nsxlib_exc from vmware_nsx.nsxlib.v3 import nsx_constants 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.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) registry.subscribe(self._ensure_default_l2_gateway, resources.PROCESS, events.BEFORE_SPAWN) def _ensure_default_l2_gateway(self, resource, event, trigger, **kwargs): """ 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.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'}]} 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 = self._core_plugin.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( 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.bridge_endpoint.delete( 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.bridge_endpoint.delete( 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.bridge_endpoint.delete(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)