Merge "RequestWideParams and RequestWideSearchContext"
This commit is contained in:
commit
0e24af410a
placement
@ -270,21 +270,13 @@ def list_allocation_candidates(req):
|
|||||||
get_schema = schema.GET_SCHEMA_1_16
|
get_schema = schema.GET_SCHEMA_1_16
|
||||||
util.validate_query_params(req, get_schema)
|
util.validate_query_params(req, get_schema)
|
||||||
|
|
||||||
requests = lib.RequestGroup.dict_from_request(req)
|
groups = lib.RequestGroup.dict_from_request(req)
|
||||||
limit = req.GET.getall('limit')
|
rqparams = lib.RequestWideParams.from_request(req)
|
||||||
# JSONschema has already confirmed that limit has the form
|
|
||||||
# of an integer.
|
|
||||||
if limit:
|
|
||||||
limit = int(limit[0])
|
|
||||||
|
|
||||||
group_policy = req.GET.getall('group_policy') or None
|
if not rqparams.group_policy:
|
||||||
# Schema ensures we get either "none" or "isolate"
|
|
||||||
if group_policy:
|
|
||||||
group_policy = group_policy[0]
|
|
||||||
else:
|
|
||||||
# group_policy is required if more than one numbered request group was
|
# group_policy is required if more than one numbered request group was
|
||||||
# specified.
|
# specified.
|
||||||
if len([rg for rg in requests.values() if rg.use_same_provider]) > 1:
|
if len([rg for rg in groups.values() if rg.use_same_provider]) > 1:
|
||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
'The "group_policy" parameter is required when specifying '
|
'The "group_policy" parameter is required when specifying '
|
||||||
'more than one "resources{N}" parameter.')
|
'more than one "resources{N}" parameter.')
|
||||||
@ -294,8 +286,7 @@ def list_allocation_candidates(req):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
cands = ac_obj.AllocationCandidates.get_by_requests(
|
cands = ac_obj.AllocationCandidates.get_by_requests(
|
||||||
context, requests, limit=limit, group_policy=group_policy,
|
context, groups, rqparams, nested_aware=nested_aware)
|
||||||
nested_aware=nested_aware)
|
|
||||||
except exception.ResourceClassNotFound as exc:
|
except exception.ResourceClassNotFound as exc:
|
||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
'Invalid resource class in resources parameter: %(error)s' %
|
'Invalid resource class in resources parameter: %(error)s' %
|
||||||
@ -304,7 +295,7 @@ def list_allocation_candidates(req):
|
|||||||
raise webob.exc.HTTPBadRequest(six.text_type(exc))
|
raise webob.exc.HTTPBadRequest(six.text_type(exc))
|
||||||
|
|
||||||
response = req.response
|
response = req.response
|
||||||
trx_cands = _transform_allocation_candidates(cands, requests, want_version)
|
trx_cands = _transform_allocation_candidates(cands, groups, want_version)
|
||||||
json_data = jsonutils.dumps(trx_cands)
|
json_data = jsonutils.dumps(trx_cands)
|
||||||
response.body = encodeutils.to_utf8(json_data)
|
response.body = encodeutils.to_utf8(json_data)
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
|
@ -272,3 +272,45 @@ class RequestGroup(object):
|
|||||||
cls._fix_forbidden(by_suffix)
|
cls._fix_forbidden(by_suffix)
|
||||||
|
|
||||||
return by_suffix
|
return by_suffix
|
||||||
|
|
||||||
|
|
||||||
|
class RequestWideParams(object):
|
||||||
|
"""GET /allocation_candidates params that apply to the request as a whole.
|
||||||
|
|
||||||
|
This is in contrast with individual request groups (list of RequestGroup
|
||||||
|
above).
|
||||||
|
"""
|
||||||
|
def __init__(self, limit=None, group_policy=None):
|
||||||
|
"""Create a RequestWideParams.
|
||||||
|
|
||||||
|
:param limit: An integer, N, representing the maximum number of
|
||||||
|
allocation candidates to return. If
|
||||||
|
CONF.placement.randomize_allocation_candidates is True this
|
||||||
|
will be a random sampling of N of the available results. If
|
||||||
|
False then the first N results, in whatever order the database
|
||||||
|
picked them, will be returned. In either case if there are
|
||||||
|
fewer than N total results, all the results will be returned.
|
||||||
|
:param group_policy: String indicating how RequestGroups with
|
||||||
|
use_same_provider=True should interact with each other. If the
|
||||||
|
value is "isolate", we will filter out allocation requests
|
||||||
|
where any such RequestGroups are satisfied by the same RP.
|
||||||
|
"""
|
||||||
|
self.limit = limit
|
||||||
|
self.group_policy = group_policy
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_request(cls, req):
|
||||||
|
limit = req.GET.getall('limit')
|
||||||
|
# JSONschema has already confirmed that limit has the form
|
||||||
|
# of an integer.
|
||||||
|
if limit:
|
||||||
|
limit = int(limit[0])
|
||||||
|
|
||||||
|
group_policy = req.GET.getall('group_policy') or None
|
||||||
|
# Schema ensures we get either "none" or "isolate"
|
||||||
|
if group_policy:
|
||||||
|
group_policy = group_policy[0]
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
limit=limit,
|
||||||
|
group_policy=group_policy)
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
import collections
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
import random
|
|
||||||
|
|
||||||
import os_traits
|
import os_traits
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -55,8 +54,7 @@ class AllocationCandidates(object):
|
|||||||
self.provider_summaries = provider_summaries
|
self.provider_summaries = provider_summaries
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_requests(cls, context, requests, limit=None, group_policy=None,
|
def get_by_requests(cls, context, groups, rqparams, nested_aware=True):
|
||||||
nested_aware=True):
|
|
||||||
"""Returns an AllocationCandidates object containing all resource
|
"""Returns an AllocationCandidates object containing all resource
|
||||||
providers matching a set of supplied resource constraints, with a set
|
providers matching a set of supplied resource constraints, with a set
|
||||||
of allocation requests constructed from that list of resource
|
of allocation requests constructed from that list of resource
|
||||||
@ -64,21 +62,9 @@ class AllocationCandidates(object):
|
|||||||
contex.config) is True (default is False) then the order of the
|
contex.config) is True (default is False) then the order of the
|
||||||
allocation requests will be randomized.
|
allocation requests will be randomized.
|
||||||
|
|
||||||
:param context: Nova RequestContext.
|
:param context: placement.context.RequestContext object.
|
||||||
:param requests: Dict, keyed by suffix, of placement.lib.RequestGroup
|
:param groups: Dict, keyed by suffix, of placement.lib.RequestGroup
|
||||||
:param limit: An integer, N, representing the maximum number of
|
:param rqparams: A RequestWideParams.
|
||||||
allocation candidates to return. If
|
|
||||||
CONF.placement.randomize_allocation_candidates is True
|
|
||||||
this will be a random sampling of N of the available
|
|
||||||
results. If False then the first N results, in whatever
|
|
||||||
order the database picked them, will be returned. In
|
|
||||||
either case if there are fewer than N total results,
|
|
||||||
all the results will be returned.
|
|
||||||
:param group_policy: String indicating how RequestGroups with
|
|
||||||
use_same_provider=True should interact with each
|
|
||||||
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
|
:param nested_aware: If False, we are blind to nested architecture and
|
||||||
can't pick resources from multiple providers even
|
can't pick resources from multiple providers even
|
||||||
if they come from the same tree.
|
if they come from the same tree.
|
||||||
@ -87,8 +73,7 @@ class AllocationCandidates(object):
|
|||||||
according to `limit`.
|
according to `limit`.
|
||||||
"""
|
"""
|
||||||
alloc_reqs, provider_summaries = cls._get_by_requests(
|
alloc_reqs, provider_summaries = cls._get_by_requests(
|
||||||
context, requests, limit=limit, group_policy=group_policy,
|
context, groups, rqparams, nested_aware=nested_aware)
|
||||||
nested_aware=nested_aware)
|
|
||||||
return cls(
|
return cls(
|
||||||
allocation_requests=alloc_reqs,
|
allocation_requests=alloc_reqs,
|
||||||
provider_summaries=provider_summaries,
|
provider_summaries=provider_summaries,
|
||||||
@ -133,31 +118,31 @@ class AllocationCandidates(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@db_api.placement_context_manager.reader
|
@db_api.placement_context_manager.reader
|
||||||
def _get_by_requests(cls, context, requests, limit=None,
|
def _get_by_requests(cls, context, groups, rqparams, nested_aware=True):
|
||||||
group_policy=None, nested_aware=True):
|
rw_ctx = res_ctx.RequestWideSearchContext(
|
||||||
has_trees = res_ctx.has_provider_trees(context)
|
context, rqparams, nested_aware)
|
||||||
sharing = res_ctx.get_sharing_providers(context)
|
sharing = res_ctx.get_sharing_providers(context)
|
||||||
|
|
||||||
candidates = {}
|
candidates = {}
|
||||||
for suffix, request in requests.items():
|
for suffix, group in groups.items():
|
||||||
try:
|
try:
|
||||||
rg_ctx = res_ctx.RequestGroupSearchContext(
|
rg_ctx = res_ctx.RequestGroupSearchContext(
|
||||||
context, request, has_trees, sharing, suffix)
|
context, group, rw_ctx.has_trees, sharing, suffix)
|
||||||
except exception.ResourceProviderNotFound:
|
except exception.ResourceProviderNotFound:
|
||||||
return [], []
|
return [], []
|
||||||
|
|
||||||
alloc_reqs, summaries = cls._get_by_one_request(rg_ctx)
|
alloc_reqs, summaries = cls._get_by_one_request(rg_ctx)
|
||||||
LOG.debug("%s (suffix '%s') returned %d matches",
|
LOG.debug("%s (suffix '%s') returned %d matches",
|
||||||
str(request), str(suffix), len(alloc_reqs))
|
str(group), str(suffix), len(alloc_reqs))
|
||||||
if not alloc_reqs:
|
if not alloc_reqs:
|
||||||
# Shortcut: If any one request resulted in no candidates, the
|
# Shortcut: If any one group resulted in no candidates, the
|
||||||
# whole operation is shot.
|
# whole operation is shot.
|
||||||
return [], []
|
return [], []
|
||||||
# Mark each allocation request according to whether its
|
# Mark each allocation request according to whether its
|
||||||
# corresponding RequestGroup required it to be restricted to a
|
# corresponding RequestGroup required it to be restricted to a
|
||||||
# single provider. We'll need this later to evaluate group_policy.
|
# single provider. We'll need this later to evaluate group_policy.
|
||||||
for areq in alloc_reqs:
|
for areq in alloc_reqs:
|
||||||
areq.use_same_provider = request.use_same_provider
|
areq.use_same_provider = group.use_same_provider
|
||||||
candidates[suffix] = alloc_reqs, summaries
|
candidates[suffix] = alloc_reqs, summaries
|
||||||
|
|
||||||
# At this point, each (alloc_requests, summary_obj) in `candidates` is
|
# At this point, each (alloc_requests, summary_obj) in `candidates` is
|
||||||
@ -166,49 +151,12 @@ class AllocationCandidates(object):
|
|||||||
# `candidates` dict is guaranteed to contain entries for all suffixes,
|
# `candidates` dict is guaranteed to contain entries for all suffixes,
|
||||||
# or we would have short-circuited above.
|
# or we would have short-circuited above.
|
||||||
alloc_request_objs, summary_objs = _merge_candidates(
|
alloc_request_objs, summary_objs = _merge_candidates(
|
||||||
candidates, group_policy=group_policy)
|
candidates, rw_ctx)
|
||||||
|
|
||||||
if not nested_aware and has_trees:
|
alloc_request_objs, summary_objs = rw_ctx.exclude_nested_providers(
|
||||||
alloc_request_objs, summary_objs = _exclude_nested_providers(
|
alloc_request_objs, summary_objs)
|
||||||
alloc_request_objs, summary_objs)
|
|
||||||
|
|
||||||
return cls._limit_results(context, alloc_request_objs, summary_objs,
|
return rw_ctx.limit_results(alloc_request_objs, summary_objs)
|
||||||
limit)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _limit_results(context, alloc_request_objs, summary_objs, limit):
|
|
||||||
# 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
|
|
||||||
# columns to the DB.
|
|
||||||
if limit and limit < len(alloc_request_objs):
|
|
||||||
if context.config.placement.randomize_allocation_candidates:
|
|
||||||
alloc_request_objs = random.sample(alloc_request_objs, limit)
|
|
||||||
else:
|
|
||||||
alloc_request_objs = alloc_request_objs[:limit]
|
|
||||||
# Limit summaries to only those mentioned in the allocation reqs.
|
|
||||||
kept_summary_objs = []
|
|
||||||
alloc_req_root_uuids = set()
|
|
||||||
# Extract root resource provider uuids from the resource requests.
|
|
||||||
for aro in alloc_request_objs:
|
|
||||||
for arr in aro.resource_requests:
|
|
||||||
alloc_req_root_uuids.add(
|
|
||||||
arr.resource_provider.root_provider_uuid)
|
|
||||||
for summary in summary_objs:
|
|
||||||
rp_root_uuid = summary.resource_provider.root_provider_uuid
|
|
||||||
# Skip a summary if we are limiting and haven't selected an
|
|
||||||
# allocation request that uses the resource provider.
|
|
||||||
if rp_root_uuid not in alloc_req_root_uuids:
|
|
||||||
continue
|
|
||||||
kept_summary_objs.append(summary)
|
|
||||||
summary_objs = kept_summary_objs
|
|
||||||
LOG.debug('Limiting results yields %d allocation requests and '
|
|
||||||
'%d provider summaries', len(alloc_request_objs),
|
|
||||||
len(summary_objs))
|
|
||||||
elif context.config.placement.randomize_allocation_candidates:
|
|
||||||
random.shuffle(alloc_request_objs)
|
|
||||||
|
|
||||||
return alloc_request_objs, summary_objs
|
|
||||||
|
|
||||||
|
|
||||||
class AllocationRequest(object):
|
class AllocationRequest(object):
|
||||||
@ -749,7 +697,8 @@ def _exceeds_capacity(areq, psum_res_by_rp_rc):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _merge_candidates(candidates, group_policy=None):
|
# TODO(efried): Move _merge_candidates to rw_ctx?
|
||||||
|
def _merge_candidates(candidates, rw_ctx):
|
||||||
"""Given a dict, keyed by RequestGroup suffix, of tuples of
|
"""Given a dict, keyed by RequestGroup suffix, of tuples of
|
||||||
(allocation_requests, provider_summaries), produce a single tuple of
|
(allocation_requests, provider_summaries), produce a single tuple of
|
||||||
(allocation_requests, provider_summaries) that appropriately incorporates
|
(allocation_requests, provider_summaries) that appropriately incorporates
|
||||||
@ -764,10 +713,7 @@ def _merge_candidates(candidates, group_policy=None):
|
|||||||
|
|
||||||
:param candidates: A dict, keyed by integer suffix or '', of tuples of
|
:param candidates: A dict, keyed by integer suffix or '', of tuples of
|
||||||
(allocation_requests, provider_summaries) to be merged.
|
(allocation_requests, provider_summaries) to be merged.
|
||||||
:param group_policy: String indicating how RequestGroups should interact
|
:param rw_ctx: RequestWideSearchContext.
|
||||||
with each other. If the value is "isolate", we will filter out
|
|
||||||
candidates where AllocationRequests that came from RequestGroups
|
|
||||||
keyed by nonempty suffixes are satisfied by the same provider.
|
|
||||||
:return: A tuple of (allocation_requests, provider_summaries).
|
:return: A tuple of (allocation_requests, provider_summaries).
|
||||||
"""
|
"""
|
||||||
# Build a dict, keyed by anchor root provider UUID, of dicts, keyed by
|
# Build a dict, keyed by anchor root provider UUID, of dicts, keyed by
|
||||||
@ -836,8 +782,9 @@ def _merge_candidates(candidates, group_policy=None):
|
|||||||
# At this point, each AllocationRequest in areq_list is still
|
# At this point, each AllocationRequest in areq_list is still
|
||||||
# marked as use_same_provider. This is necessary to filter by group
|
# marked as use_same_provider. This is necessary to filter by group
|
||||||
# policy, which enforces how these interact with each other.
|
# policy, which enforces how these interact with each other.
|
||||||
|
# TODO(efried): Move _satisfies_group_policy to rw_ctx?
|
||||||
if not _satisfies_group_policy(
|
if not _satisfies_group_policy(
|
||||||
areq_list, group_policy, num_granular_groups):
|
areq_list, rw_ctx.group_policy, num_granular_groups):
|
||||||
continue
|
continue
|
||||||
# Now we go from this (where 'arr' is AllocationRequestResource):
|
# Now we go from this (where 'arr' is AllocationRequestResource):
|
||||||
# [ areq__B(arrX, arrY, arrZ),
|
# [ areq__B(arrX, arrY, arrZ),
|
||||||
@ -856,6 +803,7 @@ def _merge_candidates(candidates, group_policy=None):
|
|||||||
# *independent* queries, it's possible that the combined result
|
# *independent* queries, it's possible that the combined result
|
||||||
# now exceeds capacity where amounts of the same RP+RC were
|
# now exceeds capacity where amounts of the same RP+RC were
|
||||||
# folded together. So do a final capacity check/filter.
|
# folded together. So do a final capacity check/filter.
|
||||||
|
# TODO(efried): Move _exceeds_capacity to rw_ctx?
|
||||||
if _exceeds_capacity(areq, psum_res_by_rp_rc):
|
if _exceeds_capacity(areq, psum_res_by_rp_rc):
|
||||||
continue
|
continue
|
||||||
areqs.add(areq)
|
areqs.add(areq)
|
||||||
@ -929,40 +877,3 @@ def _satisfies_group_policy(areqs, group_policy, num_granular_groups):
|
|||||||
'request (%d): %s',
|
'request (%d): %s',
|
||||||
num_granular_groups_in_areqs, num_granular_groups, str(areqs))
|
num_granular_groups_in_areqs, num_granular_groups, str(areqs))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
LOG.debug('Excluding nested providers yields %d allocation requests and '
|
|
||||||
'%d provider summaries', len(allocation_requests),
|
|
||||||
len(provider_summaries))
|
|
||||||
return allocation_requests, provider_summaries
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
import collections
|
import collections
|
||||||
import os_traits
|
import os_traits
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
import random
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import sql
|
from sqlalchemy import sql
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ class RequestGroupSearchContext(object):
|
|||||||
"""An adapter object that represents the search for allocation candidates
|
"""An adapter object that represents the search for allocation candidates
|
||||||
for a single request group.
|
for a single request group.
|
||||||
"""
|
"""
|
||||||
def __init__(self, context, request, has_trees, sharing, suffix=''):
|
def __init__(self, context, group, has_trees, sharing, suffix=''):
|
||||||
"""Initializes the object retrieving and caching matching providers
|
"""Initializes the object retrieving and caching matching providers
|
||||||
for each conditions like resource and aggregates from database.
|
for each conditions like resource and aggregates from database.
|
||||||
|
|
||||||
@ -65,16 +66,16 @@ class RequestGroupSearchContext(object):
|
|||||||
# resource class being requested by the group.
|
# resource class being requested by the group.
|
||||||
self.resources = {
|
self.resources = {
|
||||||
rc_cache.RC_CACHE.id_from_string(key): value
|
rc_cache.RC_CACHE.id_from_string(key): value
|
||||||
for key, value in request.resources.items()
|
for key, value in group.resources.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
# A list of lists of aggregate UUIDs that the providers matching for
|
# A list of lists of aggregate UUIDs that the providers matching for
|
||||||
# that request group must be members of
|
# that request group must be members of
|
||||||
self.member_of = request.member_of
|
self.member_of = group.member_of
|
||||||
|
|
||||||
# A list of aggregate UUIDs that the providers matching for
|
# A list of aggregate UUIDs that the providers matching for
|
||||||
# that request group must not be members of
|
# that request group must not be members of
|
||||||
self.forbidden_aggs = request.forbidden_aggs
|
self.forbidden_aggs = group.forbidden_aggs
|
||||||
|
|
||||||
# A set of provider ids that matches the requested positive aggregates
|
# A set of provider ids that matches the requested positive aggregates
|
||||||
self.rps_in_aggs = set()
|
self.rps_in_aggs = set()
|
||||||
@ -88,22 +89,22 @@ class RequestGroupSearchContext(object):
|
|||||||
# satisfied by a single resource provider. If False, represents a
|
# satisfied by a single resource provider. If False, represents a
|
||||||
# request for resources in any resource provider in the same tree,
|
# request for resources in any resource provider in the same tree,
|
||||||
# or a sharing provider.
|
# or a sharing provider.
|
||||||
self.use_same_provider = request.use_same_provider
|
self.use_same_provider = group.use_same_provider
|
||||||
|
|
||||||
# maps the trait name to the trait internal ID
|
# maps the trait name to the trait internal ID
|
||||||
self.required_trait_map = {}
|
self.required_trait_map = {}
|
||||||
self.forbidden_trait_map = {}
|
self.forbidden_trait_map = {}
|
||||||
for trait_map, traits in (
|
for trait_map, traits in (
|
||||||
(self.required_trait_map, request.required_traits),
|
(self.required_trait_map, group.required_traits),
|
||||||
(self.forbidden_trait_map, request.forbidden_traits)):
|
(self.forbidden_trait_map, group.forbidden_traits)):
|
||||||
if traits:
|
if traits:
|
||||||
trait_map.update(trait_obj.ids_from_names(context, traits))
|
trait_map.update(trait_obj.ids_from_names(context, traits))
|
||||||
|
|
||||||
# Internal id of a root provider. If provided, this RequestGroup must
|
# Internal id of a root provider. If provided, this RequestGroup must
|
||||||
# be satisfied by resource provider(s) under the root provider.
|
# be satisfied by resource provider(s) under the root provider.
|
||||||
self.tree_root_id = None
|
self.tree_root_id = None
|
||||||
if request.in_tree:
|
if group.in_tree:
|
||||||
tree_ids = provider_ids_from_uuid(context, request.in_tree)
|
tree_ids = provider_ids_from_uuid(context, group.in_tree)
|
||||||
if tree_ids is None:
|
if tree_ids is None:
|
||||||
raise exception.ResourceProviderNotFound()
|
raise exception.ResourceProviderNotFound()
|
||||||
self.tree_root_id = tree_ids.root_id
|
self.tree_root_id = tree_ids.root_id
|
||||||
@ -160,6 +161,101 @@ class RequestGroupSearchContext(object):
|
|||||||
return self._rps_with_resource.get(rc_id)
|
return self._rps_with_resource.get(rc_id)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestWideSearchContext(object):
|
||||||
|
"""An adapter object that represents the search for allocation candidates
|
||||||
|
for a request-wide parameters.
|
||||||
|
"""
|
||||||
|
def __init__(self, context, rqparams, nested_aware):
|
||||||
|
"""Create a RequestWideSearchContext.
|
||||||
|
|
||||||
|
:param context: placement.context.RequestContext object
|
||||||
|
:param rqparams: A RequestWideParams.
|
||||||
|
:param nested_aware: Boolean, True if we are at a microversion that
|
||||||
|
supports trees; False otherwise.
|
||||||
|
"""
|
||||||
|
self._ctx = context
|
||||||
|
self._limit = rqparams.limit
|
||||||
|
self.group_policy = rqparams.group_policy
|
||||||
|
self._nested_aware = nested_aware
|
||||||
|
self.has_trees = _has_provider_trees(context)
|
||||||
|
|
||||||
|
def exclude_nested_providers(
|
||||||
|
self, allocation_requests, provider_summaries):
|
||||||
|
"""Exclude allocation requests and provider summaries for old
|
||||||
|
microversions if they involve more than one provider from the same
|
||||||
|
tree.
|
||||||
|
"""
|
||||||
|
if self._nested_aware or not self.has_trees:
|
||||||
|
return allocation_requests, provider_summaries
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
LOG.debug(
|
||||||
|
'Excluding nested providers yields %d allocation requests and '
|
||||||
|
'%d provider summaries', len(allocation_requests),
|
||||||
|
len(provider_summaries))
|
||||||
|
return allocation_requests, provider_summaries
|
||||||
|
|
||||||
|
def limit_results(self, 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 complex sql or add additional columns to the DB.
|
||||||
|
if self._limit and self._limit < len(alloc_request_objs):
|
||||||
|
if self._ctx.config.placement.randomize_allocation_candidates:
|
||||||
|
alloc_request_objs = random.sample(
|
||||||
|
alloc_request_objs, self._limit)
|
||||||
|
else:
|
||||||
|
alloc_request_objs = alloc_request_objs[:self._limit]
|
||||||
|
# Limit summaries to only those mentioned in the allocation reqs.
|
||||||
|
kept_summary_objs = []
|
||||||
|
alloc_req_root_uuids = set()
|
||||||
|
# Extract root resource provider uuids from the resource requests.
|
||||||
|
for aro in alloc_request_objs:
|
||||||
|
for arr in aro.resource_requests:
|
||||||
|
alloc_req_root_uuids.add(
|
||||||
|
arr.resource_provider.root_provider_uuid)
|
||||||
|
for summary in summary_objs:
|
||||||
|
rp_root_uuid = summary.resource_provider.root_provider_uuid
|
||||||
|
# Skip a summary if we are limiting and haven't selected an
|
||||||
|
# allocation request that uses the resource provider.
|
||||||
|
if rp_root_uuid not in alloc_req_root_uuids:
|
||||||
|
continue
|
||||||
|
kept_summary_objs.append(summary)
|
||||||
|
summary_objs = kept_summary_objs
|
||||||
|
LOG.debug('Limiting results yields %d allocation requests and '
|
||||||
|
'%d provider summaries', len(alloc_request_objs),
|
||||||
|
len(summary_objs))
|
||||||
|
elif self._ctx.config.placement.randomize_allocation_candidates:
|
||||||
|
random.shuffle(alloc_request_objs)
|
||||||
|
|
||||||
|
return alloc_request_objs, summary_objs
|
||||||
|
|
||||||
|
|
||||||
def provider_ids_from_rp_ids(context, rp_ids):
|
def provider_ids_from_rp_ids(context, rp_ids):
|
||||||
"""Given an iterable of internal resource provider IDs, returns a dict,
|
"""Given an iterable of internal resource provider IDs, returns a dict,
|
||||||
keyed by internal provider Id, of ProviderIds namedtuples describing those
|
keyed by internal provider Id, of ProviderIds namedtuples describing those
|
||||||
@ -1007,7 +1103,7 @@ def anchors_for_sharing_providers(context, rp_ids):
|
|||||||
|
|
||||||
|
|
||||||
@db_api.placement_context_manager.reader
|
@db_api.placement_context_manager.reader
|
||||||
def has_provider_trees(ctx):
|
def _has_provider_trees(ctx):
|
||||||
"""Simple method that returns whether provider trees (i.e. nested resource
|
"""Simple method that returns whether provider trees (i.e. nested resource
|
||||||
providers) are in use in the deployment at all. This information is used to
|
providers) are in use in the deployment at all. This information is used to
|
||||||
switch code paths when attempting to retrieve allocation candidate
|
switch code paths when attempting to retrieve allocation candidate
|
||||||
|
@ -41,7 +41,7 @@ def _req_group_search_context(context, **kwargs):
|
|||||||
forbidden_aggs=kwargs.get('forbidden_aggs', []),
|
forbidden_aggs=kwargs.get('forbidden_aggs', []),
|
||||||
in_tree=kwargs.get('in_tree', None),
|
in_tree=kwargs.get('in_tree', None),
|
||||||
)
|
)
|
||||||
has_trees = res_ctx.has_provider_trees(context)
|
has_trees = res_ctx._has_provider_trees(context)
|
||||||
sharing = res_ctx.get_sharing_providers(context)
|
sharing = res_ctx.get_sharing_providers(context)
|
||||||
rg_ctx = res_ctx.RequestGroupSearchContext(
|
rg_ctx = res_ctx.RequestGroupSearchContext(
|
||||||
context, request, has_trees, sharing)
|
context, request, has_trees, sharing)
|
||||||
@ -930,14 +930,15 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
# _validate_allocation_requests to make failure results more readable.
|
# _validate_allocation_requests to make failure results more readable.
|
||||||
self.rp_uuid_to_name = {}
|
self.rp_uuid_to_name = {}
|
||||||
|
|
||||||
def _get_allocation_candidates(self, requests=None, limit=None,
|
def _get_allocation_candidates(self, groups=None, rqparams=None):
|
||||||
group_policy=None):
|
if groups is None:
|
||||||
if requests is None:
|
groups = {'': placement_lib.RequestGroup(
|
||||||
requests = {'': placement_lib.RequestGroup(
|
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources=self.requested_resources)}
|
resources=self.requested_resources)}
|
||||||
return ac_obj.AllocationCandidates.get_by_requests(self.ctx, requests,
|
if rqparams is None:
|
||||||
limit, group_policy)
|
rqparams = placement_lib.RequestWideParams()
|
||||||
|
return ac_obj.AllocationCandidates.get_by_requests(
|
||||||
|
self.ctx, groups, rqparams)
|
||||||
|
|
||||||
def _validate_allocation_requests(self, expected, candidates,
|
def _validate_allocation_requests(self, expected, candidates,
|
||||||
expect_suffix=False):
|
expect_suffix=False):
|
||||||
@ -1044,9 +1045,10 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
requests = {'': placement_lib.RequestGroup(
|
requests = {'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False, resources=self.requested_resources,
|
use_same_provider=False, resources=self.requested_resources,
|
||||||
required_traits=missing)}
|
required_traits=missing)}
|
||||||
self.assertRaises(exception.TraitNotFound,
|
self.assertRaises(
|
||||||
ac_obj.AllocationCandidates.get_by_requests,
|
exception.TraitNotFound,
|
||||||
self.ctx, requests)
|
ac_obj.AllocationCandidates.get_by_requests,
|
||||||
|
self.ctx, requests, placement_lib.RequestWideParams())
|
||||||
|
|
||||||
def test_allc_req_and_prov_summary(self):
|
def test_allc_req_and_prov_summary(self):
|
||||||
"""Simply test with one resource provider that the allocation
|
"""Simply test with one resource provider that the allocation
|
||||||
@ -1221,7 +1223,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
|
|
||||||
# Ask for just one candidate.
|
# Ask for just one candidate.
|
||||||
limit = 1
|
limit = 1
|
||||||
alloc_cands = self._get_allocation_candidates(limit=limit)
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
rqparams=placement_lib.RequestWideParams(limit=limit))
|
||||||
allocation_requests = alloc_cands.allocation_requests
|
allocation_requests = alloc_cands.allocation_requests
|
||||||
self.assertEqual(limit, len(allocation_requests))
|
self.assertEqual(limit, len(allocation_requests))
|
||||||
|
|
||||||
@ -1235,7 +1238,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
|
|
||||||
# Ask for two candidates.
|
# Ask for two candidates.
|
||||||
limit = 2
|
limit = 2
|
||||||
alloc_cands = self._get_allocation_candidates(limit=limit)
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
rqparams=placement_lib.RequestWideParams(limit=limit))
|
||||||
allocation_requests = alloc_cands.allocation_requests
|
allocation_requests = alloc_cands.allocation_requests
|
||||||
self.assertEqual(limit, len(allocation_requests))
|
self.assertEqual(limit, len(allocation_requests))
|
||||||
|
|
||||||
@ -1246,7 +1250,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
limit = 5
|
limit = 5
|
||||||
# We still only expect 2 because cn3 does not match default requests.
|
# We still only expect 2 because cn3 does not match default requests.
|
||||||
expected_length = 2
|
expected_length = 2
|
||||||
alloc_cands = self._get_allocation_candidates(limit=limit)
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
rqparams=placement_lib.RequestWideParams(limit=limit))
|
||||||
allocation_requests = alloc_cands.allocation_requests
|
allocation_requests = alloc_cands.allocation_requests
|
||||||
self.assertEqual(expected_length, len(allocation_requests))
|
self.assertEqual(expected_length, len(allocation_requests))
|
||||||
|
|
||||||
@ -1327,7 +1332,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
# #1705071, this resulted in a KeyError
|
# #1705071, this resulted in a KeyError
|
||||||
|
|
||||||
alloc_cands = self._get_allocation_candidates(
|
alloc_cands = self._get_allocation_candidates(
|
||||||
requests={'': placement_lib.RequestGroup(
|
groups={'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources={
|
resources={
|
||||||
'DISK_GB': 10,
|
'DISK_GB': 10,
|
||||||
@ -1506,7 +1511,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
alloc_cands = self._get_allocation_candidates(
|
alloc_cands = self._get_allocation_candidates(
|
||||||
requests={'': placement_lib.RequestGroup(
|
groups={'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False, resources=requested_resources)})
|
use_same_provider=False, resources=requested_resources)})
|
||||||
|
|
||||||
# Verify the allocation requests that are returned. There should be 2
|
# Verify the allocation requests that are returned. There should be 2
|
||||||
@ -1628,7 +1633,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
tb.set_traits(cn, os_traits.HW_CPU_X86_AVX2)
|
tb.set_traits(cn, os_traits.HW_CPU_X86_AVX2)
|
||||||
|
|
||||||
alloc_cands = self._get_allocation_candidates(
|
alloc_cands = self._get_allocation_candidates(
|
||||||
requests={'': placement_lib.RequestGroup(
|
groups={'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources=self.requested_resources,
|
resources=self.requested_resources,
|
||||||
required_traits=set([os_traits.HW_CPU_X86_AVX2]),
|
required_traits=set([os_traits.HW_CPU_X86_AVX2]),
|
||||||
@ -1949,7 +1954,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
tb.add_inventory(ss2, orc.SRIOV_NET_VF, 16)
|
tb.add_inventory(ss2, orc.SRIOV_NET_VF, 16)
|
||||||
tb.add_inventory(ss2, orc.DISK_GB, 1600)
|
tb.add_inventory(ss2, orc.DISK_GB, 1600)
|
||||||
|
|
||||||
alloc_cands = self._get_allocation_candidates(requests={
|
alloc_cands = self._get_allocation_candidates(groups={
|
||||||
'': placement_lib.RequestGroup(
|
'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources={
|
resources={
|
||||||
@ -2973,7 +2978,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
resources={
|
resources={
|
||||||
orc.SRIOV_NET_VF: 1,
|
orc.SRIOV_NET_VF: 1,
|
||||||
}),
|
}),
|
||||||
}, group_policy='none')
|
}, rqparams=placement_lib.RequestWideParams(group_policy='none'))
|
||||||
# 4 VF providers each providing 2, 1, or 0 inventory makes 6
|
# 4 VF providers each providing 2, 1, or 0 inventory makes 6
|
||||||
# different combinations, plus two more that are effectively
|
# different combinations, plus two more that are effectively
|
||||||
# the same but satisfying different suffix mappings.
|
# the same but satisfying different suffix mappings.
|
||||||
@ -3002,7 +3007,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
resources={
|
resources={
|
||||||
orc.SRIOV_NET_VF: 2,
|
orc.SRIOV_NET_VF: 2,
|
||||||
}),
|
}),
|
||||||
}, group_policy='isolate')
|
}, rqparams=placement_lib.RequestWideParams(group_policy='isolate'))
|
||||||
self.assertEqual(4, len(alloc_cands.allocation_requests))
|
self.assertEqual(4, len(alloc_cands.allocation_requests))
|
||||||
|
|
||||||
def test_nested_result_suffix_mappings(self):
|
def test_nested_result_suffix_mappings(self):
|
||||||
@ -3027,7 +3032,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
resources={
|
resources={
|
||||||
orc.SRIOV_NET_VF: 1,
|
orc.SRIOV_NET_VF: 1,
|
||||||
}),
|
}),
|
||||||
}, group_policy='isolate')
|
}, rqparams=placement_lib.RequestWideParams(group_policy='isolate'))
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
[('cn1', orc.VCPU, 2, ''),
|
[('cn1', orc.VCPU, 2, ''),
|
||||||
|
@ -314,19 +314,19 @@ class ResourceProviderTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
root_rp.destroy()
|
root_rp.destroy()
|
||||||
|
|
||||||
def test_has_provider_trees(self):
|
def test_has_provider_trees(self):
|
||||||
"""The has_provider_trees() helper method should return False unless
|
"""The _has_provider_trees() helper method should return False unless
|
||||||
there is a resource provider that is a parent.
|
there is a resource provider that is a parent.
|
||||||
"""
|
"""
|
||||||
self.assertFalse(res_ctx.has_provider_trees(self.ctx))
|
self.assertFalse(res_ctx._has_provider_trees(self.ctx))
|
||||||
self._create_provider('cn')
|
self._create_provider('cn')
|
||||||
|
|
||||||
# No parents yet. Should still be False.
|
# No parents yet. Should still be False.
|
||||||
self.assertFalse(res_ctx.has_provider_trees(self.ctx))
|
self.assertFalse(res_ctx._has_provider_trees(self.ctx))
|
||||||
|
|
||||||
self._create_provider('numa0', parent=uuidsentinel.cn)
|
self._create_provider('numa0', parent=uuidsentinel.cn)
|
||||||
|
|
||||||
# OK, now we've got a parent, so should be True
|
# OK, now we've got a parent, so should be True
|
||||||
self.assertTrue(res_ctx.has_provider_trees(self.ctx))
|
self.assertTrue(res_ctx._has_provider_trees(self.ctx))
|
||||||
|
|
||||||
def test_destroy_resource_provider(self):
|
def test_destroy_resource_provider(self):
|
||||||
created_resource_provider = self._create_provider(
|
created_resource_provider = self._create_provider(
|
||||||
@ -1120,7 +1120,7 @@ class SharedProviderTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
resources={orc.VCPU: 2,
|
resources={orc.VCPU: 2,
|
||||||
orc.MEMORY_MB: 256,
|
orc.MEMORY_MB: 256,
|
||||||
orc.DISK_GB: 1500})
|
orc.DISK_GB: 1500})
|
||||||
has_trees = res_ctx.has_provider_trees(self.ctx)
|
has_trees = res_ctx._has_provider_trees(self.ctx)
|
||||||
sharing = res_ctx.get_sharing_providers(self.ctx)
|
sharing = res_ctx.get_sharing_providers(self.ctx)
|
||||||
rg_ctx = res_ctx.RequestGroupSearchContext(
|
rg_ctx = res_ctx.RequestGroupSearchContext(
|
||||||
self.ctx, request, has_trees, sharing)
|
self.ctx, request, has_trees, sharing)
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from placement.objects import allocation_candidate
|
from placement import lib as placement_lib
|
||||||
|
from placement.objects import research_context as res_ctx
|
||||||
from placement.tests.unit.objects import base
|
from placement.tests.unit.objects import base
|
||||||
|
|
||||||
|
|
||||||
class TestAllocationCandidatesNoDB(base.TestCase):
|
class TestAllocationCandidatesNoDB(base.TestCase):
|
||||||
|
@mock.patch('placement.objects.research_context._has_provider_trees',
|
||||||
|
new=mock.Mock(return_value=True))
|
||||||
def test_limit_results(self):
|
def test_limit_results(self):
|
||||||
# Results are limited based on their root provider uuid, not uuid.
|
# Results are limited based on their root provider uuid, not uuid.
|
||||||
# For a more "real" test of this functionality, one that exercises
|
# For a more "real" test of this functionality, one that exercises
|
||||||
@ -47,7 +50,8 @@ class TestAllocationCandidatesNoDB(base.TestCase):
|
|||||||
sum7 = mock.Mock(resource_provider=mock.Mock(root_provider_uuid=7))
|
sum7 = mock.Mock(resource_provider=mock.Mock(root_provider_uuid=7))
|
||||||
sum6 = mock.Mock(resource_provider=mock.Mock(root_provider_uuid=6))
|
sum6 = mock.Mock(resource_provider=mock.Mock(root_provider_uuid=6))
|
||||||
sum_in = [sum1, sum0, sum4, sum8, sum5, sum7, sum6]
|
sum_in = [sum1, sum0, sum4, sum8, sum5, sum7, sum6]
|
||||||
aro, sum = allocation_candidate.AllocationCandidates._limit_results(
|
rw_ctx = res_ctx.RequestWideSearchContext(
|
||||||
self.context, aro_in, sum_in, 2)
|
self.context, placement_lib.RequestWideParams(limit=2), True)
|
||||||
|
aro, sum = rw_ctx.limit_results(aro_in, sum_in)
|
||||||
self.assertEqual(aro_in[:2], aro)
|
self.assertEqual(aro_in[:2], aro)
|
||||||
self.assertEqual(set([sum1, sum0, sum4, sum8, sum5]), set(sum))
|
self.assertEqual(set([sum1, sum0, sum4, sum8, sum5]), set(sum))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user