[NSXv3]: Add support for L3SPAN
This patch adds support for L3SPAN to the existing tap as a service NSXv3 driver. If the source and destination port are not on the same host, the mirror session is L3SPAN. Backend supports L3SPAN with SwitchingProfiles of type PortMirroring. Hence, creation of a tap-flow will result in creation of a switching profile with the destination port's IP address and then updates the source port with this newly created switching profile. Change-Id: I74ea40f8b9c9d1c343a4d9681c3a9ec77b521b6e
This commit is contained in:
parent
7a9336f09f
commit
3eb8e148d4
@ -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 = []
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user