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 68011c40ae)
(cherry picked from commit e1d55af408)
(cherry picked from commit 7827890421)
This commit is contained in:
Matt Riedemann 2018-08-15 13:33:16 +08:00 committed by Elod Illes
parent 9ea269ed62
commit af5df70f61
4 changed files with 44 additions and 8 deletions

View File

@ -17,8 +17,10 @@ assigned to only one guest and cannot be shared.
.. note:: .. note::
For information on attaching virtual SR-IOV devices to guests, refer to the For information on creating servers with virtual SR-IOV devices to guests,
`Networking Guide`_. refer to the `Networking Guide`_. Attaching SR-IOV ports to existing servers
is not currently supported, see
`bug 1708433 <https://bugs.launchpad.net/nova/+bug/1708433>`_ for details.
To enable PCI passthrough, follow the steps below: To enable PCI passthrough, follow the steps below:

View File

@ -837,6 +837,12 @@ class PortUpdateFailed(Invalid):
msg_fmt = _("Port update failed for port %(port_id)s: %(reason)s") 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): class FixedIpExists(NovaException):
msg_fmt = _("Fixed IP %(address)s already exists.") msg_fmt = _("Fixed IP %(address)s already exists.")

View File

@ -524,7 +524,7 @@ class API(base_api.NetworkAPI):
"for port '%s'"), port_id) "for port '%s'"), port_id)
def _validate_requested_port_ids(self, context, instance, neutron, def _validate_requested_port_ids(self, context, instance, neutron,
requested_networks): requested_networks, attach=False):
"""Processes and validates requested networks for allocation. """Processes and validates requested networks for allocation.
Iterates over the list of NetworkRequest objects, validating the Iterates over the list of NetworkRequest objects, validating the
@ -535,6 +535,11 @@ class API(base_api.NetworkAPI):
:type instance: nova.objects.Instance :type instance: nova.objects.Instance
:param neutron: neutron client session :param neutron: neutron client session
:type neutron: neutronclient.v2_0.client.Client :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: :returns: tuple of:
- ports: dict mapping of port id to port dict - ports: dict mapping of port id to port dict
- ordered_networks: list of nova.objects.NetworkRequest objects - ordered_networks: list of nova.objects.NetworkRequest objects
@ -548,6 +553,8 @@ class API(base_api.NetworkAPI):
attached to another instance. attached to another instance.
:raises nova.exception.PortNotUsableDNS: If a requested port has a :raises nova.exception.PortNotUsableDNS: If a requested port has a
value assigned to its dns_name attribute. value assigned to its dns_name attribute.
:raises nova.exception.AttachSRIOVPortNotSupported: If a requested port
is an SR-IOV port and ``attach=True``.
""" """
ports = {} ports = {}
ordered_networks = [] ordered_networks = []
@ -585,6 +592,16 @@ class API(base_api.NetworkAPI):
# Make sure the port is usable # Make sure the port is usable
_ensure_no_port_binding_failure(port) _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 # If requesting a specific port, automatically process
# the network for that port as if it were explicitly # the network for that port as if it were explicitly
# requested. # requested.
@ -814,7 +831,8 @@ class API(base_api.NetworkAPI):
def allocate_for_instance(self, context, instance, vpn, def allocate_for_instance(self, context, instance, vpn,
requested_networks, macs=None, requested_networks, macs=None,
security_groups=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. """Allocate network resources for the instance.
:param context: The request context. :param context: The request context.
@ -837,6 +855,8 @@ class API(base_api.NetworkAPI):
are already formatted for the neutron v2 api. are already formatted for the neutron v2 api.
See nova/virt/driver.py:dhcp_options_for_instance for an example. 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 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() :returns: network info as from get_instance_nw_info()
""" """
LOG.debug('allocate_for_instance()', instance=instance) LOG.debug('allocate_for_instance()', instance=instance)
@ -852,7 +872,7 @@ class API(base_api.NetworkAPI):
# Validate ports and networks with neutron # Validate ports and networks with neutron
# #
ports, ordered_networks = self._validate_requested_port_ids( 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( nets = self._validate_requested_network_ids(
context, instance, neutron, requested_networks, ordered_networks) context, instance, neutron, requested_networks, ordered_networks)
@ -1242,7 +1262,7 @@ class API(base_api.NetworkAPI):
tag=tag)]) tag=tag)])
return self.allocate_for_instance(context, instance, vpn=False, return self.allocate_for_instance(context, instance, vpn=False,
requested_networks=requested_networks, 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): def deallocate_port_for_instance(self, context, instance, port_id):
"""Remove a specified port from the instance. """Remove a specified port from the instance.

View File

@ -5460,7 +5460,8 @@ class TestAllocateForInstance(test.NoDBTestCase):
self.assertEqual(requested_networks[0], ordered_networks[0]) self.assertEqual(requested_networks[0], ordered_networks[0])
self.assertEqual('net-2', ordered_networks[1].network_id) 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() api = neutronapi.API()
mock_client = mock.Mock() mock_client = mock.Mock()
requested_networks = objects.NetworkRequestList(objects=[ requested_networks = objects.NetworkRequestList(objects=[
@ -5474,7 +5475,8 @@ class TestAllocateForInstance(test.NoDBTestCase):
mock_client.show_port.return_value = {"port": port} mock_client.show_port.return_value = {"port": port}
self.assertRaises(exception, api._validate_requested_port_ids, 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): def test_validate_requested_port_ids_raise_not_usable(self):
self._assert_validate_requested_port_ids_raises( self._assert_validate_requested_port_ids_raises(
@ -5496,6 +5498,12 @@ class TestAllocateForInstance(test.NoDBTestCase):
exception.PortBindingFailed, exception.PortBindingFailed,
{"binding:vif_type": model.VIF_TYPE_BINDING_FAILED}) {"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): def test_validate_requested_network_ids_success_auto_net(self):
requested_networks = [] requested_networks = []
ordered_networks = [] ordered_networks = []