def __init__(self, context, request, has_trees, sharing, suffix=''): """Initializes the object retrieving and caching matching providers for each conditions like resource and aggregates from database. :raises placement.exception.ResourceProviderNotFound if there is no provider found which satisfies the request. """ # TODO(tetsuro): split this into smaller functions reordering self.context = context # The request group suffix self.suffix = suffix # A dict, keyed by resource class internal ID, of the amounts of that # resource class being requested by the group. self.resources = { rc_cache.RC_CACHE.id_from_string(key): value for key, value in request.resources.items() } # A list of lists of aggregate UUIDs that the providers matching for # that request group must be members of self.member_of = request.member_of # A list of aggregate UUIDs that the providers matching for # that request group must not be members of self.forbidden_aggs = request.forbidden_aggs # A set of provider ids that matches the requested positive aggregates self.rps_in_aggs = set() if self.member_of: self.rps_in_aggs = provider_ids_matching_aggregates( context, self.member_of) if not self.rps_in_aggs: raise exception.ResourceProviderNotFound() # If True, this RequestGroup represents requests which must be # satisfied by a single resource provider. If False, represents a # request for resources in any resource provider in the same tree, # or a sharing provider. self.use_same_provider = request.use_same_provider # maps the trait name to the trait internal ID self.required_trait_map = {} self.forbidden_trait_map = {} for trait_map, traits in ((self.required_trait_map, request.required_traits), (self.forbidden_trait_map, request.forbidden_traits)): if traits: trait_map.update(trait_obj.ids_from_names(context, traits)) # Internal id of a root provider. If provided, this RequestGroup must # be satisfied by resource provider(s) under the root provider. self.tree_root_id = None if request.in_tree: tree_ids = provider_ids_from_uuid(context, request.in_tree) if tree_ids is None: raise exception.ResourceProviderNotFound() self.tree_root_id = tree_ids.root_id LOG.debug( "getting allocation candidates in the same tree " "with the root provider %s", tree_ids.root_uuid) self._rps_with_resource = {} for rc_id, amount in self.resources.items(): # NOTE(tetsuro): We could pass rps in requested aggregates to # get_providers_with_resource here once we explicitly put # aggregates to nested (non-root) providers (the aggregate # flows down feature) rather than applying later the implicit rule # that aggregate on root spans the whole tree provs_with_resource = get_providers_with_resource( context, rc_id, amount, tree_root_id=self.tree_root_id) if not provs_with_resource: raise exception.ResourceProviderNotFound() self._rps_with_resource[rc_id] = provs_with_resource # a set of resource provider IDs that share some inventory for some # resource class. self._sharing_providers = sharing # bool indicating there is some level of nesting in the environment self.has_trees = has_trees
def _get_all_by_filters_from_db(context, filters): # Eg. filters can be: # filters = { # 'name': <name>, # 'uuid': <uuid>, # 'member_of': [[<aggregate_uuid>, <aggregate_uuid>], # [<aggregate_uuid>]] # 'forbidden_aggs': [<aggregate_uuid>, <aggregate_uuid>] # 'resources': { # 'VCPU': 1, # 'MEMORY_MB': 1024 # }, # 'in_tree': <uuid>, # 'required': [<trait_name>, ...] # } if not filters: filters = {} else: # Since we modify the filters, copy them so that we don't modify # them in the calling program. filters = copy.deepcopy(filters) name = filters.pop('name', None) uuid = filters.pop('uuid', None) member_of = filters.pop('member_of', []) forbidden_aggs = filters.pop('forbidden_aggs', []) required = set(filters.pop('required', [])) forbidden = set([trait for trait in required if trait.startswith('!')]) required = required - forbidden forbidden = set([trait.lstrip('!') for trait in forbidden]) resources = filters.pop('resources', {}) in_tree = filters.pop('in_tree', None) rp = sa.alias(_RP_TBL, name="rp") root_rp = sa.alias(_RP_TBL, name="root_rp") parent_rp = sa.alias(_RP_TBL, name="parent_rp") cols = [ rp.c.id, rp.c.uuid, rp.c.name, rp.c.generation, rp.c.updated_at, rp.c.created_at, root_rp.c.uuid.label("root_provider_uuid"), parent_rp.c.uuid.label("parent_provider_uuid"), ] rp_to_root = sa.join( rp, root_rp, rp.c.root_provider_id == root_rp.c.id) rp_to_parent = sa.outerjoin( rp_to_root, parent_rp, rp.c.parent_provider_id == parent_rp.c.id) query = sa.select(cols).select_from(rp_to_parent) if name: query = query.where(rp.c.name == name) if uuid: query = query.where(rp.c.uuid == uuid) if in_tree: # The 'in_tree' parameter is the UUID of a resource provider that # the caller wants to limit the returned providers to only those # within its "provider tree". So, we look up the resource provider # having the UUID specified by the 'in_tree' parameter and grab the # root_provider_id value of that record. We can then ask for only # those resource providers having a root_provider_id of that value. tree_ids = res_ctx.provider_ids_from_uuid(context, in_tree) if tree_ids is None: # List operations should simply return an empty list when a # non-existing resource provider UUID is given. return [] root_id = tree_ids.root_id query = query.where(rp.c.root_provider_id == root_id) if required: trait_map = trait_obj.ids_from_names(context, required) trait_rps = res_ctx._get_provider_ids_having_all_traits( context, trait_map) if not trait_rps: return [] query = query.where(rp.c.id.in_(trait_rps)) if forbidden: trait_map = trait_obj.ids_from_names(context, forbidden) trait_rps = res_ctx.get_provider_ids_having_any_trait( context, trait_map) if trait_rps: query = query.where(~rp.c.id.in_(trait_rps)) if member_of: rps_in_aggs = res_ctx.provider_ids_matching_aggregates( context, member_of) if not rps_in_aggs: return [] query = query.where(rp.c.id.in_(rps_in_aggs)) if forbidden_aggs: rps_bad_aggs = res_ctx.provider_ids_matching_aggregates( context, [forbidden_aggs]) if rps_bad_aggs: query = query.where(~rp.c.id.in_(rps_bad_aggs)) for rc_name, amount in resources.items(): rc_id = context.rc_cache.id_from_string(rc_name) rps_with_resource = res_ctx.get_providers_with_resource( context, rc_id, amount) rps_with_resource = (rp[0] for rp in rps_with_resource) query = query.where(rp.c.id.in_(rps_with_resource)) return context.session.execute(query).fetchall()
def _get_by_one_request(context, request, sharing_providers, has_trees): """Get allocation candidates for one RequestGroup. Must be called from within an placement_context_manager.reader (or writer) context. :param context: Nova RequestContext. :param request: One placement.lib.RequestGroup :param sharing_providers: dict, keyed by resource class internal ID, of the set of provider IDs containing shared inventory of that resource class :param has_trees: bool indicating there is some level of nesting in the environment (if there isn't, we take faster, simpler code paths) :return: A tuple of (allocation_requests, provider_summaries) satisfying `request`. """ # Transform resource string names to internal integer IDs resources = { rc_cache.RC_CACHE.id_from_string(key): value for key, value in request.resources.items() } # maps the trait name to the trait internal ID required_trait_map = {} forbidden_trait_map = {} for trait_map, traits in ( (required_trait_map, request.required_traits), (forbidden_trait_map, request.forbidden_traits)): if traits: trait_map.update(trait_obj.ids_from_names(context, traits)) member_of = request.member_of tree_root_id = None if request.in_tree: tree_ids = rp_obj.provider_ids_from_uuid(context, request.in_tree) if tree_ids is None: # List operations should simply return an empty list when a # non-existing resource provider UUID is given for in_tree. return [], [] tree_root_id = tree_ids.root_id LOG.debug("getting allocation candidates in the same tree " "with the root provider %s", tree_ids.root_uuid) any_sharing = any(sharing_providers.values()) if not request.use_same_provider and (has_trees or any_sharing): # TODO(jaypipes): The check/callout to handle trees goes here. # Build a dict, keyed by resource class internal ID, of lists of # internal IDs of resource providers that share some inventory for # each resource class requested. # If there aren't any providers that have any of the # required traits, just exit early... if required_trait_map: # TODO(cdent): Now that there is also a forbidden_trait_map # it should be possible to further optimize this attempt at # a quick return, but we leave that to future patches for # now. trait_rps = rp_obj.get_provider_ids_having_any_trait( context, required_trait_map) if not trait_rps: return [], [] rp_candidates = rp_obj.get_trees_matching_all( context, resources, required_trait_map, forbidden_trait_map, sharing_providers, member_of, tree_root_id) return _alloc_candidates_multiple_providers( context, resources, required_trait_map, forbidden_trait_map, rp_candidates) # Either we are processing a single-RP request group, or there are no # sharing providers that (help) satisfy the request. Get a list of # tuples of (internal provider ID, root provider ID) that have ALL # the requested resources and more efficiently construct the # allocation requests. rp_tuples = rp_obj.get_provider_ids_matching( context, resources, required_trait_map, forbidden_trait_map, member_of, tree_root_id) return _alloc_candidates_single_provider(context, resources, rp_tuples)