diff --git a/nova/compute/api.py b/nova/compute/api.py index ee34e17279a5..e979daf14171 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -809,8 +809,9 @@ class API(base.Base): # InstancePCIRequests object pci_request_info = pci_request.get_pci_requests_from_flavor( instance_type) - network_metadata = self.network_api.create_resource_requests( + result = self.network_api.create_resource_requests( context, requested_networks, pci_request_info) + network_metadata, port_resource_requests = result base_options = { 'reservation_id': reservation_id, diff --git a/nova/network/api.py b/nova/network/api.py index e7e4508cac84..22678360296e 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -390,10 +390,22 @@ class API(base_api.NetworkAPI): pci_requests=None): """Retrieve all information for the networks passed at the time of creating the server. + + :param context: The request context. + :param requested_networks: The networks requested for the server. + :type requested_networks: nova.objects.NetworkRequestList + :param pci_requests: The list of PCI requests to which additional PCI + requests created here will be added. + :type pci_requests: nova.objects.InstancePCIRequests + + :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 + port """ # This is NOOP for Nova network since it doesn't support SR-IOV or # NUMA-aware vSwitch functionality. - pass + return None, [] def get_dns_domains(self, context): """Returns a list of available dns domains. diff --git a/nova/network/base_api.py b/nova/network/base_api.py index c04419f1064c..e3011f151dfb 100644 --- a/nova/network/base_api.py +++ b/nova/network/base_api.py @@ -273,19 +273,21 @@ class NetworkAPI(base.Base): """ raise NotImplementedError() - def create_resource_requests(self, context, requested_networks): + def create_resource_requests(self, context, requested_networks, + pci_requests=None): """Retrieve all information for the networks passed at the time of creating the server. :param context: The request context. :param requested_networks: The networks requested for the server. - :type requested_networks: nova.objects.RequestedNetworkList + :type requested_networks: nova.objects.NetworkRequestList :param pci_requests: The list of PCI requests to which additional PCI - requests created here will be added. + requests created here will be added. :type pci_requests: nova.objects.InstancePCIRequests - :returns: An instance of ``objects.NetworkMetadata`` for use by the - scheduler or None. + :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 request ports """ raise NotImplementedError() diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index bfcc5de8061c..e5d79a7e4039 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1876,13 +1876,13 @@ class API(base_api.NetworkAPI): :param neutron: The Neutron client :param port_id: The id of port to be queried - :return: A tuple of vNIC type, trusted status and network ID. Trusted - status only affects SR-IOV ports and will always be None for other - port types. + :return: A tuple of vNIC type, trusted status, network ID and resource + request of the port if any. Trusted status only affects SR-IOV + ports and will always be None for other port types. """ port = self._show_port(context, port_id, neutron_client=neutron, fields=['binding:vnic_type', BINDING_PROFILE, - 'network_id']) + 'network_id', 'resource_request']) network_id = port.get('network_id') trusted = None vnic_type = port.get('binding:vnic_type', @@ -1890,7 +1890,12 @@ class API(base_api.NetworkAPI): if vnic_type in network_model.VNIC_TYPES_SRIOV: trusted = self._get_trusted_mode_from_port(port) - return vnic_type, trusted, network_id + # 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. + resource_request = port.get('resource_request', None) + return vnic_type, trusted, network_id, resource_request def create_resource_requests(self, context, requested_networks, pci_requests=None): @@ -1899,21 +1904,25 @@ class API(base_api.NetworkAPI): :param context: The request context. :param requested_networks: The networks requested for the server. - :type requested_networks: nova.objects.RequestedNetworkList + :type requested_networks: nova.objects.NetworkRequestList :param pci_requests: The list of PCI requests to which additional PCI requests created here will be added. :type pci_requests: nova.objects.InstancePCIRequests - :returns: An instance of ``objects.NetworkMetadata`` for use by the - scheduler or None. + :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 + port """ if not requested_networks or requested_networks.no_allocate: - return None + return None, [] physnets = set() tunneled = False neutron = get_client(context, admin=True) + resource_requests = [] + for request_net in requested_networks: physnet = None trusted = None @@ -1922,10 +1931,20 @@ class API(base_api.NetworkAPI): pci_request_id = None if request_net.port_id: - vnic_type, trusted, network_id = self._get_port_vnic_info( + result = self._get_port_vnic_info( context, neutron, request_net.port_id) + vnic_type, trusted, network_id, resource_request = result physnet, tunneled_ = self._get_physnet_tunneled_info( context, neutron, network_id) + + if resource_request: + # NOTE(gibi): explicitly orphan the RequestGroup as we + # never intended to save it to the DB. + resource_requests.append( + objects.RequestGroup.from_port_request( + context=None, + port_resource_request=resource_request)) + elif request_net.network_id and not request_net.auto_allocate: network_id = request_net.network_id physnet, tunneled_ = self._get_physnet_tunneled_info( @@ -1969,7 +1988,8 @@ class API(base_api.NetworkAPI): # Add pci_request_id into the requested network request_net.pci_request_id = pci_request_id - return objects.NetworkMetadata(physnets=physnets, tunneled=tunneled) + return (objects.NetworkMetadata(physnets=physnets, tunneled=tunneled), + resource_requests) def _can_auto_allocate_network(self, context, neutron): """Helper method to determine if we can auto-allocate networks diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py index f6736906126b..a959ef47bda1 100644 --- a/nova/tests/unit/api/openstack/fakes.py +++ b/nova/tests/unit/api/openstack/fakes.py @@ -203,7 +203,7 @@ def stub_out_nw_api(test, cls=None, private=None, publics=None): def create_resource_requests(self, context, requested_networks, pci_requests): - pass + return None, [] if cls is None: cls = Fake diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 7fdfec9a9b43..a817ab6910c9 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -219,7 +219,8 @@ class _ComputeAPIUnitTestMixIn(object): port_id=port)]) with mock.patch.object(self.compute_api.network_api, - 'create_resource_requests'): + 'create_resource_requests', + return_value=(None, [])): self.compute_api.create(self.context, instance_type, 'image_id', requested_networks=requested_networks, max_count=None) diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 96e00708005e..e6bc03d26eb0 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -3015,7 +3015,8 @@ class TestNeutronv2(TestNeutronv2Base): def _test_get_port_vnic_info(self, mock_get_client, binding_vnic_type, - expected_vnic_type): + expected_vnic_type, + port_resource_request=None): api = neutronapi.API() self.mox.ResetAll() test_port = { @@ -3026,19 +3027,24 @@ class TestNeutronv2(TestNeutronv2Base): if binding_vnic_type: test_port['port']['binding:vnic_type'] = binding_vnic_type + if port_resource_request: + test_port['port']['resource_request'] = port_resource_request mock_get_client.reset_mock() mock_client = mock_get_client() mock_client.show_port.return_value = test_port - vnic_type, trusted, network_id = api._get_port_vnic_info( + result = api._get_port_vnic_info( self.context, mock_client, test_port['port']['id']) + vnic_type, trusted, network_id, resource_request = result mock_client.show_port.assert_called_once_with(test_port['port']['id'], - fields=['binding:vnic_type', 'binding:profile', 'network_id']) + fields=['binding:vnic_type', 'binding:profile', 'network_id', + 'resource_request']) self.assertEqual(expected_vnic_type, vnic_type) self.assertEqual('net-id', network_id) self.assertIsNone(trusted) + self.assertEqual(port_resource_request, resource_request) @mock.patch.object(neutronapi, 'get_client', return_value=mock.MagicMock()) def test_get_port_vnic_info_1(self, mock_get_client): @@ -3055,6 +3061,22 @@ 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()) + 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, + port_resource_request={ + "resources": { + "NET_BW_EGR_KILOBIT_PER_SEC": 6000, + "NET_BW_IGR_KILOBIT_PER_SEC": 6000, + }, + "required": [ + "CUSTOM_PHYSNET_PHYSNET0", + "CUSTOM_VNIC_TYPE_NORMAL" + ] + } + ) + class TestNeutronv2WithMock(_TestNeutronv2Common): """Used to test Neutron V2 API with mock.""" @@ -3401,14 +3423,17 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): mock_client = mock_get_client() mock_client.show_port.return_value = test_port mock_client.list_extensions.return_value = test_ext_list - vnic_type, trusted, network_id = self.api._get_port_vnic_info( + result = self.api._get_port_vnic_info( self.context, mock_client, test_port['port']['id']) + vnic_type, trusted, network_id, resource_requests = result mock_client.show_port.assert_called_once_with(test_port['port']['id'], - fields=['binding:vnic_type', 'binding:profile', 'network_id']) + fields=['binding:vnic_type', 'binding:profile', 'network_id', + 'resource_request']) self.assertEqual(model.VNIC_TYPE_DIRECT, vnic_type) self.assertEqual('net-id', network_id) self.assertTrue(trusted) + self.assertIsNone(resource_requests) @mock.patch('nova.network.neutronv2.api.API._show_port') def test_deferred_ip_port_immediate_allocation(self, mock_show): @@ -5154,11 +5179,13 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): pci_requests = objects.InstancePCIRequests() api = neutronapi.API() - network_metadata = api.create_resource_requests( + result = api.create_resource_requests( self.context, requested_networks, pci_requests) + network_metadata, port_resource_requests = result self.assertFalse(mock_get_client.called) self.assertIsNone(network_metadata) + self.assertEqual([], port_resource_requests) @mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info') @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock()) @@ -5174,18 +5201,22 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): pci_requests = objects.InstancePCIRequests() api = neutronapi.API() - network_metadata = api.create_resource_requests( + result = api.create_resource_requests( self.context, requested_networks, pci_requests) + network_metadata, port_resource_requests = result mock_get_physnet_tunneled_info.assert_not_called() self.assertEqual(set(), network_metadata.physnets) self.assertFalse(network_metadata.tunneled) + self.assertEqual([], port_resource_requests) + @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_get_port_vnic_info, mock_get_physnet_tunneled_info, + mock_request_spec): requested_networks = objects.NetworkRequestList( objects = [ objects.NetworkRequest(port_id=uuids.portid_1), @@ -5199,12 +5230,14 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): # _get_port_vnic_info should be called for every NetworkRequest with a # port_id attribute (so six times) mock_get_port_vnic_info.side_effect = [ - (model.VNIC_TYPE_DIRECT, None, 'netN'), - (model.VNIC_TYPE_NORMAL, None, 'netN'), - (model.VNIC_TYPE_MACVTAP, None, 'netN'), - (model.VNIC_TYPE_MACVTAP, None, 'netN'), - (model.VNIC_TYPE_DIRECT_PHYSICAL, None, 'netN'), - (model.VNIC_TYPE_DIRECT, True, 'netN'), + (model.VNIC_TYPE_DIRECT, None, 'netN', None), + (model.VNIC_TYPE_NORMAL, None, 'netN', + mock.sentinel.resource_request1), + (model.VNIC_TYPE_MACVTAP, None, 'netN', None), + (model.VNIC_TYPE_MACVTAP, None, 'netN', None), + (model.VNIC_TYPE_DIRECT_PHYSICAL, None, 'netN', None), + (model.VNIC_TYPE_DIRECT, True, 'netN', + mock.sentinel.resource_request2), ] # _get_physnet_tunneled_info should be called for every NetworkRequest # (so seven times) @@ -5215,9 +5248,19 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): ] api = neutronapi.API() - network_metadata = api.create_resource_requests( - self.context, requested_networks, pci_requests) + mock_request_spec.side_effect = [ + mock.sentinel.request_group1, + mock.sentinel.request_group2, + ] + result = api.create_resource_requests( + self.context, requested_networks, pci_requests) + network_metadata, port_resource_requests = result + + self.assertEqual([ + mock.sentinel.request_group1, + mock.sentinel.request_group2], + port_resource_requests) self.assertEqual(5, len(pci_requests.requests)) has_pci_request_id = [net.pci_request_id is not None for net in requested_networks.objects] @@ -5239,6 +5282,14 @@ class TestNeutronv2WithMock(_TestNeutronv2Common): ['physnet1', 'physnet2', 'physnet3', 'physnet4'], network_metadata.physnets) self.assertTrue(network_metadata.tunneled) + mock_request_spec.assert_has_calls([ + mock.call( + context=None, + port_resource_request=mock.sentinel.resource_request1), + mock.call( + context=None, + port_resource_request=mock.sentinel.resource_request2), + ]) @mock.patch.object(neutronapi, 'get_client') def test_associate_floating_ip_conflict(self, mock_get_client):