51b36d9c9c
Changes: - Use privsep execution. - Streamline tox.ini and setup.cfg - Remove lower-constraints job Depends-On: https://review.opendev.org/c/openstack/neutron/+/764015 Change-Id: I0017028ff35fe1f375d67e15e9f03f6917f8ccff
272 lines
11 KiB
Python
272 lines
11 KiB
Python
# 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,
|
|
privsep_exec=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,
|
|
privsep_exec=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,
|
|
privsep_exec=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}
|