diff --git a/config.yaml b/config.yaml index 3182a8a2..36e5298d 100644 --- a/config.yaml +++ b/config.yaml @@ -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 + . + : + . + 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 + . + : + . + Multiple devices can be configured by providing multi values delimited by + spaces. diff --git a/hooks/neutron_ovs_context.py b/hooks/neutron_ovs_context.py index 56b5863d..08d793b0 100644 --- a/hooks/neutron_ovs_context.py +++ b/hooks/neutron_ovs_context.py @@ -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()) diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py index d897ff72..d82774ab 100755 --- a/hooks/neutron_ovs_hooks.py +++ b/hooks/neutron_ovs_hooks.py @@ -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) diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py index f24076d1..24b4b5df 100644 --- a/hooks/neutron_ovs_utils.py +++ b/hooks/neutron_ovs_utils.py @@ -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: + # :[ :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 ''' diff --git a/hooks/pci.py b/hooks/pci.py index 90299d18..034ac21f 100644 --- a/hooks/pci.py +++ b/hooks/pci.py @@ -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,6 +27,126 @@ def format_pci_addr(pci_addr): func) +def get_sysnet_interfaces_and_macs(): + '''Catalog interface information from local system + + each device dict contains: + + 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 + + :returns: array: of dict objects containing details of each interface + ''' + net_devs = [] + for sdir in glob.glob('/sys/class/net/*'): + sym_link = sdir + "/device" + if os.path.islink(sym_link): + fq_path = os.path.realpath(sym_link) + path = fq_path.split('/') + if 'virtio' in path[-1]: + pci_address = path[-2] + else: + pci_address = path[-1] + device = { + 'interface': get_sysnet_interface(sdir), + 'mac_address': get_sysnet_mac(sdir), + 'pci_address': pci_address, + '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(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() + return mac + + +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() + return state + + +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): @@ -37,59 +154,38 @@ class PCINetDevice(object): 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): - self.update_interface_info_eth() - - def update_interface_info_eth(self): - net_devices = self.get_sysnet_interfaces_and_macs() + 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 get_sysnet_interfaces_and_macs(self): - net_devs = [] - for sdir in glob.glob('/sys/class/net/*'): - sym_link = sdir + "/device" - if os.path.islink(sym_link): - fq_path = os.path.realpath(sym_link) - path = fq_path.split('/') - if 'virtio' in path[-1]: - 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), - 'pci_address': pci_address, - 'state': self.get_sysnet_device_state(sdir), - }) - return net_devs + def set_sriov_numvfs(self, numvfs): + '''Set the number of VF devices for a SR-IOV PF - def get_sysnet_mac(self, sysdir): - 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 + Assuming the device is an SR-IOV device, this function will attempt + to change the number of VF's created by the PF. - def get_sysnet_device_state(self, sysdir): - 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): - return sysdir.split('/')[-1] + @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): @@ -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 diff --git a/templates/mitaka/sriov_agent.ini b/templates/mitaka/sriov_agent.ini new file mode 100644 index 00000000..3c7b1383 --- /dev/null +++ b/templates/mitaka/sriov_agent.ini @@ -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 = diff --git a/unit_tests/test_neutron_ovs_hooks.py b/unit_tests/test_neutron_ovs_hooks.py index 76f9f9c9..26b596c7 100644 --- a/unit_tests/test_neutron_ovs_hooks.py +++ b/unit_tests/test_neutron_ovs_hooks.py @@ -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') diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py index 75a4f78c..aef7b3d3 100644 --- a/unit_tests/test_neutron_ovs_utils.py +++ b/unit_tests/test_neutron_ovs_utils.py @@ -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) diff --git a/unit_tests/test_pci.py b/unit_tests/test_pci.py index 4a99b984..a5043b35 100644 --- a/unit_tests/test_pci.py +++ b/unit_tests/test_pci.py @@ -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( diff --git a/unit_tests/test_pci_helper.py b/unit_tests/test_pci_helper.py index 1c21ebe9..5ef9497c 100644 --- a/unit_tests/test_pci_helper.py +++ b/unit_tests/test_pci_helper.py @@ -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(