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},
|
'required_by_policy': True, 'is_visible': True},
|
||||||
'direction': {'allow_post': True, 'allow_put': False,
|
'direction': {'allow_post': True, 'allow_put': False,
|
||||||
'validate': {'type:values': direction_enum},
|
'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']
|
direction_enum = ['IN', 'OUT', 'BOTH']
|
||||||
@ -172,7 +175,8 @@ extension
|
|||||||
"name": "flow1",
|
"name": "flow1",
|
||||||
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
|
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
|
||||||
"tap_service_id": "69bd12b2-0e13-45ec-9045-b674fd9f0468",
|
"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",
|
"name": "flow1",
|
||||||
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
|
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
|
||||||
"tap_service_id": "69bd12b2-0e13-45ec-9045-b674fd9f0468",
|
"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",
|
"name": "flow1",
|
||||||
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
|
"source_port": "775a58bb-e2c6-4529-a918-2f019169b5b1",
|
||||||
"tap_service_id": "c352f537-ad49-48eb-ab05-1c6b8cb900ff",
|
"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,n-cond,n-cpu,n-crt,n-sch,placement-api
|
||||||
OVERRIDE_ENABLED_SERVICES+=,n-api-meta
|
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+=,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
|
OVERRIDE_ENABLED_SERVICES+=,tempest,dstat
|
||||||
export OVERRIDE_ENABLED_SERVICES
|
export OVERRIDE_ENABLED_SERVICES
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ function configure_taas_plugin {
|
|||||||
cp $TAAS_PLUGIN_PATH/etc/taas_plugin.ini $TAAS_PLUGIN_CONF_FILE
|
cp $TAAS_PLUGIN_PATH/etc/taas_plugin.ini $TAAS_PLUGIN_CONF_FILE
|
||||||
neutron_server_config_add $TAAS_PLUGIN_CONF_FILE
|
neutron_server_config_add $TAAS_PLUGIN_CONF_FILE
|
||||||
neutron_service_plugin_class_add taas
|
neutron_service_plugin_class_add taas
|
||||||
|
neutron_deploy_rootwrap_filters $TAAS_PLUGIN_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_service_enabled taas; then
|
if is_service_enabled taas; then
|
||||||
|
@ -2,5 +2,3 @@
|
|||||||
ABSOLUTE_PATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)
|
ABSOLUTE_PATH=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)
|
||||||
TAAS_PLUGIN_PATH=$ABSOLUTE_PATH/..
|
TAAS_PLUGIN_PATH=$ABSOLUTE_PATH/..
|
||||||
TAAS_PLUGIN_CONF_FILE="/etc/neutron/taas_plugin.ini"
|
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
|
munch==2.2.0
|
||||||
netaddr==0.7.19
|
netaddr==0.7.19
|
||||||
netifaces==0.10.6
|
netifaces==0.10.6
|
||||||
neutron-lib==1.20.0
|
neutron-lib==1.25.0
|
||||||
|
neutron==14.0.0.0b3
|
||||||
openstacksdk==0.12.0
|
openstacksdk==0.12.0
|
||||||
os-client-config==1.29.0
|
os-client-config==1.29.0
|
||||||
os-service-types==1.2.0
|
os-service-types==1.2.0
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# Copyright (C) 2018 AT&T
|
||||||
# Copyright (C) 2015 Midokura SARL.
|
# Copyright (C) 2015 Midokura SARL.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
@ -14,3 +15,6 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
TAAS = 'TAAS'
|
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 Ericsson AB
|
||||||
# Copyright (c) 2015 Gigamon
|
# Copyright (c) 2015 Gigamon
|
||||||
#
|
#
|
||||||
@ -60,6 +61,7 @@ class TapFlow(model_base.BASEV2, model_base.HasId,
|
|||||||
nullable=False)
|
nullable=False)
|
||||||
status = sa.Column(sa.String(16), nullable=False,
|
status = sa.Column(sa.String(16), nullable=False,
|
||||||
server_default=constants.ACTIVE)
|
server_default=constants.ACTIVE)
|
||||||
|
vlan_filter = sa.Column(sa.String(1024), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class TapIdAssociation(model_base.BASEV2):
|
class TapIdAssociation(model_base.BASEV2):
|
||||||
@ -128,7 +130,8 @@ class Taas_db_Mixin(taas.TaasPluginBase):
|
|||||||
'description': tap_flow['description'],
|
'description': tap_flow['description'],
|
||||||
'source_port': tap_flow['source_port'],
|
'source_port': tap_flow['source_port'],
|
||||||
'direction': tap_flow['direction'],
|
'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)
|
return db_utils.resource_fields(res, fields)
|
||||||
|
|
||||||
@ -209,6 +212,7 @@ class Taas_db_Mixin(taas.TaasPluginBase):
|
|||||||
source_port=t_f['source_port'],
|
source_port=t_f['source_port'],
|
||||||
direction=t_f['direction'],
|
direction=t_f['direction'],
|
||||||
status=constants.ACTIVE,
|
status=constants.ACTIVE,
|
||||||
|
vlan_filter=t_f['vlan_filter'],
|
||||||
)
|
)
|
||||||
context.session.add(tap_flow_db)
|
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 Ericsson AB
|
||||||
# Copyright (c) 2015 Gigamon
|
# Copyright (c) 2015 Gigamon
|
||||||
#
|
#
|
||||||
@ -15,35 +16,54 @@
|
|||||||
|
|
||||||
|
|
||||||
from neutron import manager
|
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.common import topics
|
||||||
from neutron_taas.services.taas.agents import taas_agent_api as api
|
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 neutron_lib import rpc as n_rpc
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
import oslo_messaging as messaging
|
||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TaasOvsPluginApi(api.TaasPluginApiMixin):
|
class TaasPluginApi(api.TaasPluginApiMixin):
|
||||||
# Currently there are not any APIs from the the agent towards plugin
|
|
||||||
|
|
||||||
def __init__(self, topic, host):
|
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
|
return
|
||||||
|
|
||||||
|
|
||||||
class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
|
class TaasAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
|
||||||
|
|
||||||
def __init__(self, conf, driver_type):
|
def __init__(self, conf, driver_type):
|
||||||
LOG.debug("TaaS OVS Agent initialize called")
|
|
||||||
|
LOG.debug("TaaS Agent initialize called")
|
||||||
|
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.driver_type = driver_type
|
self.driver_type = driver_type
|
||||||
|
|
||||||
super(TaasOvsAgentRpcCallback, self).__init__()
|
super(TaasAgentRpcCallback, self).__init__()
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.taas_driver = manager.NeutronManager.load_class_for_provider(
|
self.taas_driver = manager.NeutronManager.load_class_for_provider(
|
||||||
@ -52,7 +72,7 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
|
|||||||
self.taas_driver.initialize()
|
self.taas_driver.initialize()
|
||||||
|
|
||||||
self._taas_rpc_setup()
|
self._taas_rpc_setup()
|
||||||
TaasOvsAgentService(self).start()
|
TaasAgentService(self).start(self.taas_plugin_rpc, self.conf.host)
|
||||||
|
|
||||||
def consume_api(self, agent_api):
|
def consume_api(self, agent_api):
|
||||||
self.agent_api = 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):
|
def delete_tap_flow(self, context, tap_flow_msg, host):
|
||||||
if host != self.conf.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
|
return
|
||||||
LOG.debug("In RPC Call for Delete Tap Flow: MSG=%s" % tap_flow_msg)
|
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):
|
def _taas_rpc_setup(self):
|
||||||
# setup RPC to msg taas plugin
|
# setup RPC to msg taas plugin
|
||||||
self.taas_plugin_rpc = TaasOvsPluginApi(
|
self.taas_plugin_rpc = TaasPluginApi(
|
||||||
topics.TAAS_PLUGIN, self.conf.host)
|
topics.TAAS_PLUGIN, self.conf.host)
|
||||||
|
|
||||||
endpoints = [self]
|
endpoints = [self]
|
||||||
@ -123,22 +146,31 @@ class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):
|
|||||||
conn.consume_in_threads()
|
conn.consume_in_threads()
|
||||||
|
|
||||||
def periodic_tasks(self):
|
def periodic_tasks(self):
|
||||||
#
|
return self._invoke_driver_for_plugin_api(
|
||||||
# Regenerate the flow in br-tun's TAAS_SEND_FLOOD table
|
context=None,
|
||||||
# to ensure all existing tunnel ports are included.
|
args=None,
|
||||||
#
|
func_name='periodic_tasks')
|
||||||
self.taas_driver.update_tunnel_flood_flow()
|
|
||||||
|
def get_driver_type(self):
|
||||||
|
return self.driver_type
|
||||||
|
|
||||||
|
|
||||||
class TaasOvsAgentService(service.Service):
|
class TaasAgentService(service.Service):
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
super(TaasOvsAgentService, self).__init__()
|
super(TaasAgentService, self).__init__()
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
def start(self):
|
def start(self, taas_plugin_rpc, host):
|
||||||
super(TaasOvsAgentService, self).start()
|
super(TaasAgentService, self).start()
|
||||||
|
|
||||||
|
if self.driver.get_driver_type() == \
|
||||||
|
taas_ovs_consts.EXTENSION_DRIVER_TYPE:
|
||||||
self.tg.add_timer(
|
self.tg.add_timer(
|
||||||
int(cfg.CONF.taas_agent_periodic_interval),
|
int(cfg.CONF.taas_agent_periodic_interval),
|
||||||
self.driver.periodic_tasks,
|
self.driver.periodic_tasks,
|
||||||
None
|
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_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_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
OPTS = [
|
OPTS = [
|
||||||
cfg.IntOpt(
|
cfg.IntOpt(
|
||||||
'taas_agent_periodic_interval',
|
'taas_agent_periodic_interval',
|
||||||
@ -71,7 +70,7 @@ class TaasAgentExtension(l2_extension.L2AgentExtension):
|
|||||||
|
|
||||||
def initialize(self, connection, driver_type):
|
def initialize(self, connection, driver_type):
|
||||||
"""Initialize agent extension."""
|
"""Initialize agent extension."""
|
||||||
self.taas_agent = taas_ovs_agent.TaasOvsAgentRpcCallback(
|
self.taas_agent = taas_agent.TaasAgentRpcCallback(
|
||||||
cfg.CONF, driver_type)
|
cfg.CONF, driver_type)
|
||||||
self.taas_agent.consume_api(self.agent_api)
|
self.taas_agent.consume_api(self.agent_api)
|
||||||
self.taas_agent.initialize()
|
self.taas_agent.initialize()
|
||||||
|
@ -26,3 +26,6 @@ TAAS_DST_CHECK = 36
|
|||||||
TAAS_SRC_CHECK = 37
|
TAAS_SRC_CHECK = 37
|
||||||
TAAS_DST_RESPOND = 38
|
TAAS_DST_RESPOND = 38
|
||||||
TAAS_SRC_RESPOND = 39
|
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
|
# Setup key-value manager for ingress BCMC flows
|
||||||
self.bcmc_kvm = taas_ovs_utils.key_value_mgr(4096)
|
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):
|
def setup_ovs_bridges(self):
|
||||||
#
|
#
|
||||||
# br-int : Integration Bridge
|
# 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__)
|
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):
|
class TaasAgentApi(object):
|
||||||
"""RPC calls to agent APIs"""
|
"""RPC calls to agent APIs"""
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# Copyright (C) 2018 AT&T
|
||||||
# Copyright (C) 2016 Midokura SARL.
|
# Copyright (C) 2016 Midokura SARL.
|
||||||
# Copyright (C) 2015 Ericsson AB
|
# Copyright (C) 2015 Ericsson AB
|
||||||
# Copyright (c) 2015 Gigamon
|
# Copyright (c) 2015 Gigamon
|
||||||
@ -14,25 +15,89 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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 exceptions as n_exc
|
||||||
from neutron_lib import rpc as n_rpc
|
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.common import topics
|
||||||
from neutron_taas.services.taas import service_drivers
|
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.service_drivers import taas_agent_api
|
||||||
|
from neutron_taas.services.taas.taas_plugin import TaasPlugin
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class TaasRpcDriver(service_drivers.TaasBaseDriver):
|
||||||
"""Taas Rpc Service Driver class"""
|
"""Taas Rpc Service Driver class"""
|
||||||
|
|
||||||
def __init__(self, service_plugin):
|
def __init__(self, service_plugin):
|
||||||
LOG.debug("Loading TaasRpcDriver.")
|
LOG.debug("Loading TaasRpcDriver.")
|
||||||
super(TaasRpcDriver, self).__init__(service_plugin)
|
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 = n_rpc.Connection()
|
||||||
self.conn.create_consumer(topics.TAAS_PLUGIN,
|
self.conn.create_consumer(topics.TAAS_PLUGIN,
|
||||||
self.endpoints, fanout=False)
|
self.endpoints, fanout=False)
|
||||||
@ -125,12 +190,19 @@ class TaasRpcDriver(service_drivers.TaasBaseDriver):
|
|||||||
tf['source_port'])
|
tf['source_port'])
|
||||||
host = port['binding:host_id']
|
host = port['binding:host_id']
|
||||||
port_mac = port['mac_address']
|
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
|
# Send RPC message to both the source port host and
|
||||||
# tap service(destination) port host
|
# tap service(destination) port host
|
||||||
rpc_msg = {'tap_flow': tf,
|
rpc_msg = {'tap_flow': tf,
|
||||||
'port_mac': port_mac,
|
'port_mac': port_mac,
|
||||||
'taas_id': taas_id,
|
'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)
|
self.agent_rpc.create_tap_flow(context._plugin_context, rpc_msg, host)
|
||||||
return
|
return
|
||||||
@ -147,12 +219,59 @@ class TaasRpcDriver(service_drivers.TaasBaseDriver):
|
|||||||
tf['source_port'])
|
tf['source_port'])
|
||||||
host = port['binding:host_id']
|
host = port['binding:host_id']
|
||||||
port_mac = port['mac_address']
|
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
|
# Send RPC message to both the source port host and
|
||||||
# tap service(destination) port host
|
# tap service(destination) port host
|
||||||
rpc_msg = {'tap_flow': tf,
|
rpc_msg = {'tap_flow': tf,
|
||||||
'port_mac': port_mac,
|
'port_mac': port_mac,
|
||||||
'taas_id': taas_id,
|
'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)
|
self.agent_rpc.delete_tap_flow(context._plugin_context, rpc_msg, host)
|
||||||
return
|
return
|
||||||
|
@ -22,7 +22,7 @@ from neutron_lib.callbacks import registry
|
|||||||
from neutron_lib.callbacks import resources
|
from neutron_lib.callbacks import resources
|
||||||
from neutron_lib import exceptions as n_exc
|
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.db import taas_db
|
||||||
from neutron_taas.extensions import taas as taas_ex
|
from neutron_taas.extensions import taas as taas_ex
|
||||||
from neutron_taas.services.taas.service_drivers import (service_driver_context
|
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
|
@registry.has_registry_receivers
|
||||||
class TaasPlugin(taas_db.Taas_db_Mixin):
|
class TaasPlugin(taas_db.Taas_db_Mixin):
|
||||||
|
|
||||||
supported_extension_aliases = ["taas"]
|
supported_extension_aliases = ["taas",
|
||||||
|
"taas-vlan-filter"]
|
||||||
path_prefix = "/taas"
|
path_prefix = "/taas"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
LOG.debug("TAAS PLUGIN INITIALIZED")
|
LOG.debug("TAAS PLUGIN INITIALIZED")
|
||||||
self.service_type_manager = st_db.ServiceTypeManager.get_instance()
|
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._load_drivers()
|
||||||
self.driver = self._get_driver_for_provider(self.default_provider)
|
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):
|
def _load_drivers(self):
|
||||||
"""Loads plugin-drivers specified in configuration."""
|
"""Loads plugin-drivers specified in configuration."""
|
||||||
self.drivers, self.default_provider = service_base.load_drivers(
|
self.drivers, self.default_provider = service_base.load_drivers(
|
||||||
'TAAS', self)
|
taas_consts.TAAS, self)
|
||||||
|
|
||||||
def _get_driver_for_provider(self, provider):
|
def _get_driver_for_provider(self, provider):
|
||||||
if provider in self.drivers:
|
if provider in self.drivers:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# Copyright (C) 2018 AT&T
|
||||||
# Copyright 2015 NEC Corporation
|
# Copyright 2015 NEC Corporation
|
||||||
# All Rights Reserved
|
# All Rights Reserved
|
||||||
#
|
#
|
||||||
@ -47,7 +48,8 @@ class ListTapFlow(extension.ClientExtensionList, TapFlow):
|
|||||||
"""List tap flows."""
|
"""List tap flows."""
|
||||||
|
|
||||||
shell_command = 'tap-flow-list'
|
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
|
pagination_support = True
|
||||||
sorting_support = True
|
sorting_support = True
|
||||||
|
|
||||||
@ -77,6 +79,11 @@ class CreateTapFlow(extension.ClientExtensionCreate, TapFlow):
|
|||||||
choices=['IN', 'OUT', 'BOTH'],
|
choices=['IN', 'OUT', 'BOTH'],
|
||||||
type=utils.convert_to_uppercase,
|
type=utils.convert_to_uppercase,
|
||||||
help=_('Direction of the Tap flow.'))
|
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):
|
def args2body(self, parsed_args):
|
||||||
client = self.get_client()
|
client = self.get_client()
|
||||||
@ -88,7 +95,8 @@ class CreateTapFlow(extension.ClientExtensionCreate, TapFlow):
|
|||||||
parsed_args.tap_service)
|
parsed_args.tap_service)
|
||||||
body = {'source_port': source_port,
|
body = {'source_port': source_port,
|
||||||
'tap_service_id': tap_service_id}
|
'tap_service_id': tap_service_id}
|
||||||
neutronv20.update_dict(parsed_args, body, ['tenant_id', 'direction'])
|
neutronv20.update_dict(parsed_args, body, ['tenant_id', 'direction',
|
||||||
|
'vlan_filter'])
|
||||||
_updatable_args2body(parsed_args, body)
|
_updatable_args2body(parsed_args, body)
|
||||||
return {self.resource: body}
|
return {self.resource: body}
|
||||||
|
|
||||||
|
18
neutron_taas/tests/base.py
Normal file
18
neutron_taas/tests/base.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 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 oslotest import base
|
||||||
|
|
||||||
|
|
||||||
|
class TaasTestCase(base.BaseTestCase):
|
||||||
|
"""Test case base class for all unit tests."""
|
@ -46,14 +46,16 @@ class TaaSDbTestCase(testlib_api.SqlTestCase):
|
|||||||
"port_id": port_id}}
|
"port_id": port_id}}
|
||||||
|
|
||||||
def _get_tap_flow_data(self, tap_service_id, name='tf-1',
|
def _get_tap_flow_data(self, tap_service_id, name='tf-1',
|
||||||
direction='BOTH', source_port=None):
|
direction='BOTH', source_port=None,
|
||||||
|
vlan_filter=None):
|
||||||
source_port = source_port or _uuid()
|
source_port = source_port or _uuid()
|
||||||
return {"tap_flow": {"name": name,
|
return {"tap_flow": {"name": name,
|
||||||
"tenant_id": self.tenant_id,
|
"tenant_id": self.tenant_id,
|
||||||
"description": "test tap flow",
|
"description": "test tap flow",
|
||||||
"tap_service_id": tap_service_id,
|
"tap_service_id": tap_service_id,
|
||||||
"source_port": source_port,
|
"source_port": source_port,
|
||||||
"direction": direction}}
|
"direction": direction,
|
||||||
|
"vlan_filter": vlan_filter}}
|
||||||
|
|
||||||
def _get_tap_service(self, tap_service_id):
|
def _get_tap_service(self, tap_service_id):
|
||||||
"""Helper method to retrieve tap service."""
|
"""Helper method to retrieve tap service."""
|
||||||
@ -180,6 +182,25 @@ class TaaSDbTestCase(testlib_api.SqlTestCase):
|
|||||||
self.assertEqual(tf_direction, tf['direction'])
|
self.assertEqual(tf_direction, tf['direction'])
|
||||||
self.assertEqual(tf_source_port, tf['source_port'])
|
self.assertEqual(tf_source_port, tf['source_port'])
|
||||||
|
|
||||||
|
def test_tap_flow_create_with_vlan_filter(self):
|
||||||
|
"""Test to create a tap flow (with vlan_filter) in the database."""
|
||||||
|
ts_data = self._get_tap_service_data()
|
||||||
|
ts = self._create_tap_service(ts_data)
|
||||||
|
tf_name = 'test-tap-flow'
|
||||||
|
tf_direction = 'IN'
|
||||||
|
tf_source_port = _uuid()
|
||||||
|
tf_vlan_filter = '9-18,27,36-45'
|
||||||
|
tf_data = self._get_tap_flow_data(tap_service_id=ts['id'],
|
||||||
|
name=tf_name,
|
||||||
|
source_port=tf_source_port,
|
||||||
|
direction=tf_direction,
|
||||||
|
vlan_filter=tf_vlan_filter)
|
||||||
|
tf = self._create_tap_flow(tf_data)
|
||||||
|
self.assertEqual(tf_name, tf['name'])
|
||||||
|
self.assertEqual(tf_direction, tf['direction'])
|
||||||
|
self.assertEqual(tf_source_port, tf['source_port'])
|
||||||
|
self.assertEqual(tf_vlan_filter, tf['vlan_filter'])
|
||||||
|
|
||||||
def test_tap_flow_list(self):
|
def test_tap_flow_list(self):
|
||||||
"""Test to retrieve all tap flows from the database."""
|
"""Test to retrieve all tap flows from the database."""
|
||||||
ts_data = self._get_tap_service_data()
|
ts_data = self._get_tap_service_data()
|
||||||
|
@ -72,6 +72,9 @@ class TaasExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
|
|||||||
def test_delete_tap_service(self):
|
def test_delete_tap_service(self):
|
||||||
self._test_entity_delete('tap_service')
|
self._test_entity_delete('tap_service')
|
||||||
|
|
||||||
|
def _get_expected_tap_flow(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
def test_create_tap_flow(self):
|
def test_create_tap_flow(self):
|
||||||
tenant_id = _uuid()
|
tenant_id = _uuid()
|
||||||
tap_flow_data = {
|
tap_flow_data = {
|
||||||
@ -84,7 +87,8 @@ class TaasExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
|
|||||||
'project_id': tenant_id,
|
'project_id': tenant_id,
|
||||||
}
|
}
|
||||||
data = {'tap_flow': tap_flow_data}
|
data = {'tap_flow': tap_flow_data}
|
||||||
expected_ret_val = copy.copy(data['tap_flow'])
|
expected_data = self._get_expected_tap_flow(data)
|
||||||
|
expected_ret_val = copy.copy(expected_data['tap_flow'])
|
||||||
expected_ret_val.update({'id': _uuid()})
|
expected_ret_val.update({'id': _uuid()})
|
||||||
instance = self.plugin.return_value
|
instance = self.plugin.return_value
|
||||||
instance.create_tap_flow.return_value = expected_ret_val
|
instance.create_tap_flow.return_value = expected_ret_val
|
||||||
@ -94,7 +98,7 @@ class TaasExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
|
|||||||
content_type='application/%s' % self.fmt)
|
content_type='application/%s' % self.fmt)
|
||||||
instance.create_tap_flow.assert_called_with(
|
instance.create_tap_flow.assert_called_with(
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
tap_flow=data)
|
tap_flow=expected_data)
|
||||||
self.assertEqual(exc.HTTPCreated.code, res.status_int)
|
self.assertEqual(exc.HTTPCreated.code, res.status_int)
|
||||||
res = self.deserialize(res)
|
res = self.deserialize(res)
|
||||||
self.assertIn('tap_flow', res)
|
self.assertIn('tap_flow', res)
|
||||||
|
105
neutron_taas/tests/unit/extensions/test_vlan_filter.py
Normal file
105
neutron_taas/tests/unit/extensions/test_vlan_filter.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Copyright (C) 2018 AT&T
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from neutron_taas.common import constants as taas_consts
|
||||||
|
from neutron_taas.extensions import taas as taas_ext
|
||||||
|
from neutron_taas.extensions import vlan_filter as vlan_filter_ext
|
||||||
|
from neutron_taas.tests.unit.extensions import test_taas as test_taas_ext
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
from webob import exc
|
||||||
|
|
||||||
|
from neutron.tests.unit.api.v2 import test_base as test_api_v2
|
||||||
|
import webtest
|
||||||
|
|
||||||
|
|
||||||
|
_uuid = uuidutils.generate_uuid
|
||||||
|
_get_path = test_api_v2._get_path
|
||||||
|
|
||||||
|
|
||||||
|
class VlanFilterExtensionTestCase(test_taas_ext.TaasExtensionTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(test_taas_ext.TaasExtensionTestCase, self).setUp()
|
||||||
|
|
||||||
|
attr_map = taas_ext.RESOURCE_ATTRIBUTE_MAP
|
||||||
|
attr_map['tap_flows'].update(
|
||||||
|
vlan_filter_ext.EXTENDED_ATTRIBUTES_2_0['tap_flows'])
|
||||||
|
self.setup_extension(
|
||||||
|
'neutron_taas.extensions.taas.TaasPluginBase',
|
||||||
|
'TAAS',
|
||||||
|
taas_ext.Taas,
|
||||||
|
'taas',
|
||||||
|
plural_mappings={}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_expected_tap_flow(self, data):
|
||||||
|
ret = super(VlanFilterExtensionTestCase,
|
||||||
|
self)._get_expected_tap_flow(data)
|
||||||
|
ret['tap_flow'].update(
|
||||||
|
vlan_filter=data['tap_flow'].get('vlan_filter', None))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def test_create_tap_flow_with_vlan_filter(self):
|
||||||
|
tenant_id = _uuid()
|
||||||
|
tap_flow_data = {
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'name': 'MyTapFlow',
|
||||||
|
'description': 'This is my tap flow',
|
||||||
|
'direction': 'BOTH',
|
||||||
|
'tap_service_id': _uuid(),
|
||||||
|
'source_port': _uuid(),
|
||||||
|
'project_id': tenant_id,
|
||||||
|
'vlan_filter': taas_consts.VLAN_RANGE,
|
||||||
|
}
|
||||||
|
data = {'tap_flow': tap_flow_data}
|
||||||
|
expected_data = self._get_expected_tap_flow(data)
|
||||||
|
expected_ret_val = copy.copy(expected_data['tap_flow'])
|
||||||
|
expected_ret_val.update({'id': _uuid()})
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.create_tap_flow.return_value = expected_ret_val
|
||||||
|
|
||||||
|
res = self.api.post(
|
||||||
|
_get_path(test_taas_ext.TAP_FLOW_PATH, fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
instance.create_tap_flow.assert_called_with(
|
||||||
|
mock.ANY,
|
||||||
|
tap_flow=expected_data)
|
||||||
|
self.assertEqual(exc.HTTPCreated.code, res.status_int)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertIn('tap_flow', res)
|
||||||
|
self.assertEqual(expected_ret_val, res['tap_flow'])
|
||||||
|
|
||||||
|
def test_create_tap_flow_invalid_vlan_filter_value(self):
|
||||||
|
tenant_id = _uuid()
|
||||||
|
tap_flow_data = {
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'name': 'MyTapFlow',
|
||||||
|
'description': 'This is my tap flow',
|
||||||
|
'direction': 'BOTH',
|
||||||
|
'tap_service_id': _uuid(),
|
||||||
|
'source_port': _uuid(),
|
||||||
|
'project_id': tenant_id,
|
||||||
|
'vlan_filter': '10-25,',
|
||||||
|
}
|
||||||
|
data = {'tap_flow': tap_flow_data}
|
||||||
|
self.assertRaises(
|
||||||
|
webtest.app.AppError,
|
||||||
|
self.api.post,
|
||||||
|
_get_path(test_taas_ext.TAP_FLOW_PATH, fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt)
|
@ -0,0 +1,224 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from neutron_taas.services.taas.drivers.linux import sriov_nic_exceptions \
|
||||||
|
as taas_exc
|
||||||
|
from neutron_taas.services.taas.drivers.linux import sriov_nic_taas
|
||||||
|
from neutron_taas.tests import base
|
||||||
|
|
||||||
|
FAKE_PORT_PARAMS = {
|
||||||
|
'mac': '52:54:00:12:35:02', 'pci_slot': 3, 'vf_index': '89',
|
||||||
|
'pf_device': 'net_enp0s3_52_54_00_12_35_02', 'src_vlans': '20'}
|
||||||
|
|
||||||
|
FAKE_TAP_SERVICE = {'port': {
|
||||||
|
'id': 'fake_1', 'mac_address': "52:54:00:12:35:02",
|
||||||
|
'binding:profile': {'pci_slot': 3},
|
||||||
|
'binding:vif_details': {'vlan': '20'}}}
|
||||||
|
|
||||||
|
FAKE_TAP_FLOW = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'ts_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'source_vlans_list': ['4-6', '8-10', '15-18,20'],
|
||||||
|
'vlan_filter_list': '1-5,9,18,20,27-30,4000-4095',
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}}
|
||||||
|
|
||||||
|
|
||||||
|
class TestSriovNicTaas(base.TaasTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestSriovNicTaas, self).setUp()
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_service(self, mock_sriov_utils):
|
||||||
|
tap_service = FAKE_TAP_SERVICE
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
return_value = FAKE_PORT_PARAMS
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
obj.create_tap_service(tap_service)
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
assert_called_once_with(tap_service['port'])
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_service_no_pf_device_and_vf_index(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_service = FAKE_TAP_SERVICE
|
||||||
|
temp_fake_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
temp_fake_port_params['pf_device'] = None
|
||||||
|
temp_fake_port_params['vf_index'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
return_value = FAKE_PORT_PARAMS
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertIsNone(obj.create_tap_service(tap_service))
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
assert_called_once_with(tap_service['port'])
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_delete_tap_service(self, mock_sriov_utils):
|
||||||
|
tap_service = FAKE_TAP_SERVICE
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
return_value = FAKE_PORT_PARAMS
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
obj.create_tap_service(tap_service)
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
assert_called_once_with(tap_service['port'])
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_delete_tap_service_no_pf_device_and_vf_index(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_service = FAKE_TAP_SERVICE
|
||||||
|
temp_fake_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
temp_fake_port_params['pf_device'] = None
|
||||||
|
temp_fake_port_params['vf_index'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
return_value = FAKE_PORT_PARAMS
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertIsNone(obj.create_tap_service(tap_service))
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
assert_called_once_with(tap_service['port'])
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_flow(self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'vlan_filter_list': '1-5,9,18,20,27-30,4000-4095',
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}}
|
||||||
|
src_port_params = ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
obj.create_tap_flow(tap_flow)
|
||||||
|
mock_sriov_utils.SriovNicUtils().execute_sysfs_command.\
|
||||||
|
assert_called_once_with('add', ts_port_params, src_port_params,
|
||||||
|
'20', False, 'IN')
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_flow_no_vlan_filter_on_source_and_probe(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}}
|
||||||
|
src_port_params = ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params['vlan_filter'] = None
|
||||||
|
src_port_params['src_vlans'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
obj.create_tap_flow(tap_flow)
|
||||||
|
mock_sriov_utils.SriovNicUtils().execute_sysfs_command.\
|
||||||
|
assert_called_once_with('add', ts_port_params, src_port_params,
|
||||||
|
'20', False, 'IN')
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_flow_no_source_pci_slot(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': 20}}
|
||||||
|
src_port_params = ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
src_port_params['pci_slot'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertRaises(
|
||||||
|
taas_exc.PciSlotNotFound, obj.create_tap_flow, tap_flow)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_flow_no_ts_pci_slot(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': 20}}
|
||||||
|
src_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params['pci_slot'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertRaises(
|
||||||
|
taas_exc.PciSlotNotFound, obj.create_tap_flow, tap_flow)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_create_tap_flow_different_pf_devices(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': 20}}
|
||||||
|
src_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params['pf_device'] = 'net_enp0s3_52_54_00_12_35_02'
|
||||||
|
src_port_params['pf_device'] = 'net_enp0s8_52_54_00_12_35_01'
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertIsNotNone(obj.create_tap_flow, tap_flow)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_delete_tap_flow(self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'source_vlans_list': ['4-6', '8-10', '15-18,20'],
|
||||||
|
'vlan_filter_list': ['1-5,9,18,20,27-30,4000-4095'],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': '20'}}
|
||||||
|
src_port_params = ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertIsNone(obj.delete_tap_flow(tap_flow))
|
||||||
|
self.assertEqual(2, mock_sriov_utils.SriovNicUtils().
|
||||||
|
execute_sysfs_command.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_delete_tap_flow_no_source_pci_slot(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'source_vlans_list': [4, 5, 9],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': 20}}
|
||||||
|
src_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
src_port_params['pci_slot'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertRaises(taas_exc.PciSlotNotFound, obj.delete_tap_flow,
|
||||||
|
tap_flow)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_taas, 'sriov_utils')
|
||||||
|
def test_delete_tap_flow_no_ts_pci_slot(
|
||||||
|
self, mock_sriov_utils):
|
||||||
|
tap_flow = {'port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_service_port': FAKE_TAP_SERVICE['port'],
|
||||||
|
'tap_flow': {'direction': 'IN', 'vlan_filter': 20}}
|
||||||
|
src_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params = copy.deepcopy(FAKE_PORT_PARAMS)
|
||||||
|
ts_port_params['pci_slot'] = None
|
||||||
|
mock_sriov_utils.SriovNicUtils().get_sriov_port_params.\
|
||||||
|
side_effect = [src_port_params, ts_port_params]
|
||||||
|
obj = sriov_nic_taas.SriovNicTaasDriver()
|
||||||
|
obj.initialize()
|
||||||
|
self.assertRaises(taas_exc.PciSlotNotFound, obj.delete_tap_flow,
|
||||||
|
tap_flow)
|
@ -0,0 +1,269 @@
|
|||||||
|
# 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 copy
|
||||||
|
import mock
|
||||||
|
import re
|
||||||
|
|
||||||
|
from neutron_taas.common import utils as common_utils
|
||||||
|
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
|
||||||
|
from neutron_taas.tests import base
|
||||||
|
|
||||||
|
FAKE_SRIOV_PORT = {
|
||||||
|
'id': 'fake_1', 'mac_address': "52:54:00:12:35:02",
|
||||||
|
'binding:profile': {
|
||||||
|
'pci_slot': None}, 'binding:vif_details': {'vlan': 20}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestSriovNicUtils(base.TaasTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestSriovNicUtils, self).setUp()
|
||||||
|
|
||||||
|
def test_get_sysfs_netdev_path_with_pf_interface(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"/sys/bus/pci/devices/12/physfn/net",
|
||||||
|
sriov_nic_utils.SriovNicUtils().
|
||||||
|
_get_sysfs_netdev_path(12, True))
|
||||||
|
|
||||||
|
def test_get_sysfs_netdev_path_without_pf_interface(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"/sys/bus/pci/devices/12/net",
|
||||||
|
sriov_nic_utils.SriovNicUtils().
|
||||||
|
_get_sysfs_netdev_path(12, False))
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_get_ifname_by_pci_address(self, mock_os):
|
||||||
|
mock_os.listdir.return_value = ['random1', 'random2']
|
||||||
|
self.assertEqual(sriov_nic_utils.SriovNicUtils().
|
||||||
|
get_ifname_by_pci_address(12, False), 'random2')
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_get_ifname_by_pci_address_no_dev_info(self, mock_os):
|
||||||
|
mock_os.listdir.return_value = list()
|
||||||
|
self.assertRaises(
|
||||||
|
taas_exc.PciDeviceNotFoundById,
|
||||||
|
sriov_nic_utils.SriovNicUtils().get_ifname_by_pci_address, 12, 9)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'open', create=True)
|
||||||
|
def test_get_mac_by_pci_address(self, mock_open, mock_os):
|
||||||
|
mock_os.listdir.return_value = ['random1', 'random2']
|
||||||
|
mock_os.path.join.return_value = 'random'
|
||||||
|
fake_file_handle = ["52:54:00:12:35:02"]
|
||||||
|
fake_file_iter = fake_file_handle.__iter__()
|
||||||
|
mock_open.return_value.__enter__.return_value = fake_file_iter
|
||||||
|
self.assertEqual(
|
||||||
|
"52:54:00:12:35:02", sriov_nic_utils.SriovNicUtils().
|
||||||
|
get_mac_by_pci_address(12, False))
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'open', create=True)
|
||||||
|
def test_get_mac_by_pci_address_no_content(self, mock_open, mock_os):
|
||||||
|
mock_os.listdir.return_value = ['random1', 'random2']
|
||||||
|
mock_os.path.join.return_value = 'random'
|
||||||
|
fake_file_handle = []
|
||||||
|
fake_file_iter = fake_file_handle.__iter__()
|
||||||
|
mock_open.return_value.__enter__.return_value = fake_file_iter
|
||||||
|
self.assertRaises(
|
||||||
|
taas_exc.PciDeviceNotFoundById,
|
||||||
|
sriov_nic_utils.SriovNicUtils().get_mac_by_pci_address, 12, False)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_get_mac_by_pci_address_wrong_dev_path(self, mock_os):
|
||||||
|
mock_os.listdir.return_value = ['random1', 'random2']
|
||||||
|
mock_os.path.join.return_value = 'random'
|
||||||
|
self.assertRaises(
|
||||||
|
taas_exc.PciDeviceNotFoundById,
|
||||||
|
sriov_nic_utils.SriovNicUtils().get_mac_by_pci_address, 12, False)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'open', create=True)
|
||||||
|
def test_get_net_name_by_vf_pci_address(self, mock_open, mock_os):
|
||||||
|
mock_os.listdir.return_value = ['enp0s3', 'enp0s2']
|
||||||
|
mock_os.path.join.return_value = 'random'
|
||||||
|
fake_file_handle = ["52:54:00:12:35:02"]
|
||||||
|
fake_file_iter = fake_file_handle.__iter__()
|
||||||
|
mock_open.return_value.__enter__.return_value = fake_file_iter
|
||||||
|
self.assertEqual(
|
||||||
|
'net_enp0s3_52_54_00_12_35_02',
|
||||||
|
sriov_nic_utils.SriovNicUtils().
|
||||||
|
get_net_name_by_vf_pci_address(12))
|
||||||
|
|
||||||
|
def _common_merge_utility(self, value):
|
||||||
|
output_list = list()
|
||||||
|
for v in value:
|
||||||
|
output_list.append(v)
|
||||||
|
return output_list
|
||||||
|
|
||||||
|
def test_get_ranges_str_from_list(self):
|
||||||
|
input_list = [4, 11, 12, 13, 25, 26, 27]
|
||||||
|
self.assertEqual("4,11-13,25-27", common_utils.
|
||||||
|
get_ranges_str_from_list(input_list))
|
||||||
|
|
||||||
|
def test_get_list_from_ranges_str(self):
|
||||||
|
input_str = "4,6,10-13,25-27"
|
||||||
|
expected_output = [4, 6, 10, 11, 12, 13, 25, 26, 27]
|
||||||
|
self.assertEqual(expected_output, common_utils.
|
||||||
|
get_list_from_ranges_str(input_str))
|
||||||
|
|
||||||
|
def test_get_vf_num_by_pci_address_neg(self):
|
||||||
|
self.assertRaises(
|
||||||
|
taas_exc.PciDeviceNotFoundById,
|
||||||
|
sriov_nic_utils.SriovNicUtils().get_vf_num_by_pci_address, 12)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'glob')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 're')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_get_vf_num_by_pci_address(self, mock_os, mock_re, mock_glob):
|
||||||
|
mock_glob.iglob.return_value = ['file1']
|
||||||
|
mock_os.readlink.return_value = 12
|
||||||
|
mock_re.compile().search.return_value = re.match(r"(\d+)", "89")
|
||||||
|
self.assertEqual(
|
||||||
|
'89', sriov_nic_utils.SriovNicUtils().
|
||||||
|
get_vf_num_by_pci_address(12))
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'glob')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 're')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'open', create=True)
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'portbindings')
|
||||||
|
def test_get_sriov_port_params(self, mock_port_bindings, mock_open,
|
||||||
|
mock_os, mock_re, mock_glob):
|
||||||
|
sriov_port = copy.deepcopy(FAKE_SRIOV_PORT)
|
||||||
|
fake_profile = mock_port_bindings.PROFILE = 'binding:profile'
|
||||||
|
mock_port_bindings.VIF_DETAILS = 'binding:vif_details'
|
||||||
|
sriov_port[fake_profile]['pci_slot'] = 3
|
||||||
|
mock_glob.iglob.return_value = ['file1']
|
||||||
|
mock_os.readlink.return_value = 12
|
||||||
|
mock_re.compile().search.return_value = re.match(r"(\d+)", "89")
|
||||||
|
mock_os.listdir.return_value = ['net_enp0s2_52_54_00_12_35_02',
|
||||||
|
'net_enp0s3_52_54_00_12_35_02']
|
||||||
|
mock_os.path.join.return_value = 'random'
|
||||||
|
fake_file_handle = ["52:54:00:12:35:02"]
|
||||||
|
fake_file_iter = fake_file_handle.__iter__()
|
||||||
|
mock_open.return_value.__enter__.return_value = fake_file_iter
|
||||||
|
expected_output = {
|
||||||
|
'mac': '52:54:00:12:35:02', 'pci_slot': 3, 'vf_index': '89',
|
||||||
|
'pf_device': 'net_enp0s3_52_54_00_12_35_02', 'src_vlans': 20}
|
||||||
|
self.assertEqual(
|
||||||
|
expected_output, sriov_nic_utils.SriovNicUtils().
|
||||||
|
get_sriov_port_params(sriov_port))
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'glob')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 're')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'open', create=True)
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'portbindings')
|
||||||
|
def test_get_sriov_port_params_no_pci_slot(self, mock_port_bindings,
|
||||||
|
mock_open, mock_os, mock_re,
|
||||||
|
mock_glob):
|
||||||
|
sriov_port = copy.deepcopy(FAKE_SRIOV_PORT)
|
||||||
|
mock_port_bindings.PROFILE = 'binding:profile'
|
||||||
|
mock_port_bindings.VIF_DETAILS = 'binding:vif_details'
|
||||||
|
mock_glob.iglob.return_value = ['file1']
|
||||||
|
mock_os.readlink.return_value = 12
|
||||||
|
mock_re.compile().search.return_value = re.match(r"(\d+)", "89")
|
||||||
|
mock_os.listdir.return_value = ['enp0s3', 'enp0s2']
|
||||||
|
mock_os.path.join.return_value = 'random'
|
||||||
|
fake_file_handle = ["52:54:00:12:35:02"]
|
||||||
|
fake_file_iter = fake_file_handle.__iter__()
|
||||||
|
mock_open.return_value.__enter__.return_value = fake_file_iter
|
||||||
|
self.assertIsNone(sriov_nic_utils.SriovNicUtils().
|
||||||
|
get_sriov_port_params(sriov_port))
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_egress_add(self, mock_os,
|
||||||
|
mock_neutron_utils):
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'add', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", True, "OUT")
|
||||||
|
egress_cmd = ['i40e_sysfs_command', 'p2p1', '18',
|
||||||
|
'egress_mirror', 'add', '9']
|
||||||
|
mock_neutron_utils.execute.assert_called_once_with(
|
||||||
|
egress_cmd, run_as_root=True)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_ingress_add(self, mock_os,
|
||||||
|
mock_neutron_utils):
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'add', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", True, "IN")
|
||||||
|
ingress_cmd = ['i40e_sysfs_command', 'p2p1', '18',
|
||||||
|
'ingress_mirror', 'add', '9']
|
||||||
|
mock_neutron_utils.execute.assert_called_once_with(
|
||||||
|
ingress_cmd, run_as_root=True)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_both_add(
|
||||||
|
self, mock_os, mock_neutron_utils):
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'add', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", True, "BOTH")
|
||||||
|
self.assertEqual(2, mock_neutron_utils.execute.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_egress_rem(self, mock_os,
|
||||||
|
mock_neutron_utils):
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", True, "OUT")
|
||||||
|
egress_cmd = ['i40e_sysfs_command', 'p2p1', '18',
|
||||||
|
'egress_mirror', 'rem', '9']
|
||||||
|
mock_neutron_utils.execute.assert_called_once_with(
|
||||||
|
egress_cmd, run_as_root=True)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_ingress_rem(self, mock_os,
|
||||||
|
mock_neutron_utils):
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", True, "IN")
|
||||||
|
ingress_cmd = ['i40e_sysfs_command', 'p2p1', '18',
|
||||||
|
'ingress_mirror', 'rem', '9']
|
||||||
|
mock_neutron_utils.execute.assert_called_once_with(
|
||||||
|
ingress_cmd, run_as_root=True)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_both_rem(
|
||||||
|
self, mock_os, mock_neutron_utils):
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", True, "BOTH")
|
||||||
|
self.assertEqual(2, mock_neutron_utils.execute.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'utils')
|
||||||
|
@mock.patch.object(sriov_nic_utils, 'os')
|
||||||
|
def test_execute_sysfs_command_not_both_vf_to_vf_all_vlans_False(
|
||||||
|
self, mock_os, mock_neutron_utils):
|
||||||
|
cmd = ['i40e_sysfs_command', 'p2p1', '9',
|
||||||
|
'vlan_mirror', 'rem', '4,11-13']
|
||||||
|
sriov_nic_utils.SriovNicUtils().execute_sysfs_command(
|
||||||
|
'rem', {'pf_device': 'p2p1', 'vf_index': '9'}, {'vf_index': '18'},
|
||||||
|
"4,11-13", False, "FAKE")
|
||||||
|
mock_neutron_utils.execute.assert_called_once_with(
|
||||||
|
cmd, run_as_root=True)
|
@ -40,8 +40,6 @@ class TestTaasPlugin(testlib_api.SqlTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestTaasPlugin, self).setUp()
|
super(TestTaasPlugin, self).setUp()
|
||||||
mock.patch.object(n_rpc, 'Connection', auto_spec=True).start()
|
mock.patch.object(n_rpc, 'Connection', auto_spec=True).start()
|
||||||
mock.patch.object(taas_agent_api,
|
|
||||||
'TaasCallbacks', auto_spec=True).start()
|
|
||||||
mock.patch.object(taas_agent_api,
|
mock.patch.object(taas_agent_api,
|
||||||
'TaasAgentApi', auto_spec=True).start()
|
'TaasAgentApi', auto_spec=True).start()
|
||||||
self.driver = mock.MagicMock()
|
self.driver = mock.MagicMock()
|
||||||
@ -70,6 +68,7 @@ class TestTaasPlugin(testlib_api.SqlTestCase):
|
|||||||
'port_id': self._port_id,
|
'port_id': self._port_id,
|
||||||
'project_id': self._project_id,
|
'project_id': self._project_id,
|
||||||
}
|
}
|
||||||
|
self.vlan_filter = "1-5,9,18,27-30,99-108,4000-4095"
|
||||||
self._tap_flow = {
|
self._tap_flow = {
|
||||||
'description': 'This is my tap flow',
|
'description': 'This is my tap flow',
|
||||||
'direction': 'BOTH',
|
'direction': 'BOTH',
|
||||||
@ -77,6 +76,7 @@ class TestTaasPlugin(testlib_api.SqlTestCase):
|
|||||||
'source_port': self._port_id,
|
'source_port': self._port_id,
|
||||||
'tenant_id': self._tenant_id,
|
'tenant_id': self._tenant_id,
|
||||||
'project_id': self._project_id,
|
'project_id': self._project_id,
|
||||||
|
'vlan_filter': self.vlan_filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@ -115,6 +115,7 @@ class TestTaasPlugin(testlib_api.SqlTestCase):
|
|||||||
self._tap_flow['id'] = mock.ANY
|
self._tap_flow['id'] = mock.ANY
|
||||||
self._tap_flow['status'] = 'ACTIVE'
|
self._tap_flow['status'] = 'ACTIVE'
|
||||||
self._tap_service['id'] = mock.ANY
|
self._tap_service['id'] = mock.ANY
|
||||||
|
self._tap_flow['vlan_filter'] = mock.ANY
|
||||||
|
|
||||||
self.driver.assert_has_calls([
|
self.driver.assert_has_calls([
|
||||||
mock.call.create_tap_flow_precommit(mock.ANY),
|
mock.call.create_tap_flow_precommit(mock.ANY),
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
# Enable tap-as-a-service
|
# Enable tap-as-a-service
|
||||||
export PROJECTS="openstack/tap-as-a-service $PROJECTS"
|
export PROJECTS="openstack/tap-as-a-service $PROJECTS"
|
||||||
export ENABLED_SERVICES=taas,taas_openvswitch_agent
|
export ENABLED_SERVICES=taas,taas_agent
|
||||||
|
|
||||||
export DEVSTACK_GATE_SETTINGS=/opt/stack/new/tap-as-a-service/devstack/devstackgaterc
|
export DEVSTACK_GATE_SETTINGS=/opt/stack/new/tap-as-a-service/devstack/devstackgaterc
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
Neutron TaaS is integrated with new TaaS Agent driver (i.e. SRIOV) for
|
||||||
|
Intel i40e driver backend.
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
[`blueprint port-mirroring-sriov-vf <https://blueprints.launchpad.net/neutron/+spec/port-mirroring-sriov-vf>`_]
|
||||||
|
* Adds a new TaaS Agent driver (i.e. SRIOV) for Intel i40e driver backend.
|
||||||
|
* Neutron TaaS Agent is refactored to decouple its tight binding with ovs
|
||||||
|
driver. This prepares the taas agent code-base for addition of a new
|
||||||
|
TaaS Agent driver (SRIOV).
|
||||||
|
* Adds a new API extension, i.e. taas-vlan-filter for adding a new
|
||||||
|
attribute (vlan_filter) in tap-flow data model.
|
@ -5,7 +5,7 @@
|
|||||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
Babel!=2.4.0,>=2.3.4 # BSD
|
Babel!=2.4.0,>=2.3.4 # BSD
|
||||||
neutron>=12.0.0 # Apache-2.0
|
neutron>=12.0.0 # Apache-2.0
|
||||||
neutron-lib>=1.20.0 # Apache-2.0
|
neutron-lib>=1.25.0 # Apache-2.0
|
||||||
|
|
||||||
# Opt-in for neutron-lib consumption patches
|
# Opt-in for neutron-lib consumption patches
|
||||||
# http://lists.openstack.org/pipermail/openstack-dev/2018-September/135063.html
|
# http://lists.openstack.org/pipermail/openstack-dev/2018-September/135063.html
|
||||||
|
@ -22,6 +22,13 @@ classifier =
|
|||||||
packages =
|
packages =
|
||||||
neutron_taas
|
neutron_taas
|
||||||
|
|
||||||
|
data_files =
|
||||||
|
etc/neutron/rootwrap.d =
|
||||||
|
etc/neutron/rootwrap.d/taas-i40e-sysfs.filters
|
||||||
|
|
||||||
|
scripts =
|
||||||
|
bin/i40e_sysfs_command
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
@ -54,6 +61,7 @@ neutron.agent.l2.extensions =
|
|||||||
taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension
|
taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension
|
||||||
neutron_taas.taas.agent_drivers =
|
neutron_taas.taas.agent_drivers =
|
||||||
ovs = neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver
|
ovs = neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver
|
||||||
|
sriov = neutron_taas.services.taas.drivers.linux.sriov_nic_taas:SriovNicTaasDriver
|
||||||
neutron.service_plugins =
|
neutron.service_plugins =
|
||||||
taas = neutron_taas.services.taas.taas_plugin:TaasPlugin
|
taas = neutron_taas.services.taas.taas_plugin:TaasPlugin
|
||||||
neutron.db.alembic_migrations =
|
neutron.db.alembic_migrations =
|
||||||
|
Loading…
Reference in New Issue
Block a user