Merge "[NSXv3]: Add support for L3SPAN"

This commit is contained in:
Jenkins 2016-08-09 07:01:59 +00:00 committed by Gerrit Code Review
commit 458a652fc2
3 changed files with 214 additions and 45 deletions

View File

@ -166,6 +166,15 @@ class SwitchingProfile(AbstractRESTResource):
mac_learning=mac_learning,
source_mac_change_allowed=True)
def create_port_mirror_profile(self, display_name, description,
direction, destinations, tags=None):
return self.create(SwitchingProfileTypes.PORT_MIRRORING,
display_name=display_name,
description=description,
tags=tags or [],
direction=direction,
destinations=destinations)
@classmethod
def build_switch_profile_ids(cls, client, *profiles):
ids = []

View File

@ -14,6 +14,10 @@
# 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
@ -21,11 +25,12 @@ 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
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__)
@ -39,14 +44,11 @@ class NsxV3Driver(base_driver.TaasBaseDriver,
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 and monitored port belong to the
# same network.
if source_port['network_id'] != dest_port['network_id']:
msg = (_("Destination port %(dest)s and source port %(src)s "
"should be on the same network") %
{'dest': dest_port['id'], 'src': source_port['id']})
raise nsx_exc.NsxTaaSDriverException(msg=msg)
# 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 "
@ -99,32 +101,181 @@ class NsxV3Driver(base_driver.TaasBaseDriver,
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, tf.get('source_port'))
context._plugin_context.session, src_port_id)
nsx_dest_ports = self._convert_to_backend_dest_port(
context._plugin_context.session, ts.get('port_id'))
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=nsx_direction,
direction=direction,
description=tf.get('description'),
name=tf.get('name'),
tags=tags)
@ -155,19 +306,20 @@ class NsxV3Driver(base_driver.TaasBaseDriver,
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'])
# Delete port mirroring session on the backend
try:
nsxlib.delete_port_mirror_session(
pm_session_mapping['port_mirror_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_mapping['port_mirror_session_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
@ -179,4 +331,36 @@ class NsxV3Driver(base_driver.TaasBaseDriver,
except db_exc.DBError:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Unable to delete port mirror session db "
"mappings for tap flow %s"), tf['id'])
"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)

View File

@ -53,13 +53,6 @@ class TestNsxV3TaaSDriver(test_taas_db.TaaSDbTestCase,
self.driver._validate_tap_flow,
src_port['port'], src_port['port'])
def test_validate_tap_flow_different_network_different_port_fail(self):
with self.port() as src_port, self.port() as dest_port:
self.assertRaises(nsx_exc.NsxTaaSDriverException,
self.driver._validate_tap_flow,
src_port['port'],
dest_port['port'])
def test_validate_tap_flow_same_network_different_port(self):
with self.network() as network:
with self.subnet(network=network) as subnet:
@ -169,23 +162,6 @@ class TestNsxV3TaaSDriver(test_taas_db.TaaSDbTestCase,
self.ctx,
tf_data)
def test_create_tap_flow_different_network_different_port_fail(self):
tf_name = 'test-tap-flow'
with self.port(tenant_id=self.tenant_id) as src_port:
with self.port(tenant_id=self.tenant_id) as dest_port:
ts_data = self._get_tap_service_data(
port_id=dest_port['port']['id'])
ts = self.taas_plugin.create_tap_service(
self.ctx, ts_data)
tf_data = self._get_tap_flow_data(
tap_service_id=ts['id'],
source_port=src_port['port']['id'],
name=tf_name)
self.assertRaises(nsx_exc.NsxTaaSDriverException,
self.taas_plugin.create_tap_flow,
self.ctx,
tf_data)
def test_delete_tap_flow(self):
tf_name = 'test-tap-flow'
with self.network() as network: