Browse Source

Add SRIOV mirroring support to Tap as a Service.

The following patch allows VF to VF mirroring in Tap-as-a-Service
Code-changes are applicable for new tap agent driver for sriov on
Intel i40e nic.

Vlan Mirror input parameter is part of tap-flow-create API

The current TaaS SRIOV driver is based on Intel i40e NIC driver with
following requirements:-

hardware: Intel Ethernet Network Adapter XXV710 (25GbE)
Driver: Intel i40e v4.16.0

Ref Spec: openstack/neutron-specs/specs/rocky/port-mirroring-sriov-vfs.rst
Commit: https://review.openstack.org/#/c/574477/

Change-Id: Id3aa83d7e1e22ae1806cef0c93e5dd61169c6735
changes/01/603501/91
Deepak Tiwari 3 years ago
parent
commit
2c83eb26a5
  1. 14
      API_REFERENCE.rst
  2. 28
      bin/i40e_sysfs_command
  3. 2
      devstack/devstackgaterc
  4. 1
      devstack/plugin.sh
  5. 2
      devstack/settings
  6. 9
      etc/neutron/rootwrap.d/taas-i40e-sysfs.filters
  7. 3
      lower-constraints.txt
  8. 4
      neutron_taas/common/constants.py
  9. 49
      neutron_taas/common/utils.py
  10. 2
      neutron_taas/db/migration/alembic_migration/versions/EXPAND_HEAD
  11. 35
      neutron_taas/db/migration/alembic_migration/versions/stein/expand/ccbcc559d175_add_vlan_filter_to_tap_flow.py
  12. 6
      neutron_taas/db/taas_db.py
  13. 59
      neutron_taas/extensions/vlan_filter.py
  14. 0
      neutron_taas/services/taas/agents/common/__init__.py
  15. 76
      neutron_taas/services/taas/agents/common/taas_agent.py
  16. 5
      neutron_taas/services/taas/agents/extensions/taas.py
  17. 3
      neutron_taas/services/taas/drivers/linux/ovs_constants.py
  18. 7
      neutron_taas/services/taas/drivers/linux/ovs_taas.py
  19. 32
      neutron_taas/services/taas/drivers/linux/sriov_nic_exceptions.py
  20. 364
      neutron_taas/services/taas/drivers/linux/sriov_nic_taas.py
  21. 268
      neutron_taas/services/taas/drivers/linux/sriov_nic_utils.py
  22. 9
      neutron_taas/services/taas/service_drivers/taas_agent_api.py
  23. 125
      neutron_taas/services/taas/service_drivers/taas_rpc.py
  24. 10
      neutron_taas/services/taas/taas_plugin.py
  25. 12
      neutron_taas/taas_client/tapflow.py
  26. 18
      neutron_taas/tests/base.py
  27. 25
      neutron_taas/tests/unit/db/test_taas_db.py
  28. 8
      neutron_taas/tests/unit/extensions/test_taas.py
  29. 105
      neutron_taas/tests/unit/extensions/test_vlan_filter.py
  30. 0
      neutron_taas/tests/unit/services/drivers/__init__.py
  31. 224
      neutron_taas/tests/unit/services/drivers/test_linux_sriov_nic_driver.py
  32. 269
      neutron_taas/tests/unit/services/drivers/test_linux_sriov_utils.py
  33. 5
      neutron_taas/tests/unit/services/taas/test_taas_plugin.py
  34. 2
      playbooks/legacy/tempest-dsvm-tap-as-a-service/run.yaml
  35. 13
      releasenotes/notes/bp-port-mirroring-sriov-vf-879bc2aa53c2c8d4.yaml
  36. 2
      requirements.txt
  37. 8
      setup.cfg

14
API_REFERENCE.rst

@ -69,7 +69,10 @@ TapFlow Represents the port from which the traffic needs to be mirrored.
'required_by_policy': True, 'is_visible': True},
'direction': {'allow_post': True, 'allow_put': False,
'validate': {'type:values': direction_enum},
'is_visible': True}
'is_visible': True},
'vlan_filter': {'allow_post': True, 'allow_put': False,
'validate': {'type:regex_or_none': RANGE_REGEX},
'is_visible': True, 'default': None}
}
direction_enum = ['IN', 'OUT', 'BOTH']
@ -172,7 +175,8 @@ extension
"name": "flow1",
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
"tap_service_id": "69bd12b2-0e13-45ec-9045-b674fd9f0468",
"tenant_id": "97e1586d580745d7b311406697aaf097"
"tenant_id": "97e1586d580745d7b311406697aaf097",
"vlan_filter": "9,18-27,36,45,54-63"
}
}
@ -190,7 +194,8 @@ extension
"name": "flow1",
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
"tap_service_id": "69bd12b2-0e13-45ec-9045-b674fd9f0468",
"tenant_id": "97e1586d580745d7b311406697aaf097"
"tenant_id": "97e1586d580745d7b311406697aaf097",
"vlan_filter": "9,18-27,36,45,54-63"
}
}
@ -215,7 +220,8 @@ extension
"name": "flow1",
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
"tap_service_id": "c352f537-ad49-48eb-ab05-1c6b8cb900ff",
"tenant_id": "97e1586d580745d7b311406697aaf097"
"tenant_id": "97e1586d580745d7b311406697aaf097",
"vlan_filter": "9,18-27,36,45,54-63"
}
]
}

28
bin/i40e_sysfs_command

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright (c) 2018 AT&T Corporation
# 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.
help_msg="Incorrect arguments supplied!! Aborting....\nUsage: 1. VLANs to VF mirroring: $0 <phy-device-name> <dest-vf-index> <'vlan_mirror'> <'add'|'rem'> <vlan_ranges_string>\nUsage: 2. VF to VF mirroring: $0 <phy-device-name> <src-vf-index> <'ingress_mirror'|'egress_mirror'> <'add'|'rem'> <dest-vf-index>"
(($#!=5)) && { echo -e ${help_msg} && exit 1; }
if [ -f /sys/class/net/${1}/device/sriov/${2}/${3} ]
then
echo ${4} ${5} > /sys/class/net/${1}/device/sriov/${2}/${3}
else
echo "Invalid sysfs path: /sys/class/net/${1}/device/sriov/${2}/${3}"
exit 1
fi

2
devstack/devstackgaterc

@ -24,7 +24,7 @@ OVERRIDE_ENABLED_SERVICES+=,g-api,g-reg
OVERRIDE_ENABLED_SERVICES+=,n-api,n-cond,n-cpu,n-crt,n-sch,placement-api
OVERRIDE_ENABLED_SERVICES+=,n-api-meta
OVERRIDE_ENABLED_SERVICES+=,q-agt,q-dhcp,q-l3,q-meta,q-metering,q-svc,quantum
OVERRIDE_ENABLED_SERVICES+=,taas,taas_openvswitch_agent
OVERRIDE_ENABLED_SERVICES+=,taas,taas_agent
OVERRIDE_ENABLED_SERVICES+=,tempest,dstat
export OVERRIDE_ENABLED_SERVICES

1
devstack/plugin.sh

@ -26,6 +26,7 @@ function configure_taas_plugin {
cp $TAAS_PLUGIN_PATH/etc/taas_plugin.ini $TAAS_PLUGIN_CONF_FILE
neutron_server_config_add $TAAS_PLUGIN_CONF_FILE
neutron_service_plugin_class_add taas
neutron_deploy_rootwrap_filters $TAAS_PLUGIN_PATH
}
if is_service_enabled taas; then

2
devstack/settings

@ -2,5 +2,3 @@
ABSOLUTE_PATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)
TAAS_PLUGIN_PATH=$ABSOLUTE_PATH/..
TAAS_PLUGIN_CONF_FILE="/etc/neutron/taas_plugin.ini"
TAAS_OVS_AGENT_BINARY="$NEUTRON_BIN_DIR/neutron-taas-openvswitch-agent"
TAAS_OVS_AGENT_CONF_FILE="/etc/neutron/taas.ini"

9
etc/neutron/rootwrap.d/taas-i40e-sysfs.filters

@ -0,0 +1,9 @@
# taas-i40e-sysfs filters
# This file should be owned by (and only-writeable by) the root user
[Filters]
# This is needed to allow taas to insert/remove vlan id to the
# target vf under /sys/class/net/[device-name]/device/sriov/[vf-index]/[mirror]
i40e_sysfs_command: RegExpFilter, i40e_sysfs_command, root, i40e_sysfs_command, (?!.*\.\..*|.*\/.*).*, [0-9]+, (vlan|egress|ingress)_mirror, (?i)(add|rem), .*

3
lower-constraints.txt

@ -53,7 +53,8 @@ msgpack==0.5.6
munch==2.2.0
netaddr==0.7.19
netifaces==0.10.6
neutron-lib==1.20.0
neutron-lib==1.25.0
neutron==14.0.0.0b3
openstacksdk==0.12.0
os-client-config==1.29.0
os-service-types==1.2.0

4
neutron_taas/common/constants.py

@ -1,3 +1,4 @@
# Copyright (C) 2018 AT&T
# Copyright (C) 2015 Midokura SARL.
# All Rights Reserved.
#
@ -14,3 +15,6 @@
# under the License.
TAAS = 'TAAS'
# Complete VLAN Id Range
VLAN_RANGE = '0-4095'

49
neutron_taas/common/utils.py

@ -0,0 +1,49 @@
# Copyright (C) 2018 AT&T
#
# 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.
def get_list_from_ranges_str(ranges_str):
"""Convert the range in string format to ranges list
And yield the merged ranges in order. The argument must be a
string having comma separated vlan and vlan-ranges.
get_list_from_ranges_str("4,6,10-13,25-27,100-103")
[4, 6, 10, 11, 12, 13, 25, 26, 27, 100, 101, 102, 103]
"""
return sum(((list(range(*[int(range_start) + range_index
for range_index, range_start in
enumerate(range_item.split('-'))]))
if '-' in range_item else [int(range_item)])
for range_item in ranges_str.split(',')), [])
def get_ranges_str_from_list(ranges):
"""Convert the ranges list to string format
And yield the merged ranges in order in string format.
The argument must be an iterable of pairs (start, stop).
get_ranges_str_from_list([4, 11, 12, 13, 25, 26, 27, 101, 102, 103])
"4,11-13,25-27,101-103"
"""
ranges_str = []
for val in sorted(ranges):
if not ranges_str or ranges_str[-1][-1] + 1 != val:
ranges_str.append([val])
else:
ranges_str[-1].append(val)
return ",".join([str(range_item[0]) if len(range_item) == 1
else str(range_item[0]) + "-" + str(range_item[-1])
for range_item in ranges_str])

2
neutron_taas/db/migration/alembic_migration/versions/EXPAND_HEAD

@ -1 +1 @@
fddbdec8711a
ccbcc559d175

35
neutron_taas/db/migration/alembic_migration/versions/stein/expand/ccbcc559d175_add_vlan_filter_to_tap_flow.py

@ -0,0 +1,35 @@
# Copyright (C) 2018 AT&T
#
# 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.
"""add_vlan_filter_to_tap_flow
Revision ID: ccbcc559d175
Revises: fddbdec8711a
Create Date: 2018-09-18 19:33:32.119458
"""
# revision identifiers, used by Alembic.
revision = 'ccbcc559d175'
down_revision = 'fddbdec8711a'
from alembic import op
import sqlalchemy as sa
TABLE_NAME = 'tap_flows'
def upgrade():
op.add_column(TABLE_NAME, sa.Column('vlan_filter', sa.String(1024),
nullable=True))

6
neutron_taas/db/taas_db.py

@ -1,3 +1,4 @@
# Copyright (C) 2018 AT&T
# Copyright (C) 2015 Ericsson AB
# Copyright (c) 2015 Gigamon
#
@ -60,6 +61,7 @@ class TapFlow(model_base.BASEV2, model_base.HasId,
nullable=False)
status = sa.Column(sa.String(16), nullable=False,
server_default=constants.ACTIVE)
vlan_filter = sa.Column(sa.String(1024), nullable=True)
class TapIdAssociation(model_base.BASEV2):
@ -128,7 +130,8 @@ class Taas_db_Mixin(taas.TaasPluginBase):
'description': tap_flow['description'],
'source_port': tap_flow['source_port'],
'direction': tap_flow['direction'],
'status': tap_flow['status']}
'status': tap_flow['status'],
'vlan_filter': tap_flow['vlan_filter']}
return db_utils.resource_fields(res, fields)
@ -209,6 +212,7 @@ class Taas_db_Mixin(taas.TaasPluginBase):
source_port=t_f['source_port'],
direction=t_f['direction'],
status=constants.ACTIVE,
vlan_filter=t_f['vlan_filter'],
)
context.session.add(tap_flow_db)

59
neutron_taas/extensions/vlan_filter.py

@ -0,0 +1,59 @@
# Copyright (C) 2018 AT&T
#
# 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.
#
from neutron_lib.api import extensions
# Regex for a comma-seperate list of integer values (VLANs)
# For ex. "9,18,27-36,45-54" or "0-4095" or "9,18,27,36"
RANGE_REGEX = r"^([0-9]+(-[0-9]+)?)(,([0-9]+(-[0-9]+)?))*$"
EXTENDED_ATTRIBUTES_2_0 = {
'tap_flows': {
'vlan_filter': {'allow_post': True, 'allow_put': False,
'validate': {'type:regex_or_none': RANGE_REGEX},
'is_visible': True, 'default': None}
}
}
class Vlan_filter(extensions.ExtensionDescriptor):
"""Extension class supporting vlan_filter for tap_flows."""
@classmethod
def get_name(cls):
return "TaaS Vlan Filter Extension"
@classmethod
def get_alias(cls):
return 'taas-vlan-filter'
@classmethod
def get_description(cls):
return "Vlan Filter support for Tap Flows."
@classmethod
def get_updated(cls):
return "2019-01-23T00:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
return {}
def get_required_extensions(self):
return ["taas"]
def get_optional_extensions(self):
return []

0
neutron_taas/services/taas/agents/ovs/__init__.py → neutron_taas/services/taas/agents/common/__init__.py

76
neutron_taas/services/taas/agents/ovs/taas_ovs_agent.py → neutron_taas/services/taas/agents/common/taas_agent.py

@ -1,3 +1,4 @@
# Copyright (C) 2018 AT&T
# Copyright (C) 2015 Ericsson AB
# Copyright (c) 2015 Gigamon
#
@ -15,35 +16,54 @@
from neutron import manager
from neutron_taas.services.taas.drivers.linux \
import ovs_constants as taas_ovs_consts
from neutron_taas.common import topics
from neutron_taas.services.taas.agents import taas_agent_api as api
from neutron_lib import context as neutron_context
from neutron_lib import rpc as n_rpc
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_service import service
LOG = logging.getLogger(__name__)
class TaasOvsPluginApi(api.TaasPluginApiMixin):
# Currently there are not any APIs from the the agent towards plugin
class TaasPluginApi(api.TaasPluginApiMixin):
def __init__(self, topic, host):
super(TaasOvsPluginApi, self).__init__(topic, host)
super(TaasPluginApi, self).__init__(topic, host)
target = messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
return
def sync_tap_resources(self, sync_tap_res, host):
"""Send Rpc to plugin to recreate pre-existing tap resources."""
LOG.debug("In RPC Call for Sync Tap Resources: Host=%s, MSG=%s" %
(host, sync_tap_res))
class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
context = neutron_context.get_admin_context()
cctxt = self.client.prepare(fanout=False)
cctxt.cast(context, 'sync_tap_resources', sync_tap_res=sync_tap_res,
host=host)
return
class TaasAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
def __init__(self, conf, driver_type):
LOG.debug("TaaS OVS Agent initialize called")
LOG.debug("TaaS Agent initialize called")
self.conf = conf
self.driver_type = driver_type
super(TaasOvsAgentRpcCallback, self).__init__()
super(TaasAgentRpcCallback, self).__init__()
def initialize(self):
self.taas_driver = manager.NeutronManager.load_class_for_provider(
@ -52,7 +72,7 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
self.taas_driver.initialize()
self._taas_rpc_setup()
TaasOvsAgentService(self).start()
TaasAgentService(self).start(self.taas_plugin_rpc, self.conf.host)
def consume_api(self, agent_api):
self.agent_api = agent_api
@ -104,6 +124,9 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
def delete_tap_flow(self, context, tap_flow_msg, host):
if host != self.conf.host:
LOG.debug("RPC Call for Delete Tap Flow. Host value [%s]"
"(received in RPC) doesn't match the host value "
"stored in agent [%s]" % (host, self.conf.host))
return
LOG.debug("In RPC Call for Delete Tap Flow: MSG=%s" % tap_flow_msg)
@ -114,7 +137,7 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
def _taas_rpc_setup(self):
# setup RPC to msg taas plugin
self.taas_plugin_rpc = TaasOvsPluginApi(
self.taas_plugin_rpc = TaasPluginApi(
topics.TAAS_PLUGIN, self.conf.host)
endpoints = [self]
@ -123,22 +146,31 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
conn.consume_in_threads()
def periodic_tasks(self):
#
# Regenerate the flow in br-tun's TAAS_SEND_FLOOD table
# to ensure all existing tunnel ports are included.
#
self.taas_driver.update_tunnel_flood_flow()
return self._invoke_driver_for_plugin_api(
context=None,
args=None,
func_name='periodic_tasks')
def get_driver_type(self):
return self.driver_type
class TaasOvsAgentService(service.Service):
class TaasAgentService(service.Service):
def __init__(self, driver):
super(TaasOvsAgentService, self).__init__()
super(TaasAgentService, self).__init__()
self.driver = driver
def start(self):
super(TaasOvsAgentService, self).start()
self.tg.add_timer(
int(cfg.CONF.taas_agent_periodic_interval),
self.driver.periodic_tasks,
None
)
def start(self, taas_plugin_rpc, host):
super(TaasAgentService, self).start()
if self.driver.get_driver_type() == \
taas_ovs_consts.EXTENSION_DRIVER_TYPE:
self.tg.add_timer(
int(cfg.CONF.taas_agent_periodic_interval),
self.driver.periodic_tasks,
None
)
# Indicate the TaaS plugin to recreate the taas resources
rpc_msg = {'host_id': host}
taas_plugin_rpc.sync_tap_resources(rpc_msg, host)

5
neutron_taas/services/taas/agents/extensions/taas.py

@ -18,14 +18,13 @@ import six
from neutron_lib.agent import l2_extension
from neutron_taas.services.taas.agents.ovs import taas_ovs_agent
from neutron_taas.services.taas.agents.common import taas_agent
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
OPTS = [
cfg.IntOpt(
'taas_agent_periodic_interval',
@ -71,7 +70,7 @@ class TaasAgentExtension(l2_extension.L2AgentExtension):
def initialize(self, connection, driver_type):
"""Initialize agent extension."""
self.taas_agent = taas_ovs_agent.TaasOvsAgentRpcCallback(
self.taas_agent = taas_agent.TaasAgentRpcCallback(
cfg.CONF, driver_type)
self.taas_agent.consume_api(self.agent_api)
self.taas_agent.initialize()

3
neutron_taas/services/taas/drivers/linux/ovs_constants.py

@ -26,3 +26,6 @@ TAAS_DST_CHECK = 36
TAAS_SRC_CHECK = 37
TAAS_DST_RESPOND = 38
TAAS_SRC_RESPOND = 39
# OVS TaaS extension driver type
EXTENSION_DRIVER_TYPE = 'ovs'

7
neutron_taas/services/taas/drivers/linux/ovs_taas.py

@ -56,6 +56,13 @@ class OvsTaasDriver(taas_base.TaasAgentDriver):
# Setup key-value manager for ingress BCMC flows
self.bcmc_kvm = taas_ovs_utils.key_value_mgr(4096)
def periodic_tasks(self, args=None):
#
# Regenerate the flow in br-tun's TAAS_SEND_FLOOD table
# to ensure all existing tunnel ports are included.
#
self.update_tunnel_flood_flow()
def setup_ovs_bridges(self):
#
# br-int : Integration Bridge

32
neutron_taas/services/taas/drivers/linux/sriov_nic_exceptions.py

@ -0,0 +1,32 @@
# Copyright (C) 2018 AT&T
#
# 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.
from neutron_lib import exceptions as qexception
# TaaS SR-IOV driver exception handling classes
class SriovNicSwitchDriverInvocationError(qexception.Invalid):
message = _("Failed to invoke SR-IOV TaaS driver command: "
"%(tap_service_pf_device)s, %(tap_service_vf_index)s, "
"%(source_vf_index)s, %(vlan_filter)s, "
"%(vf_to_vf_all_vlans)s, %(direction)s")
class PciDeviceNotFoundById(qexception.NotFound):
message = _("PCI device %(id)s not found")
class PciSlotNotFound(qexception.NotFound):
message = _("PCI slot (Port-id, MAC): %(port_id)s, %(mac)s not found")

364
neutron_taas/services/taas/drivers/linux/sriov_nic_taas.py

@ -0,0 +1,364 @@
# Copyright (C) 2018 AT&T
#
# 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.
from neutron.conf.agent import common
from neutron_taas.common import constants as taas_consts
from neutron_taas.common import utils as common_utils
from neutron_taas.services.taas.agents.extensions import taas as taas_base
from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \
as taas_exc
from neutron_taas.services.taas.drivers.linux import sriov_nic_utils \
as sriov_utils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
import threading
LOG = logging.getLogger(__name__)
TaaS_DRIVER_NAME = 'Taas SRIOV NIC Switch driver'
class SriovNicTaasDriver(taas_base.TaasAgentDriver):
def __init__(self):
super(SriovNicTaasDriver, self).__init__()
LOG.debug("Initializing Taas SRIOV NIC Switch Driver")
self.agent_api = None
self.root_helper = common.get_root_helper(cfg.CONF)
self.lock = threading.Lock()
def initialize(self):
LOG.debug("Initialize routine called for Taas SRIOV NIC Switch Driver")
self.sriov_utils = sriov_utils.SriovNicUtils()
return
def consume_api(self, agent_api):
self.agent_api = agent_api
def create_tap_service(self, tap_service):
ts_port = tap_service['port']
LOG.debug("SRIOV Driver: Inside create_tap_service: "
"Port-id: %(port_id)s",
{'port_id': ts_port['id']})
port_params = self.sriov_utils.get_sriov_port_params(ts_port)
LOG.info("TaaS SRIOV: create_tap_service RPC invoked for "
"port %(id)s, MAC %(ts_port_mac)s, PCI %(ts_pci_slot)s, "
"VF-Index %(vf_index)s, PF-Device %(pf_device)s, "
"src_vlans %(src_vlans)s; ",
{'id': ts_port['id'],
'ts_port_mac': port_params['mac'],
'ts_pci_slot': port_params['pci_slot'],
'vf_index': port_params['vf_index'],
'pf_device': port_params['pf_device'],
'src_vlans': port_params['src_vlans']})
return
def delete_tap_service(self, tap_service):
ts_port = tap_service['port']
LOG.debug("SRIOV Driver: Inside delete_tap_service: "
"Port-id: %(port_id)s",
{'port_id': ts_port['id']})
port_params = self.sriov_utils.get_sriov_port_params(ts_port)
LOG.info("TaaS SRIOV: delete_tap_service RPC invoked for "
"port %(id)s, MAC %(ts_port_mac)s, PCI %(ts_pci_slot)s, "
"VF-Index %(vf_index)s, PF-Device %(pf_device)s, "
"src_vlans %(src_vlans)s; ",
{'id': ts_port['id'],
'ts_port_mac': port_params['mac'],
'ts_pci_slot': port_params['pci_slot'],
'vf_index': port_params['vf_index'],
'pf_device': port_params['pf_device'],
'src_vlans': port_params['src_vlans']})
return
def create_tap_flow(self, tap_flow):
source_port = tap_flow['port']
ts_port = tap_flow['tap_service_port']
direction = tap_flow['tap_flow']['direction']
vlan_filter = tap_flow['tap_flow']['vlan_filter']
vf_to_vf_all_vlans = False
LOG.debug("SRIOV Driver: Inside create_tap_flow: "
"SRC Port-id: %(src_port_id)s, "
"DEST Port-id: %(dest_port_id)s "
"Direction: %(direction)s",
{'src_port_id': source_port['id'],
'dest_port_id': ts_port['id'],
'direction': direction})
src_port_params = self.sriov_utils.get_sriov_port_params(source_port)
ts_port_params = self.sriov_utils.get_sriov_port_params(ts_port)
LOG.info("TaaS src_port_params "
"port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, "
"VF-Index %(vf_index)s, PF-Device %(pf_device)s, "
"src_vlans %(src_vlans)s; ",
{'id': source_port['id'],
'port_mac': src_port_params['mac'],
'pci_slot': src_port_params['pci_slot'],
'vf_index': src_port_params['vf_index'],
'pf_device': src_port_params['pf_device'],
'src_vlans': src_port_params['src_vlans']})
LOG.info("TaaS ts_port_params "
"port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, "
"VF-Index %(vf_index)s, PF-Device %(pf_device)s, "
"VLAN-Filter %(vlan_filter)s, src_vlans %(src_vlans)s; ",
{'id': ts_port['id'],
'port_mac': ts_port_params['mac'],
'pci_slot': ts_port_params['pci_slot'],
'vf_index': ts_port_params['vf_index'],
'pf_device': ts_port_params['pf_device'],
'vlan_filter': vlan_filter,
'src_vlans': ts_port_params['src_vlans']})
# If no VLAN filter configured on source port, then include all vlans
if not src_port_params['src_vlans'] or \
src_port_params['src_vlans'] == '0':
src_port_params['src_vlans'] = taas_consts.VLAN_RANGE
LOG.debug("TaaS no src_vlans in src_port")
# If no VLAN filter configured on probe port, then include all vlans
if not vlan_filter:
vlan_filter = taas_consts.VLAN_RANGE
vf_to_vf_all_vlans = True
LOG.debug("VF to VF mirroring for all VLANs. "
"Direction %(direction)s",
{'direction': direction})
if not src_port_params['pci_slot']:
LOG.error("No PCI Slot for source_port %(id)s with MAC %(mac)s; ",
{'id': source_port['id'],
'mac': src_port_params['mac'],
'source_vlans': src_port_params['src_vlans']})
raise taas_exc.PciSlotNotFound(port_id=source_port['id'],
mac=src_port_params['mac'])
if not ts_port_params['pci_slot']:
LOG.error("No PCI Slot for ts_port %(id)s with MAC %(mac)s; ",
{'id': ts_port['id'], 'mac': ts_port_params['mac'],
'vlan_filter': vlan_filter})
raise taas_exc.PciSlotNotFound(port_id=ts_port['id'],
mac=ts_port_params['mac'])
if src_port_params['pf_device'] != ts_port_params['pf_device']:
LOG.error("SRIOV NIC Driver only supports mirroring b/w "
"VF_src %(VF_src)s -> VF_probe %(VF_probe)s on same PF. "
"PF_src %(PF_src)s and PF_probe %(PF_probe)s "
"are different; ",
{'VF_src': src_port_params['vf_index'],
'VF_probe': ts_port_params['vf_index'],
'PF_src': src_port_params['pf_device'],
'PF_probe': ts_port_params['pf_device']})
return
# Fetch common VLAN tags
src_vlans_list = sorted(set(common_utils.get_list_from_ranges_str(
src_port_params['src_vlans'])))
vlan_filter_list = sorted(set(
common_utils.get_list_from_ranges_str(vlan_filter)))
common_vlans_list = list(set(src_vlans_list).intersection(
vlan_filter_list))
common_vlans_rng_str = common_utils.get_ranges_str_from_list(
common_vlans_list)
LOG.info("TaaS src_vlans_list %(src_vlans_list)s, "
"vlan_filter_list %(vlan_filter_list)s, "
"common_vlans_list %(common_vlans_list)s, "
"common_vlans_rng_str %(common_vlans_rng_str)s; ",
{'src_vlans_list': src_vlans_list,
'vlan_filter_list': vlan_filter_list,
'common_vlans_list': common_vlans_list,
'common_vlans_rng_str': common_vlans_rng_str})
if ts_port_params['pf_device'] and \
ts_port_params['vf_index'] and \
src_port_params['vf_index']:
with self.lock:
try:
LOG.info("TaaS invoking execute_sysfs_command")
self.sriov_utils.execute_sysfs_command(
'add',
ts_port_params,
src_port_params,
common_vlans_rng_str,
vf_to_vf_all_vlans,
direction)
except Exception:
LOG.error("TaaS error in invoking execute_sysfs_command")
with excutils.save_and_reraise_exception():
raise taas_exc.SriovNicSwitchDriverInvocationError(
ts_pf_dev=ts_port_params['pf_device'],
ts_vf_index=ts_port_params['vf_index'],
source_vf_index=src_port_params['vf_index'],
common_vlans_rng_str=common_vlans_rng_str,
vf_to_vf_all_vlans=vf_to_vf_all_vlans,
direction=direction)
return
def delete_tap_flow(self, tap_flow):
source_port = tap_flow['port']
ts_port = tap_flow['tap_service_port']
vlan_filter = tap_flow['tap_flow']['vlan_filter']
direction = tap_flow['tap_flow']['direction']
LOG.debug("SRIOV Driver: Inside delete_tap_flow: "
"SRC Port-id: %(src_port_id)s, "
"DEST Port-id: %(dest_port_id)s "
"Direction: %(direction)s",
{'src_port_id': source_port['id'],
'dest_port_id': ts_port['id'],
'direction': direction})
src_port_params = self.sriov_utils.get_sriov_port_params(source_port)
ts_port_params = self.sriov_utils.get_sriov_port_params(ts_port)
LOG.info("TaaS src_port_params "
"port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, "
"VF-Index %(vf_index)s, PF-Device %(pf_device)s, "
"src_vlans %(src_vlans)s; ",
{'id': source_port['id'],
'port_mac': src_port_params['mac'],
'pci_slot': src_port_params['pci_slot'],
'vf_index': src_port_params['vf_index'],
'pf_device': src_port_params['pf_device'],
'src_vlans': src_port_params['src_vlans']})
LOG.info("TaaS ts_port_params "
"port %(id)s, MAC %(port_mac)s, PCI %(pci_slot)s, "
"VF-Index %(vf_index)s, PF-Device %(pf_device)s, "
"VLAN-Filter %(vlan_filter)s, src_vlans %(src_vlans)s; ",
{'id': ts_port['id'],
'port_mac': ts_port_params['mac'],
'pci_slot': ts_port_params['pci_slot'],
'vf_index': ts_port_params['vf_index'],
'pf_device': ts_port_params['pf_device'],
'vlan_filter': vlan_filter,
'src_vlans': ts_port_params['src_vlans']})
# If no VLAN filter configured on source port, then include all vlans
if not src_port_params['src_vlans']:
src_port_params['src_vlans'] = taas_consts.VLAN_RANGE
LOG.debug("TaaS no src_vlans in src_port")
# If no VLAN filter configured on probe port, then include all vlans
if not vlan_filter:
vf_to_vf_all_vlans = True
LOG.debug("VF to VF mirroring for all VLANs. "
"Direction %(direction)s",
{'direction': direction})
if not src_port_params['pci_slot']:
LOG.error("No PCI Slot for source_port %(id)s with MAC %(mac)s; ",
{'id': source_port['id'],
'mac': src_port_params['mac'],
'source_vlans': src_port_params['src_vlans']})
raise taas_exc.PciSlotNotFound(port_id=source_port['id'],
mac=src_port_params['mac'])
if not ts_port_params['pci_slot']:
LOG.error("No PCI Slot for ts_port %(id)s with MAC %(mac)s; ",
{'id': ts_port['id'], 'mac': ts_port_params['mac']})
raise taas_exc.PciSlotNotFound(port_id=ts_port['id'],
mac=ts_port_params['mac'])
# Fetch common VLAN tags
src_vlans_list = []
for src_vlans_str in tap_flow['source_vlans_list']:
src_vlans_list.extend(common_utils.get_list_from_ranges_str(
src_vlans_str))
src_vlans_list = sorted(set(src_vlans_list))
vlan_filter_list = []
for vlan_filter_str in tap_flow['vlan_filter_list']:
vlan_filter_list.extend(common_utils.get_list_from_ranges_str(
vlan_filter_str))
vlan_filter_list = sorted(set(vlan_filter_list))
common_vlans_list = \
list(set(src_vlans_list).intersection(vlan_filter_list))
common_vlans_rng_str = \
common_utils.get_ranges_str_from_list(common_vlans_list)
LOG.info("TaaS src_vlans_list %(src_vlans_list)s, "
"vlan_filter_list %(vlan_filter_list)s, "
"common_vlans_list %(common_vlans_list)s, "
"common_vlans_rng_str %(common_vlans_rng_str)s; ",
{'src_vlans_list': src_vlans_list,
'vlan_filter_list': vlan_filter_list,
'common_vlans_list': common_vlans_list,
'common_vlans_rng_str': common_vlans_rng_str})
if ts_port_params['pf_device'] and \
ts_port_params['vf_index'] and \
src_port_params['vf_index']:
with self.lock:
try:
LOG.info("TaaS invoking execute_sysfs_command")
self.sriov_utils.execute_sysfs_command('rem',
ts_port_params,
src_port_params,
taas_consts.
VLAN_RANGE,
False,
'BOTH')
except Exception:
LOG.error("TaaS error in invoking execute_sysfs_command")
with excutils.save_and_reraise_exception():
raise taas_exc.SriovNicSwitchDriverInvocationError(
ts_pf_dev=ts_port_params['pf_device'],
ts_vf_index=ts_port_params['vf_index'],
source_vf_index=src_port_params['vf_index'],
common_vlans_rng_str=taas_consts.VLAN_RANGE,
vf_to_vf_all_vlans=vf_to_vf_all_vlans,
direction=direction)
if common_vlans_rng_str:
try:
LOG.info("TaaS invoking execute_sysfs_command")
self.sriov_utils.execute_sysfs_command(
'add',
ts_port_params,
src_port_params,
common_vlans_rng_str,
False,
'BOTH')
except Exception:
LOG.error("TaaS error in invoking "
"execute_sysfs_command")
with excutils.save_and_reraise_exception():
raise taas_exc.SriovNicSwitchDriverInvocationError(
ts_pf_dev=ts_port_params['pf_device'],
ts_vf_index=ts_port_params['vf_index'],
source_vf_index=src_port_params['vf_index'],
common_vlans_rng_str=common_vlans_rng_str,
vf_to_vf_all_vlans=vf_to_vf_all_vlans,
direction=direction)
return

268
neutron_taas/services/taas/drivers/linux/sriov_nic_utils.py

@ -0,0 +1,268 @@
# Copyright (C) 2018 AT&T
#
# 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.
#
# This class implements a utility functions for SRIOV NIC Switch Driver
#
import os
from oslo_log import log as logging
from neutron.agent.linux import utils
from neutron_lib.api.definitions import portbindings
from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \
as taas_exc
import glob
import re
LOG = logging.getLogger(__name__)
class SriovNicUtils(object):
#
# Initializes internal state for specified # keys
#
def __init__(self):
LOG.debug("SriovNicUtils: init called")
return
#
# Returns specified key-value affilation, if it exists.
#
def execute_sysfs_command(self, command, ts_port_params,
src_port_params,
common_vlans_ranges_str,
vf_to_vf_all_vlans, direction):
"""Execute the SRIOV NIC Switch Driver's SysFs command.
# Mirror traffic from VF0 to VF3 on interface p2p1, ex.
echo add 3 > /sys/class/net/p2p1/device/sriov/0/ingress_mirror
echo add 3 > /sys/class/net/p2p1/device/sriov/0/egress_mirror
# Remove traffic mirroring from VF0 to VF3 on interface p2p1, ex.
echo rem 3 > /sys/class/net/p2p1/device/sriov/0/ingress_mirror
echo rem 3 > /sys/class/net/p2p1/device/sriov/0/egress_mirror
# Add VLANs 2,6,18-22 to Mirror traffic to VF3 (port p2p1), ex.
echo add 2,6,18-22 > /sys/class/net/p2p1/device/sriov/3/vlan_mirror
# Remove VLANs 2,6,18-22 to Mirror traffic to VF3 (port p2p1), ex.
echo rem 2,6,18-22 > /sys/class/net/p2p1/device/sriov/3/vlan_mirror
# Remove all VLANs from mirroring at VF3, ex.
echo rem 0-4095 > /sys/class/net/p1p1/device/sriov/3/vlan_mirror
"""
LOG.debug("TaaS sysfs command params %(command)s, "
"ts_port_params %(ts_port_params)s, "
"src_port_params %(src_port_params)s, "
"common_vlans_ranges_str %(common_vlans_ranges_str)s; "
"vf_to_vf_all_vlans %(vf_to_vf_all_vlans)s; "
"direction %(direction)s; ",
{'command': command,
'ts_port_params': ts_port_params,
'src_port_params': src_port_params,
'common_vlans_ranges_str': common_vlans_ranges_str,
'vf_to_vf_all_vlans': vf_to_vf_all_vlans,
'direction': direction})
if vf_to_vf_all_vlans:
if direction in ['OUT', 'BOTH']:
commit_cmd = ['i40e_sysfs_command',
ts_port_params['pf_device'],
src_port_params['vf_index'],
'egress_mirror',
command,
ts_port_params['vf_index']]
try:
LOG.info("TaaS executing sysfs_command %(command)s",
{'command': commit_cmd})
utils.execute(commit_cmd, run_as_root=True)
except (OSError, RuntimeError, IndexError, ValueError) as e:
LOG.error("Exception while executing Sysfs command "
"Exception: %s", e)
return
if direction in ['IN', 'BOTH']:
commit_cmd = ['i40e_sysfs_command',
ts_port_params['pf_device'],
src_port_params['vf_index'],
'ingress_mirror',
command,
ts_port_params['vf_index']]
try:
LOG.info("TaaS executing sysfs_command %(command)s",
{'command': commit_cmd})
utils.execute(commit_cmd, run_as_root=True)
except (OSError, RuntimeError, IndexError, ValueError) as e:
LOG.error("Exception while executing Sysfs command "
"Exception: %s", e)
return
else:
if direction != 'BOTH':
LOG.warning("SRIOV NIC Switch driver only supports"
"direction=BOTH for specific VLANs' mirroring")
commit_cmd = ['i40e_sysfs_command',
ts_port_params['pf_device'],
ts_port_params['vf_index'],
'vlan_mirror',
command,
common_vlans_ranges_str]
try:
LOG.info("TaaS executing sysfs_command %(command)s",
{'command': commit_cmd})
utils.execute(commit_cmd, run_as_root=True)
except (OSError, RuntimeError, IndexError, ValueError) as e:
LOG.error("Exception while executing Sysfs command "
"Exception: %s", e)
return
def _get_sysfs_netdev_path(self, pci_addr, pf_interface):
"""Get the sysfs path based on the PCI address of the device.
Assumes a networking device - will not check for the existence
of the path.
"""
if pf_interface:
return "/sys/bus/pci/devices/%s/physfn/net" % pci_addr
return "/sys/bus/pci/devices/%s/net" % pci_addr
def get_ifname_by_pci_address(self, pci_addr, pf_interface=False):
"""Get the interface name based on a VF's pci address.
The returned interface name is either the parent PF's or that of
the VF itself based on the argument of pf_interface.
"""
dev_path = self._get_sysfs_netdev_path(pci_addr, pf_interface)
try:
dev_info = os.listdir(dev_path)
return dev_info.pop()
except Exception:
raise taas_exc.PciDeviceNotFoundById(id=pci_addr)
def get_mac_by_pci_address(self, pci_addr, pf_interface=False):
"""Get the MAC address of the nic based on its PCI address.
Raises PciDeviceNotFoundById in case the pci device is not a NIC
"""
dev_path = self._get_sysfs_netdev_path(pci_addr, pf_interface)
if_name = self.get_ifname_by_pci_address(pci_addr, pf_interface)
addr_file = os.path.join(dev_path, if_name, 'address')
try:
with open(addr_file) as f:
mac = next(f).strip()
return mac
except (IOError, StopIteration) as e:
LOG.warning("Could not find the expected sysfs file for "
"determining the MAC address of the PCI device "
"%(addr)s. May not be a NIC. Error: %(e)s",
{'addr': pci_addr, 'e': e})
raise taas_exc.PciDeviceNotFoundById(id=pci_addr)
def get_vf_num_by_pci_address(self, pci_addr):
"""Get the VF number based on a VF's pci address
A VF is associated with an VF number, which ip link command uses to
configure it. This can be obtained from the PCI device filesystem.
"""
VIRTFN_RE = re.compile("virtfn(\d+)")
virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
vf_num = None
LOG.debug("TaaS: pci_addr: %(pci_addr)s "
"virtfns_path: %(virtfns_path)s",
{'pci_addr': pci_addr,
'virtfns_path': virtfns_path})
try:
for vf_path in glob.iglob(virtfns_path):
if re.search(pci_addr, os.readlink(vf_path)):
t = VIRTFN_RE.search(vf_path)
vf_num = t.group(1)
break
except Exception:
pass
if vf_num is None:
LOG.warning("TaaS: No net device was found for pci: %(pci_addr)s "
"virtfns_path: %(virtfns_path)s",
{'pci_addr': pci_addr,
'virtfns_path': virtfns_path})
raise taas_exc.PciDeviceNotFoundById(id=pci_addr)
return vf_num
def get_net_name_by_vf_pci_address(self, vfaddress, pf_interface=False):
"""Given the VF PCI address, returns the net device name.
Every VF is associated to a PCI network device. This function
returns the libvirt name given to this network device; e.g.:
<device>
<name>net_enp8s0f0_90_e2_ba_5e_a6_40</name>
...
In the libvirt parser information tree, the network device stores the
network capabilities associated to this device.
"""
LOG.debug("TaaS: vfaddr: %(vfaddr)s ",
{'vfaddr': vfaddress})
try:
mac = self.get_mac_by_pci_address(vfaddress,
pf_interface).split(':')
ifname = self.get_ifname_by_pci_address(vfaddress, pf_interface)
LOG.debug("TaaS: mac: %(mac)s, ifname: %(ifname)s",
{'mac': mac, 'ifname': ifname})
return ("net_%(ifname)s_%(mac)s" %
{'ifname': ifname, 'mac': '_'.join(mac)})
except Exception:
LOG.warning("No net device was found for VF %(vfaddress)s",
{'vfaddress': vfaddress})
return
def get_sriov_port_params(self, sriov_port):
"""Returns a dict of common SRIOV parameters for a given SRIOV port
"""
LOG.debug("TaaS: sriov_port %(id)s; ",
{'id': sriov_port['id']})
port_mac = sriov_port['mac_address']
pci_slot = None
src_vlans = None
if sriov_port.get(portbindings.PROFILE):
pci_slot = sriov_port[portbindings.PROFILE].get('pci_slot')
if sriov_port.get(portbindings.VIF_DETAILS):
src_vlans = sriov_port[portbindings.VIF_DETAILS].get('vlan')
LOG.debug("TaaS: pci_slot %(pci_slot)s; "
"src_vlans %(src_vlans)s; ",
{'pci_slot': pci_slot,
'src_vlans': src_vlans})
if not pci_slot:
LOG.error("No PCI Slot for sriov_port %(id)s with MAC %(mac)s; ",
{'id': sriov_port['id'], 'mac': port_mac})
return
vf_index = self.get_vf_num_by_pci_address(pci_slot)
pf_device = self.get_ifname_by_pci_address(pci_slot, True)
return {'mac': port_mac, 'pci_slot': pci_slot,
'vf_index': vf_index, 'pf_device': pf_device,
'src_vlans': src_vlans}

9
neutron_taas/services/taas/service_drivers/taas_agent_api.py

@ -21,15 +21,6 @@ import oslo_messaging as messaging
LOG = logging.getLogger(__name__)
class TaasCallbacks(object):
"""Currently there are no callbacks to the Taas Plugin."""
def __init__(self, plugin):
super(TaasCallbacks, self).__init__()
self.plugin = plugin
return
class TaasAgentApi(object):
"""RPC calls to agent APIs"""

125
neutron_taas/services/taas/service_drivers/taas_rpc.py

@ -1,3 +1,4 @@
# Copyright (C) 2018 AT&T
# Copyright (C) 2016 Midokura SARL.
# Copyright (C) 2015 Ericsson AB
# Copyright (c) 2015 Gigamon
@ -14,25 +15,89 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants
from neutron_lib import exceptions as n_exc
from neutron_lib import rpc as n_rpc
from neutron_taas.common import constants as taas_consts
from neutron_taas.common import topics
from neutron_taas.services.taas import service_drivers
from neutron_taas.services.taas.service_drivers import (service_driver_context
as sd_context)
from neutron_taas.services.taas.service_drivers import taas_agent_api
from neutron_taas.services.taas.taas_plugin import TaasPlugin
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
LOG = logging.getLogger(__name__)
class TaasCallbacks(object):
def __init__(self, rpc_driver, plugin):
super(TaasCallbacks, self).__init__()
self.rpc_driver = rpc_driver
self.plugin = plugin
return
def sync_tap_resources(self, context, sync_tap_res, host):
"""Handle Rpc from Agent to sync up Tap resources."""
LOG.debug("In RPC Call for Sync Tap Resources: MSG=%s" % sync_tap_res)
# Get list of configured tap-services
active_tss = self.plugin.get_tap_services(
context,
filters={'status': [constants.ACTIVE]})
for ts in active_tss:
# If tap-service port is bound to a different host than the one
# which sent this RPC, then continue.
ts_port = self.plugin._get_port_details(
context, ts['port_id'])
if ts_port['binding:host_id'] != host:
continue
driver_context = sd_context.TapServiceContext(self.plugin,
context, ts)
try:
self.rpc_driver.create_tap_service_postcommit(driver_context)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error("Failed to create tap service on driver,"
"deleting tap_service %s", ts['id'])
super(TaasPlugin, self.plugin).delete_tap_service(
context, ts['id'])
# Get all the active tap flows for current tap-service
active_tfs = self.plugin.get_tap_flows(
context,
filters={'tap_service_id': [ts['id']],
'status': [constants.ACTIVE]})
# Filter out the tap flows associated with distinct tap services
for tf in active_tfs:
driver_context = sd_context.TapFlowContext(self.plugin,
context, tf)
try:
self.rpc_driver.create_tap_flow_postcommit(driver_context)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error("Failed to create tap flow on driver,"
"deleting tap_flow %s", tf['id'])
super(TaasPlugin, self.plugin).delete_tap_flow(
context, tf['id'])
class TaasRpcDriver(service_drivers.TaasBaseDriver):
"""Taas Rpc Service Driver class"""
def __init__(self, service_plugin):
LOG.debug("Loading TaasRpcDriver.")
super(TaasRpcDriver, self).__init__(service_plugin)
self.endpoints = [taas_agent_api.TaasCallbacks(service_plugin)]
self.endpoints = [TaasCallbacks(self, service_plugin)]
self.conn = n_rpc.Connection()
self.conn.create_consumer(topics.TAAS_PLUGIN,
self.endpoints, fanout=False)
@ -125,12 +190,19 @@ class TaasRpcDriver(service_drivers.TaasBaseDriver):
tf['source_port'])
host = port['binding:host_id']
port_mac = port['mac_address']
# Extract the tap-service port
ts = self.service_plugin.get_tap_service(context._plugin_context,
tf['tap_service_id'])
ts_port = self.service_plugin._get_port_details(
context._plugin_context, ts['port_id'])
# Send RPC message to both the source port host and
# tap service(destination) port host
rpc_msg = {'tap_flow': tf,
'port_mac': port_mac,
'taas_id': taas_id,
'port': port}
'port': port,
'tap_service_port': ts_port}
self.agent_rpc.create_tap_flow(context._plugin_context, rpc_msg, host)
return
@ -147,12 +219,59 @@ class TaasRpcDriver(service_drivers.TaasBaseDriver):
tf['source_port'])
host = port['binding:host_id']
port_mac = port['mac_address']
# Extract the tap-service port
ts = self.service_plugin.get_tap_service(context._plugin_context,
tf['tap_service_id'])
ts_port = self.service_plugin._get_port_details(
context._plugin_context, ts['port_id'])
src_vlans_list = []
vlan_filter_list = []
if port.get(portbindings.VNIC_TYPE) == portbindings.VNIC_DIRECT:
# Get all the tap Flows that are associated with the Tap service
active_tfs = self.service_plugin.get_tap_flows(
context._plugin_context,
filters={'tap_service_id': [tf['tap_service_id']],
'status': [constants.ACTIVE]},
fields=['source_port', 'vlan_filter'])
for tap_flow in active_tfs:
source_port = self.service_plugin._get_port_details(
context._plugin_context, tap_flow['source_port'])
LOG.debug("taas: active TF's source_port %(source_port)s",
{'source_port': source_port})
src_vlans = ""
if source_port.get(portbindings.VIF_DETAILS):
src_vlans = source_port[portbindings.VIF_DETAILS].get(
portbindings.VIF_DETAILS_VLAN)
# If no VLAN filter configured on source port,
# then include all vlans
if not src_vlans or src_vlans == '0':
src_vlans = taas_consts.VLAN_RANGE
src_vlans_list.append(src_vlans)
vlan_filter = tap_flow['vlan_filter']
# If no VLAN filter configured for tap-flow,
# then include all vlans
if not vlan_filter:
vlan_filter = taas_consts.VLAN_RANGE
vlan_filter_list.append(vlan_filter)
# Send RPC message to both the source port host and
# tap service(destination) port host
rpc_msg = {'tap_flow': tf,
'port_mac': port_mac,
'taas_id': taas_id,
'port': port}
'port': port,
'tap_service_port': ts_port,
'source_vlans_list': src_vlans_list,
'vlan_filter_list': vlan_filter_list}
self.agent_rpc.delete_tap_flow(context._plugin_context, rpc_msg, host)
return

10
neutron_taas/services/taas/taas_plugin.py

@ -22,7 +22,7 @@ from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import exceptions as n_exc
from neutron_taas.common import constants
from neutron_taas.common import constants as taas_consts
from neutron_taas.db import taas_db
from neutron_taas.extensions import taas as taas_ex
from neutron_taas.services.taas.service_drivers import (service_driver_context
@ -43,14 +43,16 @@ def add_provider_configuration(type_manager, service_type):
@registry.has_registry_receivers
class TaasPlugin(taas_db.Taas_db_Mixin):
supported_extension_aliases = ["taas"]
supported_extension_aliases = ["taas",
"taas-vlan-filter"]
path_prefix = "/taas"
def __init__(self):
LOG.debug("TAAS PLUGIN INITIALIZED")
self.service_type_manager = st_db.ServiceTypeManager.get_instance()
add_provider_configuration(self.service_type_manager, constants.TAAS)
add_provider_configuration(self.service_type_manager,
taas_consts.TAAS)
self._load_drivers()
self.driver = self._get_driver_for_provider(self.default_provider)
@ -59,7 +61,7 @@ class TaasPlugin(taas_db.Taas_db_Mixin):
def _load_drivers(self):
"""Loads plugin-drivers specified in configuration."""
self.drivers, self.default_provider = service_base.load_drivers(
'TAAS', self)
taas_consts.TAAS, self)
def _get_driver_for_provider(self, provider):
if provider in self.drivers:

12
neutron_taas/taas_client/tapflow.py

@ -1,3 +1,4 @@
# Copyright (C) 2018 AT&T
# Copyright 2015 NEC Corporation
# All Rights Reserved
#
@ -47,7 +48,8 @@ class ListTapFlow(extension.ClientExtensionList, TapFlow):
"""List tap flows."""
shell_command = 'tap-flow-list'
list_columns = ['id', 'name', 'source_port', 'tap_service_id', 'status']
list_columns = ['id', 'name', 'source_port', 'tap_service_id', 'status',
'vlan_filter']
pagination_support = True
sorting_support = True
@ -77,6 +79,11 @@ class CreateTapFlow(extension.ClientExtensionCreate, TapFlow):
choices=['IN', 'OUT', 'BOTH'],
type=utils.convert_to_uppercase,
help=_('Direction of the Tap flow.'))
parser.add_argument(
'--vlan-filter',
required=False,
metavar="VLAN_FILTER",
help=_('VLAN Ids to be mirrored in the form of range string.'))
def args2body(self, parsed_args):
client = self.get_client()
@ -88,7 +95,8 @@ class CreateTapFlow(extension.ClientExtensionCreate, TapFlow):
parsed_args.tap_service)
body = {'source_port': source_port,
'tap_service_id': tap_service_id}
neutronv20.update_dict(parsed_args, body, ['tenant_id', 'direction'])
neutronv20.update_dict(parsed_args, body, ['tenant_id', 'direction',