OVS DPDK port representors support

Adds support for OVS DPDK port representors[1], a direct
port on a netdev datapath is considered a DPDK representor port.

Using VIFHostDevice object with netdev in its profile means the port is
a DPDK representor port.

[1] http://docs.openvswitch.org/en/latest/topics/dpdk/phy/#representors

Closes-Bug: #1829734
Change-Id: I78e7dadfa44ac7e0ba6c9f31b3070011e783589f
This commit is contained in:
Hamdy Khader 2019-05-13 13:47:26 +03:00
parent 1eef2d8a58
commit 8883e3f305
6 changed files with 186 additions and 19 deletions

View File

@ -22,5 +22,7 @@ OVS_DATAPATH_NETDEV = 'netdev'
PLATFORM_LINUX = 'linux2' PLATFORM_LINUX = 'linux2'
PLATFORM_WIN32 = 'win32' PLATFORM_WIN32 = 'win32'
OVS_DPDK_INTERFACE_TYPE = 'dpdk'
# Neutron dead VLAN. # Neutron dead VLAN.
DEAD_VLAN = 4095 DEAD_VLAN = 4095

View File

@ -47,6 +47,7 @@ PF_RE = re.compile(r"pf(\d+)", re.IGNORECASE)
PF_FUNC_RE = re.compile(r"\.(\d+)", 0) PF_FUNC_RE = re.compile(r"\.(\d+)", 0)
_SRIOV_TOTALVFS = "sriov_totalvfs" _SRIOV_TOTALVFS = "sriov_totalvfs"
NIC_NAME_LEN = 14
def _update_device_mtu(dev, mtu): def _update_device_mtu(dev, mtu):
@ -370,3 +371,18 @@ def get_vf_num_by_pci_address(pci_addr):
if vf_num is None: if vf_num is None:
raise exception.PciDeviceNotFoundById(id=pci_addr) raise exception.PciDeviceNotFoundById(id=pci_addr)
return vf_num return vf_num
def get_dpdk_representor_port_name(port_id):
devname = "vfr" + port_id
return devname[:NIC_NAME_LEN]
def get_pf_pci_from_vf(vf_pci):
"""Get physical function PCI address of a VF
:param vf_pci: the PCI address of the VF
:return: the PCI address of the PF
"""
physfn_path = os.readlink("/sys/bus/pci/devices/%s/physfn" % vf_pci)
return os.path.basename(physfn_path)

View File

@ -252,16 +252,28 @@ class OvsPlugin(plugin.PluginBase):
vif.port_profile.create_port): vif.port_profile.create_port):
self._create_vif_port(vif, vif.vif_name, instance_info) self._create_vif_port(vif, vif.vif_name, instance_info)
def _plug_vf_passthrough(self, vif, instance_info): def _plug_vf(self, vif, instance_info):
self.ovsdb.ensure_ovs_bridge( datapath = self._get_vif_datapath_type(vif)
vif.network.bridge, constants.OVS_DATAPATH_SYSTEM) self.ovsdb.ensure_ovs_bridge(vif.network.bridge, datapath)
pci_slot = vif.dev_address pci_slot = vif.dev_address
pf_ifname = linux_net.get_ifname_by_pci_address(
pci_slot, pf_interface=True, switchdev=True)
vf_num = linux_net.get_vf_num_by_pci_address(pci_slot) vf_num = linux_net.get_vf_num_by_pci_address(pci_slot)
representor = linux_net.get_representor_port(pf_ifname, vf_num) args = []
linux_net.set_interface_state(representor, 'up') kwargs = {}
self._create_vif_port(vif, representor, instance_info) if datapath == constants.OVS_DATAPATH_SYSTEM:
pf_ifname = linux_net.get_ifname_by_pci_address(
pci_slot, pf_interface=True, switchdev=True)
representor = linux_net.get_representor_port(pf_ifname, vf_num)
linux_net.set_interface_state(representor, 'up')
args = [vif, representor, instance_info]
else:
representor = linux_net.get_dpdk_representor_port_name(
vif.id)
pf_pci = linux_net.get_pf_pci_from_vf(pci_slot)
args = [vif, representor, instance_info]
kwargs = {'interface_type': constants.OVS_DPDK_INTERFACE_TYPE,
'pf_pci': pf_pci,
'vf_num': vf_num}
self._create_vif_port(*args, **kwargs)
def plug(self, vif, instance_info): def plug(self, vif, instance_info):
if not hasattr(vif, "port_profile"): if not hasattr(vif, "port_profile"):
@ -284,7 +296,7 @@ class OvsPlugin(plugin.PluginBase):
elif isinstance(vif, objects.vif.VIFVHostUser): elif isinstance(vif, objects.vif.VIFVHostUser):
self._plug_vhostuser(vif, instance_info) self._plug_vhostuser(vif, instance_info)
elif isinstance(vif, objects.vif.VIFHostDevice): elif isinstance(vif, objects.vif.VIFHostDevice):
self._plug_vf_passthrough(vif, instance_info) self._plug_vf(vif, instance_info)
def _unplug_vhostuser(self, vif, instance_info): def _unplug_vhostuser(self, vif, instance_info):
self.ovsdb.delete_ovs_vif_port(vif.network.bridge, self.ovsdb.delete_ovs_vif_port(vif.network.bridge,
@ -317,19 +329,26 @@ class OvsPlugin(plugin.PluginBase):
# so this is not removed. # so this is not removed.
self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.vif_name) self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.vif_name)
def _unplug_vf_passthrough(self, vif, instance_info): def _unplug_vf(self, vif):
"""Remove port from OVS.""" """Remove port from OVS."""
pci_slot = vif.dev_address datapath = self._get_vif_datapath_type(vif)
pf_ifname = linux_net.get_ifname_by_pci_address(pci_slot, if datapath == constants.OVS_DATAPATH_SYSTEM:
pf_interface=True, switchdev=True) pci_slot = vif.dev_address
vf_num = linux_net.get_vf_num_by_pci_address(pci_slot) pf_ifname = linux_net.get_ifname_by_pci_address(
representor = linux_net.get_representor_port(pf_ifname, vf_num) pci_slot, pf_interface=True, switchdev=True)
vf_num = linux_net.get_vf_num_by_pci_address(pci_slot)
representor = linux_net.get_representor_port(pf_ifname, vf_num)
else:
representor = linux_net.get_dpdk_representor_port_name(
vif.id)
# The representor interface can't be deleted because it bind the # The representor interface can't be deleted because it bind the
# SR-IOV VF, therefore we just need to remove it from the ovs bridge # SR-IOV VF, therefore we just need to remove it from the ovs bridge
# and set the status to down # and set the status to down
self.ovsdb.delete_ovs_vif_port( self.ovsdb.delete_ovs_vif_port(
vif.network.bridge, representor, delete_netdev=False) vif.network.bridge, representor, delete_netdev=False)
linux_net.set_interface_state(representor, 'down') if datapath == constants.OVS_DATAPATH_SYSTEM:
linux_net.set_interface_state(representor, 'down')
def unplug(self, vif, instance_info): def unplug(self, vif, instance_info):
if not hasattr(vif, "port_profile"): if not hasattr(vif, "port_profile"):
@ -352,4 +371,4 @@ class OvsPlugin(plugin.PluginBase):
elif isinstance(vif, objects.vif.VIFVHostUser): elif isinstance(vif, objects.vif.VIFVHostUser):
self._unplug_vhostuser(vif, instance_info) self._unplug_vhostuser(vif, instance_info)
elif isinstance(vif, objects.vif.VIFHostDevice): elif isinstance(vif, objects.vif.VIFHostDevice):
self._unplug_vf_passthrough(vif, instance_info) self._unplug_vf(vif)

View File

@ -62,7 +62,27 @@ class BaseOVS(object):
def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id, def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id,
mtu=None, interface_type=None, mtu=None, interface_type=None,
vhost_server_path=None, tag=None): vhost_server_path=None, tag=None,
pf_pci=None, vf_num=None):
"""Create OVS port
:param bridge: bridge name to create the port on.
:param dev: port name.
:param iface_id: port ID.
:param mac: port MAC.
:param instance_id: VM ID on which the port is attached to.
:param mtu: port MTU.
:param interface_type: OVS interface type.
:param vhost_server_path: path to socket file of vhost server.
:param tag: OVS interface tag.
:param pf_pci: PCI address of PF for dpdk representor port.
:param vf_num: VF number of PF for dpdk representor port.
.. note:: create DPDK representor port by setting all three values:
`interface_type`, `pf_pci` and `vf_num`. if interface type is
not `OVS_DPDK_INTERFACE_TYPE` then `pf_pci` and `vf_num` values
are ignored.
"""
external_ids = {'iface-id': iface_id, external_ids = {'iface-id': iface_id,
'iface-status': 'active', 'iface-status': 'active',
'attached-mac': mac, 'attached-mac': mac,
@ -75,7 +95,12 @@ class BaseOVS(object):
{'vhost-server-path': vhost_server_path})) {'vhost-server-path': vhost_server_path}))
if tag: if tag:
col_values.append(('tag', tag)) col_values.append(('tag', tag))
if (interface_type == constants.OVS_DPDK_INTERFACE_TYPE and
pf_pci and vf_num):
devargs_string = "{PF_PCI},representor=[{VF_NUM}]".format(
PF_PCI=pf_pci, VF_NUM=vf_num)
col_values.append(('options',
{'dpdk-devargs': devargs_string}))
with self.ovsdb.transaction() as txn: with self.ovsdb.transaction() as txn:
txn.add(self.ovsdb.add_port(bridge, dev)) txn.add(self.ovsdb.add_port(bridge, dev))
txn.add(self.ovsdb.db_set('Interface', dev, *col_values)) txn.add(self.ovsdb.db_set('Interface', dev, *col_values))

View File

@ -115,6 +115,38 @@ class BaseOVSTest(testtools.TestCase):
mock_update_device_mtu.assert_has_calls( mock_update_device_mtu.assert_has_calls(
[mock.call(device, mtu, interface_type=interface_type)]) [mock.call(device, mtu, interface_type=interface_type)])
def test_create_ovs_vif_port_type_dpdk(self):
iface_id = 'iface_id'
mac = 'ca:fe:ca:fe:ca:fe'
instance_id = uuidutils.generate_uuid()
interface_type = constants.OVS_DPDK_INTERFACE_TYPE
device = 'device'
bridge = 'bridge'
mtu = 1500
pf_pci = '0000:02:00.1'
vf_num = '0'
external_ids = {'iface-id': iface_id,
'iface-status': 'active',
'attached-mac': mac,
'vm-uuid': instance_id}
values = [('external_ids', external_ids),
('type', interface_type),
('options', {'dpdk-devargs':
'0000:02:00.1,representor=[0]'})]
with mock.patch.object(self.br, 'update_device_mtu',
return_value=True) as mock_update_device_mtu, \
mock.patch.object(self.br, '_ovs_supports_mtu_requests',
return_value=True):
self.br.create_ovs_vif_port(bridge, device, iface_id, mac,
instance_id, mtu=mtu,
interface_type=interface_type,
pf_pci=pf_pci, vf_num=vf_num)
self.mock_add_port.assert_has_calls([mock.call(bridge, device)])
self.mock_db_set.assert_has_calls(
[mock.call('Interface', device, *values)])
mock_update_device_mtu.assert_has_calls(
[mock.call(device, mtu, interface_type=interface_type)])
def test_update_ovs_vif_port(self): def test_update_ovs_vif_port(self):
with mock.patch.object(self.br, 'update_device_mtu') as \ with mock.patch.object(self.br, 'update_device_mtu') as \
mock_update_device_mtu: mock_update_device_mtu:

View File

@ -61,6 +61,10 @@ class PluginTest(testtools.TestCase):
interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
datapath_type='netdev') datapath_type='netdev')
self.profile_ovs_system = objects.vif.VIFPortProfileOpenVSwitch(
interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
datapath_type='system')
self.profile_ovs_smart_nic = objects.vif.VIFPortProfileOpenVSwitch( self.profile_ovs_smart_nic = objects.vif.VIFPortProfileOpenVSwitch(
interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa', interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
create_port=True) create_port=True)
@ -114,6 +118,14 @@ class PluginTest(testtools.TestCase):
dev_type=fields.VIFHostDeviceDevType.ETHERNET, dev_type=fields.VIFHostDeviceDevType.ETHERNET,
dev_address='0002:24:12.3', dev_address='0002:24:12.3',
bridge_name='br-int', bridge_name='br-int',
port_profile=self.profile_ovs_system)
self.vif_ovs_vf_dpdk = 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',
port_profile=self.profile_ovs) port_profile=self.profile_ovs)
self.instance = objects.instance_info.InstanceInfo( self.instance = objects.instance_info.InstanceInfo(
@ -470,3 +482,64 @@ class PluginTest(testtools.TestCase):
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.unplug(self.vif_ovs_smart_nic, self.instance) plugin.unplug(self.vif_ovs_smart_nic, self.instance)
delete_port.assert_called_once() delete_port.assert_called_once()
@mock.patch.object(linux_net, 'get_dpdk_representor_port_name')
@mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
@mock.patch.object(linux_net, 'get_vf_num_by_pci_address')
@mock.patch.object(linux_net, 'get_pf_pci_from_vf')
@mock.patch.object(ovs.OvsPlugin, '_create_vif_port')
def test_plug_ovs_vf_dpdk(self, _create_vif_port,
get_pf_pci_from_vf,
get_vf_num_by_pci_address,
ensure_ovs_bridge,
get_dpdk_representor_port_name):
pf_pci = self.vif_ovs_vf_dpdk.dev_address
devname = 'vfrb679325f-ca'
get_vf_num_by_pci_address.return_value = '2'
get_pf_pci_from_vf.return_value = pf_pci
get_dpdk_representor_port_name.return_value = devname
calls = {
'ensure_ovs_bridge': [mock.call('br0',
constants.OVS_DATAPATH_NETDEV)],
'get_vf_num_by_pci_address': [mock.call('0002:24:12.3')],
'get_pf_pci_from_vf': [mock.call(pf_pci)],
'get_dpdk_representor_port_name': [mock.call(
self.vif_ovs_vf_dpdk.id)],
'_create_vif_port': [mock.call(
self.vif_ovs_vf_dpdk,
devname,
self.instance,
interface_type='dpdk',
pf_pci=pf_pci,
vf_num='2')]}
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.plug(self.vif_ovs_vf_dpdk, self.instance)
ensure_ovs_bridge.assert_has_calls(
calls['ensure_ovs_bridge'])
get_vf_num_by_pci_address.assert_has_calls(
calls['get_vf_num_by_pci_address'])
get_pf_pci_from_vf.assert_has_calls(
calls['get_pf_pci_from_vf'])
get_dpdk_representor_port_name.assert_has_calls(
calls['get_dpdk_representor_port_name'])
_create_vif_port.assert_has_calls(
calls['_create_vif_port'])
@mock.patch.object(linux_net, 'get_dpdk_representor_port_name')
@mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port')
def test_unplug_ovs_vf_dpdk(self, delete_ovs_vif_port,
get_dpdk_representor_port_name):
devname = 'vfrb679325f-ca'
get_dpdk_representor_port_name.return_value = devname
calls = {
'get_dpdk_representor_port_name': [mock.call(
self.vif_ovs_vf_dpdk.id)],
'delete_ovs_vif_port': [mock.call('br0', devname,
delete_netdev=False)]}
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.unplug(self.vif_ovs_vf_dpdk, self.instance)
get_dpdk_representor_port_name.assert_has_calls(
calls['get_dpdk_representor_port_name'])
delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])