Prepare objects for allocation request mappings

We need to carry a 'suffix' through the process of generating
allocation requests in order to be able to present those suffixes
as a 'mappings' key in a forthcoming microversion.

In this patch, the suffix is tracked, but the data is not used
when presenting results.

A test is added to validate results. Existing tests which
check the count of results are adjusted to reflect new results:
The possible combinations is now increased because we are
accounting for the suffix as a differentiator.

Change-Id: I3fdd46a0a92bf9666696a1c5f98afc402cf43b33
Story: 2005575
Task: 33536
This commit is contained in:
Chris Dent
2019-06-03 16:03:32 +01:00
parent b04a15cac7
commit eb07913442
3 changed files with 83 additions and 26 deletions

View File

@@ -142,7 +142,7 @@ class AllocationCandidates(object):
for suffix, request in requests.items():
try:
rg_ctx = res_ctx.RequestGroupSearchContext(
context, request, has_trees, sharing)
context, request, has_trees, sharing, suffix)
except exception.ResourceProviderNotFound:
return [], []
@@ -244,31 +244,33 @@ class AllocationRequest(object):
def __hash__(self):
# We need a stable sort order on the resource requests to get an
# accurate hash. Since we might have either > 1 of the same resource
# class or > 1 of the same resource provider we need to sort on both.
sorted_rr = sorted(
self.resource_requests,
key=lambda x: (x.resource_class, x.resource_provider.id))
# accurate hash. To avoid needing to update the method everytime
# the structure of an AllocationRequestResource changes, we can
# sort on the hash of each request resource.
sorted_rr = sorted(self.resource_requests, key=lambda x: hash(x))
return hash(tuple(sorted_rr))
class AllocationRequestResource(object):
def __init__(self, resource_provider=None, resource_class=None,
amount=None):
amount=None, suffix=''):
self.resource_provider = resource_provider
self.resource_class = resource_class
self.amount = amount
self.suffix = suffix
def __eq__(self, other):
return ((self.resource_provider.id == other.resource_provider.id) and
(self.resource_class == other.resource_class) and
(self.amount == other.amount))
(self.amount == other.amount) and
(self.suffix == other.suffix))
def __hash__(self):
return hash((self.resource_provider.id,
self.resource_class,
self.amount))
self.amount,
self.suffix))
class ProviderSummary(object):
@@ -337,7 +339,8 @@ def _alloc_candidates_multiple_providers(rg_ctx, rp_candidates):
AllocationRequestResource(
resource_provider=rp_summary.resource_provider,
resource_class=rc_cache.RC_CACHE.string_from_id(rp.rc_id),
amount=rg_ctx.resources[rp.rc_id]))
amount=rg_ctx.resources[rp.rc_id],
suffix=rg_ctx.suffix))
# Next, build up a set of allocation requests. These allocation requests
# are AllocationRequest objects, containing resource provider UUIDs,
@@ -430,7 +433,8 @@ def _alloc_candidates_single_provider(rg_ctx, rp_tuples):
for rp_id, root_id in rp_tuples:
rp_summary = summaries[rp_id]
req_obj = _allocation_request_for_provider(
rg_ctx.context, rg_ctx.resources, rp_summary.resource_provider)
rg_ctx.context, rg_ctx.resources, rp_summary.resource_provider,
suffix=rg_ctx.suffix)
alloc_requests.append(req_obj)
# If this is a sharing provider, we have to include an extra
# AllocationRequest for every possible anchor.
@@ -448,7 +452,8 @@ def _alloc_candidates_single_provider(rg_ctx, rp_tuples):
return alloc_requests, list(summaries.values())
def _allocation_request_for_provider(ctx, requested_resources, provider):
def _allocation_request_for_provider(ctx, requested_resources, provider,
suffix):
"""Returns an AllocationRequest object containing AllocationRequestResource
objects for each resource class in the supplied requested resources dict.
@@ -462,7 +467,7 @@ def _allocation_request_for_provider(ctx, requested_resources, provider):
AllocationRequestResource(
resource_provider=provider,
resource_class=rc_cache.RC_CACHE.string_from_id(rc_id),
amount=amount,
amount=amount, suffix=suffix
) for rc_id, amount in requested_resources.items()
]
# NOTE(efried): This method only produces an AllocationRequest with its

View File

@@ -46,7 +46,7 @@ class RequestGroupSearchContext(object):
"""An adapter object that represents the search for allocation candidates
for a single request group.
"""
def __init__(self, context, request, has_trees, sharing):
def __init__(self, context, request, has_trees, sharing, suffix=''):
"""Initializes the object retrieving and caching matching providers
for each conditions like resource and aggregates from database.
@@ -56,6 +56,9 @@ class RequestGroupSearchContext(object):
# TODO(tetsuro): split this into smaller functions reordering
self.context = context
# The request group suffix
self.suffix = suffix
# A dict, keyed by resource class internal ID, of the amounts of that
# resource class being requested by the group.
self.resources = {

View File

@@ -885,7 +885,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
return ac_obj.AllocationCandidates.get_by_requests(self.ctx, requests,
limit, group_policy)
def _validate_allocation_requests(self, expected, candidates):
def _validate_allocation_requests(self, expected, candidates,
expect_suffix=False):
"""Assert correctness of allocation requests in allocation candidates.
This is set up to make it easy for the caller to specify the expected
@@ -907,8 +908,11 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
for ar in candidates.allocation_requests:
rrs = []
for rr in ar.resource_requests:
rrs.append((self.rp_uuid_to_name[rr.resource_provider.uuid],
rr.resource_class, rr.amount))
req_tuple = (self.rp_uuid_to_name[rr.resource_provider.uuid],
rr.resource_class, rr.amount)
if expect_suffix:
req_tuple = req_tuple + (rr.suffix, )
rrs.append(req_tuple)
rrs.sort()
observed.append(rrs)
observed.sort()
@@ -1007,9 +1011,10 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
)
expected = [
[('cn1', orc.VCPU, 1)]
[('cn1', orc.VCPU, 1, '')]
]
self._validate_allocation_requests(expected, alloc_cands)
self._validate_allocation_requests(
expected, alloc_cands, expect_suffix=True)
expected = {
'cn1': set([
@@ -2916,11 +2921,11 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
# Near the end of _merge candidates we expect 4 different collections
# of AllocationRequest to attempt to be added to a set. Admittance is
# controlled by the __hash__ and __eq__ of the AllocationRequest which,
# in this case, should winnow the results down to 2 since they are
# defined to be the same (they have the same resource provider, the
# same resource class and the same desired amount), but contribute
# via different suffixes.
self.assertEqual(2, len(alloc_cands.allocation_requests))
# in this case, should keep the results at 4 since they are defined to
# be different when they have different suffixes even if they have the
# same resource provider, the same resource class and the same desired
# amount.
self.assertEqual(4, len(alloc_cands.allocation_requests))
def test_nested_result_count_none(self):
"""Tests that we properly winnow allocation requests when including
@@ -2946,8 +2951,9 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
}),
}, group_policy='none')
# 4 VF providers each providing 2, 1, or 0 inventory makes 6
# workable combinations.
self.assertEqual(6, len(alloc_cands.allocation_requests))
# different combinations, plus two more that are effectively
# the same but satisfying different suffix mappings.
self.assertEqual(8, len(alloc_cands.allocation_requests))
def test_nested_result_count_different_amounts(self):
"""Tests that we properly winnow allocation requests when including
@@ -2974,3 +2980,46 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
}),
}, group_policy='isolate')
self.assertEqual(4, len(alloc_cands.allocation_requests))
def test_nested_result_suffix_mappings(self):
"""Confirm that paying attention to suffix mappings expands
the quantity of results and confirm those results.
"""
self._create_nested_trees()
# Make a granular request to check count and suffixes of results.
alloc_cands = self._get_allocation_candidates({
'': placement_lib.RequestGroup(
use_same_provider=False,
resources={
orc.VCPU: 2,
}),
'_NET1': placement_lib.RequestGroup(
use_same_provider=True,
resources={
orc.SRIOV_NET_VF: 1,
}),
'_NET2': placement_lib.RequestGroup(
use_same_provider=True,
resources={
orc.SRIOV_NET_VF: 1,
}),
}, group_policy='isolate')
expected = [
[('cn1', orc.VCPU, 2, ''),
('cn1_numa0_pf0', orc.SRIOV_NET_VF, 1, '_NET1'),
('cn1_numa1_pf1', orc.SRIOV_NET_VF, 1, '_NET2')],
[('cn1', orc.VCPU, 2, ''),
('cn1_numa0_pf0', orc.SRIOV_NET_VF, 1, '_NET2'),
('cn1_numa1_pf1', orc.SRIOV_NET_VF, 1, '_NET1')],
[('cn2', orc.VCPU, 2, ''),
('cn2_numa0_pf0', orc.SRIOV_NET_VF, 1, '_NET1'),
('cn2_numa1_pf1', orc.SRIOV_NET_VF, 1, '_NET2')],
[('cn2', orc.VCPU, 2, ''),
('cn2_numa0_pf0', orc.SRIOV_NET_VF, 1, '_NET2'),
('cn2_numa1_pf1', orc.SRIOV_NET_VF, 1, '_NET1')],
]
self.assertEqual(4, len(alloc_cands.allocation_requests))
self._validate_allocation_requests(
expected, alloc_cands, expect_suffix=True)