Add traits check in nested provider candidates

This patch adds trait check in the path used in getting allocation
candidates with nested providers creating a trait check helper
function, _check_traits_for_alloc_request().

Change-Id: I728825a03db9f6419c8f4b3fa23aef63ec8baa5e
Blueprint: nested-resource-providers-allocation-candidates
Closes-Bug: #1771707
This commit is contained in:
Tetsuro Nakamura 2018-05-10 06:13:41 +09:00
parent 737c897e48
commit 0a6b567688
2 changed files with 91 additions and 54 deletions

View File

@ -3500,6 +3500,63 @@ def _allocation_request_for_provider(ctx, requested_resources, provider):
anchor_root_provider_uuid=provider.root_provider_uuid)
def _check_traits_for_alloc_request(res_requests, summaries, prov_traits,
required_traits, forbidden_traits):
"""Given a list of AllocationRequestResource objects, check if that
combination can provide trait constraints. If it can, returns all
resource provider internal IDs in play, else return an empty list.
TODO(tetsuro): For optimization, we should move this logic to SQL in
_get_trees_matching_all().
:param res_requests: a list of AllocationRequestResource objects that have
resource providers to be checked if they collectively
satisfy trait constraints in the required_traits and
forbidden_traits parameters.
:param summaries: dict, keyed by resource provider ID, of ProviderSummary
objects containing usage and trait information for
resource providers involved in the overall request
:param prov_traits: A dict, keyed by internal resource provider ID, of
string trait names associated with that provider
:param required_traits: A map, keyed by trait string name, of required
trait internal IDs that each *allocation request's
set of providers* must *collectively* have
associated with them
:param forbidden_traits: A map, keyed by trait string name, of trait
internal IDs that a resource provider must
not have.
"""
all_prov_ids = set()
all_traits = set()
for res_req in res_requests:
rp_uuid = res_req.resource_provider.uuid
for rp_id, summary in summaries.items():
if summary.resource_provider.uuid == rp_uuid:
break
rp_traits = set(prov_traits.get(rp_id, []))
# Check if there are forbidden_traits
conflict_traits = set(forbidden_traits) & set(rp_traits)
if conflict_traits:
LOG.debug('Excluding resource provider %s, it has '
'forbidden traits: (%s).',
rp_id, ', '.join(conflict_traits))
return []
all_prov_ids.add(rp_id)
all_traits |= rp_traits
# Check if there are missing traits
missing_traits = set(required_traits) - all_traits
if missing_traits:
LOG.debug('Excluding a set of allocation candidate %s : '
'missing traits %s are not satisfied.',
all_prov_ids, ','.join(missing_traits))
return []
return all_prov_ids
def _alloc_candidates_single_provider(ctx, requested_resources, rps):
"""Returns a tuple of (allocation requests, provider summaries) for a
supplied set of requested resource amounts and resource providers. The
@ -3559,7 +3616,8 @@ def _alloc_candidates_single_provider(ctx, requested_resources, rps):
return alloc_requests, list(summaries.values())
def _alloc_candidates_nested_no_shared(ctx, requested_resources, rp_tuples):
def _alloc_candidates_nested_no_shared(ctx, requested_resources,
required_traits, forbidden_traits, rp_tuples):
"""Returns a tuple of (allocation requests, provider summaries) for a
supplied set of requested resource amounts and tuples of
(rp_id, root_id, rc_id). The supplied resource provider trees have
@ -3578,6 +3636,13 @@ def _alloc_candidates_nested_no_shared(ctx, requested_resources, rp_tuples):
:param ctx: nova.context.RequestContext object
:param requested_resources: dict, keyed by resource class ID, of amounts
being requested for that resource class
:param required_traits: A map, keyed by trait string name, of required
trait internal IDs that each *allocation request's
set of providers* must *collectively* have
associated with them
:param forbidden_traits: A map, keyed by trait string name, of trait
internal IDs that a resource provider must
not have.
:param rp_tuples: List of tuples of (provider ID, root provider ID,
resource class ID)s for providers that matched the
requested resources
@ -3629,6 +3694,9 @@ def _alloc_candidates_nested_no_shared(ctx, requested_resources, rp_tuples):
# Using itertools.product, we get all the combinations of resource
# providers in a tree.
for res_requests in itertools.product(*request_groups):
if not _check_traits_for_alloc_request(res_requests, summaries,
prov_traits, required_traits, forbidden_traits):
continue
alloc_requests.append(
AllocationRequest(ctx, resource_requests=list(res_requests),
anchor_root_provider_uuid=root_uuid)
@ -3782,38 +3850,12 @@ def _alloc_candidates_with_shared(ctx, requested_resources, required_traits,
for res_requests in itertools.product(*request_groups):
# Before we add the allocation request to our list, we first need
# to ensure that the resource providers involved in this allocation
# request have all of the traits
all_prov_ids = set()
all_traits = set()
for res_req in res_requests:
rp_uuid = res_req.resource_provider.uuid
rp_id = None
for id, summary in summaries.items():
if summary.resource_provider.uuid == rp_uuid:
rp_id = id
break
# request have all of the traits and that we don't have this
# combination in alloc_requests yet
all_prov_ids = _check_traits_for_alloc_request(res_requests,
summaries, prov_traits, required_traits, forbidden_traits)
rp_traits = set(prov_traits.get(rp_id, []))
conflict_traits = set(forbidden_traits) & set(rp_traits)
if conflict_traits:
LOG.debug('Excluding sharing provider %s, it has '
'forbidden traits: (%s).',
rp_id, ', '.join(conflict_traits))
continue
all_prov_ids.add(rp_id)
all_traits |= rp_traits
# Check if there are missing traits
missing_traits = set(required_traits) - all_traits
if missing_traits:
LOG.debug('Excluding a set of allocation candidate %s : '
'missing traits %s are not satisfied.',
all_prov_ids, ','.join(missing_traits))
continue
# Check if we already have this combination in alloc_requests
if all_prov_ids in alloc_prov_ids:
if not all_prov_ids or all_prov_ids in alloc_prov_ids:
continue
alloc_prov_ids.append(all_prov_ids)
@ -4310,7 +4352,7 @@ class AllocationCandidates(base.VersionedObject):
rp_tuples = _get_trees_matching_all(context, resources,
required_trait_map, forbidden_trait_map, member_of)
return _alloc_candidates_nested_no_shared(context, resources,
rp_tuples)
required_trait_map, forbidden_trait_map, rp_tuples)
# Either we are processing a single-RP request group, or there are no
# sharing providers that (help) satisfy the request. Get a list of

View File

@ -1858,20 +1858,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
)}
)
# Bug #1771707: If a tree has one combination of resource
# providers that satisfies required traits, the combinations
# without required traits in that tree are also returned.
# We should't get the following first combination since
# 'cn_numa0_pf0' has no traits
# expected = [
# [('cn', fields.ResourceClass.VCPU, 2),
# ('cn', fields.ResourceClass.MEMORY_MB, 256),
# ('cn_numa1_pf1', fields.ResourceClass.SRIOV_NET_VF, 1)],
# ]
expected = [
[('cn', fields.ResourceClass.VCPU, 2),
('cn', fields.ResourceClass.MEMORY_MB, 256),
('cn_numa0_pf0', fields.ResourceClass.SRIOV_NET_VF, 1)],
[('cn', fields.ResourceClass.VCPU, 2),
('cn', fields.ResourceClass.MEMORY_MB, 256),
('cn_numa1_pf1', fields.ResourceClass.SRIOV_NET_VF, 1)],
@ -1886,9 +1873,9 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
# TODO(tetsuro): Return all resource providers in the tree
# 'cn_numa0': set([]),
# 'cn_numa1': set([]),
'cn_numa0_pf0': set([
(fields.ResourceClass.SRIOV_NET_VF, 8, 0),
]),
# 'cn_numa0_pf0': set([
# (fields.ResourceClass.SRIOV_NET_VF, 8, 0),
# ]),
'cn_numa1_pf1': set([
(fields.ResourceClass.SRIOV_NET_VF, 8, 0),
]),
@ -1900,7 +1887,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
# TODO(tetsuro): Return all resource providers in the tree
# 'cn_numa0': set([]),
# 'cn_numa1': set([]),
'cn_numa0_pf0': set([]),
# 'cn_numa0_pf0': set([]),
'cn_numa1_pf1': set([os_traits.HW_NIC_OFFLOAD_GENEVE]),
}
self._validate_provider_summary_traits(expected, alloc_cands)
@ -2127,9 +2114,13 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
# let's validate providers in tree as well
provider_ids = set(p[0] for p in trees)
# Bug #1771707: Ideally, we want to have only pf1 from cn3 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
# fixed out of this _get_trees_matching_all() function by checking
# traits later again in _check_traits_for_alloc_request().
# But ideally, we'd like to have only pf1 from cn3 here using SQL
# query in _get_trees_matching_all() function for optimization.
# provider_names = cn_names + ['cn3_numa1_pf1']
# But actually we also get providers without traits
provider_names = cn_names + ['cn3_numa0_pf0', 'cn3_numa1_pf1']
expect_provider_ids = self._get_rp_ids_matching_names(provider_names)
self.assertEqual(expect_provider_ids, provider_ids)
@ -2167,9 +2158,13 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
# let's validate providers in tree as well
provider_ids = set(p[0] for p in trees)
# Bug #1771707: Ideally, we want to have only pf1 from cn3 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
# fixed out of this _get_trees_matching_all() function by checking
# traits later again in _check_traits_for_alloc_request().
# But ideally, we'd like to have only pf1 from cn3 here using SQL
# query in _get_trees_matching_all() function for optimization.
# provider_names = cn_names + ['cn3_numa1_pf1']
# But actually we also get providers without traits
provider_names = cn_names + ['cn3_numa0_pf0', 'cn3_numa1_pf1']
expect_provider_ids = self._get_rp_ids_matching_names(provider_names)
self.assertEqual(expect_provider_ids, provider_ids)