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):