diff --git a/nova/compute/api.py b/nova/compute/api.py index c3a552313d13..d8f7d2561fc7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -4382,6 +4382,17 @@ class API(base.Base): """Use hotplug to add an network adapter to an instance.""" self._record_action_start( context, instance, instance_actions.ATTACH_INTERFACE) + + # NOTE(gibi): Checking if the requested port has resource request as + # such ports are currently not supported as they would at least + # need resource allocation manipulation in placement but might also + # need a new scheduling if resource on this host is not available. + if port_id: + port = self.network_api.show_port(context, port_id) + if port['port'].get('resource_request'): + raise exception.AttachInterfaceWithQoSPolicyNotSupported( + instance_uuid=instance.uuid) + return self.compute_rpcapi.attach_interface(context, instance=instance, network_id=network_id, port_id=port_id, requested_ip=requested_ip, tag=tag) diff --git a/nova/exception.py b/nova/exception.py index 86307456fccd..2438d4217219 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2149,6 +2149,11 @@ class AttachInterfaceNotSupported(Invalid): "instance %(instance_uuid)s.") +class AttachInterfaceWithQoSPolicyNotSupported(AttachInterfaceNotSupported): + msg_fmt = _("Attaching interfaces with QoS policy is not supported for " + "instance %(instance_uuid)s.") + + 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/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py index d8b6e4ad6a7e..083a85598e29 100644 --- a/nova/tests/functional/integrated_helpers.py +++ b/nova/tests/functional/integrated_helpers.py @@ -367,7 +367,7 @@ class ProviderUsageBaseTestCase(test.TestCase, InstanceHelperMixin): super(ProviderUsageBaseTestCase, self).setUp() self.useFixture(policy_fixture.RealPolicyFixture()) - self.useFixture(nova_fixtures.NeutronFixture(self)) + self.neutron = self.useFixture(nova_fixtures.NeutronFixture(self)) self.useFixture(nova_fixtures.AllServicesCurrent()) fake_notifier.stub_notifier(self) diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 4238a7f7426a..7c52f86c7ee2 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -5373,3 +5373,47 @@ class ServerMovingTestsFromFlatToNested( source_rp_uuid) self._delete_and_check_allocations(server) + + +class PortResourceRequestBasedSchedulingTestBase( + integrated_helpers.ProviderUsageBaseTestCase): + + compute_driver = 'fake.SmallFakeDriver' + + def setUp(self): + super(PortResourceRequestBasedSchedulingTestBase, self).setUp() + self.compute1 = self._start_compute('host1') + self.compute1_rp_uuid = self._get_provider_uuid_by_host('host1') + self.flavor = self.api.get_flavors()[0] + + def _create_server(self, flavor, networks): + server_req = self._build_minimal_create_server_request( + self.api, 'bandwidth-aware-server', + image_uuid='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', + flavor_id=flavor['id'], networks=networks) + return self.api.post_server({'server': server_req}) + + +class PortResourceRequestBasedSchedulingTest( + PortResourceRequestBasedSchedulingTestBase): + """Tests for handling servers with ports having resource requests """ + + def test_interface_attach_with_port_resource_request(self): + # create a server + server = self._create_server( + flavor=self.flavor, + networks=[{'port': self.neutron.port_1['id']}]) + self._wait_for_state_change(self.admin_api, server, 'ACTIVE') + + # try to add a port with resource request + post = { + 'interfaceAttachment': { + 'port_id': self.neutron.port_with_resource_request['id'] + }} + ex = self.assertRaises(client.OpenStackApiException, + self.api.attach_interface, + server['id'], post) + self.assertEqual(400, ex.response.status_code) + self.assertIn('Attaching interfaces with QoS policy is ' + 'not supported for instance', + six.text_type(ex)) diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 3701c78158d2..7fdfec9a9b43 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -6434,6 +6434,23 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): mock_record.assert_called_once_with( self.context, instance, instance_actions.ATTACH_INTERFACE) + @mock.patch('nova.compute.api.API._record_action_start') + def test_attach_interface_qos_aware_port(self, mock_record): + instance = self._create_instance_obj() + with mock.patch.object( + self.compute_api.network_api, 'show_port', + return_value={'port': { + 'resource_request': { + 'resources': {'CUSTOM_RESOURCE_CLASS': 42} + }}}) as mock_show_port: + self.assertRaises( + exception.AttachInterfaceWithQoSPolicyNotSupported, + self.compute_api.attach_interface, + self.context, instance, + 'foo_net_id', 'foo_port_id', None + ) + mock_show_port.assert_called_once_with(self.context, 'foo_port_id') + @mock.patch('nova.compute.api.API._record_action_start') @mock.patch.object(compute_rpcapi.ComputeAPI, 'detach_interface') def test_detach_interface(self, mock_detach, mock_record): diff --git a/releasenotes/notes/reject-interface-attach-with-port-resource-request-17473ddc5a989a2a.yaml b/releasenotes/notes/reject-interface-attach-with-port-resource-request-17473ddc5a989a2a.yaml new file mode 100644 index 000000000000..5b4b8a22520b --- /dev/null +++ b/releasenotes/notes/reject-interface-attach-with-port-resource-request-17473ddc5a989a2a.yaml @@ -0,0 +1,10 @@ +--- +other: + - | + The ``POST /servers/{server_id}/os-interface`` request will be rejected + with HTTP 400 if the Neutron port referenced in the request body has + resource request as Nova currently cannot support such operation. For + example a Neutron port has resource request if a `QoS minimum bandwidth + rule`_ is attached to that port in Neutron. + + .. _QoS minimum bandwidth rule: https://docs.openstack.org/neutron/latest/admin/config-qos.html