From 94f47471e058e46441a63d5aaf77776ef75b3fba Mon Sep 17 00:00:00 2001 From: Balazs Gibizer Date: Fri, 28 May 2021 14:26:05 +0200 Subject: [PATCH] Transfer RequestLevelParams from ports to scheduling The new format of the resource_request field of the Neutron port allows expressing not just request groups but also request global parameters for the allocation candidate query. This patch adapts the neutron client in nova to parse such parameters. Then transfer this information to the scheduler to include it in the allocation candidate request. It relies on previous patches that already extended the RequestLevelParams ovo and the allocation candidate query generation. Change-Id: Icb91f6429050a161f577d0ed94d4cd906d3da461 blueprint: qos-minimum-guaranteed-packet-rate --- nova/compute/api.py | 23 +++-- nova/compute/manager.py | 19 +++- nova/network/neutron.py | 23 +++-- nova/objects/request_spec.py | 20 ++-- nova/scheduler/utils.py | 4 + nova/tests/unit/api/openstack/fakes.py | 2 +- nova/tests/unit/compute/test_api.py | 54 ++++++++--- nova/tests/unit/compute/test_compute.py | 96 +++++++++++++++----- nova/tests/unit/compute/test_compute_mgr.py | 3 +- nova/tests/unit/network/test_neutron.py | 39 +++++++- nova/tests/unit/objects/test_request_spec.py | 12 ++- nova/tests/unit/scheduler/test_utils.py | 11 ++- 12 files changed, 232 insertions(+), 74 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index b97fc303ce..292284036f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1063,7 +1063,7 @@ class API: result = self.network_api.create_resource_requests( context, requested_networks, pci_request_info, affinity_policy=pci_numa_affinity_policy) - network_metadata, port_resource_requests = result + network_metadata, port_resource_requests, req_lvl_params = result self._check_support_vnic_accelerator(context, requested_networks) @@ -1103,7 +1103,9 @@ class API: 'pci_requests': pci_request_info, 'numa_topology': numa_topology, 'system_metadata': system_metadata, - 'port_resource_requests': port_resource_requests} + 'port_resource_requests': port_resource_requests, + 'request_level_params': req_lvl_params, + } options_from_image = self._inherit_properties_from_image( boot_meta, auto_disk_config) @@ -1295,6 +1297,7 @@ class API: security_groups = security_group_api.populate_security_groups( security_groups) port_resource_requests = base_options.pop('port_resource_requests') + req_lvl_params = base_options.pop('request_level_params') instances_to_build = [] # We could be iterating over several instances with several BDMs per # instance and those BDMs could be using a lot of the same images so @@ -1324,13 +1327,15 @@ class API: # RequestSpec before the instance is created. instance_uuid = uuidutils.generate_uuid() # Store the RequestSpec that will be used for scheduling. - req_spec = objects.RequestSpec.from_components(context, - instance_uuid, boot_meta, flavor, - base_options['numa_topology'], - base_options['pci_requests'], filter_properties, - instance_group, base_options['availability_zone'], - security_groups=security_groups, - port_resource_requests=port_resource_requests) + req_spec = objects.RequestSpec.from_components( + context, + instance_uuid, boot_meta, flavor, + base_options['numa_topology'], + base_options['pci_requests'], filter_properties, + instance_group, base_options['availability_zone'], + security_groups=security_groups, + port_resource_requests=port_resource_requests, + request_level_params=req_lvl_params) if block_device_mapping: # Record whether or not we are a BFV instance diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e15fd263cb..cd0f95da17 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -7643,6 +7643,7 @@ class ComputeManager(manager.Manager): instance: 'objects.Instance', pci_reqs: 'objects.InstancePCIRequests', request_groups: ty.List['objects.RequestGroup'], + request_level_params: 'objects.RequestLevelParams', ) -> ty.Tuple[ty.Optional[ty.Dict[str, ty.List[str]]], ty.Optional[ty.Dict[str, ty.Dict[str, ty.Dict[str, int]]]]]: """Allocate resources for the request in placement @@ -7654,6 +7655,8 @@ class ComputeManager(manager.Manager): needed PCI devices :param request_groups: A list of RequestGroup objects describing the resources the port requests from placement + :param request_level_params: A RequestLevelParams object describing the + non group specific request of the port. :raises InterfaceAttachResourceAllocationFailed: if we failed to allocate resource in placement for the request :returns: A tuple of provider mappings and allocated resources or @@ -7682,7 +7685,8 @@ class ComputeManager(manager.Manager): # NOTE(gibi): when support is added for attaching a cyborg based # smart NIC the ResourceRequest could be extended to handle multiple # request groups. - rr = scheduler_utils.ResourceRequest.from_request_group(request_group) + rr = scheduler_utils.ResourceRequest.from_request_group( + request_group, request_level_params) res = self.reportclient.get_allocation_candidates(context, rr) alloc_reqs, provider_sums, version = res @@ -7805,9 +7809,14 @@ class ComputeManager(manager.Manager): instance.flavor, instance.image_meta) pci_reqs = objects.InstancePCIRequests( requests=[], instance_uuid=instance.uuid) - _, request_groups = self.network_api.create_resource_requests( - context, requested_networks, pci_reqs, - affinity_policy=pci_numa_affinity_policy) + _, request_groups, req_lvl_params = ( + self.network_api.create_resource_requests( + context, + requested_networks, + pci_reqs, + affinity_policy=pci_numa_affinity_policy + ) + ) # We only support one port per attach request so we at most have one # pci request @@ -7816,7 +7825,7 @@ class ComputeManager(manager.Manager): requested_networks[0].pci_request_id = pci_req.request_id result = self._allocate_port_resource_for_instance( - context, instance, pci_reqs, request_groups) + context, instance, pci_reqs, request_groups, req_lvl_params) provider_mappings, resources = result try: diff --git a/nova/network/neutron.py b/nova/network/neutron.py index eab727881e..88cd4a74e1 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -2050,13 +2050,15 @@ class API: :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 - port + + :returns: A three tuple with an instance of ``objects.NetworkMetadata`` + for use by the scheduler or None, a list of RequestGroup + objects representing the resource needs of each requested port and + a RequestLevelParam object that contains global scheduling + instructions not specific to any of the RequestGroups """ if not requested_networks or requested_networks.no_allocate: - return None, [] + return None, [], None if not self.support_create_with_resource_request(context): raise exception.ExtendedResourceRequestNotSupported() @@ -2068,6 +2070,7 @@ class API: has_extended_resource_request_extension = ( self._has_extended_resource_request_extension(context, neutron)) resource_requests = [] + request_level_params = objects.RequestLevelParams() for request_net in requested_networks: physnet = None @@ -2122,6 +2125,9 @@ class API: objects.RequestGroup.from_extended_port_request( context=None, port_resource_request=resource_request)) + request_level_params.extend_with( + objects.RequestLevelParams.from_port_request( + port_resource_request=resource_request)) else: # keep supporting the old format of the # resource_request @@ -2183,8 +2189,11 @@ class API: # Add pci_request_id into the requested network request_net.pci_request_id = pci_request_id - return (objects.NetworkMetadata(physnets=physnets, tunneled=tunneled), - resource_requests) + return ( + objects.NetworkMetadata(physnets=physnets, tunneled=tunneled), + resource_requests, + request_level_params + ) def _can_auto_allocate_network(self, context, neutron): """Helper method to determine if we can auto-allocate networks diff --git a/nova/objects/request_spec.py b/nova/objects/request_spec.py index 652f5fd074..854db3045c 100644 --- a/nova/objects/request_spec.py +++ b/nova/objects/request_spec.py @@ -474,10 +474,12 @@ class RequestSpec(base.NovaObject): return filt_props @classmethod - def from_components(cls, context, instance_uuid, image, flavor, - numa_topology, pci_requests, filter_properties, instance_group, - availability_zone, security_groups=None, project_id=None, - user_id=None, port_resource_requests=None): + def from_components( + cls, context, instance_uuid, image, flavor, + numa_topology, pci_requests, filter_properties, instance_group, + availability_zone, security_groups=None, project_id=None, + user_id=None, port_resource_requests=None, request_level_params=None + ): """Returns a new RequestSpec object hydrated by various components. This helper is useful in creating the RequestSpec from the various @@ -503,6 +505,7 @@ class RequestSpec(base.NovaObject): :param port_resource_requests: a list of RequestGroup objects representing the resource needs of the neutron ports + :param request_level_params: a RequestLevelParams object """ spec_obj = cls(context) spec_obj.num_instances = 1 @@ -536,10 +539,11 @@ class RequestSpec(base.NovaObject): if port_resource_requests: spec_obj.requested_resources.extend(port_resource_requests) - # NOTE(efried): We don't need to handle request_level_params here yet - # because they're set dynamically by the scheduler. That could change - # in the future. - # TODO(gibi): handle same_subtree here coming from the neutron ports + # NOTE(gibi): later the scheduler adds more request level params but + # never overrides existing ones so we can initialize them here. + if request_level_params is None: + request_level_params = objects.RequestLevelParams() + spec_obj.request_level_params = request_level_params # NOTE(sbauza): Default the other fields that are not part of the # original contract diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index b68a165480..9b6f6514cb 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -202,9 +202,13 @@ class ResourceRequest(object): def from_request_group( cls, request_group: 'objects.RequestGroup', + request_level_params: 'objects.RequestLevelParams', ) -> 'ResourceRequest': """Create a new instance of ResourceRequest from a RequestGroup.""" res_req = cls() + res_req._root_required = request_level_params.root_required + res_req._root_forbidden = request_level_params.root_forbidden + res_req._same_subtree = request_level_params.same_subtree res_req._add_request_group(request_group) res_req.strip_zeros() return res_req diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py index a9f5c4cdce..04d72a5d93 100644 --- a/nova/tests/unit/api/openstack/fakes.py +++ b/nova/tests/unit/api/openstack/fakes.py @@ -184,7 +184,7 @@ def stub_out_nw_api(test, cls=None, private=None, publics=None): def create_resource_requests( self, context, requested_networks, pci_requests=None, affinity_policy=None): - return None, [] + return None, [], objects.RequestLevelParams() if cls is None: cls = Fake diff --git a/nova/tests/unit/compute/test_api.py b/nova/tests/unit/compute/test_api.py index 98ef773674..a8f91a0458 100644 --- a/nova/tests/unit/compute/test_api.py +++ b/nova/tests/unit/compute/test_api.py @@ -223,9 +223,11 @@ class _ComputeAPIUnitTestMixIn(object): objects=[objects.NetworkRequest(address=address, port_id=port)]) - with mock.patch.object(self.compute_api.network_api, - 'create_resource_requests', - return_value=(None, [])): + with mock.patch.object( + self.compute_api.network_api, + 'create_resource_requests', + return_value=(None, [], mock.sentinel.req_lvl_params) + ): self.compute_api.create(self.context, flavor, 'image_id', requested_networks=requested_networks, max_count=None) @@ -4888,7 +4890,10 @@ class _ComputeAPIUnitTestMixIn(object): 'user_data': None, 'numa_topology': None, 'pci_requests': None, - 'port_resource_requests': None} + 'port_resource_requests': None, + 'request_level_params': + objects.RequestLevelParams(), + } security_groups = {} block_device_mapping = objects.BlockDeviceMappingList( objects=[objects.BlockDeviceMapping( @@ -5059,10 +5064,12 @@ class _ComputeAPIUnitTestMixIn(object): 'properties': {'mappings': []}, 'status': 'fake-status', 'location': 'far-away'} + numa_topology = objects.InstanceNUMATopology() + pci_requests = objects.InstancePCIRequests() base_options = {'image_ref': 'fake-ref', 'display_name': 'fake-name', 'project_id': 'fake-project', - 'availability_zone': None, + 'availability_zone': 'fake-az', 'metadata': {}, 'access_ip_v4': None, 'access_ip_v6': None, @@ -5073,9 +5080,13 @@ class _ComputeAPIUnitTestMixIn(object): 'ramdisk_id': None, 'root_device_name': None, 'user_data': None, - 'numa_topology': None, - 'pci_requests': None, - 'port_resource_requests': None} + 'numa_topology': numa_topology, + 'pci_requests': pci_requests, + 'port_resource_requests': + mock.sentinel.resource_reqs, + 'request_level_params': + mock.sentinel.req_lvl_params, + } security_groups = {} block_device_mappings = objects.BlockDeviceMappingList( objects=[objects.BlockDeviceMapping( @@ -5112,6 +5123,18 @@ class _ComputeAPIUnitTestMixIn(object): block_device_mappings, {}, mock_get_volumes.return_value, False)] * max_count) + mock_req_spec_from_components.assert_has_calls( + [ + mock.call( + ctxt, mock.ANY, boot_meta, flavor, numa_topology, + pci_requests, filter_properties, instance_group, + 'fake-az', security_groups=mock.ANY, + port_resource_requests=mock.sentinel.resource_reqs, + request_level_params=mock.sentinel.req_lvl_params + ), + ] * 2 + ) + for rs, br, im in instances_to_build: self.assertIsInstance(br.instance, objects.Instance) self.assertTrue(uuidutils.is_uuid_like(br.instance.uuid)) @@ -5165,7 +5188,10 @@ class _ComputeAPIUnitTestMixIn(object): 'user_data': None, 'numa_topology': None, 'pci_requests': None, - 'port_resource_requests': None} + 'port_resource_requests': None, + 'request_level_params': + objects.RequestLevelParams(), + } security_groups = {} block_device_mapping = objects.BlockDeviceMappingList( objects=[objects.BlockDeviceMapping( @@ -5262,7 +5288,11 @@ class _ComputeAPIUnitTestMixIn(object): 'user_data': None, 'numa_topology': None, 'pci_requests': None, - 'port_resource_requests': None} + 'port_resource_requests': None, + 'request_level_params': + objects.RequestLevelParams(), + } + security_groups = {} block_device_mapping = objects.BlockDeviceMappingList( objects=[objects.BlockDeviceMapping( @@ -5328,7 +5358,9 @@ class _ComputeAPIUnitTestMixIn(object): mock_objects.RequestSpec.from_components.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, - security_groups=secgroups, port_resource_requests=mock.ANY) + security_groups=secgroups, port_resource_requests=mock.ANY, + request_level_params=mock.ANY + ) test() def _test_rescue(self, vm_state=vm_states.ACTIVE, rescue_password=None, diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 0786165997..a7b009d377 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -8692,11 +8692,13 @@ class ComputeAPITestCase(BaseTestCase): objects=[objects.NetworkRequest(port_id=uuids.port_instance)]) with test.nested( - mock.patch.object(self.compute_api.compute_task_api, - 'schedule_and_build_instances'), - mock.patch.object(self.compute_api.network_api, - 'create_resource_requests', - return_value=(None, [])), + mock.patch.object( + self.compute_api.compute_task_api, + 'schedule_and_build_instances'), + mock.patch.object( + self.compute_api.network_api, + 'create_resource_requests', + return_value=(None, [], objects.RequestLevelParams())), ) as (mock_sbi, _mock_create_resreqs): self.compute_api.create( self.context, @@ -10195,7 +10197,8 @@ class ComputeAPITestCase(BaseTestCase): "_claim_pci_device_for_interface_attach", return_value=None) ) as (cap, mock_lock, mock_create_resource_req, mock_claim_pci): - mock_create_resource_req.return_value = (None, []) + mock_create_resource_req.return_value = ( + None, [], mock.sentinel.req_lvl_params) vif = self.compute.attach_interface(self.context, instance, network_id, @@ -10255,7 +10258,8 @@ class ComputeAPITestCase(BaseTestCase): mock_allocate_res ): request_groups = [objects.RequestGroup] - mock_create_resource_req.return_value = (None, request_groups) + mock_create_resource_req.return_value = ( + None, request_groups, mock.sentinel.req_lvl_params) mock_allocate_res.return_value = ( mock.sentinel.provider_mappings, mock.sentinel.resources) vif = self.compute.attach_interface( @@ -10300,7 +10304,9 @@ class ComputeAPITestCase(BaseTestCase): # as this port has resource request we need to call # _allocate_port_resource_for_instance for it mock_allocate_res.assert_called_once_with( - self.context, instance, pci_reqs, request_groups) + self.context, instance, pci_reqs, request_groups, + mock.sentinel.req_lvl_params + ) @mock.patch.object(compute_utils, 'notify_about_instance_action') def test_attach_sriov_interface(self, mock_notify): @@ -10337,7 +10343,7 @@ class ComputeAPITestCase(BaseTestCase): # Simulate that the requested port is an SRIOV port pci_requests.requests.append(pci_req) # without resource request - return None, [] + return None, [], mock.sentinel.req_lvl_params mock_create_resource_req.side_effect = create_resource_req @@ -10413,7 +10419,7 @@ class ComputeAPITestCase(BaseTestCase): # Simulate that the requested port is an SRIOV port pci_requests.requests.append(pci_req) # with resource request - return None, request_groups + return None, request_groups, mock.sentinel.req_lvl_params mock_create_resource_req.side_effect = create_resource_req @@ -10464,9 +10470,11 @@ class ComputeAPITestCase(BaseTestCase): self.assertIn(pci_device, instance.pci_devices.objects) # ensure that we called _allocate_port_resource_for_instance as it has - # resource reques + # resource request mock_allocate_res.assert_called_once_with( - self.context, instance, pci_reqs, request_groups) + self.context, instance, pci_reqs, request_groups, + mock.sentinel.req_lvl_params + ) @mock.patch.object(compute_utils, 'notify_about_instance_action') def test_interface_tagged_attach(self, mock_notify): @@ -10487,7 +10495,8 @@ class ComputeAPITestCase(BaseTestCase): '_claim_pci_device_for_interface_attach', return_value=None) ) as (mock_capabilities, mock_create_resource_req, mock_claim_pci): - mock_create_resource_req.return_value = (None, []) + mock_create_resource_req.return_value = ( + None, [], mock.sentinel.req_lvl_params) vif = self.compute.attach_interface(self.context, instance, network_id, @@ -10563,7 +10572,8 @@ class ComputeAPITestCase(BaseTestCase): ) as (mock_notify, mock_attach, mock_allocate, mock_deallocate, mock_dict, mock_create_resource_req, mock_claim_pci): - mock_create_resource_req.return_value = (None, []) + mock_create_resource_req.return_value = ( + None, [], mock.sentinel.req_lvl_params) mock_allocate.return_value = nwinfo mock_attach.side_effect = exception.NovaException("attach_failed") self.assertRaises(exception.InterfaceAttachFailed, @@ -10641,7 +10651,7 @@ class ComputeAPITestCase(BaseTestCase): pci_requests=None, affinity_policy=None): # Simulate that the requested port is an SRIOV port pci_requests.requests.append(pci_req) - return None, [] + return None, [], mock.sentinel.req_lvl_params mock_create_resource_req.side_effect = create_resource_req mock_allocate.return_value = nwinfo @@ -10716,7 +10726,7 @@ class ComputeAPITestCase(BaseTestCase): pci_requests=None, affinity_policy=None): # Simulate that the requested port is an SRIOV port pci_requests.requests.append(pci_req) - return None, request_groups + return None, request_groups, mock.sentinel.req_lvl_params mock_create_resource_req.side_effect = create_resource_req mock_allocate_res.return_value = ( @@ -10741,7 +10751,12 @@ class ComputeAPITestCase(BaseTestCase): self.assertNotIn(pci_req, instance.pci_requests.requests) mock_allocate_res.assert_called_once_with( - self.context, instance, pci_reqs, request_groups) + self.context, + instance, + pci_reqs, + request_groups, + mock.sentinel.req_lvl_params + ) mock_remove_res.assert_called_once_with( self.context, instance.uuid, mock.sentinel.resources) @@ -10753,6 +10768,10 @@ class ComputeAPITestCase(BaseTestCase): resources={"CUSTOM_FOO": 13}, requester_id=uuids.requester_id) ] + req_lvl_params = objects.RequestLevelParams( + root_required={"CUSTOM_BLUE"}, + same_subtree=[[uuids.group1, uuids.group2]] + ) with test.nested( mock.patch.object(objects.ComputeNode, 'get_by_nodename'), @@ -10781,7 +10800,9 @@ class ComputeAPITestCase(BaseTestCase): alloc_reqs, mock.sentinel.provider_sums, mock.sentinel.version) res = self.compute._allocate_port_resource_for_instance( - self.context, instance, pci_reqs, request_groups) + self.context, instance, pci_reqs, request_groups, + req_lvl_params + ) provider_mappings, resources = res self.assertEqual( @@ -10797,6 +10818,11 @@ class ComputeAPITestCase(BaseTestCase): request_groups[0].requester_id) self.assertEqual(request_groups[0], actual_rg) self.assertEqual(uuids.compute_node, actual_rg.in_tree) + self.assertEqual({"CUSTOM_BLUE"}, resource_request._root_required) + self.assertEqual( + [[uuids.group1, uuids.group2]], + resource_request._same_subtree + ) mock_add_res.assert_called_once_with( self.context, instance.uuid, mock.sentinel.resources) mock_update_pci.assert_called_once_with( @@ -10811,6 +10837,10 @@ class ComputeAPITestCase(BaseTestCase): resources={"CUSTOM_FOO": 13}, requester_id=uuids.requester_id) ] + req_lvl_params = objects.RequestLevelParams( + root_required={"CUSTOM_BLUE"}, + same_subtree=[[uuids.group1, uuids.group2]] + ) with test.nested( mock.patch.object(objects.ComputeNode, 'get_by_nodename'), @@ -10836,7 +10866,9 @@ class ComputeAPITestCase(BaseTestCase): self.assertRaises( exception.InterfaceAttachResourceAllocationFailed, self.compute._allocate_port_resource_for_instance, - self.context, instance, pci_reqs, request_groups) + self.context, instance, pci_reqs, request_groups, + req_lvl_params, + ) mock_get_nodename.assert_called_once_with( self.context, instance.node) @@ -10851,6 +10883,10 @@ class ComputeAPITestCase(BaseTestCase): resources={"CUSTOM_FOO": 13}, requester_id=uuids.requester_id) ] + req_lvl_params = objects.RequestLevelParams( + root_required={"CUSTOM_BLUE"}, + same_subtree=[[uuids.group1, uuids.group2]] + ) with test.nested( mock.patch.object(objects.ComputeNode, 'get_by_nodename'), @@ -10885,7 +10921,9 @@ class ComputeAPITestCase(BaseTestCase): self.assertRaises( exception.InterfaceAttachResourceAllocationFailed, self.compute._allocate_port_resource_for_instance, - self.context, instance, pci_reqs, request_groups) + self.context, instance, pci_reqs, request_groups, + req_lvl_params + ) mock_get_nodename.assert_called_once_with( self.context, instance.node) @@ -10896,6 +10934,11 @@ class ComputeAPITestCase(BaseTestCase): request_groups[0].requester_id) self.assertEqual(request_groups[0], actual_rg) self.assertEqual(uuids.compute_node, actual_rg.in_tree) + self.assertEqual({"CUSTOM_BLUE"}, resource_request._root_required) + self.assertEqual( + [[uuids.group1, uuids.group2]], + resource_request._same_subtree + ) mock_add_res.assert_called_once_with( self.context, instance.uuid, mock.sentinel.resources) @@ -10907,6 +10950,10 @@ class ComputeAPITestCase(BaseTestCase): resources={"CUSTOM_FOO": 13}, requester_id=uuids.requester_id) ] + req_lvl_params = objects.RequestLevelParams( + root_required={"CUSTOM_BLUE"}, + same_subtree=[[uuids.group1, uuids.group2]] + ) with test.nested( mock.patch.object(objects.ComputeNode, 'get_by_nodename'), @@ -10943,7 +10990,9 @@ class ComputeAPITestCase(BaseTestCase): self.assertRaises( exception.AmbiguousResourceProviderForPCIRequest, self.compute._allocate_port_resource_for_instance, - self.context, instance, pci_reqs, request_groups) + self.context, instance, pci_reqs, request_groups, + req_lvl_params + ) mock_get_nodename.assert_called_once_with( self.context, instance.node) @@ -10954,6 +11003,11 @@ class ComputeAPITestCase(BaseTestCase): request_groups[0].requester_id) self.assertEqual(request_groups[0], actual_rg) self.assertEqual(uuids.compute_node, actual_rg.in_tree) + self.assertEqual({"CUSTOM_BLUE"}, resource_request._root_required) + self.assertEqual( + [[uuids.group1, uuids.group2]], + resource_request._same_subtree + ) mock_add_res.assert_called_once_with( self.context, instance.uuid, mock.sentinel.resources) mock_update_pci.assert_called_once_with( diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 95c7987357..26fb5caa79 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -2600,7 +2600,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, def do_test( update, meth, add_fault, notify, event, mock_claim_pci, mock_create_resource_req): - mock_create_resource_req.return_value = None, [] + mock_create_resource_req.return_value = ( + None, [], mock.sentinel.req_lvl_params) self.assertRaises(exception.InterfaceAttachFailed, self.compute.attach_interface, self.context, f_instance, uuids.network_id, diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index cec0a053ed..3fb6d5f2fe 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -5789,7 +5789,7 @@ class TestAPI(TestAPIBase): result = api.create_resource_requests( self.context, requested_networks, pci_requests) - network_metadata, port_resource_requests = result + network_metadata, port_resource_requests, _ = result self.assertFalse(mock_get_client.called) self.assertIsNone(network_metadata) @@ -5816,7 +5816,7 @@ class TestAPI(TestAPIBase): result = api.create_resource_requests( self.context, requested_networks, pci_requests) - network_metadata, port_resource_requests = result + network_metadata, port_resource_requests, _ = result mock_get_physnet_tunneled_info.assert_not_called() self.assertEqual(set(), network_metadata.physnets) @@ -5875,7 +5875,7 @@ class TestAPI(TestAPIBase): result = api.create_resource_requests( self.context, requested_networks, pci_requests) - network_metadata, port_resource_requests = result + network_metadata, port_resource_requests, _ = result self.assertEqual([ mock.sentinel.request_group1, @@ -5982,7 +5982,7 @@ class TestAPI(TestAPIBase): result = self.api.create_resource_requests( self.context, requested_networks, pci_requests=None) - network_metadata, port_resource_requests = result + network_metadata, port_resource_requests, _ = result mock_get_dp_group.assert_called_once_with('smat_nic') mock_get_physnet_tunneled_info.assert_called_once_with( self.context, mock.ANY, 'netN') @@ -6046,6 +6046,12 @@ class TestAPI(TestAPIBase): @mock.patch.object( neutronapi.API, '_has_extended_resource_request_extension', return_value=True) + @mock.patch( + 'nova.objects.request_spec.RequestLevelParams.extend_with' + ) + @mock.patch( + 'nova.objects.request_spec.RequestLevelParams.from_port_request' + ) @mock.patch( 'nova.objects.request_spec.RequestGroup.from_extended_port_request') @mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info') @@ -6054,6 +6060,7 @@ class TestAPI(TestAPIBase): def test_create_resource_request_extended( self, getclient, mock_get_port_vnic_info, mock_get_physnet_tunneled_info, mock_from_port_request, + mock_req_lvl_param, mock_extened_req_lvl_param, mock_has_extended_res_req ): requested_networks = objects.NetworkRequestList( @@ -6088,10 +6095,15 @@ class TestAPI(TestAPIBase): mock.sentinel.port2_request_group2, ], ] + # also both port1 and port2 has same subtree params + mock_req_lvl_param.side_effect = [ + mock.sentinel.port1_req_lvl_param, + mock.sentinel.port2_req_lvl_param, + ] result = api.create_resource_requests( self.context, requested_networks, pci_requests) - network_metadata, port_resource_requests = result + network_metadata, port_resource_requests, req_lvl_param = result # assert that all the request groups are collected from both ports self.assertEqual( @@ -6102,6 +6114,23 @@ class TestAPI(TestAPIBase): mock.sentinel.port2_request_group2, ], port_resource_requests) + # the same subtree requests are combined from the two ports + mock_req_lvl_param.assert_has_calls( + [ + mock.call( + port_resource_request=mock.sentinel.resource_request1), + mock.call( + port_resource_request=mock.sentinel.resource_request2), + ] + + ) + mock_extened_req_lvl_param.assert_has_calls( + [ + mock.call(mock.sentinel.port1_req_lvl_param), + mock.call(mock.sentinel.port2_req_lvl_param), + ] + ) + self.assertIsInstance(req_lvl_param, objects.RequestLevelParams) mock_from_port_request.assert_has_calls([ mock.call( diff --git a/nova/tests/unit/objects/test_request_spec.py b/nova/tests/unit/objects/test_request_spec.py index 93a1672cc8..fabadfe898 100644 --- a/nova/tests/unit/objects/test_request_spec.py +++ b/nova/tests/unit/objects/test_request_spec.py @@ -412,13 +412,17 @@ class _TestRequestSpecObject(object): filter_properties = {'fake': 'property'} rg = request_spec.RequestGroup() + req_lvl_params = request_spec.RequestLevelParams() - spec = objects.RequestSpec.from_components(ctxt, instance.uuid, image, - flavor, instance.numa_topology, instance.pci_requests, - filter_properties, None, instance.availability_zone, - port_resource_requests=[rg]) + spec = objects.RequestSpec.from_components( + ctxt, instance.uuid, image, + flavor, instance.numa_topology, instance.pci_requests, + filter_properties, None, instance.availability_zone, + port_resource_requests=[rg], request_level_params=req_lvl_params + ) self.assertListEqual([rg], spec.requested_resources) + self.assertEqual(req_lvl_params, spec.request_level_params) def test_get_scheduler_hint(self): spec_obj = objects.RequestSpec(scheduler_hints={'foo_single': ['1'], diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py index 4790d4a18e..7f553f07bb 100644 --- a/nova/tests/unit/scheduler/test_utils.py +++ b/nova/tests/unit/scheduler/test_utils.py @@ -1346,8 +1346,13 @@ class TestUtils(TestUtilsBase): "CUSTOM_VNIC_TYPE_NORMAL"] } ) + req_lvl_params = objects.RequestLevelParams( + root_required={"CUSTOM_BLUE"}, + root_forbidden={"CUSTOM_DIRTY"}, + same_subtree=[[uuids.group1]], + ) - rr = utils.ResourceRequest.from_request_group(rg) + rr = utils.ResourceRequest.from_request_group(rg, req_lvl_params) self.assertEqual( f'limit=1000&' @@ -1356,7 +1361,9 @@ class TestUtils(TestUtilsBase): f'CUSTOM_VNIC_TYPE_NORMAL&' f'resources{uuids.port_id}=' f'NET_BW_EGR_KILOBIT_PER_SEC%3A1000%2C' - f'NET_BW_IGR_KILOBIT_PER_SEC%3A1000', + f'NET_BW_IGR_KILOBIT_PER_SEC%3A1000&' + f'root_required=CUSTOM_BLUE%2C%21CUSTOM_DIRTY&' + f'same_subtree={uuids.group1}', rr.to_querystring()) def test_resource_request_add_group_inserts_the_group(self):