Tap-as-a-Service is a project to introduce the functionality of port mirroring in OpenStack Neutron provisioned networks.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

268 lines
11 KiB

# 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(r"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}