Update SR-IOV support for >= Mitaka

SR-IOV network for OpenStack release later than Mitaka requires the
use of the neutron-sriov-agent to support management of SR-IOV PF
and VF interface state by Neutron - said interfaces are still
consumed directly by nova-compute/libvirt via PCI device allocation
scheduling for instances.

Add new configuration options to the neutron-openvswitch charm to
support enablement of the SR-IOV agent; this could have been done
automatically from data presented from neutron-api, but its possible
that cloud deployments may only have subsets of compute nodes that
are SR-IOV enabled in terms of hardware.

Enabling this option ('enable-sriov') will install and configure
the neutron-sriov-agent; configuration of SR-IOV PF's are made
using the 'sriov-numvfs', which by default automatically configures
all SR-IOV devices on every machine to the maximum number of VF's
supported by the device.  This option can be used to configure
devices at an individual level as well.

Finally, neutron needs to understand what underlying provider
network each SR-IOV device maps to - this is configured using the
sriov-device-mappings configuration option.

Change-Id: Ie185fd347ddc1b11e9ed13cefaf44fb7c8546ab0
This commit is contained in:
James Page 2016-11-11 17:20:58 +00:00
parent cd5fb8b51b
commit 790819c237
10 changed files with 405 additions and 67 deletions

View File

@ -172,3 +172,34 @@ options:
uio_pci_generic
.
Only used when DPDK is enabled.
enable-sriov:
type: boolean
default: false
description: |
Enable SR-IOV NIC agent on deployed units; use with sriov-device-mappings to
map SR-IOV devices to underlying provider networks. Enabling this option
allows instances to be plugged into directly into SR-IOV VF devices connected
to underlying provider networks alongside the default Open vSwitch networking
options.
sriov-device-mappings:
type: string
default:
description: |
Space-delimited list of SR-IOV device mappings with format
.
<provider>:<interface>
.
Multiple mappings can be provided, delimited by spaces.
sriov-numvfs:
type: string
default: auto
description: |
Number of VF's to configure each PF with; by default, each SR-IOV PF will be
configured with the maximum number of VF's it can support. Either use a
single integer to apply the same VF configuration to all detected SR-IOV
devices or use a per-device configuration in the following format
.
<device>:<numvfs>
.
Multiple devices can be configured by providing multi values delimited by
spaces.

View File

@ -103,6 +103,12 @@ class OVSPluginContext(context.NeutronContext):
if mappings:
ovs_ctxt['bridge_mappings'] = ','.join(mappings.split())
sriov_mappings = config('sriov-device-mappings')
if sriov_mappings:
ovs_ctxt['sriov_device_mappings'] = (
','.join(sriov_mappings.split())
)
flat_providers = config('flat-network-providers')
if flat_providers:
ovs_ctxt['network_providers'] = ','.join(flat_providers.split())

View File

@ -39,6 +39,7 @@ from neutron_ovs_utils import (
DVR_PACKAGES,
METADATA_PACKAGES,
configure_ovs,
configure_sriov,
git_install,
get_topics,
get_shared_secret,
@ -72,6 +73,7 @@ def config_changed():
git_install(config('openstack-origin-git'))
configure_ovs()
configure_sriov()
CONFIGS.write_all()
for rid in relation_ids('zeromq-configuration'):
zeromq_configuration_relation_joined(rid)

View File

@ -33,6 +33,7 @@ from charmhelpers.contrib.openstack.utils import (
make_assess_status_func,
is_unit_paused_set,
os_application_version_set,
remote_restart,
)
from collections import OrderedDict
from charmhelpers.contrib.openstack.utils import (
@ -48,6 +49,7 @@ from charmhelpers.core.hookenv import (
charm_dir,
config,
status_set,
log,
)
from charmhelpers.contrib.openstack.neutron import (
parse_bridge_mappings,
@ -79,6 +81,9 @@ from charmhelpers.fetch import (
filter_installed_packages,
)
from pci import PCINetDevices
# The interface is said to be satisfied if anyone of the interfaces in the
# list has a complete context.
# LY: Note the neutron-plugin is always present since that is the relation
@ -134,6 +139,8 @@ PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
TEMPLATES = 'templates/'
OVS_DEFAULT = '/etc/default/openvswitch-switch'
DPDK_INTERFACES = '/etc/dpdk/interfaces'
NEUTRON_SRIOV_AGENT_CONF = os.path.join(NEUTRON_CONF_DIR,
'plugins/ml2/sriov_agent.ini')
BASE_RESOURCE_MAP = OrderedDict([
(NEUTRON_CONF, {
@ -195,6 +202,12 @@ DVR_RESOURCE_MAP = OrderedDict([
'contexts': [context.ExternalPortContext()],
}),
])
SRIOV_RESOURCE_MAP = OrderedDict([
(NEUTRON_SRIOV_AGENT_CONF, {
'services': ['neutron-sriov-agent'],
'contexts': [neutron_ovs_context.OVSPluginContext()],
}),
])
TEMPLATES = 'templates/'
INT_BRIDGE = "br-int"
@ -254,6 +267,9 @@ def determine_packages():
if use_dpdk():
pkgs.append('openvswitch-switch-dpdk')
if enable_sriov_agent():
pkgs.append('neutron-sriov-agent')
return pkgs
@ -296,6 +312,10 @@ def resource_map():
)
if not use_dpdk():
drop_config.append(DPDK_INTERFACES)
if enable_sriov_agent():
resource_map.update(SRIOV_RESOURCE_MAP)
resource_map[NEUTRON_CONF]['services'].append(
'neutron-sriov-agent')
else:
drop_config.extend([OVS_CONF, DPDK_INTERFACES])
@ -409,6 +429,53 @@ def configure_ovs():
service_restart('os-charm-phy-nic-mtu')
def configure_sriov():
'''Configure SR-IOV devices based on provided configuration options'''
charm_config = config()
enable_sriov = charm_config.get('enable-sriov')
if enable_sriov and charm_config.changed('sriov-numvfs'):
devices = PCINetDevices()
sriov_numvfs = charm_config.get('sriov-numvfs')
# automatic configuration of all SR-IOV devices
if sriov_numvfs == 'auto':
log('Configuring SR-IOV device VF functions in auto mode')
for device in devices.pci_devices:
if device and device.sriov:
log("Configuring SR-IOV device"
" {} with {} VF's".format(device.interface_name,
device.sriov_totalvfs))
device.set_sriov_numvfs(device.sriov_totalvfs)
else:
# Single int blanket configuration
try:
sriov_numvfs = int(sriov_numvfs)
log('Configuring SR-IOV device VF functions'
' with blanket setting')
for device in devices.pci_devices:
if device and device.sriov:
log("Configuring SR-IOV device"
" {} with {} VF's".format(device.interface_name,
sriov_numvfs))
device.set_sriov_numvfs(sriov_numvfs)
except ValueError:
# <device>:<numvfs>[ <device>:numvfs] configuration
sriov_numvfs = sriov_numvfs.split()
for device_config in sriov_numvfs:
log('Configuring SR-IOV device VF functions per interface')
interface_name, numvfs = device_config.split(':')
device = devices.get_device_from_interface_name(
interface_name)
if device and device.sriov:
log("Configuring SR-IOV device"
" {} with {} VF's".format(device.interface_name,
numvfs))
device.set_sriov_numvfs(int(numvfs))
# Trigger remote restart in parent application
remote_restart('neutron-plugin', 'nova-compute')
def get_shared_secret():
ctxt = neutron_ovs_context.SharedSecretContext()()
if 'shared_secret' in ctxt:
@ -438,6 +505,14 @@ def use_dpdk():
return False
def enable_sriov_agent():
'''Determine with SR-IOV agent should be used'''
release = os_release('neutron-common', base='icehouse')
if (release >= 'mitaka' and config('enable-sriov')):
return True
return False
# TODO: update into charm-helpers to add port_type parameter
def dpdk_add_bridge_port(name, port, promisc=False, port_type=None):
''' Add a port to the named openvswitch bridge '''

View File

@ -18,9 +18,6 @@ import os
import glob
import subprocess
import shlex
from charmhelpers.core.hookenv import(
log,
)
def format_pci_addr(pci_addr):
@ -30,30 +27,22 @@ def format_pci_addr(pci_addr):
func)
class PCINetDevice(object):
def get_sysnet_interfaces_and_macs():
'''Catalog interface information from local system
def __init__(self, pci_address):
self.pci_address = pci_address
self.interface_name = None
self.mac_address = None
self.state = None
self.update_attributes()
each device dict contains:
def update_attributes(self):
self.update_interface_info()
interface: logical name
mac_address: MAC address
pci_address: PCI address
state: Current interface state (up/down)
sriov: Boolean indicating whether inteface is an SR-IOV
capable device.
sriov_totalvfs: Total VF capacity of device
sriov_numvfs: Configured VF capacity of device
def update_interface_info(self):
self.update_interface_info_eth()
def update_interface_info_eth(self):
net_devices = self.get_sysnet_interfaces_and_macs()
for interface in net_devices:
if self.pci_address == interface['pci_address']:
self.interface_name = interface['interface']
self.mac_address = interface['mac_address']
self.state = interface['state']
def get_sysnet_interfaces_and_macs(self):
:returns: array: of dict objects containing details of each interface
'''
net_devs = []
for sdir in glob.glob('/sys/class/net/*'):
sym_link = sdir + "/device"
@ -64,34 +53,141 @@ class PCINetDevice(object):
pci_address = path[-2]
else:
pci_address = path[-1]
net_devs.append({
'interface': self.get_sysnet_interface(sdir),
'mac_address': self.get_sysnet_mac(sdir),
device = {
'interface': get_sysnet_interface(sdir),
'mac_address': get_sysnet_mac(sdir),
'pci_address': pci_address,
'state': self.get_sysnet_device_state(sdir),
})
'state': get_sysnet_device_state(sdir),
'sriov': is_sriov(sdir)
}
if device['sriov']:
device['sriov_totalvfs'] = \
get_sriov_totalvfs(sdir)
device['sriov_numvfs'] = \
get_sriov_numvfs(sdir)
net_devs.append(device)
return net_devs
def get_sysnet_mac(self, sysdir):
def get_sysnet_mac(sysdir):
'''Read MAC address for a device
:sysdir: string: path to device /sys directory
:returns: string: MAC address of device
'''
mac_addr_file = sysdir + '/address'
with open(mac_addr_file, 'r') as f:
read_data = f.read()
mac = read_data.strip()
log('mac from {} is {}'.format(mac_addr_file, mac))
return mac
def get_sysnet_device_state(self, sysdir):
def get_sysnet_device_state(sysdir):
'''Read operational state of a device
:sysdir: string: path to device /sys directory
:returns: string: current device state
'''
state_file = sysdir + '/operstate'
with open(state_file, 'r') as f:
read_data = f.read()
state = read_data.strip()
log('state from {} is {}'.format(state_file, state))
return state
def get_sysnet_interface(self, sysdir):
def is_sriov(sysdir):
'''Determine whether a device is SR-IOV capable
:sysdir: string: path to device /sys directory
:returns: boolean: indicating whether device is SR-IOV
capable or not.
'''
return os.path.exists(os.path.join(sysdir,
'device',
'sriov_totalvfs'))
def get_sriov_totalvfs(sysdir):
'''Read total VF capacity for a device
:sysdir: string: path to device /sys directory
:returns: int: number of VF's the device supports
'''
sriov_totalvfs_file = os.path.join(sysdir,
'device',
'sriov_totalvfs')
with open(sriov_totalvfs_file, 'r') as f:
read_data = f.read()
sriov_totalvfs = int(read_data.strip())
return sriov_totalvfs
def get_sriov_numvfs(sysdir):
'''Read configured VF capacity for a device
:sysdir: string: path to device /sys directory
:returns: int: number of VF's the device is configured for
'''
sriov_numvfs_file = os.path.join(sysdir,
'device',
'sriov_numvfs')
with open(sriov_numvfs_file, 'r') as f:
read_data = f.read()
sriov_numvfs = int(read_data.strip())
return sriov_numvfs
def get_sysnet_interface(sysdir):
return sysdir.split('/')[-1]
class PCINetDevice(object):
def __init__(self, pci_address):
self.pci_address = pci_address
self.interface_name = None
self.mac_address = None
self.state = None
self.sriov = False
self.update_attributes()
def update_attributes(self):
self.update_interface_info()
def update_interface_info(self):
net_devices = get_sysnet_interfaces_and_macs()
for interface in net_devices:
if self.pci_address == interface['pci_address']:
self.interface_name = interface['interface']
self.mac_address = interface['mac_address']
self.state = interface['state']
self.sriov = interface['sriov']
if self.sriov:
self.sriov_totalvfs = interface['sriov_totalvfs']
self.sriov_numvfs = interface['sriov_numvfs']
def set_sriov_numvfs(self, numvfs):
'''Set the number of VF devices for a SR-IOV PF
Assuming the device is an SR-IOV device, this function will attempt
to change the number of VF's created by the PF.
@param numvfs: integer to set the current number of VF's to
'''
if self.sriov:
sdevice = os.path.join('/sys/class/net',
self.interface_name,
'device', 'sriov_numvfs')
with open(sdevice, 'w') as sh:
sh.write(str(numvfs))
class PCINetDevices(object):
def __init__(self):
@ -131,3 +227,9 @@ class PCINetDevices(object):
if pcidev.pci_address == pci_addr:
return pcidev
return None
def get_device_from_interface_name(self, interface_name):
for pcidev in self.pci_devices:
if pcidev.interface_name == interface_name:
return pcidev
return None

View File

@ -0,0 +1,12 @@
# mitaka
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
# Config managed by neutron-openvswitch charm
###############################################################################
[securitygroup]
firewall_driver = neutron.agent.firewall.NoopFirewallDriver
[sriov_nic]
physical_device_mappings = {{ sriov_device_mappings }}
exclude_devices =

View File

@ -41,6 +41,7 @@ TO_PATCH = [
'relation_ids',
'relation_set',
'configure_ovs',
'configure_sriov',
'use_dvr',
'install_packages',
'purge_packages',
@ -99,6 +100,7 @@ class NeutronOVSHooksTests(CharmTestCase):
self.assertTrue(self.CONFIGS.write_all.called)
self.assertTrue(_zmq_joined.called_with('relid'))
self.configure_ovs.assert_called_with()
self.configure_sriov.assert_called_with()
@patch.object(hooks, 'git_install_requested')
@patch.object(hooks, 'config_value_changed')

View File

@ -51,6 +51,8 @@ TO_PATCH = [
'status_set',
'use_dpdk',
'os_application_version_set',
'remote_restart',
'PCINetDevices',
]
head_pkg = 'linux-headers-3.15.0-5-generic'
@ -596,3 +598,99 @@ class TestNeutronOVSUtils(CharmTestCase):
asf.assert_called_once_with('some-config')
# ports=None whilst port checks are disabled.
f.assert_called_once_with('assessor', services='s1', ports=None)
def _configure_sriov_base(self, config,
changed=False):
self.mock_config = MagicMock()
self.config.side_effect = None
self.config.return_value = self.mock_config
self.mock_config.get.side_effect = lambda x: config.get(x)
self.mock_config.changed.return_value = changed
self.mock_eth_device = MagicMock()
self.mock_eth_device.sriov = False
self.mock_eth_device.interface_name = 'eth0'
self.mock_eth_device.sriov_totalvfs = 0
self.mock_sriov_device = MagicMock()
self.mock_sriov_device.sriov = True
self.mock_sriov_device.interface_name = 'ens0'
self.mock_sriov_device.sriov_totalvfs = 64
self.mock_sriov_device2 = MagicMock()
self.mock_sriov_device2.sriov = True
self.mock_sriov_device2.interface_name = 'ens49'
self.mock_sriov_device2.sriov_totalvfs = 64
self.pci_devices = {
'eth0': self.mock_eth_device,
'ens0': self.mock_sriov_device,
'ens49': self.mock_sriov_device2,
}
mock_pci_devices = MagicMock()
mock_pci_devices.pci_devices = [
self.mock_eth_device,
self.mock_sriov_device,
self.mock_sriov_device2
]
self.PCINetDevices.return_value = mock_pci_devices
mock_pci_devices.get_device_from_interface_name.side_effect = \
lambda x: self.pci_devices.get(x)
def test_configure_sriov_no_changes(self):
_config = {
'enable-sriov': True,
'sriov-numvfs': 'auto'
}
self._configure_sriov_base(_config, False)
nutils.configure_sriov()
self.assertFalse(self.remote_restart.called)
def test_configure_sriov_auto(self):
_config = {
'enable-sriov': True,
'sriov-numvfs': 'auto'
}
self._configure_sriov_base(_config, True)
nutils.configure_sriov()
self.mock_sriov_device.set_sriov_numvfs.assert_called_with(
self.mock_sriov_device.sriov_totalvfs
)
self.mock_sriov_device2.set_sriov_numvfs.assert_called_with(
self.mock_sriov_device2.sriov_totalvfs
)
self.assertTrue(self.remote_restart.called)
def test_configure_sriov_numvfs(self):
_config = {
'enable-sriov': True,
'sriov-numvfs': '32',
}
self._configure_sriov_base(_config, True)
nutils.configure_sriov()
self.mock_sriov_device.set_sriov_numvfs.assert_called_with(32)
self.mock_sriov_device2.set_sriov_numvfs.assert_called_with(32)
self.assertTrue(self.remote_restart.called)
def test_configure_sriov_numvfs_per_device(self):
_config = {
'enable-sriov': True,
'sriov-numvfs': 'ens0:32 sriov23:64'
}
self._configure_sriov_base(_config, True)
nutils.configure_sriov()
self.mock_sriov_device.set_sriov_numvfs.assert_called_with(32)
self.mock_sriov_device2.set_sriov_numvfs.assert_not_called()
self.assertTrue(self.remote_restart.called)

View File

@ -26,7 +26,6 @@ import pci
TO_PATCH = [
'glob',
'log',
'subprocess',
]
NOT_JSON = "Im not json"
@ -73,43 +72,48 @@ class PCINetDeviceTest(CharmTestCase):
'mac_address': 'a8:9d:21:cf:93:fc',
'pci_address': '0000:10:00.0',
'state': 'up',
'sriov': False,
}
self.assertTrue(check_device(net, expect))
@patch('pci.PCINetDevice.get_sysnet_interfaces_and_macs')
@patch('pci.get_sysnet_interfaces_and_macs')
@patch('pci.PCINetDevice.update_attributes')
def test_update_interface_info_eth(self, _update, _sysnet_ints):
def test_update_interface_info(self, _update, _sysnet_ints):
dev = pci.PCINetDevice('0000:10:00.0')
_sysnet_ints.return_value = [
{
'interface': 'eth2',
'mac_address': 'a8:9d:21:cf:93:fc',
'pci_address': '0000:10:00.0',
'state': 'up'
'state': 'up',
'sriov': False,
},
{
'interface': 'eth3',
'mac_address': 'a8:9d:21:cf:93:fd',
'pci_address': '0000:10:00.1',
'state': 'down'
'state': 'down',
'sriov': False,
}
]
dev.update_interface_info_eth()
dev.update_interface_info()
self.assertEqual(dev.interface_name, 'eth2')
@patch('os.path.islink')
@patch('os.path.realpath')
@patch('pci.PCINetDevice.get_sysnet_device_state')
@patch('pci.PCINetDevice.get_sysnet_mac')
@patch('pci.PCINetDevice.get_sysnet_interface')
@patch('pci.is_sriov')
@patch('pci.get_sysnet_device_state')
@patch('pci.get_sysnet_mac')
@patch('pci.get_sysnet_interface')
@patch('pci.PCINetDevice.update_attributes')
def test_get_sysnet_interfaces_and_macs(self, _update, _interface, _mac,
_state, _osrealpath, _osislink):
dev = pci.PCINetDevice('0000:06:00.0')
_state, _sriov, _osrealpath,
_osislink):
self.glob.glob.return_value = ['/sys/class/net/eth2']
_interface.return_value = 'eth2'
_mac.return_value = 'a8:9d:21:cf:93:fc'
_state.return_value = 'up'
_sriov.return_value = False
_osrealpath.return_value = ('/sys/devices/pci0000:00/0000:00:02.0/'
'0000:02:00.0/0000:03:00.0/0000:04:00.0/'
'0000:05:01.0/0000:07:00.0')
@ -118,23 +122,26 @@ class PCINetDeviceTest(CharmTestCase):
'mac_address': 'a8:9d:21:cf:93:fc',
'pci_address': '0000:07:00.0',
'state': 'up',
'sriov': False,
}
self.assertEqual(dev.get_sysnet_interfaces_and_macs(), [expect])
self.assertEqual(pci.get_sysnet_interfaces_and_macs(), [expect])
@patch('os.path.islink')
@patch('os.path.realpath')
@patch('pci.PCINetDevice.get_sysnet_device_state')
@patch('pci.PCINetDevice.get_sysnet_mac')
@patch('pci.PCINetDevice.get_sysnet_interface')
@patch('pci.is_sriov')
@patch('pci.get_sysnet_device_state')
@patch('pci.get_sysnet_mac')
@patch('pci.get_sysnet_interface')
@patch('pci.PCINetDevice.update_attributes')
def test_get_sysnet_interfaces_and_macs_virtio(self, _update, _interface,
_mac, _state, _osrealpath,
_mac, _state, _sriov,
_osrealpath,
_osislink):
dev = pci.PCINetDevice('0000:06:00.0')
self.glob.glob.return_value = ['/sys/class/net/eth2']
_interface.return_value = 'eth2'
_mac.return_value = 'a8:9d:21:cf:93:fc'
_state.return_value = 'up'
_sriov.return_value = False
_osrealpath.return_value = ('/sys/devices/pci0000:00/0000:00:07.0/'
'virtio5')
expect = {
@ -142,36 +149,34 @@ class PCINetDeviceTest(CharmTestCase):
'mac_address': 'a8:9d:21:cf:93:fc',
'pci_address': '0000:00:07.0',
'state': 'up',
'sriov': False,
}
self.assertEqual(dev.get_sysnet_interfaces_and_macs(), [expect])
self.assertEqual(pci.get_sysnet_interfaces_and_macs(), [expect])
@patch('pci.PCINetDevice.update_attributes')
def test_get_sysnet_mac(self, _update):
device = pci.PCINetDevice('0000:10:00.1')
with patch_open() as (_open, _file):
super_fh = mocked_filehandle()
_file.readlines = MagicMock()
_open.side_effect = super_fh._setfilename
_file.read.side_effect = super_fh._getfilecontents_read
macaddr = device.get_sysnet_mac('/sys/class/net/eth3')
macaddr = pci.get_sysnet_mac('/sys/class/net/eth3')
self.assertEqual(macaddr, 'a8:9d:21:cf:93:fd')
@patch('pci.PCINetDevice.update_attributes')
def test_get_sysnet_device_state(self, _update):
device = pci.PCINetDevice('0000:10:00.1')
with patch_open() as (_open, _file):
super_fh = mocked_filehandle()
_file.readlines = MagicMock()
_open.side_effect = super_fh._setfilename
_file.read.side_effect = super_fh._getfilecontents_read
state = device.get_sysnet_device_state('/sys/class/net/eth3')
state = pci.get_sysnet_device_state('/sys/class/net/eth3')
self.assertEqual(state, 'down')
@patch('pci.PCINetDevice.update_attributes')
def test_get_sysnet_interface(self, _update):
device = pci.PCINetDevice('0000:10:00.1')
self.assertEqual(
device.get_sysnet_interface('/sys/class/net/eth3'), 'eth3')
pci.get_sysnet_interface('/sys/class/net/eth3'), 'eth3')
class PCINetDevicesTest(CharmTestCase):
@ -208,12 +213,14 @@ class PCINetDevicesTest(CharmTestCase):
'mac_address': 'a8:9d:21:cf:93:fc',
'pci_address': '0000:10:00.0',
'state': 'up',
'sriov': False,
},
'0000:10:00.1': {
'interface_name': 'eth3',
'mac_address': 'a8:9d:21:cf:93:fd',
'pci_address': '0000:10:00.1',
'state': 'down',
'sriov': False,
},
}
for device in devices.pci_devices:
@ -244,6 +251,7 @@ class PCINetDevicesTest(CharmTestCase):
'mac_address': 'a8:9d:21:cf:93:fd',
'pci_address': '0000:10:00.1',
'state': 'down',
'sriov': False,
},
}
self.assertTrue(check_device(

View File

@ -86,12 +86,14 @@ def mocked_realpath(link):
return pci_responses.SYS_TREE[resolved_link]
@patch('pci.cached')
@patch('pci.log')
@patch('pci.subprocess.Popen')
@patch('pci.subprocess.check_output')
@patch('pci.glob.glob')
@patch('pci.os.path.islink')
def pci_devs(_osislink, _glob, _check_output, _Popen, _log, subproc_map=None):
def pci_devs(_osislink, _glob, _check_output, _Popen, _log,
_cached, subproc_map=None):
_glob.side_effect = mocked_globs
_osislink.side_effect = mocked_islink
_check_output.side_effect = mocked_subprocess(