diff --git a/api-guide/source/port_with_resource_request.rst b/api-guide/source/port_with_resource_request.rst index bab2962537ed..868ec33a97df 100644 --- a/api-guide/source/port_with_resource_request.rst +++ b/api-guide/source/port_with_resource_request.rst @@ -39,5 +39,15 @@ servers with neutron ports having resource requests. As of 23.0.0 (Wallaby), nova supports attaching neutron ports having QoS minimum bandwidth rules. +Extended resource request +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since neutron 19.0.0 (Xena), neutron implements an extended resource request +format via the the ``port-resource-request-groups`` neutron API extension. As +of nova 24.0.0 (Xena), nova does not support the new extension. If the +extension is enabled in neutron, then nova will reject server create and move +operations, as well as interface attach operation. Admins should not enable +this API extension in neutron. + See :nova-doc:`the admin guide ` for administrative details. diff --git a/doc/source/admin/ports-with-resource-requests.rst b/doc/source/admin/ports-with-resource-requests.rst index 7999312ca2e2..121e52b17caa 100644 --- a/doc/source/admin/ports-with-resource-requests.rst +++ b/doc/source/admin/ports-with-resource-requests.rst @@ -52,6 +52,7 @@ If the ``group_policy`` is missing from the flavor then the server create request will fail with 'No valid host was found' and a warning describing the missing policy will be logged. + Virt driver support ~~~~~~~~~~~~~~~~~~~ @@ -63,3 +64,27 @@ If the virt driver on the compute host does not support the needed capability then the PCI claim will fail on the host and re-schedule will be triggered. It is suggested not to configure bandwidth inventory in the neutron agents on these compute hosts to avoid unnecessary reschedule. + + +Extended resource request +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since neutron 19.0.0 (Xena), neutron implements an extended resource request +format via the the ``port-resource-request-groups`` neutron API extension. As +of nova 24.0.0 (Xena), Nova does not support the new extension. If the +extension is enabled in neutron, then nova will reject server create and move +operations, as well as interface attach operation. Admins should not enable +this API extension in neutron. + +The extended resource request allows a single Neutron port to request +resources in more than one request groups. This also means that using just one +port in a server create request would require a group policy to be provided +in the flavor. Today the only case when a single port generates more than one +request groups is when that port has QoS policy with both minimum bandwidth +and minimum packet rate rules. Due to the placement resource model of these +features in this case the two request groups will always be fulfilled from +separate resource providers and therefore neither the ``group_policy=none`` +nor the ``group_policy=isolate`` flavor extra specs will result in any +additional restriction on the placement of the resources. In the multi port +case the Resource Group policy section above still applies. + diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index d2ae1cd4c067..948318a270f7 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -843,7 +843,9 @@ class ServersController(wsgi.Controller): exception.CreateWithPortResourceRequestOldVersion, exception.DeviceProfileError, exception.ComputeHostNotFound, - exception.ForbiddenPortsWithAccelerator) as error: + exception.ForbiddenPortsWithAccelerator, + exception.ExtendedResourceRequestNotSupported, + ) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except INVALID_FLAVOR_IMAGE_EXCEPTIONS as error: raise exc.HTTPBadRequest(explanation=error.format_message()) diff --git a/nova/exception.py b/nova/exception.py index ec8f80fe3bf7..febbaad4672b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1955,6 +1955,12 @@ class CreateWithPortResourceRequestOldVersion(Invalid): "until microversion 2.72.") +class ExtendedResourceRequestNotSupported(Invalid): + msg_fmt = _("The port-resource-request-groups neutron API extension is " + "not yet supported by Nova. Please turn off this extension in " + "Neutron.") + + class InvalidReservedMemoryPagesOption(Invalid): msg_fmt = _("The format of the option 'reserved_huge_pages' is invalid. " "(found '%(conf)s') Please refer to the nova " diff --git a/nova/network/neutron.py b/nova/network/neutron.py index f674fd4fe681..a995ae611457 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -2001,6 +2001,15 @@ class API: return (vnic_type, trusted, network_id, resource_request, numa_policy, device_profile) + def support_create_with_resource_request(self, context): + """Returns false if neutron is configured with extended resource + request which is not currently supported. + + This function is only here temporarily to help mocking this check in + the functional test environment. + """ + return not (self._has_extended_resource_request_extension(context)) + def create_resource_requests( self, context, requested_networks, pci_requests=None, affinity_policy=None): @@ -2015,7 +2024,8 @@ class API: :type pci_requests: nova.objects.InstancePCIRequests :param affinity_policy: requested pci numa affinity policy :type affinity_policy: nova.objects.fields.PCINUMAAffinityPolicy - + :raises ExtendedResourceRequestNotSupported: if the + extended-resource-request Neutron API extension is enabled. :returns: A tuple with an instance of ``objects.NetworkMetadata`` for use by the scheduler or None and a list of RequestGroup objects representing the resource needs of each requested @@ -2024,6 +2034,9 @@ class API: if not requested_networks or requested_networks.no_allocate: return None, [] + if not self.support_create_with_resource_request(context): + raise exception.ExtendedResourceRequestNotSupported() + physnets = set() tunneled = False diff --git a/nova/tests/functional/test_servers_resource_request.py b/nova/tests/functional/test_servers_resource_request.py index b129f884ec84..4baa4715189b 100644 --- a/nova/tests/functional/test_servers_resource_request.py +++ b/nova/tests/functional/test_servers_resource_request.py @@ -2611,3 +2611,38 @@ class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase): self._delete_server_and_check_allocations( server, qos_normal_port, qos_sriov_port) + + +class ExtendedResourceRequestTempNegativeTest( + PortResourceRequestBasedSchedulingTestBase): + """A set of temporary tests to show that nova currently rejects requests + that uses the extended-resource-request Neutron API extension. These test + are expected to be removed when support for the extension is implemented + in nova. + """ + def setUp(self): + super().setUp() + self.neutron = self.useFixture( + ExtendedResourceRequestNeutronFixture(self)) + + def test_boot(self): + """The neutron fixture used in this test class enables the + extended-resource-request API extension. This results in any new + server to boot. This is harsh but without nova support for this + extension there is no way that this extension is helpful. So treat + this as a deployment configuration error. + """ + ex = self.assertRaises( + client.OpenStackApiException, + self._create_server, + flavor=self.flavor, + networks=[{'port': self.neutron.port_1['id']}], + ) + + self.assertEqual(400, ex.response.status_code) + self.assertIn( + 'The port-resource-request-groups neutron API extension is not ' + 'yet supported by Nova. Please turn off this extension in ' + 'Neutron.', + str(ex) + ) diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index 807aa0cb63fb..c444c834f6d8 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -5795,10 +5795,15 @@ class TestAPI(TestAPIBase): self.assertIsNone(network_metadata) self.assertEqual([], port_resource_requests) + @mock.patch.object( + neutronapi.API, '_has_extended_resource_request_extension', + return_value=False) @mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info') @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock()) - def test_create_resource_requests_auto_allocated(self, mock_get_client, - mock_get_physnet_tunneled_info): + def test_create_resource_requests_auto_allocated( + self, mock_get_client, mock_get_physnet_tunneled_info, + mock_has_extended_res_req + ): """Ensure physnet info is not retrieved for auto-allocated networks. This isn't possible so we shouldn't attempt to do it. @@ -5818,13 +5823,18 @@ class TestAPI(TestAPIBase): self.assertFalse(network_metadata.tunneled) self.assertEqual([], port_resource_requests) + @mock.patch.object( + neutronapi.API, '_has_extended_resource_request_extension', + return_value=False) @mock.patch('nova.objects.request_spec.RequestGroup.from_port_request') @mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info') @mock.patch.object(neutronapi.API, "_get_port_vnic_info") @mock.patch.object(neutronapi, 'get_client') - def test_create_resource_requests(self, getclient, - mock_get_port_vnic_info, mock_get_physnet_tunneled_info, - mock_from_port_request): + def test_create_resource_requests( + self, getclient, mock_get_port_vnic_info, + mock_get_physnet_tunneled_info, mock_from_port_request, + mock_has_extended_res_req + ): requested_networks = objects.NetworkRequestList( objects = [ objects.NetworkRequest(port_id=uuids.portid_1), @@ -5910,6 +5920,26 @@ class TestAPI(TestAPIBase): port_uuid=uuids.trusted_port, port_resource_request=mock.sentinel.resource_request2), ]) + mock_has_extended_res_req.assert_called_once_with(self.context) + + @mock.patch.object( + neutronapi.API, '_has_extended_resource_request_extension', + return_value=True) + def test_create_resource_request_extended_not_supported( + self, mock_has_extended_extension + ): + requested_networks = objects.NetworkRequestList( + objects=[ + objects.NetworkRequest(port_id=uuids.portid_1), + ] + ) + pci_requests = objects.InstancePCIRequests(requests=[]) + self.assertRaises( + exception.ExtendedResourceRequestNotSupported, + neutronapi.API().create_resource_requests, + self.context, requested_networks, pci_requests + ) + mock_has_extended_extension.assert_called_once_with(self.context) @mock.patch( 'nova.accelerator.cyborg._CyborgClient.get_device_request_groups')