# Copyright 2016 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. import netaddr from neutron import manager from neutron_taas.db import taas_db from neutron_taas.services.taas import service_drivers as base_driver from oslo_db import exception as db_exc from oslo_log import log as logging from oslo_utils import excutils from vmware_nsx._i18n import _, _LE, _LW from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import utils as nsx_utils from vmware_nsx.db import db as nsx_db from vmware_nsx.nsxlib import v3 as nsxlib from vmware_nsx.nsxlib.v3 import resources as nsx_resources LOG = logging.getLogger(__name__) class NsxV3Driver(base_driver.TaasBaseDriver, taas_db.Taas_db_Mixin): """Class to handle API calls for Port Mirroring and NSXv3 backend.""" def __init__(self, service_plugin): LOG.debug("Loading TaaS NsxV3Driver.") super(NsxV3Driver, self).__init__(service_plugin) @property def _nsx_plugin(self): return manager.NeutronManager.get_plugin() def _validate_tap_flow(self, source_port, dest_port): # Verify whether the source port is not same as the destination port if source_port['id'] == dest_port['id']: msg = (_("Destination port %(dest)s is same as source port " "%(src)s") % {'dest': dest_port['id'], 'src': source_port['id']}) raise nsx_exc.NsxTaaSDriverException(msg=msg) def create_tap_service_precommit(self, context): pass def create_tap_service_postcommit(self, context): pass def delete_tap_service_precommit(self, context): pass def delete_tap_service_postcommit(self, context): pass def create_tap_flow_precommit(self, context): """Validate and create database entries for creation of tap flow.""" tf = context.tap_flow # Retrieve source port details. source_port = self._get_port_details( context._plugin_context, tf.get('source_port')) # Retrieve tap service and destination port details. ts = self._get_tap_service( context._plugin_context, tf.get('tap_service_id')) dest_port = self._get_port_details( context._plugin_context, ts.get('port_id')) self._validate_tap_flow(source_port, dest_port) def _convert_to_backend_direction(self, direction): nsx_direction = None if direction == 'BOTH': nsx_direction = 'BIDIRECTIONAL' elif direction == 'IN': nsx_direction = 'INGRESS' elif direction == 'OUT': nsx_direction = 'EGRESS' return nsx_direction def _convert_to_backend_source_port(self, session, port_id): nsx_port_id = nsx_db.get_nsx_switch_and_port_id(session, port_id)[1] return [{"resource_type": "LogicalPortMirrorSource", "port_ids": [nsx_port_id]}] def _convert_to_backend_dest_port(self, session, port_id): nsx_port_id = nsx_db.get_nsx_switch_and_port_id(session, port_id)[1] return {"resource_type": "LogicalPortMirrorDestination", "port_ids": [nsx_port_id]} def _is_local_span(self, context, src_port_id, dest_port_id): """Verify whether the mirror session is Local or L3SPAN.""" # TODO(abhiraut): Create only L3SPAN until we find a way to # detect local SPAN support from backend. return False def _update_port_at_backend(self, context, port_id, switching_profile, delete_profile): """Update a logical port on the backend.""" port = self._get_port_details(context._plugin_context, port_id) # Retrieve logical port ID based on neutron port ID. nsx_port_id = nsx_db.get_nsx_switch_and_port_id( session=context._plugin_context.session, neutron_id=port_id)[1] # Retrieve source logical port from the backend. nsx_port = self._nsx_plugin._port_client.get(nsx_port_id) if delete_profile: # Prepare switching profile resources retrieved from backend # and pop the port mirror switching profile. switching_profile_ids = self._prepare_switch_profiles( nsx_port.get('switching_profile_ids', []), switching_profile) else: # Prepare switching profile resources retrieved from backend. switching_profile_ids = self._prepare_switch_profiles( nsx_port.get('switching_profile_ids', [])) # Update body with PortMirroring switching profile. switching_profile_ids.append( self._get_switching_profile_resource( switching_profile['id'], nsx_resources.SwitchingProfileTypes.PORT_MIRRORING)) address_bindings = self._nsx_plugin._build_address_bindings(port) #NOTE(abhiraut): Consider passing attachment_type self._nsx_plugin._port_client.update( lport_id=nsx_port.get('id'), vif_uuid=port_id, name=nsx_port.get('display_name'), admin_state=nsx_port.get('admin_state'), address_bindings=address_bindings, switch_profile_ids=switching_profile_ids,) def _prepare_switch_profiles(self, profiles, deleted_profile=None): switch_profile_ids = [] if not deleted_profile: for profile in profiles: # profile is a dict of type {'key': profile_type, # 'value': profile_id} profile_resource = self._get_switching_profile_resource( profile_id=profile['value'], profile_type=profile['key']) switch_profile_ids.append(profile_resource) else: for profile in profiles: if profile['value'] == deleted_profile['id']: continue profile_resource = self._get_switching_profile_resource( profile_id=profile['value'], profile_type=profile['key']) switch_profile_ids.append(profile_resource) return switch_profile_ids def _get_switching_profile_resource(self, profile_id, profile_type): return nsx_resources.SwitchingProfileTypeId( profile_type=profile_type, profile_id=profile_id) def create_tap_flow_postcommit(self, context): """Create tap flow and port mirror session on NSX backend.""" tf = context.tap_flow # Retrieve tap service. ts = self._get_tap_service(context._plugin_context, tf.get('tap_service_id')) src_port_id = tf.get('source_port') dest_port_id = ts.get('port_id') tags = nsx_utils.build_v3_tags_payload( tf, resource_type='os-neutron-mirror-id', project_name=context._plugin_context.tenant_name) nsx_direction = self._convert_to_backend_direction( tf.get('direction')) # Create a port mirroring session object if local SPAN. Otherwise # create a port mirroring switching profile for L3SPAN. if self._is_local_span(context, src_port_id, dest_port_id): self._create_local_span(context, src_port_id, dest_port_id, nsx_direction, tags) else: self._create_l3span(context, src_port_id, dest_port_id, nsx_direction, tags) def _create_l3span(self, context, src_port_id, dest_port_id, direction, tags): """Create a PortMirroring SwitchingProfile for L3SPAN.""" tf = context.tap_flow dest_port = self._get_port_details(context._plugin_context, dest_port_id) destinations = [] # Retrieve destination port's IP addresses and add it to the list # since the backend expects a list of IP addresses. for fixed_ip in dest_port['fixed_ips']: # NOTE(abhiraut): nsx-v3 doesn't seem to handle ipv6 addresses # currently so for now we remove them here and do not pass # them to the backend which would raise an error. if netaddr.IPNetwork(fixed_ip['ip_address']).version == 6: LOG.warning(_LW("Skipping IPv6 address %(ip)s for L3SPAN " "tap flow: %(tap_flow)s"), {'tap_flow': tf['id'], 'ip': fixed_ip['ip_address']}) continue destinations.append(fixed_ip['ip_address']) # Create a switch profile in the backend. try: port_mirror_profile = (self._nsx_plugin._switching_profiles. create_port_mirror_profile( display_name=tf.get('name'), description=tf.get('description'), direction=direction, destinations=destinations, tags=tags)) except nsx_exc.ManagerError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to create port mirror switch profile " "for tap flow %s on NSX backend, rolling back " "changes on neutron."), tf['id']) # Create internal mappings between tap flow and port mirror switch # profile. Ideally DB transactions must take place in precommit, but # we rely on the NSX backend to retrieve the port mirror profile UUID, # we perform the create action in postcommit. try: nsx_db.add_port_mirror_session_mapping( session=context._plugin_context.session, tf_id=tf['id'], pm_session_id=port_mirror_profile['id']) except db_exc.DBError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to create port mirror session db " "mappings for tap flow %s. Rolling back " "changes in Neutron."), tf['id']) self._nsx_plugin._switching_profiles.delete( port_mirror_profile['id']) # Update the source port to include the port mirror switch profile. try: self._update_port_at_backend(context=context, port_id=src_port_id, switching_profile=port_mirror_profile, delete_profile=False) except nsx_exc.ManagerError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to update source port %(port)s with " "switching profile %(profile) for tap flow " "%(tap_flow)s on NSX backend, rolling back " "changes on neutron."), {'tap_flow': tf['id'], 'port': src_port_id, 'profile': port_mirror_profile['id']}) self._nsx_plugin._switching_profiles.delete( port_mirror_profile['id']) def _create_local_span(self, context, src_port_id, dest_port_id, direction, tags): """Create a PortMirroring session on the backend for local SPAN.""" tf = context.tap_flow # Backend expects a list of source ports and destination ports. # Due to TaaS API requirements, we are only able to add one port # as a source port and one port as a destination port in a single # request. Hence we send a list of one port for source_ports # and dest_ports. nsx_src_ports = self._convert_to_backend_source_port( context._plugin_context.session, src_port_id) nsx_dest_ports = self._convert_to_backend_dest_port( context._plugin_context.session, dest_port_id) # Create port mirror session on the backend try: pm_session = nsxlib.create_port_mirror_session( source_ports=nsx_src_ports, dest_ports=nsx_dest_ports, direction=direction, description=tf.get('description'), name=tf.get('name'), tags=tags) except nsx_exc.ManagerError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to create port mirror session %s " "on NSX backend, rolling back " "changes on neutron."), tf['id']) # Create internal mappings between tap flow and port mirror session. # Ideally DB transactions must take place in precommit, but since we # rely on the NSX backend to retrieve the port session UUID, we perform # the create action in postcommit. try: nsx_db.add_port_mirror_session_mapping( session=context._plugin_context.session, tf_id=tf['id'], pm_session_id=pm_session['id']) except db_exc.DBError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to create port mirror session db " "mappings for tap flow %s. Rolling back " "changes in Neutron."), tf['id']) nsxlib.delete_port_mirror_session(pm_session['id']) def delete_tap_flow_precommit(self, context): pass def delete_tap_flow_postcommit(self, context): """Delete tap flow and port mirror session on NSX backend.""" tf = context.tap_flow ts = self._get_tap_service(context._plugin_context, tf.get('tap_service_id')) # Retrieve port mirroring session mappings. pm_session_mapping = nsx_db.get_port_mirror_session_mapping( session=context._plugin_context.session, tf_id=tf['id']) src_port_id = tf.get('source_port') dest_port_id = ts.get('port_id') if self._is_local_span(context, src_port_id, dest_port_id): self._delete_local_span( context, pm_session_mapping['port_mirror_session_id']) else: self._delete_l3span( context, pm_session_mapping['port_mirror_session_id']) # Delete internal mappings between tap flow and port mirror session. # Ideally DB transactions must take place in precommit, but since we # rely on the DB mapping to retrieve NSX backend UUID for the port # session mapping, we perform the delete action in postcommit. try: nsx_db.delete_port_mirror_session_mapping( session=context._plugin_context.session, tf_id=tf['id']) except db_exc.DBError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to delete port mirror session db " "mappings %(pm)s for tap flow %(tf)s"), tf['id']) def _delete_l3span(self, context, pm_profile_id): tf = context.tap_flow src_port_id = tf.get('source_port') port_mirror_profile = self._nsx_plugin._switching_profiles.get( uuid=pm_profile_id) try: # Update source port and remove L3 switching profile. self._update_port_at_backend(context=context, port_id=src_port_id, switching_profile=port_mirror_profile, delete_profile=True) except nsx_exc.ManagerError: LOG.error(_LE("Unable to update source port %(port)s " "to delete port mirror profile %(pm)s on NSX " "backend."), {'pm': pm_profile_id, 'port': src_port_id}) try: # Delete port mirroring switching profile self._nsx_plugin._switching_profiles.delete(uuid=pm_profile_id) except nsx_exc.ManagerError: LOG.error(_LE("Unable to delete port mirror switching profile " "%s on NSX backend."), pm_profile_id) def _delete_local_span(self, context, pm_session_id): # Delete port mirroring session on the backend try: nsxlib.delete_port_mirror_session(pm_session_id) except nsx_exc.ManagerError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to delete port mirror session %s " "on NSX backend."), pm_session_id)