Add any-traits support for allocation candidates
The patch I8704fe7350f74e0567e574eb00fc40b330817381 added support for any-traits in the DB query when considering RP trees. This patch extends the allocation candidates code path to be able to parse the extended 'in:' syntax of the 'required' query parameter as well as to handle the nested required trait structure both in the unnamed and in the name request groups. This patch refers to microversion 1.39 which has not been added yet so the changes in this patch cannot be triggered from the REST API. A later patch will add the microversion bump. Note that supporting the 'in:' syntax in root_required query param was not planned and it is not implemented as part of this patch series and API microversion. Story: 2005345 Story: 2005346 Change-Id: I760be29201bce8dfc6d4e571bc8a842404eaff09
This commit is contained in:
parent
faa1ad516f
commit
87e56f749b
@ -64,7 +64,8 @@ class RequestGroup(object):
|
|||||||
provider. If False, represents a request for resources and traits
|
provider. If False, represents a request for resources and traits
|
||||||
in any resource provider in the same tree, or a sharing provider.
|
in any resource provider in the same tree, or a sharing provider.
|
||||||
:param resources: A dict of { resource_class: amount, ... }
|
:param resources: A dict of { resource_class: amount, ... }
|
||||||
:param required_traits: A set of { trait_name, ... }
|
:param required_traits: A list of set of trait names. E.g.:
|
||||||
|
[{T1, T2}, {T3}] means ((T1 or T2) and T3)
|
||||||
:param forbidden_traits: A set of { trait_name, ... }
|
:param forbidden_traits: A set of { trait_name, ... }
|
||||||
:param member_of: A list of [ [aggregate_UUID],
|
:param member_of: A list of [ [aggregate_UUID],
|
||||||
[aggregate_UUID, aggregate_UUID] ... ]
|
[aggregate_UUID, aggregate_UUID] ... ]
|
||||||
@ -73,7 +74,7 @@ class RequestGroup(object):
|
|||||||
"""
|
"""
|
||||||
self.use_same_provider = use_same_provider
|
self.use_same_provider = use_same_provider
|
||||||
self.resources = resources or {}
|
self.resources = resources or {}
|
||||||
self.required_traits = required_traits or set()
|
self.required_traits = required_traits or []
|
||||||
self.forbidden_traits = forbidden_traits or set()
|
self.forbidden_traits = forbidden_traits or set()
|
||||||
self.member_of = member_of or []
|
self.member_of = member_of or []
|
||||||
self.in_tree = in_tree
|
self.in_tree = in_tree
|
||||||
@ -84,11 +85,26 @@ class RequestGroup(object):
|
|||||||
ret += ', resources={%s}' % ', '.join(
|
ret += ', resources={%s}' % ', '.join(
|
||||||
'%s:%d' % (rc, amount)
|
'%s:%d' % (rc, amount)
|
||||||
for rc, amount in sorted(list(self.resources.items())))
|
for rc, amount in sorted(list(self.resources.items())))
|
||||||
ret += ', traits=[%s]' % ', '.join(
|
|
||||||
sorted(self.required_traits) +
|
all_traits = set()
|
||||||
['!%s' % ft for ft in self.forbidden_traits])
|
fragments = []
|
||||||
|
for any_traits in self.required_traits:
|
||||||
|
if len(any_traits) == 1:
|
||||||
|
all_traits.add(list(any_traits)[0])
|
||||||
|
else:
|
||||||
|
fragments.append('(' + ' or '.join(sorted(any_traits)) + ')')
|
||||||
|
|
||||||
|
if all_traits:
|
||||||
|
fragments.append(', '.join(trait for trait in sorted(all_traits)))
|
||||||
|
if self.forbidden_traits:
|
||||||
|
fragments.append(
|
||||||
|
', '.join(
|
||||||
|
'!' + trait for trait in sorted(self.forbidden_traits)))
|
||||||
|
|
||||||
|
ret += ', traits=(%s)' % ' and '.join(fragments)
|
||||||
|
|
||||||
ret += ', aggregates=[%s]' % ', '.join(
|
ret += ', aggregates=[%s]' % ', '.join(
|
||||||
sorted('[%s]' % ', '.join(agglist)
|
sorted('[%s]' % ', '.join(sorted(agglist))
|
||||||
for agglist in sorted(self.member_of)))
|
for agglist in sorted(self.member_of)))
|
||||||
ret += ')'
|
ret += ')'
|
||||||
return ret
|
return ret
|
||||||
@ -113,11 +129,10 @@ class RequestGroup(object):
|
|||||||
request_group.resources = util.normalize_resources_qs_param(
|
request_group.resources = util.normalize_resources_qs_param(
|
||||||
val)
|
val)
|
||||||
elif prefix == _QS_REQUIRED:
|
elif prefix == _QS_REQUIRED:
|
||||||
# TODO(gibi): switch this to normalize_traits_qs_params when
|
(
|
||||||
# the data model can handle nested required_traits structure
|
request_group.required_traits,
|
||||||
# as part of the any-traits feature
|
request_group.forbidden_traits,
|
||||||
request_group.required_traits = (
|
) = util.normalize_traits_qs_params(req, suffix)
|
||||||
util.normalize_traits_qs_params_legacy(req, suffix))
|
|
||||||
elif prefix == _QS_MEMBER_OF:
|
elif prefix == _QS_MEMBER_OF:
|
||||||
# special handling of member_of qparam since we allow multiple
|
# special handling of member_of qparam since we allow multiple
|
||||||
# member_of params at microversion 1.24.
|
# member_of params at microversion 1.24.
|
||||||
@ -196,21 +211,26 @@ class RequestGroup(object):
|
|||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _fix_forbidden(by_suffix):
|
def _check_forbidden(by_suffix):
|
||||||
conflicting_traits = []
|
conflicting_traits = []
|
||||||
for suff, group in by_suffix.items():
|
for suff, group in by_suffix.items():
|
||||||
group.required_traits, group.forbidden_traits, conflicts = (
|
|
||||||
_fix_one_forbidden(group.required_traits))
|
for any_traits in group.required_traits:
|
||||||
if conflicts:
|
if all(
|
||||||
conflicting_traits.append('required%s: (%s)'
|
trait in group.forbidden_traits
|
||||||
% (suff, ', '.join(conflicts)))
|
for trait in any_traits
|
||||||
|
):
|
||||||
|
conflicting_traits.append(
|
||||||
|
'required%s: (%s)' %
|
||||||
|
(suff, ', '.join(sorted(any_traits))))
|
||||||
|
|
||||||
if conflicting_traits:
|
if conflicting_traits:
|
||||||
msg = (
|
msg = (
|
||||||
'Conflicting required and forbidden traits found in the '
|
'Conflicting required and forbidden traits found in the '
|
||||||
'following traits keys: %s')
|
'following traits keys: %s')
|
||||||
# TODO(efried): comment=errors.QUERYPARAM_BAD_VALUE
|
# TODO(efried): comment=errors.QUERYPARAM_BAD_VALUE
|
||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
msg % ', '.join(conflicting_traits))
|
msg % ', '.join(sorted(conflicting_traits)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def dict_from_request(cls, req, rqparams):
|
def dict_from_request(cls, req, rqparams):
|
||||||
@ -226,6 +246,7 @@ class RequestGroup(object):
|
|||||||
&required1=$TRAIT_NAME,$TRAIT_NAME&member_of1=$AGG_UUID
|
&required1=$TRAIT_NAME,$TRAIT_NAME&member_of1=$AGG_UUID
|
||||||
&resources2=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
|
&resources2=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
|
||||||
&required2=$TRAIT_NAME,$TRAIT_NAME&member_of2=$AGG_UUID
|
&required2=$TRAIT_NAME,$TRAIT_NAME&member_of2=$AGG_UUID
|
||||||
|
&required2=in:$TRAIT_NAME,$TRAIT_NAME
|
||||||
|
|
||||||
These are parsed in groups according to the arbitrary suffix of the key.
|
These are parsed in groups according to the arbitrary suffix of the key.
|
||||||
For each group, a RequestGroup instance is created containing that
|
For each group, a RequestGroup instance is created containing that
|
||||||
@ -238,6 +259,9 @@ class RequestGroup(object):
|
|||||||
providers in the group. That is, the trait is forbidden. Forbidden
|
providers in the group. That is, the trait is forbidden. Forbidden
|
||||||
traits are processed only if the microversion supports.
|
traits are processed only if the microversion supports.
|
||||||
|
|
||||||
|
If the value of a `required*` is prefixed with 'in:' then the traits in
|
||||||
|
the value are ORred together.
|
||||||
|
|
||||||
The return is a dict, keyed by the suffix of these RequestGroup
|
The return is a dict, keyed by the suffix of these RequestGroup
|
||||||
instances (or the empty string for the unidentified group).
|
instances (or the empty string for the unidentified group).
|
||||||
|
|
||||||
@ -252,6 +276,8 @@ class RequestGroup(object):
|
|||||||
&required1=CUSTOM_PHYSNET_PUBLIC,CUSTOM_SWITCH_A
|
&required1=CUSTOM_PHYSNET_PUBLIC,CUSTOM_SWITCH_A
|
||||||
&resources2=SRIOV_NET_VF:1
|
&resources2=SRIOV_NET_VF:1
|
||||||
&required2=!CUSTOM_PHYSNET_PUBLIC
|
&required2=!CUSTOM_PHYSNET_PUBLIC
|
||||||
|
&required2=CUSTOM_GOLD
|
||||||
|
&required2=in:CUSTOM_FOO,CUSTOM_BAR
|
||||||
|
|
||||||
...the return value will be:
|
...the return value will be:
|
||||||
|
|
||||||
@ -263,8 +289,8 @@ class RequestGroup(object):
|
|||||||
"DISK_GB" 50,
|
"DISK_GB" 50,
|
||||||
},
|
},
|
||||||
required_traits=[
|
required_traits=[
|
||||||
"HW_CPU_X86_VMX",
|
{"HW_CPU_X86_VMX"},
|
||||||
"CUSTOM_STORAGE_RAID",
|
{"CUSTOM_STORAGE_RAID"},
|
||||||
],
|
],
|
||||||
member_of=[
|
member_of=[
|
||||||
[9323b2b1-82c9-4e91-bdff-e95e808ef954],
|
[9323b2b1-82c9-4e91-bdff-e95e808ef954],
|
||||||
@ -279,8 +305,8 @@ class RequestGroup(object):
|
|||||||
"SRIOV_NET_VF": 2,
|
"SRIOV_NET_VF": 2,
|
||||||
},
|
},
|
||||||
required_traits=[
|
required_traits=[
|
||||||
"CUSTOM_PHYSNET_PUBLIC",
|
{"CUSTOM_PHYSNET_PUBLIC"},
|
||||||
"CUSTOM_SWITCH_A",
|
{"CUSTOM_SWITCH_A"},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
'2': RequestGroup(
|
'2': RequestGroup(
|
||||||
@ -288,6 +314,9 @@ class RequestGroup(object):
|
|||||||
resources={
|
resources={
|
||||||
"SRIOV_NET_VF": 1,
|
"SRIOV_NET_VF": 1,
|
||||||
},
|
},
|
||||||
|
required_traits=[
|
||||||
|
{"CUSTOM_GOLD"},
|
||||||
|
{"CUSTOM_FOO", "CUSTOM_BAR"},
|
||||||
forbidden_traits=[
|
forbidden_traits=[
|
||||||
"CUSTOM_PHYSNET_PUBLIC",
|
"CUSTOM_PHYSNET_PUBLIC",
|
||||||
],
|
],
|
||||||
@ -321,10 +350,9 @@ class RequestGroup(object):
|
|||||||
else:
|
else:
|
||||||
cls._check_for_orphans(by_suffix)
|
cls._check_for_orphans(by_suffix)
|
||||||
|
|
||||||
# Make adjustments for forbidden traits by stripping forbidden out
|
# check conflicting traits in the request
|
||||||
# of required.
|
|
||||||
if allow_forbidden:
|
if allow_forbidden:
|
||||||
cls._fix_forbidden(by_suffix)
|
cls._check_forbidden(by_suffix)
|
||||||
|
|
||||||
return by_suffix
|
return by_suffix
|
||||||
|
|
||||||
@ -353,7 +381,9 @@ class RequestWideParams(object):
|
|||||||
where any such RequestGroups are satisfied by the same RP.
|
where any such RequestGroups are satisfied by the same RP.
|
||||||
:param anchor_required_traits: Set of trait names which the anchor of
|
:param anchor_required_traits: Set of trait names which the anchor of
|
||||||
each returned allocation candidate must possess, regardless of
|
each returned allocation candidate must possess, regardless of
|
||||||
any RequestGroup filters.
|
any RequestGroup filters. Note that anchor_required_traits
|
||||||
|
does not support the any-trait format the
|
||||||
|
RequestGroup.required_traits does.
|
||||||
:param anchor_forbidden_traits: Set of trait names which the anchor of
|
:param anchor_forbidden_traits: Set of trait names which the anchor of
|
||||||
each returned allocation candidate must NOT possess, regardless
|
each returned allocation candidate must NOT possess, regardless
|
||||||
of any RequestGroup filters.
|
of any RequestGroup filters.
|
||||||
|
@ -96,13 +96,24 @@ class AllocationCandidates(object):
|
|||||||
# each resource class requested.
|
# each resource class requested.
|
||||||
# If there aren't any providers that have any of the
|
# If there aren't any providers that have any of the
|
||||||
# required traits, just exit early...
|
# required traits, just exit early...
|
||||||
if rg_ctx.required_trait_map:
|
if rg_ctx.required_traits:
|
||||||
# TODO(cdent): Now that there is also a forbidden_trait_map
|
# TODO(cdent): Now that there is also a forbidden_trait_map
|
||||||
# it should be possible to further optimize this attempt at
|
# it should be possible to further optimize this attempt at
|
||||||
# a quick return, but we leave that to future patches for
|
# a quick return, but we leave that to future patches for
|
||||||
# now.
|
# now.
|
||||||
|
# NOTE(gibi): this optimization works by flattening the
|
||||||
|
# required_trait nested list. So if the request contains
|
||||||
|
# ((A or B) and C) trait request then we check if there is any
|
||||||
|
# RP with either A, B or C. If none then we know that there is
|
||||||
|
# no RP that can satisfy the original query either.
|
||||||
trait_rps = res_ctx.get_provider_ids_having_any_trait(
|
trait_rps = res_ctx.get_provider_ids_having_any_trait(
|
||||||
rg_ctx.context, rg_ctx.required_trait_map.values())
|
rg_ctx.context,
|
||||||
|
{
|
||||||
|
trait
|
||||||
|
for any_traits in rg_ctx.required_traits
|
||||||
|
for trait in any_traits
|
||||||
|
},
|
||||||
|
)
|
||||||
if not trait_rps:
|
if not trait_rps:
|
||||||
return set()
|
return set()
|
||||||
rp_candidates = res_ctx.get_trees_matching_all(rg_ctx, rw_ctx)
|
rp_candidates = res_ctx.get_trees_matching_all(rg_ctx, rw_ctx)
|
||||||
@ -360,8 +371,8 @@ def _alloc_candidates_multiple_providers(rg_ctx, rw_ctx, rp_candidates):
|
|||||||
for res_requests in itertools.product(*request_groups):
|
for res_requests in itertools.product(*request_groups):
|
||||||
if not _check_traits_for_alloc_request(
|
if not _check_traits_for_alloc_request(
|
||||||
res_requests, rw_ctx.summaries_by_id,
|
res_requests, rw_ctx.summaries_by_id,
|
||||||
rg_ctx.required_trait_map,
|
rg_ctx.required_trait_names,
|
||||||
rg_ctx.forbidden_trait_map):
|
rg_ctx.forbidden_traits.keys()):
|
||||||
# This combination doesn't satisfy trait constraints
|
# This combination doesn't satisfy trait constraints
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -585,13 +596,13 @@ def _check_traits_for_alloc_request(res_requests, summaries, required_traits,
|
|||||||
:param summaries: dict, keyed by resource provider id, of ProviderSummary
|
:param summaries: dict, keyed by resource provider id, of ProviderSummary
|
||||||
objects containing usage and trait information for
|
objects containing usage and trait information for
|
||||||
resource providers involved in the overall request
|
resource providers involved in the overall request
|
||||||
:param required_traits: A map, keyed by trait string name, of required
|
:param required_traits: A list of set of trait names where traits
|
||||||
trait internal IDs that each *allocation request's
|
in the sets are in OR relationship while traits in
|
||||||
set of providers* must *collectively* have
|
two different sets are in AND relationship. Each
|
||||||
associated with them
|
*allocation request's set of providers* must
|
||||||
:param forbidden_traits: A map, keyed by trait string name, of trait
|
*collectively* fulfill this trait expression.
|
||||||
internal IDs that a resource provider must
|
:param forbidden_traits: A set of trait names that a resource provider must
|
||||||
not have.
|
not have.
|
||||||
"""
|
"""
|
||||||
all_prov_ids = []
|
all_prov_ids = []
|
||||||
all_traits = set()
|
all_traits = set()
|
||||||
@ -611,12 +622,27 @@ def _check_traits_for_alloc_request(res_requests, summaries, required_traits,
|
|||||||
all_prov_ids.append(rp_id)
|
all_prov_ids.append(rp_id)
|
||||||
all_traits |= rp_traits
|
all_traits |= rp_traits
|
||||||
|
|
||||||
# Check if there are missing traits
|
# We need a match for *all* the items from the outer list of the
|
||||||
missing_traits = set(required_traits) - all_traits
|
# required_traits as that describes AND relationship, and we need at least
|
||||||
if missing_traits:
|
# *one match* per nested trait set as that set describes OR relationship
|
||||||
LOG.debug('Excluding a set of allocation candidate %s : '
|
|
||||||
'missing traits %s are not satisfied.',
|
# so collect all the matches with the nested sets
|
||||||
all_prov_ids, ','.join(missing_traits))
|
trait_matches = [
|
||||||
|
any_traits.intersection(all_traits) for any_traits in required_traits]
|
||||||
|
|
||||||
|
# if some internal sets do not match to the provided traits then we have
|
||||||
|
# missing trait (trait set)
|
||||||
|
if not all(trait_matches):
|
||||||
|
missing_traits = [
|
||||||
|
'(' + ' or '.join(any_traits) + ')'
|
||||||
|
for any_traits, match in zip(required_traits, trait_matches)
|
||||||
|
if not match
|
||||||
|
]
|
||||||
|
LOG.debug(
|
||||||
|
'Excluding a set of allocation candidate %s : '
|
||||||
|
'missing traits %s are not satisfied.',
|
||||||
|
all_prov_ids,
|
||||||
|
' and '.join(any_traits for any_traits in missing_traits))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return all_prov_ids
|
return all_prov_ids
|
||||||
|
@ -92,14 +92,24 @@ class RequestGroupSearchContext(object):
|
|||||||
# or a sharing provider.
|
# or a sharing provider.
|
||||||
self.use_same_provider = group.use_same_provider
|
self.use_same_provider = group.use_same_provider
|
||||||
|
|
||||||
# maps the trait name to the trait internal ID
|
# Both required_trait_names and required_traits expresses the same
|
||||||
self.required_trait_map = {}
|
# request with the same nested list of sets structure but
|
||||||
self.forbidden_trait_map = {}
|
# required_trait_names contains trait names while required_traits
|
||||||
for trait_map, traits in (
|
# contains trait internal IDs
|
||||||
(self.required_trait_map, group.required_traits),
|
self.required_trait_names = group.required_traits
|
||||||
(self.forbidden_trait_map, group.forbidden_traits)):
|
# let's map the trait names to internal IDs this is useful for DB calls
|
||||||
if traits:
|
# expecting trait IDs. The structure of this field is the same as the
|
||||||
trait_map.update(trait_obj.ids_from_names(context, traits))
|
# required_trait_names field.
|
||||||
|
self.required_traits = []
|
||||||
|
# forbidden_traits is a dict mapping trait names to trait internal IDs
|
||||||
|
self.forbidden_traits = {}
|
||||||
|
|
||||||
|
for any_traits in group.required_traits:
|
||||||
|
self.required_traits.append(
|
||||||
|
set(trait_obj.ids_from_names(context, any_traits).values()))
|
||||||
|
if group.forbidden_traits:
|
||||||
|
self.forbidden_traits = trait_obj.ids_from_names(
|
||||||
|
context, group.forbidden_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.
|
||||||
@ -716,7 +726,7 @@ def get_trees_matching_all(rg_ctx, rw_ctx):
|
|||||||
if not provs_with_inv:
|
if not provs_with_inv:
|
||||||
return rp_candidates.RPCandidateList()
|
return rp_candidates.RPCandidateList()
|
||||||
|
|
||||||
if (not rg_ctx.required_trait_map and not rg_ctx.forbidden_trait_map) or (
|
if (not rg_ctx.required_traits and not rg_ctx.forbidden_traits) or (
|
||||||
rg_ctx.exists_sharing):
|
rg_ctx.exists_sharing):
|
||||||
# If there were no traits required, there's no difference in how we
|
# If there were no traits required, there's no difference in how we
|
||||||
# calculate allocation requests between nested and non-nested
|
# calculate allocation requests between nested and non-nested
|
||||||
@ -729,14 +739,14 @@ def get_trees_matching_all(rg_ctx, rw_ctx):
|
|||||||
# capacity and that set of providers (grouped by their tree) have all
|
# capacity and that set of providers (grouped by their tree) have all
|
||||||
# of the required traits and none of the forbidden traits
|
# of the required traits and none of the forbidden traits
|
||||||
rp_tuples_with_trait = _get_trees_with_traits(
|
rp_tuples_with_trait = _get_trees_with_traits(
|
||||||
rg_ctx.context, provs_with_inv.rps, rg_ctx.required_trait_map.values(),
|
rg_ctx.context, provs_with_inv.rps, rg_ctx.required_traits,
|
||||||
rg_ctx.forbidden_trait_map.values())
|
rg_ctx.forbidden_traits)
|
||||||
provs_with_inv.filter_by_rp(rp_tuples_with_trait)
|
provs_with_inv.filter_by_rp(rp_tuples_with_trait)
|
||||||
LOG.debug("found %d providers under %d trees after applying "
|
LOG.debug("found %d providers under %d trees after applying "
|
||||||
"traits filter - required: %s, forbidden: %s",
|
"traits filter - required: %s, forbidden: %s",
|
||||||
len(provs_with_inv.rps), len(provs_with_inv.trees),
|
len(provs_with_inv.rps), len(provs_with_inv.trees),
|
||||||
list(rg_ctx.required_trait_map),
|
list(rg_ctx.required_trait_names),
|
||||||
list(rg_ctx.forbidden_trait_map))
|
list(rg_ctx.forbidden_traits))
|
||||||
|
|
||||||
return provs_with_inv
|
return provs_with_inv
|
||||||
|
|
||||||
@ -1151,13 +1161,13 @@ def get_provider_ids_for_traits_and_aggs(rg_ctx):
|
|||||||
specified forbidden_traits.
|
specified forbidden_traits.
|
||||||
"""
|
"""
|
||||||
filtered_rps = set()
|
filtered_rps = set()
|
||||||
if rg_ctx.required_trait_map:
|
if rg_ctx.required_traits:
|
||||||
trait_rps = provider_ids_matching_required_traits(
|
trait_rps = provider_ids_matching_required_traits(
|
||||||
rg_ctx.context, rg_ctx.required_trait_map.values())
|
rg_ctx.context, rg_ctx.required_traits)
|
||||||
filtered_rps = trait_rps
|
filtered_rps = trait_rps
|
||||||
LOG.debug("found %d providers after applying required traits filter "
|
LOG.debug("found %d providers after applying required traits filter "
|
||||||
"(%s)",
|
"(%s)",
|
||||||
len(filtered_rps), list(rg_ctx.required_trait_map))
|
len(filtered_rps), list(rg_ctx.required_trait_names))
|
||||||
if not filtered_rps:
|
if not filtered_rps:
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
@ -1185,15 +1195,15 @@ def get_provider_ids_for_traits_and_aggs(rg_ctx):
|
|||||||
if not filtered_rps:
|
if not filtered_rps:
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
if rg_ctx.forbidden_trait_map:
|
if rg_ctx.forbidden_traits:
|
||||||
rps_bad_traits = get_provider_ids_having_any_trait(
|
rps_bad_traits = get_provider_ids_having_any_trait(
|
||||||
rg_ctx.context, rg_ctx.forbidden_trait_map.values())
|
rg_ctx.context, rg_ctx.forbidden_traits.values())
|
||||||
forbidden_rp_ids |= rps_bad_traits
|
forbidden_rp_ids |= rps_bad_traits
|
||||||
if filtered_rps:
|
if filtered_rps:
|
||||||
filtered_rps -= rps_bad_traits
|
filtered_rps -= rps_bad_traits
|
||||||
LOG.debug("found %d providers after applying forbidden traits "
|
LOG.debug("found %d providers after applying forbidden traits "
|
||||||
"filter (%s)", len(filtered_rps),
|
"filter (%s)", len(filtered_rps),
|
||||||
list(rg_ctx.forbidden_trait_map))
|
list(rg_ctx.forbidden_traits))
|
||||||
if not filtered_rps:
|
if not filtered_rps:
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ def _req_group_search_context(context, **kwargs):
|
|||||||
request = placement_lib.RequestGroup(
|
request = placement_lib.RequestGroup(
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources=kwargs.get('resources', resources),
|
resources=kwargs.get('resources', resources),
|
||||||
required_traits=kwargs.get('required_traits', {}),
|
required_traits=kwargs.get('required_traits', []),
|
||||||
forbidden_traits=kwargs.get('forbidden_traits', {}),
|
forbidden_traits=kwargs.get('forbidden_traits', set()),
|
||||||
member_of=kwargs.get('member_of', []),
|
member_of=kwargs.get('member_of', []),
|
||||||
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),
|
||||||
@ -186,9 +186,7 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
# associated any traits with the providers
|
# associated any traits with the providers
|
||||||
avx2_t = trait_obj.Trait.get_by_name(
|
avx2_t = trait_obj.Trait.get_by_name(
|
||||||
self.ctx, os_traits.HW_CPU_X86_AVX2)
|
self.ctx, os_traits.HW_CPU_X86_AVX2)
|
||||||
# get_provider_ids_matching()'s required_traits and forbidden_traits
|
req_traits = [{os_traits.HW_CPU_X86_AVX2}]
|
||||||
# arguments maps, keyed by trait name, of the trait internal ID
|
|
||||||
req_traits = {os_traits.HW_CPU_X86_AVX2: avx2_t.id}
|
|
||||||
rg_ctx = _req_group_search_context(
|
rg_ctx = _req_group_search_context(
|
||||||
self.ctx,
|
self.ctx,
|
||||||
resources=resources,
|
resources=resources,
|
||||||
@ -212,6 +210,41 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
rp_ids = [r[0] for r in res]
|
rp_ids = [r[0] for r in res]
|
||||||
self.assertEqual([incl_biginv_noalloc.id], rp_ids)
|
self.assertEqual([incl_biginv_noalloc.id], rp_ids)
|
||||||
|
|
||||||
|
# ask for a complex required trait query: (AVX2 and (SEE or SSE2))
|
||||||
|
# first it should match no RPs as neither has SSE nor SSE2
|
||||||
|
req_traits = [
|
||||||
|
{os_traits.HW_CPU_X86_AVX2},
|
||||||
|
{os_traits.HW_CPU_X86_SSE, os_traits.HW_CPU_X86_SSE2}
|
||||||
|
]
|
||||||
|
rg_ctx = _req_group_search_context(
|
||||||
|
self.ctx,
|
||||||
|
resources=resources,
|
||||||
|
required_traits=req_traits,
|
||||||
|
)
|
||||||
|
res = res_ctx.get_provider_ids_matching(rg_ctx)
|
||||||
|
|
||||||
|
self.assertEqual([], res)
|
||||||
|
|
||||||
|
# now add SSE to an RP that has no AVX2 so we still not have a match
|
||||||
|
sse_t = trait_obj.Trait.get_by_name(
|
||||||
|
self.ctx, os_traits.HW_CPU_X86_SSE)
|
||||||
|
incl_extra_full.set_traits([sse_t])
|
||||||
|
|
||||||
|
res = res_ctx.get_provider_ids_matching(rg_ctx)
|
||||||
|
|
||||||
|
self.assertEqual([], res)
|
||||||
|
|
||||||
|
# now add SSE2 to an RP which also has AVX2. We expect that RP is a
|
||||||
|
# match
|
||||||
|
sse2_t = trait_obj.Trait.get_by_name(
|
||||||
|
self.ctx, os_traits.HW_CPU_X86_SSE2)
|
||||||
|
incl_biginv_noalloc.set_traits([avx2_t, sse2_t])
|
||||||
|
|
||||||
|
res = res_ctx.get_provider_ids_matching(rg_ctx)
|
||||||
|
|
||||||
|
rp_ids = [r[0] for r in res]
|
||||||
|
self.assertEqual([incl_biginv_noalloc.id], rp_ids)
|
||||||
|
|
||||||
# Let's see if the in_tree filter works
|
# Let's see if the in_tree filter works
|
||||||
rg_ctx = _req_group_search_context(
|
rg_ctx = _req_group_search_context(
|
||||||
self.ctx,
|
self.ctx,
|
||||||
@ -242,8 +275,7 @@ class ProviderDBHelperTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
tb.add_inventory(rp3, orc.VCPU, 64)
|
tb.add_inventory(rp3, orc.VCPU, 64)
|
||||||
|
|
||||||
resources = {orc.VCPU: 4}
|
resources = {orc.VCPU: 4}
|
||||||
forbidden_traits = {trait_two.name: trait_two.id,
|
forbidden_traits = {trait_two.name, trait_three.name}
|
||||||
trait_three.name: trait_three.id}
|
|
||||||
member_of = [[uuids.agg1]]
|
member_of = [[uuids.agg1]]
|
||||||
|
|
||||||
rg_ctx = _req_group_search_context(
|
rg_ctx = _req_group_search_context(
|
||||||
@ -633,10 +665,7 @@ class ProviderTreeDBHelperTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
|
|
||||||
geneve_t = trait_obj.Trait.get_by_name(
|
geneve_t = trait_obj.Trait.get_by_name(
|
||||||
self.ctx, os_traits.HW_NIC_OFFLOAD_GENEVE)
|
self.ctx, os_traits.HW_NIC_OFFLOAD_GENEVE)
|
||||||
# required_traits parameter is a dict of trait name to internal ID
|
req_traits = [{geneve_t.name}]
|
||||||
req_traits = {
|
|
||||||
geneve_t.name: geneve_t.id,
|
|
||||||
}
|
|
||||||
expected_trees = ['cn3']
|
expected_trees = ['cn3']
|
||||||
# NOTE(tetsuro): Actually we also get providers without traits here.
|
# NOTE(tetsuro): Actually we also get providers without traits here.
|
||||||
# This is reported as bug#1771707 and from users' view the bug is now
|
# This is reported as bug#1771707 and from users' view the bug is now
|
||||||
@ -652,18 +681,12 @@ class ProviderTreeDBHelperTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
# verify that there are no returned allocation candidates
|
# verify that there are no returned allocation candidates
|
||||||
avx2_t = trait_obj.Trait.get_by_name(
|
avx2_t = trait_obj.Trait.get_by_name(
|
||||||
self.ctx, os_traits.HW_CPU_X86_AVX2)
|
self.ctx, os_traits.HW_CPU_X86_AVX2)
|
||||||
# required_traits parameter is a dict of trait name to internal ID
|
req_traits = [{geneve_t.name}, {avx2_t.name}]
|
||||||
req_traits = {
|
|
||||||
geneve_t.name: geneve_t.id,
|
|
||||||
avx2_t.name: avx2_t.id,
|
|
||||||
}
|
|
||||||
_run_test([], [], required_traits=req_traits)
|
_run_test([], [], required_traits=req_traits)
|
||||||
|
|
||||||
# If we add the AVX2 trait as forbidden, not required, then we
|
# If we add the AVX2 trait as forbidden, not required, then we
|
||||||
# should get back the original cn3
|
# should get back the original cn3
|
||||||
req_traits = {
|
req_traits = [{geneve_t.name}]
|
||||||
geneve_t.name: geneve_t.id,
|
|
||||||
}
|
|
||||||
forbidden_traits = {
|
forbidden_traits = {
|
||||||
avx2_t.name: avx2_t.id,
|
avx2_t.name: avx2_t.id,
|
||||||
}
|
}
|
||||||
@ -1333,7 +1356,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
self.assertEqual(expected, observed)
|
self.assertEqual(expected, observed)
|
||||||
|
|
||||||
def test_unknown_traits(self):
|
def test_unknown_traits(self):
|
||||||
missing = set(['UNKNOWN_TRAIT'])
|
missing = [{'UNKNOWN_TRAIT'}]
|
||||||
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)}
|
||||||
@ -1440,7 +1463,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}]
|
||||||
)},
|
)},
|
||||||
)
|
)
|
||||||
self._validate_allocation_requests([], alloc_cands)
|
self._validate_allocation_requests([], alloc_cands)
|
||||||
@ -1453,7 +1476,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}]
|
||||||
)},
|
)},
|
||||||
)
|
)
|
||||||
# Only cn2 should be in our allocation requests now since that's the
|
# Only cn2 should be in our allocation requests now since that's the
|
||||||
@ -1496,6 +1519,107 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
]
|
]
|
||||||
self._validate_allocation_requests(expected, alloc_cands)
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Now create a more complex trait query: (AVX2 and (SSE or SSE2))
|
||||||
|
# First no result is expected as none of the RPs has SSE or SSE2 traits
|
||||||
|
required_traits = [
|
||||||
|
{os_traits.HW_CPU_X86_AVX2},
|
||||||
|
{os_traits.HW_CPU_X86_SSE, os_traits.HW_CPU_X86_SSE2}
|
||||||
|
]
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
)},
|
||||||
|
)
|
||||||
|
|
||||||
|
self._validate_allocation_requests([], alloc_cands)
|
||||||
|
|
||||||
|
# Next we add SSE to one of the RPs that has no AVX2, so we still
|
||||||
|
# expect empty result
|
||||||
|
tb.set_traits(cn1, 'HW_CPU_X86_SSE')
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
)},
|
||||||
|
)
|
||||||
|
|
||||||
|
self._validate_allocation_requests([], alloc_cands)
|
||||||
|
|
||||||
|
# Next we add SSE2 to the cn2 where there are AVX2 too, and we expect
|
||||||
|
# that cn2 is a match now
|
||||||
|
tb.set_traits(cn2, 'HW_CPU_X86_AVX2', 'HW_CPU_X86_SSE2')
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
)},
|
||||||
|
)
|
||||||
|
expected = [
|
||||||
|
[('cn2', orc.VCPU, 1),
|
||||||
|
('cn2', orc.MEMORY_MB, 64),
|
||||||
|
('cn2', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
p_sums = alloc_cands.provider_summaries
|
||||||
|
self.assertEqual(1, len(p_sums))
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'cn2': set([
|
||||||
|
(orc.VCPU, 24 * 16.0, 0),
|
||||||
|
(orc.MEMORY_MB, 32768 * 1.5, 0),
|
||||||
|
(orc.DISK_GB, 2000 - 100, 0),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
self._validate_provider_summary_resources(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Next forbid SSE2 in the request so the trait query becomes
|
||||||
|
# (AVX2 and (SSE or SSE2) and !SSE2) this should lead to no candidate
|
||||||
|
# as cn2 has SSE2
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
forbidden_traits={'HW_CPU_X86_SSE2'},
|
||||||
|
)},
|
||||||
|
)
|
||||||
|
|
||||||
|
self._validate_allocation_requests([], alloc_cands)
|
||||||
|
|
||||||
|
# But if we forbid SSE instead of SSE2 then we get back cn2
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
forbidden_traits={'HW_CPU_X86_SSE'}
|
||||||
|
)},
|
||||||
|
)
|
||||||
|
expected = [
|
||||||
|
[('cn2', orc.VCPU, 1),
|
||||||
|
('cn2', orc.MEMORY_MB, 64),
|
||||||
|
('cn2', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
p_sums = alloc_cands.provider_summaries
|
||||||
|
self.assertEqual(1, len(p_sums))
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'cn2': set([
|
||||||
|
(orc.VCPU, 24 * 16.0, 0),
|
||||||
|
(orc.MEMORY_MB, 32768 * 1.5, 0),
|
||||||
|
(orc.DISK_GB, 2000 - 100, 0),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
self._validate_provider_summary_resources(expected, alloc_cands)
|
||||||
|
|
||||||
def test_all_local_limit(self):
|
def test_all_local_limit(self):
|
||||||
"""Create some resource providers that can satisfy the request for
|
"""Create some resource providers that can satisfy the request for
|
||||||
resources with local (non-shared) resources, limit them, and verify
|
resources with local (non-shared) resources, limit them, and verify
|
||||||
@ -1658,7 +1782,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}],
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1679,7 +1803,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}],
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1724,12 +1848,12 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
|
|
||||||
# Require the AVX2 trait but forbid CUSTOM_EXTRA_FASTER, which is
|
# Require the AVX2 trait but forbid CUSTOM_EXTRA_FASTER, which is
|
||||||
# added to cn2
|
# added to cn2
|
||||||
tb.set_traits(cn2, 'CUSTOM_EXTRA_FASTER')
|
tb.set_traits(cn2, 'HW_CPU_X86_AVX2', 'CUSTOM_EXTRA_FASTER')
|
||||||
alloc_cands = self._get_allocation_candidates(
|
alloc_cands = self._get_allocation_candidates(
|
||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}],
|
||||||
forbidden_traits=set(['CUSTOM_EXTRA_FASTER']),
|
forbidden_traits=set(['CUSTOM_EXTRA_FASTER']),
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
@ -1747,7 +1871,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}],
|
||||||
forbidden_traits=set(['MISC_SHARES_VIA_AGGREGATE']),
|
forbidden_traits=set(['MISC_SHARES_VIA_AGGREGATE']),
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
@ -1758,6 +1882,97 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
]
|
]
|
||||||
self._validate_allocation_requests(expected, alloc_cands)
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Now create a more complex trait query. (AVX2 and (SSE or SSE2)
|
||||||
|
# Right now none of the RPs has SEE nor SSE2 so we expect no candidates
|
||||||
|
required_traits = [
|
||||||
|
{os_traits.HW_CPU_X86_AVX2},
|
||||||
|
{os_traits.HW_CPU_X86_SSE, os_traits.HW_CPU_X86_SSE2}
|
||||||
|
]
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
)}
|
||||||
|
)
|
||||||
|
|
||||||
|
# We have not yet associated the SSE or SSE2 traits to any provider,
|
||||||
|
# so we should get zero allocation candidates
|
||||||
|
p_sums = alloc_cands.provider_summaries
|
||||||
|
self.assertEqual([], alloc_cands.allocation_requests)
|
||||||
|
self.assertEqual(0, len(p_sums))
|
||||||
|
|
||||||
|
# Next associate SSE to the sharing provider that is enough to get
|
||||||
|
# matches. cn1 with shared storage is a match as ss provides SSE but
|
||||||
|
# cn1 with local disk is not a match as then ss is not used and
|
||||||
|
# therefore no SSE is provided. cn2 is a match with ss.
|
||||||
|
tb.set_traits(ss, "MISC_SHARES_VIA_AGGREGATE", "HW_CPU_X86_SSE")
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
)}
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
[('cn1', orc.VCPU, 1),
|
||||||
|
('cn1', orc.MEMORY_MB, 64),
|
||||||
|
('shared storage', orc.DISK_GB, 1500)],
|
||||||
|
[('cn2', orc.VCPU, 1),
|
||||||
|
('cn2', orc.MEMORY_MB, 64),
|
||||||
|
('shared storage', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Now add SSE2 to cn1 so cn1 + local disk will also be a match
|
||||||
|
tb.set_traits(cn1, "HW_CPU_X86_AVX2", "HW_CPU_X86_SSE2")
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
)}
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
[('cn1', orc.VCPU, 1),
|
||||||
|
('cn1', orc.MEMORY_MB, 64),
|
||||||
|
('shared storage', orc.DISK_GB, 1500)],
|
||||||
|
[('cn1', orc.VCPU, 1),
|
||||||
|
('cn1', orc.MEMORY_MB, 64),
|
||||||
|
('cn1', orc.DISK_GB, 1500)],
|
||||||
|
[('cn2', orc.VCPU, 1),
|
||||||
|
('cn2', orc.MEMORY_MB, 64),
|
||||||
|
('shared storage', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Now change the trait query to
|
||||||
|
# (AVX2 and (SSE or SSE2) and not CUSTOM_EXTRA_FASTER)
|
||||||
|
# cn2 has the CUSTOM_EXTRA_FASTER trait so that is expected to be
|
||||||
|
# filtered out
|
||||||
|
alloc_cands = self._get_allocation_candidates(
|
||||||
|
{'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
forbidden_traits={'CUSTOM_EXTRA_FASTER'},
|
||||||
|
)}
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
[('cn1', orc.VCPU, 1),
|
||||||
|
('cn1', orc.MEMORY_MB, 64),
|
||||||
|
('shared storage', orc.DISK_GB, 1500)],
|
||||||
|
[('cn1', orc.VCPU, 1),
|
||||||
|
('cn1', orc.MEMORY_MB, 64),
|
||||||
|
('cn1', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
def test_local_with_shared_custom_resource(self):
|
def test_local_with_shared_custom_resource(self):
|
||||||
"""Create some resource providers that can satisfy the request for
|
"""Create some resource providers that can satisfy the request for
|
||||||
resources with local VCPU and MEMORY_MB but rely on a shared resource
|
resources with local VCPU and MEMORY_MB but rely on a shared resource
|
||||||
@ -1907,7 +2122,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': 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=[{os_traits.HW_CPU_X86_AVX2}],
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1928,7 +2143,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
groups={'': 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=[{os_traits.HW_CPU_X86_AVX2}],
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1976,9 +2191,9 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
'': placement_lib.RequestGroup(
|
'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources=self.requested_resources,
|
resources=self.requested_resources,
|
||||||
required_traits=set([
|
required_traits=[
|
||||||
os_traits.HW_CPU_X86_AVX2, os_traits.STORAGE_DISK_SSD
|
{os_traits.HW_CPU_X86_AVX2}, {os_traits.STORAGE_DISK_SSD}
|
||||||
]),
|
],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2004,6 +2219,76 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
}
|
}
|
||||||
self._validate_provider_summary_traits(expected, alloc_cands)
|
self._validate_provider_summary_traits(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Let's have an even more complex trait query
|
||||||
|
# (AVX2 and (SSD or SSE) and not SSE2). As no SEE or SSE2 is in the
|
||||||
|
# current trees we still get back cn3 that has AVX and SSD
|
||||||
|
required_traits = [
|
||||||
|
{os_traits.HW_CPU_X86_AVX2},
|
||||||
|
{os_traits.STORAGE_DISK_SSD, os_traits.HW_CPU_X86_SSE}
|
||||||
|
]
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates({
|
||||||
|
'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
forbidden_traits={os_traits.HW_CPU_X86_SSE2}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
# There should be only cn3 in the returned allocation candidates
|
||||||
|
expected = [
|
||||||
|
[('cn3', orc.VCPU, 1),
|
||||||
|
('cn3', orc.MEMORY_MB, 64),
|
||||||
|
('cn3', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'cn3': set([
|
||||||
|
(orc.VCPU, 24 * 16.0, 0),
|
||||||
|
(orc.MEMORY_MB, 1024 * 1.5, 0),
|
||||||
|
(orc.DISK_GB, 2000 - 100, 0),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
self._validate_provider_summary_resources(expected, alloc_cands)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'cn3': set(['HW_CPU_X86_AVX2', 'STORAGE_DISK_SSD'])
|
||||||
|
}
|
||||||
|
self._validate_provider_summary_traits(expected, alloc_cands)
|
||||||
|
|
||||||
|
# Next we add SSE to cn1 and both SSE and SSE2 to cn2. This will make
|
||||||
|
# cn1 a match while cn2 still be ignored due to SSE2. cn3 is good as
|
||||||
|
# before
|
||||||
|
tb.set_traits(
|
||||||
|
cn1, os_traits.HW_CPU_X86_AVX2, os_traits.HW_CPU_X86_SSE)
|
||||||
|
tb.set_traits(
|
||||||
|
cn2,
|
||||||
|
os_traits.HW_CPU_X86_AVX2,
|
||||||
|
os_traits.HW_CPU_X86_SSE,
|
||||||
|
os_traits.HW_CPU_X86_SSE2
|
||||||
|
)
|
||||||
|
|
||||||
|
alloc_cands = self._get_allocation_candidates({
|
||||||
|
'': placement_lib.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources=self.requested_resources,
|
||||||
|
required_traits=required_traits,
|
||||||
|
forbidden_traits={os_traits.HW_CPU_X86_SSE2}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
[('cn1', orc.VCPU, 1),
|
||||||
|
('cn1', orc.MEMORY_MB, 64),
|
||||||
|
('shared storage', orc.DISK_GB, 1500)],
|
||||||
|
[('cn3', orc.VCPU, 1),
|
||||||
|
('cn3', orc.MEMORY_MB, 64),
|
||||||
|
('cn3', orc.DISK_GB, 1500)],
|
||||||
|
]
|
||||||
|
self._validate_allocation_requests(expected, alloc_cands)
|
||||||
|
|
||||||
def test_common_rc(self):
|
def test_common_rc(self):
|
||||||
"""Candidates when cn and shared have inventory in the same class."""
|
"""Candidates when cn and shared have inventory in the same class."""
|
||||||
cn = self._create_provider('cn', uuids.agg1)
|
cn = self._create_provider('cn', uuids.agg1)
|
||||||
@ -2097,8 +2382,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
{'': placement_lib.RequestGroup(
|
{'': placement_lib.RequestGroup(
|
||||||
use_same_provider=False,
|
use_same_provider=False,
|
||||||
resources=self.requested_resources,
|
resources=self.requested_resources,
|
||||||
required_traits=set(['HW_CPU_X86_SSE', 'STORAGE_DISK_SSD',
|
required_traits=[
|
||||||
'CUSTOM_RAID'])
|
{'HW_CPU_X86_SSE'}, {'STORAGE_DISK_SSD'}, {'CUSTOM_RAID'}]
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2921,7 +3206,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
orc.MEMORY_MB: 256,
|
orc.MEMORY_MB: 256,
|
||||||
orc.SRIOV_NET_VF: 1,
|
orc.SRIOV_NET_VF: 1,
|
||||||
},
|
},
|
||||||
required_traits=[os_traits.HW_NIC_OFFLOAD_GENEVE],
|
required_traits=[{os_traits.HW_NIC_OFFLOAD_GENEVE}],
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -3056,7 +3341,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
orc.MEMORY_MB: 256,
|
orc.MEMORY_MB: 256,
|
||||||
orc.SRIOV_NET_VF: 1,
|
orc.SRIOV_NET_VF: 1,
|
||||||
},
|
},
|
||||||
required_traits=[os_traits.HW_NIC_OFFLOAD_GENEVE],
|
required_traits=[{os_traits.HW_NIC_OFFLOAD_GENEVE}],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -3271,7 +3556,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
|
|||||||
orc.SRIOV_NET_VF: 1,
|
orc.SRIOV_NET_VF: 1,
|
||||||
orc.DISK_GB: 1500,
|
orc.DISK_GB: 1500,
|
||||||
},
|
},
|
||||||
required_traits=[os_traits.HW_NIC_OFFLOAD_GENEVE])
|
required_traits=[{os_traits.HW_NIC_OFFLOAD_GENEVE}])
|
||||||
})
|
})
|
||||||
|
|
||||||
# cn1_numa0_pf0 is not in the allocation candidates because it
|
# cn1_numa0_pf0 is not in the allocation candidates because it
|
||||||
|
@ -686,10 +686,9 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
'VCPU': 2,
|
'VCPU': 2,
|
||||||
'MEMORY_MB': 2048,
|
'MEMORY_MB': 2048,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'HW_CPU_X86_VMX',
|
{'HW_CPU_X86_VMX'}, {'CUSTOM_GOLD'}
|
||||||
'CUSTOM_GOLD',
|
],
|
||||||
},
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
self.assertRequestGroupsEqual(expected, self.do_parse(qs))
|
self.assertRequestGroupsEqual(expected, self.do_parse(qs))
|
||||||
@ -778,18 +777,17 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
'VCPU': 2,
|
'VCPU': 2,
|
||||||
'MEMORY_MB': 2048,
|
'MEMORY_MB': 2048,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'HW_CPU_X86_VMX',
|
{'HW_CPU_X86_VMX'}, {'CUSTOM_GOLD'}
|
||||||
'CUSTOM_GOLD',
|
],
|
||||||
},
|
|
||||||
),
|
),
|
||||||
pl.RequestGroup(
|
pl.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
'CUSTOM_MAGIC': 123,
|
'CUSTOM_MAGIC': 123,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_GOLD',
|
{'CUSTOM_GOLD'}
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
pl.RequestGroup(
|
pl.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
@ -816,19 +814,19 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
'MEMORY_MB': 4096,
|
'MEMORY_MB': 4096,
|
||||||
'DISK_GB': 10,
|
'DISK_GB': 10,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'HW_CPU_X86_VMX',
|
{'HW_CPU_X86_VMX'},
|
||||||
'CUSTOM_MEM_FLASH',
|
{'CUSTOM_MEM_FLASH'},
|
||||||
'STORAGE_DISK_SSD',
|
{'STORAGE_DISK_SSD'}
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
pl.RequestGroup(
|
pl.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
'SRIOV_NET_VF': 2,
|
'SRIOV_NET_VF': 2,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_PHYSNET_PRIVATE',
|
{'CUSTOM_PHYSNET_PRIVATE'},
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
pl.RequestGroup(
|
pl.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
@ -836,10 +834,10 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
'NET_INGRESS_BYTES_SEC': 20000,
|
'NET_INGRESS_BYTES_SEC': 20000,
|
||||||
'NET_EGRESS_BYTES_SEC': 10000,
|
'NET_EGRESS_BYTES_SEC': 10000,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_SWITCH_BIG',
|
{'CUSTOM_SWITCH_BIG'},
|
||||||
'CUSTOM_PHYSNET_PROD',
|
{'CUSTOM_PHYSNET_PROD'},
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
pl.RequestGroup(
|
pl.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
@ -1033,9 +1031,9 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
'VCPU': 2,
|
'VCPU': 2,
|
||||||
'MEMORY_MB': 2048,
|
'MEMORY_MB': 2048,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_PHYSNET1',
|
{'CUSTOM_PHYSNET1'},
|
||||||
},
|
],
|
||||||
forbidden_traits={
|
forbidden_traits={
|
||||||
'CUSTOM_SWITCH_BIG',
|
'CUSTOM_SWITCH_BIG',
|
||||||
}
|
}
|
||||||
@ -1077,9 +1075,9 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
resources={
|
resources={
|
||||||
'CUSTOM_MAGIC': 1,
|
'CUSTOM_MAGIC': 1,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_PHYSNET1',
|
{'CUSTOM_PHYSNET1'},
|
||||||
},
|
],
|
||||||
forbidden_traits={
|
forbidden_traits={
|
||||||
'CUSTOM_PHYSNET2',
|
'CUSTOM_PHYSNET2',
|
||||||
}
|
}
|
||||||
@ -1098,9 +1096,9 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
resources={
|
resources={
|
||||||
'CUSTOM_MAGIC': 1,
|
'CUSTOM_MAGIC': 1,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_PHYSNET1',
|
{'CUSTOM_PHYSNET1'},
|
||||||
}
|
],
|
||||||
),
|
),
|
||||||
pl.RequestGroup(
|
pl.RequestGroup(
|
||||||
use_same_provider=True,
|
use_same_provider=True,
|
||||||
@ -1150,9 +1148,9 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
resources={
|
resources={
|
||||||
'CUSTOM_MAGIC': 1,
|
'CUSTOM_MAGIC': 1,
|
||||||
},
|
},
|
||||||
required_traits={
|
required_traits=[
|
||||||
'CUSTOM_PHYSNET1',
|
{'CUSTOM_PHYSNET1'},
|
||||||
}
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
self.assertRequestGroupsEqual(
|
self.assertRequestGroupsEqual(
|
||||||
@ -1160,6 +1158,147 @@ class TestParseQsRequestGroups(testtools.TestCase):
|
|||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
webob.exc.HTTPBadRequest, self.do_parse, qs, version=(1, 22))
|
webob.exc.HTTPBadRequest, self.do_parse, qs, version=(1, 22))
|
||||||
|
|
||||||
|
def test_any_traits_1_38(self):
|
||||||
|
qs = 'resources1=RABBIT:1&required1=in:WHITE,BLACK'
|
||||||
|
|
||||||
|
exc = self.assertRaises(
|
||||||
|
webob.exc.HTTPBadRequest, self.do_parse, qs, version=(1, 38))
|
||||||
|
self.assertIn(
|
||||||
|
"The format 'in:HW_CPU_X86_VMX,CUSTOM_MAGIC' only supported since "
|
||||||
|
"microversion 1.39.",
|
||||||
|
str(exc))
|
||||||
|
|
||||||
|
# TODO(gibi): remove the mock when microversion 1.39 is fully added
|
||||||
|
@mock.patch(
|
||||||
|
'placement.microversion.max_version_string',
|
||||||
|
new=mock.Mock(return_value='1.39'))
|
||||||
|
def test_any_traits_1_39(self):
|
||||||
|
qs = 'resources1=RABBIT:1&required1=in:WHITE,BLACK'
|
||||||
|
expected = [
|
||||||
|
pl.RequestGroup(
|
||||||
|
use_same_provider=True,
|
||||||
|
resources={
|
||||||
|
'RABBIT': 1,
|
||||||
|
},
|
||||||
|
required_traits=[
|
||||||
|
{'WHITE', 'BLACK'},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRequestGroupsEqual(
|
||||||
|
expected, self.do_parse(qs, version=(1, 39)))
|
||||||
|
|
||||||
|
# TODO(gibi): remove the mock when microversion 1.39 is fully added
|
||||||
|
@mock.patch(
|
||||||
|
'placement.microversion.max_version_string',
|
||||||
|
new=mock.Mock(return_value='1.39'))
|
||||||
|
def test_any_traits_repeated(self):
|
||||||
|
qs = 'resources1=CUSTOM_MAGIC:1&required1=in:T1,T2&required1=T3,!T4'
|
||||||
|
expected = [
|
||||||
|
pl.RequestGroup(
|
||||||
|
use_same_provider=True,
|
||||||
|
resources={
|
||||||
|
'CUSTOM_MAGIC': 1,
|
||||||
|
},
|
||||||
|
required_traits=[
|
||||||
|
{'T1', 'T2'},
|
||||||
|
{'T3'},
|
||||||
|
],
|
||||||
|
forbidden_traits={
|
||||||
|
'T4'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRequestGroupsEqual(
|
||||||
|
expected, self.do_parse(qs, version=(1, 39)))
|
||||||
|
|
||||||
|
# TODO(gibi): remove the mock when microversion 1.39 is fully added
|
||||||
|
@mock.patch(
|
||||||
|
'placement.microversion.max_version_string',
|
||||||
|
new=mock.Mock(return_value='1.39'))
|
||||||
|
def test_any_traits_multiple_groups(self):
|
||||||
|
qs = ('resources=RABBIT:1&required=in:WHITE,BLACK&'
|
||||||
|
'resources2=CAT:2&required2=in:SILVER,RED&required2=!SPOTTED')
|
||||||
|
expected = [
|
||||||
|
pl.RequestGroup(
|
||||||
|
use_same_provider=False,
|
||||||
|
resources={
|
||||||
|
'RABBIT': 1,
|
||||||
|
},
|
||||||
|
required_traits=[
|
||||||
|
{'WHITE', 'BLACK'},
|
||||||
|
],
|
||||||
|
forbidden_traits={
|
||||||
|
},
|
||||||
|
),
|
||||||
|
pl.RequestGroup(
|
||||||
|
use_same_provider=True,
|
||||||
|
resources={
|
||||||
|
'CAT': 2,
|
||||||
|
},
|
||||||
|
required_traits=[
|
||||||
|
{'SILVER', 'RED'},
|
||||||
|
],
|
||||||
|
forbidden_traits={
|
||||||
|
'SPOTTED'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRequestGroupsEqual(
|
||||||
|
expected, self.do_parse(qs, version=(1, 39)))
|
||||||
|
|
||||||
|
# TODO(gibi): remove the mock when microversion 1.39 is fully added
|
||||||
|
@mock.patch(
|
||||||
|
'placement.microversion.max_version_string',
|
||||||
|
new=mock.Mock(return_value='1.39'))
|
||||||
|
def test_any_traits_forbidden_conflict(self):
|
||||||
|
# going against one part of an OR expression is not a conflict as the
|
||||||
|
# other parts still can match and fulfill the query
|
||||||
|
qs = ('resources=VCPU:2'
|
||||||
|
'&required=in:CUSTOM_PHYSNET1,CUSTOM_PHYSNET2'
|
||||||
|
'&required=!CUSTOM_PHYSNET1')
|
||||||
|
|
||||||
|
rgs = self.do_parse(qs, version=(1, 39))
|
||||||
|
self.assertEqual(1, len(rgs))
|
||||||
|
|
||||||
|
# but going against all parts of an OR expression is a conflict
|
||||||
|
qs = ('resources=VCPU:2'
|
||||||
|
'&required=in:CUSTOM_PHYSNET1,CUSTOM_PHYSNET2'
|
||||||
|
'&required=!CUSTOM_PHYSNET1,!CUSTOM_PHYSNET2')
|
||||||
|
|
||||||
|
expected_message = (
|
||||||
|
'Conflicting required and forbidden traits found '
|
||||||
|
'in the following traits keys: required: '
|
||||||
|
'(CUSTOM_PHYSNET1, CUSTOM_PHYSNET2)')
|
||||||
|
|
||||||
|
exc = self.assertRaises(
|
||||||
|
webob.exc.HTTPBadRequest, self.do_parse, qs, version=(1, 39))
|
||||||
|
self.assertEqual(expected_message, str(exc))
|
||||||
|
|
||||||
|
# TODO(gibi): remove the mock when microversion 1.39 is fully added
|
||||||
|
@mock.patch(
|
||||||
|
'placement.microversion.max_version_string',
|
||||||
|
new=mock.Mock(return_value='1.39'))
|
||||||
|
def test_stringification(self):
|
||||||
|
agg1 = uuidsentinel.agg1
|
||||||
|
agg2 = uuidsentinel.agg2
|
||||||
|
qs = (f'resources1=CAT:2&required1=in:SILVER,RED&'
|
||||||
|
f'required1=TABBY,!SPOTTED&member_of1=in:{agg1},{agg2}')
|
||||||
|
|
||||||
|
rgs = self.do_parse(qs, version=(1, 39))
|
||||||
|
self.assertEqual(1, len(rgs))
|
||||||
|
self.assertEqual(
|
||||||
|
'RequestGroup('
|
||||||
|
'use_same_provider=True, '
|
||||||
|
'resources={CAT:2}, '
|
||||||
|
'traits=((RED or SILVER) and TABBY and !SPOTTED), '
|
||||||
|
f'aggregates=[[{", ".join(sorted([agg1, agg2]))}]])',
|
||||||
|
str(rgs[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestPickLastModified(base.ContextTestCase):
|
class TestPickLastModified(base.ContextTestCase):
|
||||||
|
|
||||||
|
@ -336,35 +336,6 @@ def normalize_traits_qs_param_to_legacy_value(val, allow_forbidden=False):
|
|||||||
return legacy_traits
|
return legacy_traits
|
||||||
|
|
||||||
|
|
||||||
# TODO(gibi): remove this once the allocation candidate code path also
|
|
||||||
# supports nested required_traits structure.
|
|
||||||
def normalize_traits_qs_params_legacy(req, suffix=''):
|
|
||||||
"""Given a webob.Request object, validate and collect required querystring
|
|
||||||
parameters.
|
|
||||||
|
|
||||||
We begin supporting forbidden traits in microversion 1.22.
|
|
||||||
|
|
||||||
:param req: a webob.Request object to read the params from
|
|
||||||
:param suffix: the string suffix of the request group to read from the
|
|
||||||
request. If empty then the unnamed request group is processed.
|
|
||||||
:returns: a set of trait names, including forbidden traits with an '!'
|
|
||||||
prefix.
|
|
||||||
:raises webob.exc.HTTPBadRequest: if the format of the query param is not
|
|
||||||
valid
|
|
||||||
"""
|
|
||||||
want_version = req.environ[placement.microversion.MICROVERSION_ENVIRON]
|
|
||||||
allow_forbidden = want_version.matches((1, 22))
|
|
||||||
|
|
||||||
traits = set()
|
|
||||||
|
|
||||||
# NOTE(gibi): This means if the same query param is repeated then only
|
|
||||||
# the last one will be considered
|
|
||||||
for value in req.GET.getall('required' + suffix):
|
|
||||||
traits = normalize_traits_qs_param_to_legacy_value(
|
|
||||||
value, allow_forbidden)
|
|
||||||
return traits
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_traits_qs_param(
|
def normalize_traits_qs_param(
|
||||||
val, allow_forbidden=False, allow_any_traits=False
|
val, allow_forbidden=False, allow_any_traits=False
|
||||||
):
|
):
|
||||||
|
Loading…
Reference in New Issue
Block a user