diff --git a/nova/conf/pci.py b/nova/conf/pci.py index f8464b48ad..f69571806c 100644 --- a/nova/conf/pci.py +++ b/nova/conf/pci.py @@ -92,7 +92,7 @@ Possible values: * "devname": Device name of the device (for e.g. interface name). Not all PCI devices have a name. * "": Additional and used for matching PCI devices. - Supported : "physical_network". + Supported : "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: diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 505855c8af..6ee5d9d4f1 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -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], diff --git a/nova/pci/request.py b/nova/pci/request.py index d37fcc6a36..c16c491029 100644 --- a/nova/pci/request.py +++ b/nova/pci/request.py @@ -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 = { diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 9c50a63a17..6aeaa488aa 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -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 diff --git a/releasenotes/notes/trusted-vfs-abee6dff7c9b6940.yaml b/releasenotes/notes/trusted-vfs-abee6dff7c9b6940.yaml new file mode 100644 index 0000000000..35339be077 --- /dev/null +++ b/releasenotes/notes/trusted-vfs-abee6dff7c9b6940.yaml @@ -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 \ + --name sriov_port \ + --vnic-type direct \ + --binding:profile type=dict trusted=true \ No newline at end of file