3009f37757
Co-authored-by: Armando Migliaccio <armamig@gmail.com> Change-Id: I7c7efbaa7a53f8d244f1b19ea3a7c8a8900602be Depends-on: I7331e914234c5f0b7abe836604fdd7e4067551cf Depends-on: Ia91dfbf9d93e19b43c0dd0b58b95fc0080b0ad7c Depends-on: I48cd9257f419ad949ba0cecc9aca98a624ca4dcc
376 lines
18 KiB
Python
376 lines
18 KiB
Python
# 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_lib.plugins import directory
|
|
|
|
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.db import db as nsx_db
|
|
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
|
|
from vmware_nsxlib import v3 as nsxlib
|
|
from vmware_nsxlib.v3 import exceptions as nsxlib_exc
|
|
from vmware_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 directory.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')
|
|
nsxlib = v3_utils.get_nsxlib_wrapper()
|
|
tags = nsxlib.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
|
|
# Verify whether destination port is L3 reachable. i.e. destination
|
|
# port has a floating IP address.
|
|
fips = self._nsx_plugin.get_floatingips(
|
|
context._plugin_context, filters={'port_id': [dest_port_id]})
|
|
if not fips:
|
|
msg = (_("Destination port %s must have a floating IP for "
|
|
"L3 SPAN") % dest_port_id)
|
|
raise nsx_exc.NsxTaaSDriverException(msg=msg)
|
|
destinations = []
|
|
# Retrieve destination port's IP addresses and add it to the list
|
|
# since the backend expects a list of IP addresses.
|
|
for fip in fips:
|
|
# 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(fip['floating_ip_address']).version == 6:
|
|
LOG.warning(_LW("Skipping IPv6 address %(ip)s for L3SPAN "
|
|
"tap flow: %(tap_flow)s"),
|
|
{'tap_flow': tf['id'],
|
|
'ip': fip['floating_ip_address']})
|
|
continue
|
|
destinations.append(fip['floating_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 nsxlib_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 nsxlib_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:
|
|
nsxlib = v3_utils.get_nsxlib_wrapper()
|
|
pm_session = nsxlib.port_mirror.create_session(
|
|
source_ports=nsx_src_ports,
|
|
dest_ports=nsx_dest_ports,
|
|
direction=direction,
|
|
description=tf.get('description'),
|
|
name=tf.get('name'),
|
|
tags=tags)
|
|
except nsxlib_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.port_mirror.delete_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 nsxlib_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 nsxlib_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.port_mirror.delete_session(pm_session_id)
|
|
except nsxlib_exc.ManagerError:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_LE("Unable to delete port mirror session %s "
|
|
"on NSX backend."), pm_session_id)
|