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:
		
				
					committed by
					
						
						Matt Riedemann
					
				
			
			
				
	
			
			
			
						parent
						
							f9ddddc358
						
					
				
				
					commit
					88e21d8e5e
				
			@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user