network: update pci request spec to handle trusted tags

Read port info to extract the trusted tag from binding profile then
set it in the request spec.

The test_create_pci_requests_for_sriov_ports test is updated and
re-written in mock.

Implements blueprint sriov-trusted-vfs
Signed-off-by: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@redhat.com>
Change-Id: Iaea17b7a02d53463d2b815bdc5f4e83e422188eb
This commit is contained in:
Sahid Orentino Ferdjaoui 2017-04-21 13:43:01 +02:00 committed by Matt Riedemann
parent f9ddddc358
commit 88e21d8e5e
5 changed files with 155 additions and 44 deletions

View File

@ -92,7 +92,7 @@ Possible values:
* "devname": Device name of the device (for e.g. interface name). Not all
PCI devices have a name.
* "<tag>": Additional <tag> and <tag_value> used for matching PCI devices.
Supported <tag>: "physical_network".
Supported <tag>: "physical_network", "trusted".
The address key supports traditional glob style and regular expression
syntax. Valid examples are:
@ -116,6 +116,8 @@ Possible values:
"bus": "02", "slot": "0[1-2]",
"function": ".*"},
"physical_network":"physnet1"}
passthrough_whitelist = {"devname": "eth0", "physical_network":"physnet1",
"trusted": "true"}
The following are invalid, as they specify mutually exclusive options:

View File

@ -23,6 +23,7 @@ from neutronclient.common import exceptions as neutron_client_exc
from neutronclient.v2_0 import client as clientv20
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from oslo_utils import uuidutils
import six
@ -1552,21 +1553,44 @@ class API(base_api.NetworkAPI):
phynet_name = net.get('provider:physical_network')
return phynet_name
@staticmethod
def _get_trusted_mode_from_port(port):
"""Returns whether trusted mode is requested
If port binding does not provide any information about trusted
status this function is returning None
"""
value = _get_binding_profile(port).get('trusted')
if value is not None:
# This allows the user to specify things like '1' and 'yes' in
# the port binding profile and we can handle it as a boolean.
return strutils.bool_from_string(value)
def _get_port_vnic_info(self, context, neutron, port_id):
"""Retrieve port vnic info
Invoked with a valid port_id.
Return vnic type and the attached physical network name.
:param context: The request context
:param neutron: The Neutron client
:param port_id: The id of port to be queried
:return: A triplet composed of the VNIC type (see:
network_model.VNIC_TYPES_*), the attached physical
network name, for SR-IOV whether the port should be
considered as trusted or None for other VNIC types.
"""
trusted = None
phynet_name = None
port = self._show_port(context, port_id, neutron_client=neutron,
fields=['binding:vnic_type', 'network_id'])
fields=['binding:vnic_type', 'network_id',
BINDING_PROFILE])
vnic_type = port.get('binding:vnic_type',
network_model.VNIC_TYPE_NORMAL)
if vnic_type in network_model.VNIC_TYPES_SRIOV:
net_id = port['network_id']
phynet_name = self._get_phynet_info(context, neutron, net_id)
return vnic_type, phynet_name
trusted = self._get_trusted_mode_from_port(port)
return vnic_type, phynet_name, trusted
def create_pci_requests_for_sriov_ports(self, context, pci_requests,
requested_networks):
@ -1581,11 +1605,16 @@ class API(base_api.NetworkAPI):
neutron = get_client(context, admin=True)
for request_net in requested_networks:
phynet_name = None
trusted = None
vnic_type = network_model.VNIC_TYPE_NORMAL
if request_net.port_id:
vnic_type, phynet_name = self._get_port_vnic_info(
vnic_type, phynet_name, trusted = self._get_port_vnic_info(
context, neutron, request_net.port_id)
LOG.debug("Creating PCI device request for port_id=%s, "
"vnic_type=%s, phynet_name=%s, trusted=%s",
request_net.port_id, vnic_type, phynet_name,
trusted)
pci_request_id = None
if vnic_type in network_model.VNIC_TYPES_SRIOV:
# TODO(moshele): To differentiate between the SR-IOV legacy
@ -1598,6 +1627,12 @@ class API(base_api.NetworkAPI):
dev_type = pci_request.DEVICE_TYPE_FOR_VNIC_TYPE.get(vnic_type)
if dev_type:
spec[pci_request.PCI_DEVICE_TYPE_TAG] = dev_type
if trusted is not None:
# We specifically have requested device on a pool
# with a tag trusted set to true or false. We
# convert the value to string since tags are
# compared in that way.
spec[pci_request.PCI_TRUSTED_TAG] = str(trusted)
request = objects.InstancePCIRequest(
count=1,
spec=[spec],

View File

@ -51,6 +51,7 @@ from nova.objects import fields as obj_fields
from nova.pci import utils
PCI_NET_TAG = 'physical_network'
PCI_TRUSTED_TAG = 'trusted'
PCI_DEVICE_TYPE_TAG = 'dev_type'
DEVICE_TYPE_FOR_VNIC_TYPE = {

View File

@ -44,6 +44,7 @@ from nova.network.neutronv2 import constants
from nova import objects
from nova.objects import network_request as net_req_obj
from nova.pci import manager as pci_manager
from nova.pci import request as pci_request
from nova.pci import utils as pci_utils
from nova.pci import whitelist as pci_whitelist
from nova import policy
@ -3146,7 +3147,7 @@ class TestNeutronv2(TestNeutronv2Base):
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
mock_client.show_network.return_value = test_net
vnic_type, phynet_name = api._get_port_vnic_info(
vnic_type, phynet_name, trusted = api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
mock_client.show_network.assert_called_once_with(
@ -3176,7 +3177,7 @@ class TestNeutronv2(TestNeutronv2Base):
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
mock_client.show_network.return_value = test_net
vnic_type, phynet_name = api._get_port_vnic_info(
vnic_type, phynet_name, trusted = api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
mock_client.show_network.assert_called_with(
@ -3228,16 +3229,17 @@ class TestNeutronv2(TestNeutronv2Base):
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
mock_client.show_network.return_value = test_net
vnic_type, phynet_name = api._get_port_vnic_info(
vnic_type, phynet_name, trusted = api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
mock_client.show_port.assert_called_once_with(test_port['port']['id'],
fields=['binding:vnic_type', 'network_id'])
fields=['binding:vnic_type', 'network_id', 'binding:profile'])
mock_client.show_network.assert_called_once_with(
test_port['port']['network_id'],
fields='provider:physical_network')
self.assertEqual(model.VNIC_TYPE_DIRECT, vnic_type)
self.assertEqual('phynet1', phynet_name)
self.assertIsNone(trusted)
def _test_get_port_vnic_info(self, mock_get_client,
binding_vnic_type=None):
@ -3255,13 +3257,14 @@ class TestNeutronv2(TestNeutronv2Base):
mock_get_client.reset_mock()
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
vnic_type, phynet_name = api._get_port_vnic_info(
vnic_type, phynet_name, trusted = api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
mock_client.show_port.assert_called_once_with(test_port['port']['id'],
fields=['binding:vnic_type', 'network_id'])
fields=['binding:vnic_type', 'network_id', 'binding:profile'])
self.assertEqual(model.VNIC_TYPE_NORMAL, vnic_type)
self.assertFalse(phynet_name)
self.assertIsNone(trusted)
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_get_port_vnic_info_2(self, mock_get_client):
@ -3272,38 +3275,6 @@ class TestNeutronv2(TestNeutronv2Base):
def test_get_port_vnic_info_3(self, mock_get_client):
self._test_get_port_vnic_info(mock_get_client)
@mock.patch.object(neutronapi.API, "_get_port_vnic_info")
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_create_pci_requests_for_sriov_ports(self, mock_get_client,
mock_get_port_vnic_info):
api = neutronapi.API()
self.mox.ResetAll()
requested_networks = objects.NetworkRequestList(
objects = [
objects.NetworkRequest(port_id=uuids.portid_1),
objects.NetworkRequest(network_id='net1'),
objects.NetworkRequest(port_id=uuids.portid_2),
objects.NetworkRequest(port_id=uuids.portid_3),
objects.NetworkRequest(port_id=uuids.portid_4),
objects.NetworkRequest(port_id=uuids.portid_5)])
pci_requests = objects.InstancePCIRequests(requests=[])
mock_get_port_vnic_info.side_effect = [
(model.VNIC_TYPE_DIRECT, 'phynet1'),
(model.VNIC_TYPE_NORMAL, ''),
(model.VNIC_TYPE_MACVTAP, 'phynet1'),
(model.VNIC_TYPE_MACVTAP, 'phynet2'),
(model.VNIC_TYPE_DIRECT_PHYSICAL, 'phynet3')
]
api.create_pci_requests_for_sriov_ports(
None, pci_requests, requested_networks)
self.assertEqual(4, len(pci_requests.requests))
has_pci_request_id = [net.pci_request_id is not None for net in
requested_networks.objects]
self.assertEqual(pci_requests.requests[3].spec[0]["dev_type"],
"type-PF")
expected_results = [True, False, False, True, True, True]
self.assertEqual(expected_results, has_pci_request_id)
class TestNeutronv2WithMock(test.TestCase):
"""Used to test Neutron V2 API with mock."""
@ -3315,6 +3286,34 @@ class TestNeutronv2WithMock(test.TestCase):
'fake-user', 'fake-project',
auth_token='bff4a5a6b9eb4ea2a6efec6eefb77936')
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_get_port_vnic_info_trusted(self, mock_get_client):
test_port = {
'port': {'id': 'my_port_id1',
'network_id': 'net-id',
'binding:vnic_type': model.VNIC_TYPE_DIRECT,
'binding:profile': {"trusted": "Yes"},
},
}
test_net = {'network': {'provider:physical_network': 'phynet1'}}
test_ext_list = {'extensions': []}
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
mock_client.show_network.return_value = test_net
vnic_type, phynet_name, trusted = self.api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
mock_client.show_port.assert_called_once_with(test_port['port']['id'],
fields=['binding:vnic_type', 'network_id', 'binding:profile'])
mock_client.show_network.assert_called_once_with(
test_port['port']['network_id'],
fields='provider:physical_network')
self.assertEqual(model.VNIC_TYPE_DIRECT, vnic_type)
self.assertEqual('phynet1', phynet_name)
self.assertTrue(trusted)
@mock.patch('nova.network.neutronv2.api.API._show_port')
def test_deferred_ip_port_immediate_allocation(self, mock_show):
port = {'network_id': 'my_netid1',
@ -4962,6 +4961,48 @@ class TestNeutronv2WithMock(test.TestCase):
self.context, pci_requests, requested_networks)
self.assertFalse(getclient.called)
@mock.patch.object(neutronapi.API, "_get_port_vnic_info")
@mock.patch.object(neutronapi, 'get_client')
def test_create_pci_requests_for_sriov_ports(self, getclient,
mock_get_port_vnic_info):
requested_networks = objects.NetworkRequestList(
objects = [
objects.NetworkRequest(port_id=uuids.portid_1),
objects.NetworkRequest(network_id='net1'),
objects.NetworkRequest(port_id=uuids.portid_2),
objects.NetworkRequest(port_id=uuids.portid_3),
objects.NetworkRequest(port_id=uuids.portid_4),
objects.NetworkRequest(port_id=uuids.portid_5),
objects.NetworkRequest(port_id=uuids.trusted_port)])
pci_requests = objects.InstancePCIRequests(requests=[])
mock_get_port_vnic_info.side_effect = [
(model.VNIC_TYPE_DIRECT, 'phynet1', None),
(model.VNIC_TYPE_NORMAL, '', None),
(model.VNIC_TYPE_MACVTAP, 'phynet1', None),
(model.VNIC_TYPE_MACVTAP, 'phynet2', None),
(model.VNIC_TYPE_DIRECT_PHYSICAL, 'phynet3', None),
(model.VNIC_TYPE_DIRECT, 'phynet4', True)
]
api = neutronapi.API()
api.create_pci_requests_for_sriov_ports(
self.context, pci_requests, requested_networks)
self.assertEqual(5, len(pci_requests.requests))
has_pci_request_id = [net.pci_request_id is not None for net in
requested_networks.objects]
self.assertEqual(pci_requests.requests[3].spec[0]["dev_type"],
"type-PF")
expected_results = [True, False, False, True, True, True, True]
self.assertEqual(expected_results, has_pci_request_id)
# Make sure only the trusted VF has the 'trusted' tag set in the spec.
for pci_req in pci_requests.requests:
spec = pci_req.spec[0]
if spec[pci_request.PCI_NET_TAG] == 'phynet4':
# trusted should be true in the spec for this request
self.assertIn(pci_request.PCI_TRUSTED_TAG, spec)
self.assertEqual('True', spec[pci_request.PCI_TRUSTED_TAG])
else:
self.assertNotIn(pci_request.PCI_TRUSTED_TAG, spec)
@mock.patch.object(neutronapi, 'get_client')
def test_associate_floating_ip_conflict(self, mock_get_client):
"""Tests that if Neutron raises a Conflict we handle it and re-raise

View File

@ -0,0 +1,32 @@
features:
- |
The libvirt compute driver now allows users to create instances
with SR-IOV virtual functions which will be configured as trusted.
The operator will have to create pools of devices with tag
trusted=true.
For example, modify ``/etc/nova/nova.conf`` and set:
.. code-block:: ini
[pci]
passthrough_whitelist = {"devname": "eth0", "trusted": "true",
"physical_network":"sriovnet1"}
Where "eth0" is the interface name related to the physical
function.
Ensure that the version of ``ip-link`` on the compute host supports setting
the trust mode on the device.
Ports from the physical network will have to be created with a
binding profile to match the trusted tag. Only ports with
``binding:vif_type=hw_veb`` and ``binding:vnic_type=direct`` are supported.
.. code-block:: ini
$ neutron port-create <net-id> \
--name sriov_port \
--vnic-type direct \
--binding:profile type=dict trusted=true