Merge "[NSXv3]: Add support for L3SPAN"
This commit is contained in:
commit
458a652fc2
@ -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