Return all resources in provider_summaries

The response of ``GET /allocation_candidates`` API provides two fields
of ``allocation_requests`` and ``provider_summaries``. The callers,
like the filter scheduler in nova, would use information in
``provider_summaries`` in sorting or filtering providers to allocate
consumers. However, currently ``provider_summaries`` doesn't contain
resource classes that aren't requested.

With this patch, ``GET /allocation_candidates`` API returns all
resource classes with a new microversion.

Change-Id: Ic491f190ebd97d94c18931a0e78d779a55ee47a1
Closes-Bug: #1760276
Blueprint: placement-return-all-resources
This commit is contained in:
Tetsuro Nakamura 2018-03-31 18:27:11 +09:00
parent f9a419e40f
commit 97530c2ca3
9 changed files with 104 additions and 60 deletions

View File

@ -116,10 +116,14 @@ def _transform_allocation_requests_list(alloc_reqs):
return results
def _transform_provider_summaries(p_sums, include_traits=False):
def _transform_provider_summaries(p_sums, requests, include_traits=False,
include_all_resources=False):
"""Turn supplied list of ProviderSummary objects into a dict, keyed by
resource provider UUID, of dicts of provider and inventory information. The
traits only show up when `include_traits` is `True`.
resource provider UUID, of dicts of provider and inventory information.
The traits only show up when `include_traits` is `True`.
When `include_all_resources` is `True`, all the resource classes are
shown while only requested resources are included in the
`provider_summaries` when `include_all_resources` is `False`.
{
RP_UUID_1: {
@ -158,13 +162,21 @@ def _transform_provider_summaries(p_sums, include_traits=False):
"""
ret = {}
requested_resources = set()
for requested_group in requests.values():
requested_resources |= set(requested_group.resources.keys())
# if include_all_resources is false, only requested resources are
# included in the provider_summaries.
for ps in p_sums:
resources = {
psr.resource_class: {
'capacity': psr.capacity,
'used': psr.used,
} for psr in ps.resources
} for psr in ps.resources if (
include_all_resources or
psr.resource_class in requested_resources)
}
ret[ps.resource_provider.uuid] = {'resources': resources}
@ -176,7 +188,7 @@ def _transform_provider_summaries(p_sums, include_traits=False):
return ret
def _transform_allocation_candidates(alloc_cands, want_version):
def _transform_allocation_candidates(alloc_cands, requests, want_version):
"""Turn supplied AllocationCandidates object into a dict containing
allocation requests and provider summaries.
@ -193,8 +205,11 @@ def _transform_allocation_candidates(alloc_cands, want_version):
alloc_cands.allocation_requests)
include_traits = want_version.matches((1, 17))
p_sums = _transform_provider_summaries(alloc_cands.provider_summaries,
include_traits=include_traits)
include_all_resources = want_version.matches((1, 27))
p_sums = _transform_provider_summaries(
alloc_cands.provider_summaries, requests,
include_traits=include_traits,
include_all_resources=include_all_resources)
return {
'allocation_requests': a_reqs,
'provider_summaries': p_sums,
@ -255,7 +270,7 @@ def list_allocation_candidates(req):
raise webob.exc.HTTPBadRequest(six.text_type(exc))
response = req.response
trx_cands = _transform_allocation_candidates(cands, want_version)
trx_cands = _transform_allocation_candidates(cands, requests, want_version)
json_data = jsonutils.dumps(trx_cands)
response.body = encodeutils.to_utf8(json_data)
response.content_type = 'application/json'

View File

@ -70,6 +70,9 @@ VERSIONS = [
# querystring groups in GET /allocation_candidates
'1.26', # Add ability to specify inventory with reserved value equal to
# total.
'1.27', # Include all resource class inventories in `provider_summaries`
# field in response of `GET /allocation_candidates` API even if
# the resource class is not in the requested resources.
]

View File

@ -3684,45 +3684,16 @@ def _merge_candidates(candidates, group_policy=None):
# Now we have to produce provider summaries. The provider summaries in
# the candidates input contain all the information; we just need to
# filter it down to only the providers and resource classes* in our
# merged list of allocation requests.
# *With blueprint placement-return-all-resources, all resource classes
# should be included, so that condition will need to be removed either
# here or there, depending which lands first.
# To make this easier, first index all our allocation requests as a
# dict, keyed by resource provider UUID, of sets of resource class
# names.
rcs_by_rp = collections.defaultdict(set)
# filter it down to only the providers in our merged list of allocation
# requests.
rps_in_areq = set()
for areq in areqs:
for arr in areq.resource_requests:
rcs_by_rp[arr.resource_provider.uuid].add(arr.resource_class)
# Now walk the input candidates' provider summaries, building a dict,
# keyed by resource provider UUID, of ProviderSummary representing
# that provider, and including any of its resource classes found in the
# index we built from our allocation requests above*.
# *See above.
psums_by_rp = {}
for psum in all_psums:
rp_uuid = psum.resource_provider.uuid
# If everything from this provider was filtered out, don't add an
# (empty) entry for it.
if rp_uuid not in rcs_by_rp:
continue
if rp_uuid not in psums_by_rp:
psums_by_rp[rp_uuid] = ProviderSummary(
resource_provider=psum.resource_provider, resources=[],
# Should always be the same; no need to check/update below.
traits=psum.traits)
# NOTE(efried): To subsume blueprint placement-return-all-resources
# replace this loop with:
# psums_by_rp[rp_uuid].resources = psum.resources
resources = set(psums_by_rp[rp_uuid].resources)
for psumres in psum.resources:
if psumres.resource_class in rcs_by_rp[rp_uuid]:
resources.add(psumres)
psums_by_rp[rp_uuid].resources = list(resources)
rps_in_areq.add(arr.resource_provider.uuid)
psums = [psum for psum in all_psums if
psum.resource_provider.uuid in rps_in_areq]
return areqs, list(psums_by_rp.values())
return areqs, psums
@base.VersionedObjectRegistry.register_if(False)

View File

@ -329,3 +329,10 @@ non-sharing tree or associated via the specified aggregate(s).
Starting with this version, it is allowed to set the reserved value of the
resource provider inventory to be equal to total.
1.27 Include all resource class inventories in provider_summaries
-----------------------------------------------------------------
Include all resource class inventories in the ``provider_summaries`` field in
response of the ``GET /allocation_candidates`` API even if the resource class
is not in the requested resources.

View File

@ -399,12 +399,8 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
expected = {
'cn1': set([
(fields.ResourceClass.VCPU, 8, 0),
# TODO(tetsuro) - Bug#1760276: provider_summaries should
# include all the resources that the resource provider has,
# but currently it includes only resources that are requested.
#
# (fields.ResourceClass.MEMORY_MB, 2048, 0),
# (fields.ResourceClass.DISK_GB, 2000, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
(fields.ResourceClass.DISK_GB, 2000, 0)
]),
}
self._validate_provider_summary_resources(expected, alloc_cands)
@ -1104,6 +1100,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
'cn': set([
(fields.ResourceClass.VCPU, 24, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
(fields.ResourceClass.DISK_GB, 1600, 0),
]),
'ss': set([
(fields.ResourceClass.DISK_GB, 2000, 0),
@ -1154,10 +1151,7 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
'cn': set([
(fields.ResourceClass.VCPU, 24, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
# NOTE(efried): We don't (yet) populate provider summaries with
# provider resources that aren't part of the result. With
# blueprint placement-return-all-requests, uncomment this line:
# (fields.ResourceClass.DISK_GB, 1600, 0),
(fields.ResourceClass.DISK_GB, 1600, 0),
]),
'ss': set([
(fields.ResourceClass.DISK_GB, 1600, 0),
@ -1370,10 +1364,12 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
expected = {
'cn1': set([
(fields.ResourceClass.VCPU, 24, 0)
(fields.ResourceClass.VCPU, 24, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
]),
'cn2': set([
(fields.ResourceClass.VCPU, 24, 0)
(fields.ResourceClass.VCPU, 24, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
]),
'ss1': set([
(fields.ResourceClass.DISK_GB, 1600, 0),
@ -1434,9 +1430,11 @@ class AllocationCandidatesTestCase(tb.PlacementDbBaseTestCase):
expected = {
'cn1': set([
(fields.ResourceClass.VCPU, 24, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
]),
'cn2': set([
(fields.ResourceClass.VCPU, 24, 0),
(fields.ResourceClass.MEMORY_MB, 2048, 0),
]),
'ss1': set([
(fields.ResourceClass.DISK_GB, 1600, 0),

View File

@ -277,13 +277,51 @@ tests:
$.allocation_requests.`len`: 0
$.provider_summaries.`len`: 0
# Bug#1760276: provider_summaries should include all the resources that the
# resource provider has, ut currently it includes only resources that are requested.
# Before microversion 1.27, the ``provider_summaries`` field in the response
# of the ``GET /allocation_candidates`` API included inventories of resource
# classes that are requested.
- name: get allocation candidates provider summaries with requested resource
GET: /allocation_candidates?resources=VCPU:1
status: 200
request_headers:
openstack-api-version: placement 1.26
response_json_paths:
$.allocation_requests.`len`: 2
$.provider_summaries.`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].resources.`len`: 1
$.provider_summaries["$ENVIRON['CN1_UUID']"].resources:
VCPU:
capacity: 384 # 16.0 * 24
used: 0
$.provider_summaries["$ENVIRON['CN2_UUID']"].resources.`len`: 1
$.provider_summaries["$ENVIRON['CN2_UUID']"].resources:
VCPU:
capacity: 384 # 16.0 * 24
used: 0
# From microversion 1.27, the ``provider_summaries`` field includes
# all the resource class inventories regardless of whether it is requested.
- name: get allocation candidates provider summaries with all resources
GET: /allocation_candidates?resources=VCPU:1
status: 200
request_headers:
openstack-api-version: placement 1.27
response_json_paths:
$.allocation_requests.`len`: 2
$.provider_summaries.`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].resources.`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].resources:
VCPU:
capacity: 384 # 16.0 * 24
used: 0
MEMORY_MB:
capacity: 196608 # 1.5 * 128G
used: 0
$.provider_summaries["$ENVIRON['CN2_UUID']"].resources.`len`: 2
$.provider_summaries["$ENVIRON['CN2_UUID']"].resources:
VCPU:
capacity: 384 # 16.0 * 24
used: 0
MEMORY_MB:
capacity: 196608 # 1.5 * 128G
used: 0

View File

@ -167,6 +167,9 @@ tests:
MEMORY_MB:
capacity: 4096
used: 0
DISK_GB:
capacity: 500
used: 0
$.provider_summaries["$ENVIRON['CN_MIDDLE']"].resources:
VCPU:
capacity: 8

View File

@ -39,13 +39,13 @@ tests:
response_json_paths:
$.errors[0].title: Not Acceptable
- name: latest microversion is 1.26
- name: latest microversion is 1.27
GET: /
request_headers:
openstack-api-version: placement latest
response_headers:
vary: /openstack-api-version/
openstack-api-version: placement 1.26
openstack-api-version: placement 1.27
- name: other accept header bad version
GET: /

View File

@ -0,0 +1,9 @@
---
features:
- |
From microversion 1.27, the ``provider_summaries`` field in the
response of the ``GET /allocation_candidates`` API includes all
the resource class inventories, while it had only requested
resource class inventories with older microversions.
Now callers can use this additional inventory information in
making further sorting or filtering decisions.