diff --git a/nova/api/openstack/placement/handlers/allocation_candidate.py b/nova/api/openstack/placement/handlers/allocation_candidate.py index f5425cdf4ffe..e6cfb65c6e9f 100644 --- a/nova/api/openstack/placement/handlers/allocation_candidate.py +++ b/nova/api/openstack/placement/handlers/allocation_candidate.py @@ -205,40 +205,6 @@ def _transform_provider_summaries(p_sums, requests, want_version): return ret -def _exclude_nested_providers(alloc_cands): - """Exclude allocation requests and provider summaries for old microversions - if they involve more than one provider from the same tree. - """ - # Build a temporary dict, keyed by root RP UUID of sets of UUIDs of all RPs - # in that tree. - tree_rps_by_root = collections.defaultdict(set) - for ps in alloc_cands.provider_summaries: - rp_uuid = ps.resource_provider.uuid - root_uuid = ps.resource_provider.root_provider_uuid - tree_rps_by_root[root_uuid].add(rp_uuid) - # We use this to get a list of sets of providers in each tree - tree_sets = list(tree_rps_by_root.values()) - - for a_req in alloc_cands.allocation_requests[:]: - alloc_rp_uuids = set([ - arr.resource_provider.uuid for arr in a_req.resource_requests]) - # If more than one allocation is provided by the same tree, kill - # that allocation request. - if any(len(tree_set & alloc_rp_uuids) > 1 for tree_set in tree_sets): - alloc_cands.allocation_requests.remove(a_req) - - # Exclude eliminated providers from the provider summaries. - all_rp_uuids = set() - for a_req in alloc_cands.allocation_requests: - all_rp_uuids |= set( - arr.resource_provider.uuid for arr in a_req.resource_requests) - for ps in alloc_cands.provider_summaries[:]: - if ps.resource_provider.uuid not in all_rp_uuids: - alloc_cands.provider_summaries.remove(ps) - - return alloc_cands - - def _transform_allocation_candidates(alloc_cands, requests, want_version): """Turn supplied AllocationCandidates object into a dict containing allocation requests and provider summaries. @@ -248,10 +214,6 @@ def _transform_allocation_candidates(alloc_cands, requests, want_version): 'provider_summaries': , } """ - # exclude nested providers with old microversions - if not want_version.matches((1, 29)): - alloc_cands = _exclude_nested_providers(alloc_cands) - if want_version.matches((1, 12)): a_reqs = _transform_allocation_requests_dict( alloc_cands.allocation_requests) @@ -311,9 +273,13 @@ def list_allocation_candidates(req): _('The "group_policy" parameter is required when specifying ' 'more than one "resources{N}" parameter.')) + # We can't be aware of nested architecture with old microversions + nested_aware = want_version.matches((1, 29)) + try: cands = rp_obj.AllocationCandidates.get_by_requests( - context, requests, limit=limit, group_policy=group_policy) + context, requests, limit=limit, group_policy=group_policy, + nested_aware=nested_aware) except exception.ResourceClassNotFound as exc: raise webob.exc.HTTPBadRequest( _('Invalid resource class in resources parameter: %(error)s') % diff --git a/nova/api/openstack/placement/objects/resource_provider.py b/nova/api/openstack/placement/objects/resource_provider.py index 87e75ec65305..910f19c71c27 100644 --- a/nova/api/openstack/placement/objects/resource_provider.py +++ b/nova/api/openstack/placement/objects/resource_provider.py @@ -3983,6 +3983,40 @@ def _merge_candidates(candidates, group_policy=None): return areqs, psums +def _exclude_nested_providers(allocation_requests, provider_summaries): + """Exclude allocation requests and provider summaries for old microversions + if they involve more than one provider from the same tree. + """ + # Build a temporary dict, keyed by root RP UUID of sets of UUIDs of all RPs + # in that tree. + tree_rps_by_root = collections.defaultdict(set) + for ps in provider_summaries: + rp_uuid = ps.resource_provider.uuid + root_uuid = ps.resource_provider.root_provider_uuid + tree_rps_by_root[root_uuid].add(rp_uuid) + # We use this to get a list of sets of providers in each tree + tree_sets = list(tree_rps_by_root.values()) + + for a_req in allocation_requests[:]: + alloc_rp_uuids = set([ + arr.resource_provider.uuid for arr in a_req.resource_requests]) + # If more than one allocation is provided by the same tree, kill + # that allocation request. + if any(len(tree_set & alloc_rp_uuids) > 1 for tree_set in tree_sets): + allocation_requests.remove(a_req) + + # Exclude eliminated providers from the provider summaries. + all_rp_uuids = set() + for a_req in allocation_requests: + all_rp_uuids |= set( + arr.resource_provider.uuid for arr in a_req.resource_requests) + for ps in provider_summaries[:]: + if ps.resource_provider.uuid not in all_rp_uuids: + provider_summaries.remove(ps) + + return allocation_requests, provider_summaries + + @base.VersionedObjectRegistry.register_if(False) class AllocationCandidates(base.VersionedObject): """The AllocationCandidates object is a collection of possible allocations @@ -4002,7 +4036,8 @@ class AllocationCandidates(base.VersionedObject): } @classmethod - def get_by_requests(cls, context, requests, limit=None, group_policy=None): + def get_by_requests(cls, context, requests, limit=None, group_policy=None, + nested_aware=True): """Returns an AllocationCandidates object containing all resource providers matching a set of supplied resource constraints, with a set of allocation requests constructed from that list of resource @@ -4026,12 +4061,16 @@ class AllocationCandidates(base.VersionedObject): other. If the value is "isolate", we will filter out allocation requests where any such RequestGroups are satisfied by the same RP. + :param nested_aware: If False, we are blind to nested architecture and + can't pick resources from multiple providers even + if they come from the same tree. :return: An instance of AllocationCandidates with allocation_requests and provider_summaries satisfying `requests`, limited according to `limit`. """ alloc_reqs, provider_summaries = cls._get_by_requests( - context, requests, limit=limit, group_policy=group_policy) + context, requests, limit=limit, group_policy=group_policy, + nested_aware=nested_aware) return cls( context, allocation_requests=alloc_reqs, @@ -4117,7 +4156,7 @@ class AllocationCandidates(base.VersionedObject): # reader when that migration is no longer happening. @db_api.placement_context_manager.writer def _get_by_requests(cls, context, requests, limit=None, - group_policy=None): + group_policy=None, nested_aware=True): # TODO(jaypipes): Make a RequestGroupContext object and put these # pieces of information in there, passing the context to the various # internal functions handling that part of the request. @@ -4156,6 +4195,10 @@ class AllocationCandidates(base.VersionedObject): alloc_request_objs, summary_objs = _merge_candidates( candidates, group_policy=group_policy) + if not nested_aware and has_trees: + alloc_request_objs, summary_objs = _exclude_nested_providers( + alloc_request_objs, summary_objs) + # Limit the number of allocation request objects. We do this after # creating all of them so that we can do a random slice without # needing to mess with the complex sql above or add additional