hardware offload support for openvswitch

In Kernel 4.8 we introduced Traffic Control (TC see [1]) hardware offloads
framework for SR-IOV VFs which allows us to configure the NIC [2].
Subsequent OVS patches [3] allow us to use the TC framework
to offload OVS datapath rules.

This patch adds plug/unplug when using ovs vif 'OVS_ACCELERATION'. The
plug method will lookup the VF representor and connect it to 'br-int',
while the unplug method will remove the VF representor from 'br-int'.

[1] https://linux.die.net/man/8/tc
[2] http://netdevconf.org/1.2/papers/efraim-gerlitz-sriov-ovs-final.pdf
[3] https://mail.openvswitch.org/pipermail/ovs-dev/2017-April/330606.html

Co-Authored-By: Stephen Finucane <sfinucan@redhat.com>

Change-Id: Ia8214d4da5edfdfc188dc4cf791613cb475dc4c3
This commit is contained in:
Moshe Levi 2017-04-26 18:54:33 +00:00
parent b720323d61
commit 2a09ede8fb
8 changed files with 470 additions and 7 deletions

View File

@ -163,3 +163,48 @@ Glossary
Refer to the `IEEE spec`__ for more information.
__ http://www.ieee802.org/1/pages/802.1br.html
tc
A framework for interacting with traffic control settings (QoS,
essentially) in the Linux kernel.
Refer to the `tc(8) man page`__ for more information.
__ https://linux.die.net/man/8/tc
SR-IOV
Single Root I/O Virtualization
An extension to the PCI Express (PCIe) specification that allows a device,
typically a network adapter, to split access to its resources among
various PCIe hardware functions, :term:`physical <PF>` or :term:`virtual
<VF>`.
Refer to this `article by Scott Lowe`__ or the original `PCI-SIG spec`__
(paywall) for more information.
__ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/
__ https://members.pcisig.com/wg/PCI-SIG/document/download/8272
PF
Physical Function
In SR-IOV, a PCIe function that has full configuration resources. An
SR-IOV device can have *up to* 8 PFs, though this varies between devices.
A PF would typically correspond to a single interface on a NIC.
Refer to this `article by Scott Lowe`__ for more information.
__ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/
VF
Virtual Function
In SR-IOV, a PCIe function that lacks configuration resources. An SR-IOV
device can have *up to* 256 VFs, though this varies between devices. A VF
must be of the same type as the parent device's :term:`PF`.
Refer to this `article by Scott Lowe`__ for more information.
__ http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/

View File

@ -32,6 +32,30 @@ The Open vSwitch plugin provides support for the following VIF types:
Refer to :ref:`vif-vhostuser` for more information.
`VIFHostDevice`
Configuration where an :term:`SR-IOV` PCI device :term:`VF` is passed through
to a guest. The ``hw-tc-offload`` feature should be enabled on the SR-IOV
:term:`PF` using ``ethtool``:
.. code-block:: shell
ethtool -K <PF> hw-tc-offload
This will create a *VF representor* per VF. The VF representor plays the same
role as TAP devices in Para-Virtual (PV) setup. In this case the ``plug()``
method connects the VF representor to the OpenVSwitch bridge.
.. important::
Support for this feature requires Linux Kernel >= 4.8 and Open vSwitch
2.8. These add support for :term:`tc`-based hardware offloads for SR-IOV
VFs and offloading of OVS datapath rules using tc, respectively.
Refer to :ref:`vif-hostdevice` for more information.
.. versionadded:: 1.5.0
For information on the VIF type objects, refer to :doc:`../vif_types`. Note
that only the above VIF types are supported by this plugin.

View File

@ -146,10 +146,11 @@ class TestOSVIF(base.TestCase):
self.assertEqual(info.plugin_info[1].plugin_name, "ovs")
vif_info = info.plugin_info[1].vif_info
self.assertEqual(len(vif_info), 3)
self.assertEqual(len(vif_info), 4)
self.assertEqual(vif_info[0].vif_object_name, "VIFBridge")
self.assertEqual(vif_info[1].vif_object_name, "VIFOpenVSwitch")
self.assertEqual(vif_info[2].vif_object_name, "VIFVHostUser")
self.assertEqual(vif_info[3].vif_object_name, "VIFHostDevice")
def test_host_info_filtered(self):
os_vif.initialize()

View File

@ -26,3 +26,12 @@ class MissingPortProfile(osv_exception.ExceptionBase):
class WrongPortProfile(osv_exception.ExceptionBase):
msg_fmt = _('Port profile %(profile)s is not a subclass '
'of VIFPortProfileOpenVSwitch')
class RepresentorNotFound(osv_exception.ExceptionBase):
msg_fmt = _('Failed getting representor port for PF %(ifname)s with '
'%(vf_num)s')
class PciDeviceNotFoundById(osv_exception.ExceptionBase):
msg_fmt = _("PCI device %(id)s not found")

View File

@ -19,7 +19,9 @@
"""Implements vlans, bridges using linux utilities."""
import glob
import os
import re
import sys
from oslo_concurrency import processutils
@ -32,6 +34,8 @@ from vif_plug_ovs import privsep
LOG = logging.getLogger(__name__)
VIRTFN_RE = re.compile("virtfn(\d+)")
def _ovs_vsctl(args, timeout=None):
full_args = ['ovs-vsctl']
@ -153,7 +157,7 @@ def ensure_bridge(bridge):
process_input='1',
check_exit_code=[0, 1])
# we bring up the bridge to allow it to switch packets
processutils.execute('ip', 'link', 'set', bridge, 'up')
set_interface_state(bridge, 'up')
@privsep.vif_plug.entrypoint
@ -197,6 +201,12 @@ def _set_device_mtu(dev, mtu):
check_exit_code=[0, 2, 254])
@privsep.vif_plug.entrypoint
def set_interface_state(interface_name, port_state):
processutils.execute('ip', 'link', 'set', interface_name, port_state,
check_exit_code=[0, 2, 254])
@privsep.vif_plug.entrypoint
def _set_mtu_request(dev, mtu, timeout=None):
args = ['--', 'set', 'interface', dev,
@ -212,3 +222,107 @@ def _ovs_supports_mtu_requests(timeout=None):
' a column whose name matches "mtu_request"'):
return False
return True
def get_representor_port(pf_ifname, vf_num):
"""Get the representor netdevice which is corresponding to the VF.
This method gets PF interface name and number of VF. It iterates over all
the interfaces under the PF location and looks for interface that has the
VF number in the phys_port_name. That interface is the representor for
the requested VF.
"""
pf_path = "/sys/class/net/%s" % pf_ifname
pf_sw_id_file = os.path.join(pf_path, "phys_switch_id")
pf_sw_id = None
try:
with open(pf_sw_id_file, 'r') as fd:
pf_sw_id = fd.readline().rstrip()
except (OSError, IOError):
raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)
pf_subsystem_file = os.path.join(pf_path, "subsystem")
try:
devices = os.listdir(pf_subsystem_file)
except (OSError, IOError):
raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)
for device in devices:
if device == pf_ifname:
continue
device_path = "/sys/class/net/%s" % device
device_sw_id_file = os.path.join(device_path, "phys_switch_id")
try:
with open(device_sw_id_file, 'r') as fd:
device_sw_id = fd.readline().rstrip()
except (OSError, IOError):
continue
if device_sw_id != pf_sw_id:
continue
device_port_name_file = (
os.path.join(device_path, 'phys_port_name'))
if not os.path.isfile(device_port_name_file):
continue
try:
with open(device_sw_id_file, 'r') as fd:
representor_num = fd.readline().rstrip()
except (OSError, IOError):
continue
try:
if int(representor_num) == int(vf_num):
return device
except (ValueError):
continue
raise exception.RepresentorNotFound(ifname=pf_ifname, vf_num=vf_num)
def _get_sysfs_netdev_path(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(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 or that of the VF
itself based on the argument of pf_interface.
"""
dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
try:
dev_info = os.listdir(dev_path)
return dev_info.pop()
except Exception:
raise exception.PciDeviceNotFoundById(id=pci_addr)
def get_vf_num_by_pci_address(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 number can be obtained from the PCI device filesystem.
"""
virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
vf_num = None
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:
raise exception.PciDeviceNotFoundById(id=pci_addr)
return vf_num

View File

@ -79,7 +79,11 @@ class OvsPlugin(plugin.PluginBase):
objects.host_info.HostVIFInfo(
vif_object_name=objects.vif.VIFVHostUser.__name__,
min_version="1.0",
max_version="1.0")
max_version="1.0"),
objects.host_info.HostVIFInfo(
vif_object_name=objects.vif.VIFHostDevice.__name__,
min_version="1.0",
max_version="1.0"),
])
def _get_mtu(self, vif):
@ -151,6 +155,17 @@ class OvsPlugin(plugin.PluginBase):
constants.OVS_DATAPATH_SYSTEM)
self._create_vif_port(vif, vif.id, instance_info)
def _plug_vf_passthrough(self, vif, instance_info):
linux_net.ensure_ovs_bridge(
vif.network.bridge, constants.OVS_DATAPATH_SYSTEM)
pci_slot = vif.dev_address
pf_ifname = linux_net.get_ifname_by_pci_address(
pci_slot, pf_interface=True)
vf_num = linux_net.get_vf_num_by_pci_address(pci_slot)
representor = linux_net.get_representor_port(pf_ifname, vf_num)
linux_net.set_interface_state(representor, 'up')
self._create_vif_port(vif, representor, instance_info)
def plug(self, vif, instance_info):
if not hasattr(vif, "port_profile"):
raise exception.MissingPortProfile()
@ -172,6 +187,8 @@ class OvsPlugin(plugin.PluginBase):
self._plug_vif_windows(vif, instance_info)
elif isinstance(vif, objects.vif.VIFVHostUser):
self._plug_vhostuser(vif, instance_info)
elif isinstance(vif, objects.vif.VIFHostDevice):
self._plug_vf_passthrough(vif, instance_info)
def _unplug_vhostuser(self, vif, instance_info):
linux_net.delete_ovs_vif_port(vif.network.bridge,
@ -200,6 +217,16 @@ class OvsPlugin(plugin.PluginBase):
linux_net.delete_ovs_vif_port(vif.network.bridge, vif.id,
timeout=self.config.ovs_vsctl_timeout)
def _unplug_vf_passthrough(self, vif, instance_info):
"""Remove port from OVS."""
pci_slot = vif.dev_address
pf_ifname = linux_net.get_ifname_by_pci_address(pci_slot,
pf_interface=True)
vf_num = linux_net.get_vf_num_by_pci_address(pci_slot)
representor = linux_net.get_representor_port(pf_ifname, vf_num)
linux_net.delete_ovs_vif_port(vif.network.bridge, representor)
linux_net.set_interface_state(representor, 'down')
def unplug(self, vif, instance_info):
if not hasattr(vif, "port_profile"):
raise exception.MissingPortProfile()
@ -218,3 +245,5 @@ class OvsPlugin(plugin.PluginBase):
self._unplug_vif_windows(vif, instance_info)
elif isinstance(vif, objects.vif.VIFVHostUser):
self._unplug_vhostuser(vif, instance_info)
elif isinstance(vif, objects.vif.VIFHostDevice):
self._unplug_vf_passthrough(vif, instance_info)

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import glob
import mock
import os.path
import testtools
@ -17,6 +18,7 @@ import testtools
from oslo_concurrency import processutils
from vif_plug_ovs import constants
from vif_plug_ovs import exception
from vif_plug_ovs import linux_net
from vif_plug_ovs import privsep
@ -34,7 +36,8 @@ class LinuxNetTest(testtools.TestCase):
linux_net.ensure_bridge("br0")
self.assertEqual(mock_execute.mock_calls, [
mock.call('ip', 'link', 'set', 'br0', 'up'),
mock.call('ip', 'link', 'set', 'br0', 'up',
check_exit_code=[0, 2, 254]),
])
self.assertEqual(mock_dev_exists.mock_calls, [
mock.call("br0"),
@ -53,7 +56,8 @@ class LinuxNetTest(testtools.TestCase):
mock.call('brctl', 'stp', 'br0', "off"),
mock.call('tee', '/sys/class/net/br0/bridge/multicast_snooping',
check_exit_code=[0, 1], process_input='0'),
mock.call('ip', 'link', 'set', 'br0', 'up'),
mock.call('ip', 'link', 'set', 'br0', 'up',
check_exit_code=[0, 2, 254]),
])
self.assertEqual(mock_dev_exists.mock_calls, [
mock.call("br0")
@ -74,7 +78,8 @@ class LinuxNetTest(testtools.TestCase):
check_exit_code=[0, 1], process_input='0'),
mock.call('tee', '/proc/sys/net/ipv6/conf/br0/disable_ipv6',
check_exit_code=[0, 1], process_input='1'),
mock.call('ip', 'link', 'set', 'br0', 'up'),
mock.call('ip', 'link', 'set', 'br0', 'up',
check_exit_code=[0, 2, 254]),
])
self.assertEqual(mock_dev_exists.mock_calls, [
mock.call("br0")
@ -296,3 +301,149 @@ class LinuxNetTest(testtools.TestCase):
result = linux_net._ovs_supports_mtu_requests(timeout=timeout)
mock_vsctl.assert_called_with(args, timeout=timeout)
self.assertTrue(result)
@mock.patch('six.moves.builtins.open')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'listdir')
def test_get_representor_port(self, mock_listdir, mock_isfile, mock_open):
mock_listdir.return_value = [
'pf_ifname', 'rep_vf_1', 'rep_vf_2'
]
mock_isfile.side_effect = [True, True]
mock_open.return_value.__enter__ = lambda s: s
readline_mock = mock_open.return_value.readline
readline_mock.side_effect = (
['pf_sw_id', 'pf_sw_id', '1', 'pf_sw_id', '2'])
ifname = linux_net.get_representor_port('pf_ifname', '2')
self.assertEqual('rep_vf_2', ifname)
@mock.patch('six.moves.builtins.open')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'listdir')
def test_get_representor_port_2_pfs(
self, mock_listdir, mock_isfile, mock_open):
mock_listdir.return_value = [
'pf_ifname1', 'pf_ifname2', 'rep_pf1_vf_1', 'rep_pf1_vf_2',
'rep_pf2_vf_1', 'rep_pf2_vf_2',
]
mock_isfile.side_effect = [True, True, True, True]
mock_open.return_value.__enter__ = lambda s: s
readline_mock = mock_open.return_value.readline
readline_mock.side_effect = (
['pf1_sw_id', 'pf1_sw_id', 'pf2_sw_id', '1', 'pf1_sw_id', '2'])
ifname = linux_net.get_representor_port('pf_ifname1', '2')
self.assertEqual('rep_pf1_vf_2', ifname)
@mock.patch('six.moves.builtins.open')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'listdir')
def test_get_representor_port_not_found(
self, mock_listdir, mock_isfile, mock_open):
mock_listdir.return_value = [
'pf_ifname', 'rep_vf_1', 'rep_vf_2'
]
mock_isfile.side_effect = [True, True]
mock_open.return_value.__enter__ = lambda s: s
readline_mock = mock_open.return_value.readline
readline_mock.side_effect = (
['pf_sw_id', 'pf_sw_id', '1', 'pf_sw_id', '2'])
self.assertRaises(
exception.RepresentorNotFound,
linux_net.get_representor_port,
'pf_ifname', '3'),
@mock.patch('six.moves.builtins.open')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'listdir')
def test_get_representor_port_exception_io_error(
self, mock_listdir, mock_isfile, mock_open):
mock_listdir.return_value = [
'pf_ifname', 'rep_vf_1', 'rep_vf_2'
]
mock_isfile.side_effect = [True, True]
mock_open.return_value.__enter__ = lambda s: s
readline_mock = mock_open.return_value.readline
readline_mock.side_effect = (
['pf_sw_id', 'pf_sw_id', IOError(), 'pf_sw_id', '2'])
self.assertRaises(
exception.RepresentorNotFound,
linux_net.get_representor_port,
'pf_ifname', '3')
@mock.patch('six.moves.builtins.open')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'listdir')
def test_get_representor_port_exception_value_error(
self, mock_listdir, mock_isfile, mock_open):
mock_listdir.return_value = [
'pf_ifname', 'rep_vf_1', 'rep_vf_2'
]
mock_isfile.side_effect = [True, True]
mock_open.return_value.__enter__ = lambda s: s
readline_mock = mock_open.return_value.readline
readline_mock.side_effect = (
['pf_sw_id', 'pf_sw_id', '1', 'pf_sw_id', 'a'])
self.assertRaises(
exception.RepresentorNotFound,
linux_net.get_representor_port,
'pf_ifname', '3')
@mock.patch.object(os, 'listdir')
def test_physical_function_inferface_name(self, mock_listdir):
mock_listdir.return_value = ['foo', 'bar']
ifname = linux_net.get_ifname_by_pci_address(
'0000:00:00.1', pf_interface=True)
self.assertEqual(ifname, 'bar')
@mock.patch.object(os, 'listdir')
def test_virtual_function_inferface_name(self, mock_listdir):
mock_listdir.return_value = ['foo', 'bar']
ifname = linux_net.get_ifname_by_pci_address(
'0000:00:00.1', pf_interface=False)
self.assertEqual(ifname, 'bar')
@mock.patch.object(os, 'listdir')
def test_get_ifname_by_pci_address_exception(self, mock_listdir):
mock_listdir.side_effect = OSError('No such file or directory')
self.assertRaises(
exception.PciDeviceNotFoundById,
linux_net.get_ifname_by_pci_address,
'0000:00:00.1'
)
@mock.patch.object(os, 'readlink')
@mock.patch.object(glob, 'iglob')
def test_vf_number_found(self, mock_iglob, mock_readlink):
mock_iglob.return_value = [
'/sys/bus/pci/devices/0000:00:00.1/physfn/virtfn3',
]
mock_readlink.return_value = '../../0000:00:00.1'
vf_num = linux_net.get_vf_num_by_pci_address('0000:00:00.1')
self.assertEqual(vf_num, '3')
@mock.patch.object(os, 'readlink')
@mock.patch.object(glob, 'iglob')
def test_vf_number_not_found(self, mock_iglob, mock_readlink):
mock_iglob.return_value = [
'/sys/bus/pci/devices/0000:00:00.1/physfn/virtfn3',
]
mock_readlink.return_value = '../../0000:00:00.2'
self.assertRaises(
exception.PciDeviceNotFoundById,
linux_net.get_vf_num_by_pci_address,
'0000:00:00.1'
)
@mock.patch.object(os, 'readlink')
@mock.patch.object(glob, 'iglob')
def test_get_vf_num_by_pci_address_exception(
self, mock_iglob, mock_readlink):
mock_iglob.return_value = [
'/sys/bus/pci/devices/0000:00:00.1/physfn/virtfn3',
]
mock_readlink.side_effect = OSError('No such file or directory')
self.assertRaises(
exception.PciDeviceNotFoundById,
linux_net.get_vf_num_by_pci_address,
'0000:00:00.1'
)

View File

@ -14,6 +14,7 @@ import mock
import testtools
from os_vif import objects
from os_vif.objects import fields
from vif_plug_ovs import constants
from vif_plug_ovs import linux_net
@ -88,6 +89,15 @@ class PluginTest(testtools.TestCase):
mode='server', # qemu server mode <=> ovs client mode
port_profile=self.profile_ovs)
self.vif_ovs_vf_passthrough = objects.vif.VIFHostDevice(
id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
address='ca:fe:de:ad:be:ef',
network=self.network_ovs,
dev_type=fields.VIFHostDeviceDevType.ETHERNET,
dev_address='0002:24:12.3',
bridge_name='br-int',
port_profile=self.profile_ovs)
self.instance = objects.instance_info.InstanceInfo(
name='demo',
uuid='f0000000-0000-0000-0000-000000000001')
@ -133,6 +143,7 @@ class PluginTest(testtools.TestCase):
ensure_ovs_bridge.assert_called_once_with(
self.vif_ovs.network.bridge, constants.OVS_DATAPATH_SYSTEM)
@mock.patch.object(linux_net, 'set_interface_state')
@mock.patch.object(linux_net, 'ensure_ovs_bridge')
@mock.patch.object(ovs.OvsPlugin, '_update_vif_port')
@mock.patch.object(ovs.OvsPlugin, '_create_vif_port')
@ -145,7 +156,8 @@ class PluginTest(testtools.TestCase):
def test_plug_ovs_bridge(self, mock_sys, ensure_bridge, device_exists,
create_veth_pair, update_veth_pair,
add_bridge_port, _create_vif_port,
_update_vif_port, ensure_ovs_bridge):
_update_vif_port, ensure_ovs_bridge,
set_interface_state):
calls = {
'device_exists': [mock.call('qvob679325f-ca')],
'create_veth_pair': [mock.call('qvbb679325f-ca',
@ -155,6 +167,8 @@ class PluginTest(testtools.TestCase):
'qvob679325f-ca',
1500)],
'ensure_bridge': [mock.call('qbrvif-xxx-yyy')],
'set_interface_state': [mock.call('qbrvif-xxx-yyy',
'up')],
'add_bridge_port': [mock.call('qbrvif-xxx-yyy',
'qvbb679325f-ca')],
'_update_vif_port': [mock.call(self.vif_ovs_hybrid,
@ -309,3 +323,79 @@ class PluginTest(testtools.TestCase):
plugin = ovs.OvsPlugin.load("ovs")
plugin.unplug(self.vif_vhostuser, self.instance)
delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])
@mock.patch.object(linux_net, 'ensure_ovs_bridge')
@mock.patch.object(linux_net, 'get_ifname_by_pci_address')
@mock.patch.object(linux_net, 'get_vf_num_by_pci_address')
@mock.patch.object(linux_net, 'get_representor_port')
@mock.patch.object(linux_net, 'set_interface_state')
@mock.patch.object(ovs.OvsPlugin, '_create_vif_port')
def test_plug_ovs_vf_passthrough(self, _create_vif_port,
set_interface_state,
get_representor_port,
get_vf_num_by_pci_address,
get_ifname_by_pci_address,
ensure_ovs_bridge):
get_ifname_by_pci_address.return_value = 'eth0'
get_vf_num_by_pci_address.return_value = '2'
get_representor_port.return_value = 'eth0_2'
calls = {
'ensure_ovs_bridge': [mock.call('br0',
constants.OVS_DATAPATH_SYSTEM)],
'get_ifname_by_pci_address': [mock.call('0002:24:12.3',
pf_interface=True)],
'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')],
'get_representor_port': [mock.call('eth0', '2')],
'set_interface_state': [mock.call('eth0_2', 'up')],
'_create_vif_port': [mock.call(
self.vif_ovs_vf_passthrough, 'eth0_2',
self.instance)]
}
plugin = ovs.OvsPlugin.load("ovs")
plugin.plug(self.vif_ovs_vf_passthrough, self.instance)
ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge'])
get_ifname_by_pci_address.assert_has_calls(
calls['get_ifname_by_pci_address'])
get_vf_num_by_pci_address.assert_has_calls(
calls['get_vf_num_by_pci_address'])
get_representor_port.assert_has_calls(
calls['get_representor_port'])
set_interface_state.assert_has_calls(calls['set_interface_state'])
_create_vif_port.assert_has_calls(calls['_create_vif_port'])
@mock.patch.object(linux_net, 'get_ifname_by_pci_address')
@mock.patch.object(linux_net, 'get_vf_num_by_pci_address')
@mock.patch.object(linux_net, 'get_representor_port')
@mock.patch.object(linux_net, 'set_interface_state')
@mock.patch.object(linux_net, 'delete_ovs_vif_port')
def test_unplug_ovs_vf_passthrough(self, delete_ovs_vif_port,
set_interface_state,
get_representor_port,
get_vf_num_by_pci_address,
get_ifname_by_pci_address):
calls = {
'get_ifname_by_pci_address': [mock.call('0002:24:12.3',
pf_interface=True)],
'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')],
'get_representor_port': [mock.call('eth0', '2')],
'set_interface_state': [mock.call('eth0_2', 'down')],
'delete_ovs_vif_port': [mock.call('br0', 'eth0_2')]
}
get_ifname_by_pci_address.return_value = 'eth0'
get_vf_num_by_pci_address.return_value = '2'
get_representor_port.return_value = 'eth0_2'
plugin = ovs.OvsPlugin.load("ovs")
plugin.unplug(self.vif_ovs_vf_passthrough, self.instance)
get_ifname_by_pci_address.assert_has_calls(
calls['get_ifname_by_pci_address'])
get_vf_num_by_pci_address.assert_has_calls(
calls['get_vf_num_by_pci_address'])
get_representor_port.assert_has_calls(
calls['get_representor_port'])
delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])
set_interface_state.assert_has_calls(calls['set_interface_state'])