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:
parent
f9ddddc358
commit
88e21d8e5e
nova
releasenotes/notes
@ -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:
|
||||
|
||||
|
@ -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],
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
32
releasenotes/notes/trusted-vfs-abee6dff7c9b6940.yaml
Normal file
32
releasenotes/notes/trusted-vfs-abee6dff7c9b6940.yaml
Normal 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
|
Loading…
x
Reference in New Issue
Block a user