diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 58efc8f18dc3..c5a3eadb430a 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -651,7 +651,7 @@ class ServersController(wsgi.Controller): exception.UnableToAutoAllocateNetwork, exception.MultiattachNotSupportedOldMicroversion, exception.CertificateValidationFailed, - exception.ServerCreateWithQoSPortNotSupported) as error: + exception.CreateWithPortResourceRequestOldVersion) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except (exception.PortInUse, exception.InstanceExists, diff --git a/nova/compute/api.py b/nova/compute/api.py index e290423c3d19..28d1c0194c36 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -814,8 +814,11 @@ class API(base.Base): context, requested_networks, pci_request_info) network_metadata, port_resource_requests = result + # Creating servers with ports that have resource requests, like QoS + # minimum bandwidth rules, is only supported in a requested minimum + # microversion. if port_resource_requests and not supports_port_resource_request: - raise exception.ServerCreateWithQoSPortNotSupported() + raise exception.CreateWithPortResourceRequestOldVersion() base_options = { 'reservation_id': reservation_id, diff --git a/nova/exception.py b/nova/exception.py index ca46075b044e..0be8f0f95934 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2159,9 +2159,12 @@ class NetworksWithQoSPolicyNotSupported(Invalid): "instance %(instance_uuid)s. (Network ID is %(network_id)s)") -class ServerCreateWithQoSPortNotSupported(Invalid): - msg_fmt = _("Creating server with port having QoS policy is not " - "supported.") +class CreateWithPortResourceRequestOldVersion(Invalid): + # TODO(gibi): Mention the specific microversion needed for the support + # after such microversion is merged + msg_fmt = _("Creating servers with ports having resource requests, like a " + "port with a QoS minimum bandwidth policy, is not supported " + "with this microversion") class InvalidReservedMemoryPagesOption(Invalid): diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 16250a71d2e7..c1b6dd64c806 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -522,6 +522,10 @@ class API(base_api.NetworkAPI): # need resource allocation manipulation in placement but might also # need a new scheduling if resource on this host is not available. if port.get('resource_request', None): + msg = _( + "The auto-created port %(port_id)s is being deleted due " + "to its network having QoS policy.") + LOG.info(msg, {'port_id': port_id}) self._cleanup_created_port(port_client, port_id, instance) # NOTE(gibi): This limitation regarding server create can be # removed when the port creation is moved to the conductor. But @@ -1932,8 +1936,8 @@ class API(base_api.NetworkAPI): # NOTE(gibi): Get the port resource_request which may or may not be # set depending on neutron configuration, e.g. if QoS rules are - # applied to the port/network and the resource_request API extension is - # enabled. + # applied to the port/network and the port-resource-request API + # extension is enabled. resource_request = port.get('resource_request', None) return vnic_type, trusted, network_id, resource_request @@ -1978,8 +1982,8 @@ class API(base_api.NetworkAPI): context, neutron, network_id) if resource_request: - # NOTE(gibi): explicitly orphan the RequestGroup as we - # never intended to save it to the DB. + # NOTE(gibi): explicitly orphan the RequestGroup by setting + # context=None as we never intended to save it to the DB. resource_requests.append( objects.RequestGroup.from_port_request( context=None, diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 76e1e225c315..eb3d74cdbb23 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -5616,20 +5616,16 @@ class PortResourceRequestBasedSchedulingTest( server['fault']['message']) def test_create_server_with_port_resource_request_old_microversion(self): - server_req = self._build_minimal_create_server_request( - self.api, 'bandwidth-aware-server', - image_uuid='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', - flavor_id=self.flavor['id'], - networks=[{'port': self.neutron.port_with_resource_request['id']}]) - ex = self.assertRaises( - client.OpenStackApiException, - self.api.post_server, {'server': server_req}) + client.OpenStackApiException, self._create_server, + flavor=self.flavor, + networks=[{'port': self.neutron.port_with_resource_request['id']}]) self.assertEqual(400, ex.response.status_code) self.assertIn( - 'Creating server with port having QoS policy is not supported.', - six.text_type(ex)) + "Creating servers with ports having resource requests, like a " + "port with a QoS minimum bandwidth policy, is not supported with " + "this microversion", six.text_type(ex)) def test_resize_server_with_port_resource_request_old_microversion(self): server = self._create_server( diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 4e520df8e885..2cf87e0d27bc 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -6430,50 +6430,6 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): self.assertItemsEqual(['default', uuids.secgroup_uuid], security_groups) - @mock.patch('nova.network.neutronv2.api.API.validate_networks') - @mock.patch('nova.network.neutronv2.api.API.create_resource_requests') - def test_validate_and_build_base_options_checks_resource_request( - self, mock_neutron_create_resource_requests, - mock_validate_network): - """Checks that validate_and_build_base_options raises if the request - contains port with resource request but API request does not use the - microversion enabling such support. - """ - instance_type = objects.Flavor(**test_flavor.fake_flavor) - boot_meta = metadata = {} - kernel_id = ramdisk_id = key_name = key_data = user_data = \ - access_ip_v4 = access_ip_v6 = config_drive = \ - auto_disk_config = reservation_id = None - requested_secgroups = ['default'] - requested_networks = objects.NetworkRequestList(objects=[ - objects.NetworkRequest(port_id=uuids.port_id)]) - mock_neutron_create_resource_requests.return_value = ( - None, [objects.RequestGroup()]) - max_count = 1 - - # This expected not to raise - supports_port_resource_request = True - self.compute_api._validate_and_build_base_options( - self.context, instance_type, boot_meta, uuids.image_href, - mock.sentinel.image_id, kernel_id, ramdisk_id, - 'fake-display-name', 'fake-description', key_name, - key_data, requested_secgroups, 'fake-az', user_data, - metadata, access_ip_v4, access_ip_v6, requested_networks, - config_drive, auto_disk_config, reservation_id, max_count, - supports_port_resource_request) - - supports_port_resource_request = False - self.assertRaises( - exception.ServerCreateWithQoSPortNotSupported, - self.compute_api._validate_and_build_base_options, - self.context, instance_type, boot_meta, uuids.image_href, - mock.sentinel.image_id, kernel_id, ramdisk_id, - 'fake-display-name', 'fake-description', key_name, - key_data, requested_secgroups, 'fake-az', user_data, - metadata, access_ip_v4, access_ip_v6, requested_networks, - config_drive, auto_disk_config, reservation_id, max_count, - supports_port_resource_request) - @mock.patch('nova.compute.api.API._record_action_start') @mock.patch.object(compute_rpcapi.ComputeAPI, 'attach_interface') def test_tagged_interface_attach(self, mock_attach, mock_record): diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index cea34a500081..2612c8b0ca69 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -3070,7 +3070,7 @@ class TestNeutronv2(TestNeutronv2Base): self._test_get_port_vnic_info(mock_get_client, None, model.VNIC_TYPE_NORMAL) - @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock()) + @mock.patch.object(neutronapi, 'get_client') def test_get_port_vnic_info_requested_resources(self, mock_get_client): self._test_get_port_vnic_info( mock_get_client, None, model.VNIC_TYPE_NORMAL, @@ -5256,7 +5256,7 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): @mock.patch.object(neutronapi, 'get_client') def test_create_resource_requests(self, getclient, mock_get_port_vnic_info, mock_get_physnet_tunneled_info, - mock_request_spec): + mock_from_port_request): requested_networks = objects.NetworkRequestList( objects = [ objects.NetworkRequest(port_id=uuids.portid_1), @@ -5288,7 +5288,7 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): ] api = neutronapi.API() - mock_request_spec.side_effect = [ + mock_from_port_request.side_effect = [ mock.sentinel.request_group1, mock.sentinel.request_group2, ] @@ -5322,7 +5322,7 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): ['physnet1', 'physnet2', 'physnet3', 'physnet4'], network_metadata.physnets) self.assertTrue(network_metadata.tunneled) - mock_request_spec.assert_has_calls([ + mock_from_port_request.assert_has_calls([ mock.call( context=None, port_uuid=uuids.portid_2,