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
This commit is contained in:
parent
92263b67a2
commit
2c83eb26a5
@ -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
Normal file
28
bin/i40e_sysfs_command
Normal file
@ -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
|
@ -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
|
||||
|
||||
|
@ -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,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
Normal file
9
etc/neutron/rootwrap.d/taas-i40e-sysfs.filters
Normal file
@ -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), .*
|
@ -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
|
||||
|
@ -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
Normal file
49
neutron_taas/common/utils.py
Normal file
@ -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])
|
@ -1 +1 @@
|
||||
fddbdec8711a
|
||||
ccbcc559d175
|
||||
|
@ -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))
|
@ -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
Normal file
59
neutron_taas/extensions/vlan_filter.py
Normal file
@ -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 []
|
@ -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))
|
||||
|
||||
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 TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
|
||||
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)
|
@ -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()
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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
Normal file
364
neutron_taas/services/taas/drivers/linux/sriov_nic_taas.py
Normal file
@ -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
Normal file
268
neutron_taas/services/taas/drivers/linux/sriov_nic_utils.py
Normal file
@ -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}
|
@ -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"""
|
||||
|
||||
|
@ -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(
|
||||
portbinding |