From af5df70f617b03f86cd3dc898c889a1825630c9e Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 15 Aug 2018 13:33:16 +0800 Subject: [PATCH] Explicitly fail if trying to attach SR-IOV port Attaching SR-IOV ports to existing instances is not supported since the compute service does not perform any kind of PCI device allocation, so we should fail fast with a clear error if attempted. Note that the compute RPC API "attach_interface" method is an RPC call from nova-api to nova-compute so the error raised here will result in a 400 response to the user. Blueprint sriov-interface-attach-detach would need to be implemented to support this use case, and could arguably involve a microversion to indicate when the feature was made available. A related neutron docs patch https://review.openstack.org/695409 is posted for mentioning the limitation with SR-IOV port attach as well. Conflicts due to no having Ifcc327f9f97e57d3d6f0db7045b56ffe60203eb9 and I4440a19370da9807cc8c32b681542c7048c9977e in Pike. Change-Id: Ibbf2bd3cdd45bcd61eebff883c30ded525b2495d Closes-Bug: #1708433 (cherry picked from commit 68011c40ae2ab0900674408a88f62a60a802fef7) (cherry picked from commit e1d55af4089fe6b76680285e36069ab0f57404ab) (cherry picked from commit 7827890421a61a4e2029939cacf4e3fecb95282a) --- doc/source/admin/pci-passthrough.rst | 6 +++-- nova/exception.py | 6 +++++ nova/network/neutronv2/api.py | 28 +++++++++++++++++++---- nova/tests/unit/network/test_neutronv2.py | 12 ++++++++-- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/doc/source/admin/pci-passthrough.rst b/doc/source/admin/pci-passthrough.rst index a130b4ff15ba..7d2a96168cd7 100644 --- a/doc/source/admin/pci-passthrough.rst +++ b/doc/source/admin/pci-passthrough.rst @@ -17,8 +17,10 @@ assigned to only one guest and cannot be shared. .. note:: - For information on attaching virtual SR-IOV devices to guests, refer to the - `Networking Guide`_. + For information on creating servers with virtual SR-IOV devices to guests, + refer to the `Networking Guide`_. Attaching SR-IOV ports to existing servers + is not currently supported, see + `bug 1708433 `_ for details. To enable PCI passthrough, follow the steps below: diff --git a/nova/exception.py b/nova/exception.py index bd2170a308fb..85fe8f8b0f3e 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -837,6 +837,12 @@ class PortUpdateFailed(Invalid): msg_fmt = _("Port update failed for port %(port_id)s: %(reason)s") +class AttachSRIOVPortNotSupported(Invalid): + msg_fmt = _('Attaching SR-IOV port %(port_id)s to server ' + '%(instance_uuid)s is not supported. SR-IOV ports must be ' + 'specified during server creation.') + + class FixedIpExists(NovaException): msg_fmt = _("Fixed IP %(address)s already exists.") diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 819da0637f73..07730171bfb4 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -524,7 +524,7 @@ class API(base_api.NetworkAPI): "for port '%s'"), port_id) def _validate_requested_port_ids(self, context, instance, neutron, - requested_networks): + requested_networks, attach=False): """Processes and validates requested networks for allocation. Iterates over the list of NetworkRequest objects, validating the @@ -535,6 +535,11 @@ class API(base_api.NetworkAPI): :type instance: nova.objects.Instance :param neutron: neutron client session :type neutron: neutronclient.v2_0.client.Client + :param requested_networks: List of user-requested networks and/or ports + :type requested_networks: nova.objects.NetworkRequestList + :param attach: Boolean indicating if a port is being attached to an + existing running instance. Should be False during server create. + :type attach: bool :returns: tuple of: - ports: dict mapping of port id to port dict - ordered_networks: list of nova.objects.NetworkRequest objects @@ -548,6 +553,8 @@ class API(base_api.NetworkAPI): attached to another instance. :raises nova.exception.PortNotUsableDNS: If a requested port has a value assigned to its dns_name attribute. + :raises nova.exception.AttachSRIOVPortNotSupported: If a requested port + is an SR-IOV port and ``attach=True``. """ ports = {} ordered_networks = [] @@ -585,6 +592,16 @@ class API(base_api.NetworkAPI): # Make sure the port is usable _ensure_no_port_binding_failure(port) + # Make sure the port can be attached. + if attach: + # SR-IOV port attach is not supported. + vnic_type = port.get('binding:vnic_type', + network_model.VNIC_TYPE_NORMAL) + if vnic_type in network_model.VNIC_TYPES_SRIOV: + raise exception.AttachSRIOVPortNotSupported( + port_id=port['id'], + instance_uuid=instance.uuid) + # If requesting a specific port, automatically process # the network for that port as if it were explicitly # requested. @@ -814,7 +831,8 @@ class API(base_api.NetworkAPI): def allocate_for_instance(self, context, instance, vpn, requested_networks, macs=None, security_groups=None, - dhcp_options=None, bind_host_id=None): + dhcp_options=None, bind_host_id=None, + attach=False): """Allocate network resources for the instance. :param context: The request context. @@ -837,6 +855,8 @@ class API(base_api.NetworkAPI): are already formatted for the neutron v2 api. See nova/virt/driver.py:dhcp_options_for_instance for an example. :param bind_host_id: the host ID to attach to the ports being created. + :param attach: Boolean indicating if a port is being attached to an + existing running instance. Should be False during server create. :returns: network info as from get_instance_nw_info() """ LOG.debug('allocate_for_instance()', instance=instance) @@ -852,7 +872,7 @@ class API(base_api.NetworkAPI): # Validate ports and networks with neutron # ports, ordered_networks = self._validate_requested_port_ids( - context, instance, neutron, requested_networks) + context, instance, neutron, requested_networks, attach=attach) nets = self._validate_requested_network_ids( context, instance, neutron, requested_networks, ordered_networks) @@ -1242,7 +1262,7 @@ class API(base_api.NetworkAPI): tag=tag)]) return self.allocate_for_instance(context, instance, vpn=False, requested_networks=requested_networks, - bind_host_id=bind_host_id) + bind_host_id=bind_host_id, attach=True) def deallocate_port_for_instance(self, context, instance, port_id): """Remove a specified port from the instance. diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 9ff3308ab819..de41578ba2b1 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -5460,7 +5460,8 @@ class TestAllocateForInstance(test.NoDBTestCase): self.assertEqual(requested_networks[0], ordered_networks[0]) self.assertEqual('net-2', ordered_networks[1].network_id) - def _assert_validate_requested_port_ids_raises(self, exception, extras): + def _assert_validate_requested_port_ids_raises(self, exception, extras, + attach=False): api = neutronapi.API() mock_client = mock.Mock() requested_networks = objects.NetworkRequestList(objects=[ @@ -5474,7 +5475,8 @@ class TestAllocateForInstance(test.NoDBTestCase): mock_client.show_port.return_value = {"port": port} self.assertRaises(exception, api._validate_requested_port_ids, - self.context, self.instance, mock_client, requested_networks) + self.context, self.instance, mock_client, requested_networks, + attach=attach) def test_validate_requested_port_ids_raise_not_usable(self): self._assert_validate_requested_port_ids_raises( @@ -5496,6 +5498,12 @@ class TestAllocateForInstance(test.NoDBTestCase): exception.PortBindingFailed, {"binding:vif_type": model.VIF_TYPE_BINDING_FAILED}) + def test_validate_requested_port_ids_raise_sriov(self): + self._assert_validate_requested_port_ids_raises( + exception.AttachSRIOVPortNotSupported, + {"binding:vnic_type": model.VNIC_TYPE_DIRECT}, + attach=True) + def test_validate_requested_network_ids_success_auto_net(self): requested_networks = [] ordered_networks = []