From bd6f33070bbcd1901535c9ff8febfae6b32221cc Mon Sep 17 00:00:00 2001 From: Balazs Gibizer Date: Tue, 22 May 2018 15:13:55 -0700 Subject: [PATCH] Reject interface attach with QoS aware port Attaching a port with minimum bandwidth policy would require to update the allocation of the server. But for that nova would need to select the proper networking resource provider under the compute resource provider the server is running on. For the first iteration of the feature we consider this out of scope. To avoid resource allocation inconsistencies this patch propose to reject such attach interface request. Rejecting such interface attach does not break existing functionality as today only the SRIOV Neutron backend supports the minimum bandwidth policy but Nova does not support interface attach with SRIOV interfaces today. A subsequent patch will handle attaching a network that has QoS policy. Co-Authored-By: Elod Illes Change-Id: Id8b5c48a6e8cf65dc0a7dc13a80a0a72684f70d9 blueprint: bandwidth-resource-provider --- nova/compute/api.py | 11 +++++ nova/exception.py | 5 +++ nova/tests/functional/integrated_helpers.py | 2 +- nova/tests/functional/test_servers.py | 44 +++++++++++++++++++ nova/tests/unit/compute/test_compute_api.py | 17 +++++++ ...ort-resource-request-17473ddc5a989a2a.yaml | 10 +++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/reject-interface-attach-with-port-resource-request-17473ddc5a989a2a.yaml diff --git a/nova/compute/api.py b/nova/compute/api.py index 8cf7a52a0bcc..ee34e17279a5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -4377,6 +4377,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 a8fcd354ea45..b5ee5a4a4fad 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2143,6 +2143,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 d1af06c171c1..9fc03758c8e3 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -5351,3 +5351,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