[placement] add two ways to GET allocations

Add two resources on the placement api for retrieving allocations.

One, /resource_providers/{uuid}/allocations, returns all the
allocations currently against this resource provider in this format:

    {'resource_provider_generation': GENERATION,
     'allocations':
        CONSUMER_ID_1: {
            'resources': {
                'DISK_GB': 4,
                'VCPU': 2
            }
        },
        CONSUMER_ID_2: {
            'resources': {
                'DISK_GB': 6,
                'VCPU': 3
            }
        }
    }

Another, /allocations/{consumer_id}, returns all the allocations for
the current consumer in this format:

    {'allocations':
      RP_UUID_1: {
        'generation': GENERATION,
        'resources': {
            'DISK_GB': 4,
            'VCPU': 2
        }
      },
      RP_UUID_2: {
          'generation': GENERATION,
          'resources': {
              'DISK_GB': 6,
              'VCPU': 3
          }
      }
    }

The formats are eminently usable on the client side, by having a
comparable dict reachable by a predictable key, but not self
describing (we don't know that the keys are consumer ids or resource
uuids).

In the first format the descent into a resources key is to maintain
parity with the second format and provide room for expansion.

Change-Id: I69fbc4e9834ec6dc80dacf43f8fd9dc6ec139006
Closes-Bug: #1621059
This commit is contained in:
Chris Dent 2016-09-07 14:27:24 +00:00
parent 2bd800ce25
commit eab8100b42
3 changed files with 277 additions and 4 deletions

View File

@ -70,7 +70,11 @@ ROUTE_DECLARATIONS = {
'/resource_providers/{uuid}/usages': {
'GET': usage.list_usages
},
'/resource_providers/{uuid}/allocations': {
'GET': allocation.list_for_resource_provider,
},
'/allocations/{consumer_uuid}': {
'GET': allocation.list_for_consumer,
'PUT': allocation.set_allocations,
'DELETE': allocation.delete_allocations,
},

View File

@ -11,6 +11,8 @@
# under the License.
"""Placement API handlers for setting and deleting allocations."""
import collections
import jsonschema
from oslo_serialization import jsonutils
import webob
@ -62,6 +64,28 @@ ALLOCATION_SCHEMA = {
}
def _allocations_dict(allocations, key_fetcher, resource_provider=None):
"""Turn allocations into a dict of resources keyed by key_fetcher."""
allocation_data = collections.defaultdict(dict)
for allocation in allocations:
key = key_fetcher(allocation)
if 'resources' not in allocation_data[key]:
allocation_data[key]['resources'] = {}
resource_class = allocation.resource_class
allocation_data[key]['resources'][resource_class] = allocation.used
if not resource_provider:
generation = allocation.resource_provider.generation
allocation_data[key]['generation'] = generation
result = {'allocations': allocation_data}
if resource_provider:
result['resource_provider_generation'] = resource_provider.generation
return result
def _extract_allocations(body, schema):
"""Extract allocation data from a JSON body."""
try:
@ -80,6 +104,111 @@ def _extract_allocations(body, schema):
return data
def _serialize_allocations_for_consumer(allocations):
"""Turn a list of allocations into a dict by resource provider uuid.
{'allocations':
RP_UUID_1: {
'generation': GENERATION,
'resources': {
'DISK_GB': 4,
'VCPU': 2
}
},
RP_UUID_2: {
'generation': GENERATION,
'resources': {
'DISK_GB': 6,
'VCPU': 3
}
}
}
"""
return _allocations_dict(allocations,
lambda x: x.resource_provider.uuid)
def _serialize_allocations_for_resource_provider(allocations,
resource_provider):
"""Turn a list of allocations into a dict by consumer id.
{'resource_provider_generation': GENERATION,
'allocations':
CONSUMER_ID_1: {
'resources': {
'DISK_GB': 4,
'VCPU': 2
}
},
CONSUMER_ID_2: {
'resources': {
'DISK_GB': 6,
'VCPU': 3
}
}
}
"""
return _allocations_dict(allocations, lambda x: x.consumer_id,
resource_provider=resource_provider)
@webob.dec.wsgify
@util.check_accept('application/json')
def list_for_consumer(req):
"""List allocations associated with a consumer."""
context = req.environ['placement.context']
consumer_id = util.wsgi_path_item(req.environ, 'consumer_uuid')
# NOTE(cdent): There is no way for a 404 to be returned here,
# only an empty result. We do not have a way to validate a
# consumer id.
allocations = objects.AllocationList.get_all_by_consumer_id(
context, consumer_id)
allocations_json = jsonutils.dumps(
_serialize_allocations_for_consumer(allocations))
req.response.status = 200
req.response.body = allocations_json
req.response.content_type = 'application/json'
return req.response
@webob.dec.wsgify
@util.check_accept('application/json')
def list_for_resource_provider(req):
"""List allocations associated with a resource provider."""
# TODO(cdent): On a shared resource provider (for example a
# giant disk farm) this list could get very long. At the moment
# we have no facility for limiting the output. Given that we are
# using a dict of dicts for the output we are potentially limiting
# ourselves in terms of sorting and filtering.
context = req.environ['placement.context']
uuid = util.wsgi_path_item(req.environ, 'uuid')
# confirm existence of resource provider so we get a reasonable
# 404 instead of empty list
try:
resource_provider = objects.ResourceProvider.get_by_uuid(
context, uuid)
except exception.NotFound as exc:
raise webob.exc.HTTPNotFound(
"Resource provider '%s' not found: %s" % (uuid, exc),
json_formatter=util.json_error_formatter)
allocations = objects.AllocationList.get_all_by_resource_provider_uuid(
context, uuid)
allocations_json = jsonutils.dumps(
_serialize_allocations_for_resource_provider(
allocations, resource_provider))
req.response.status = 200
req.response.body = allocations_json
req.response.content_type = 'application/json'
return req.response
@webob.dec.wsgify
@util.require_content('application/json')
def set_allocations(req):

View File

@ -18,11 +18,10 @@ tests:
GET: /allocations
status: 404
- name: get allocations is 405
- name: get allocations is empty dict
GET: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
status: 405
response_headers:
allow: /(PUT, DELETE|DELETE, PUT)/
response_json_paths:
$.allocations: {}
- name: put an allocation no resource provider
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
@ -200,3 +199,144 @@ tests:
response_json_paths:
$.resource_provider_generation: 7
$.usages.DISK_GB: 40
- name: check allocations for the resource provider
GET: /resource_providers/$ENVIRON['RP_UUID']/allocations
response_json_paths:
$.resource_provider_generation: 7
# allocations are keyed by consumer id, jsonpath-rw needs us
# to quote the uuids or its parser gets confused that maybe
# they are numbers on which math needs to be done.
$.allocations['833f0885-f78c-4788-bb2b-3607b0656be7'].resources.DISK_GB: 20
$.allocations['833f0885-f78c-4788-bb2b-3607b0656be7'].resources.VCPU: 4
$.allocations['599ffd2d-526a-4b2e-8683-f13ad25f9958'].resources.DISK_GB: 10
$.allocations['39715579-2167-4c63-8247-301311cc6703'].resources.DISK_GB: 10
- name: confirm 404 for allocations of bad resource provider
GET: /resource_providers/cb8a3007-b93a-471f-9e1f-4d58355678bd/allocations
status: 404
- name: check allocations by consumer id
GET: /allocations/833f0885-f78c-4788-bb2b-3607b0656be7
response_json_paths:
# TODO(cdent): Can't currently do substituion on left hand
# side of json path in gabbi, a bug has been made. In the
# meantime we have to jump over it. This works because we only
# have one resource provider in this output.
$.allocations..generation: 7
$.allocations..resources.DISK_GB: 20
$.allocations..resources.VCPU: 4
- name: check allocations by diferent consumer id
GET: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
response_json_paths:
# TODO(cdent): Can't currently do substituion on left hand
# side of json path in gabbi, a bug has been made. In the
# meantime we have to jump over the resource provider (which
# we don't know as a static value). This works because we only
# have one resource provider in this output.
$.allocations..generation: 7
$.allocations..DISK_GB: 10
# - name: create another two resource providers
- name: create resource provider 1
POST: /resource_providers
request_headers:
content-type: application/json
data:
name: rp1
uuid: 9229b2fc-d556-4e38-9c18-443e4bc6ceae
status: 201
- name: create resource provider 2
POST: /resource_providers
request_headers:
content-type: application/json
data:
name: rp2
uuid: fcfa516a-abbe-45d1-8152-d5225d82e596
status: 201
- name: set inventory on rp1
PUT: /resource_providers/9229b2fc-d556-4e38-9c18-443e4bc6ceae/inventories
request_headers:
content-type: application/json
data:
# TODO(cdent): This format is going to go out of date because
# of other changes
resource_provider_generation: 0
inventories:
- resource_class: VCPU
total: 32
- resource_class: DISK_GB
total: 10
- name: set inventory on rp2
PUT: /resource_providers/fcfa516a-abbe-45d1-8152-d5225d82e596/inventories
request_headers:
content-type: application/json
data:
# TODO(cdent): This format is going to go out of date because
# of other changes
resource_provider_generation: 0
inventories:
- resource_class: VCPU
total: 16
- resource_class: DISK_GB
total: 20
status: 200
- name: put allocations on both those providers one
PUT: /allocations/1835b1c9-1c61-45af-9eb3-3e0e9f29487b
request_headers:
content-type: application/json
data:
allocations:
- resource_provider:
uuid: fcfa516a-abbe-45d1-8152-d5225d82e596
resources:
DISK_GB: 10
VCPU: 8
- resource_provider:
uuid: 9229b2fc-d556-4e38-9c18-443e4bc6ceae
resources:
DISK_GB: 5
VCPU: 16
status: 204
- name: put allocations on both those providers two
PUT: /allocations/75d0f5f7-75d9-458c-b204-f90ac91604ec
request_headers:
content-type: application/json
data:
allocations:
- resource_provider:
uuid: fcfa516a-abbe-45d1-8152-d5225d82e596
resources:
DISK_GB: 5
VCPU: 4
- resource_provider:
uuid: 9229b2fc-d556-4e38-9c18-443e4bc6ceae
resources:
DISK_GB: 2
VCPU: 8
status: 204
- name: get those allocations for consumer
GET: /allocations/1835b1c9-1c61-45af-9eb3-3e0e9f29487b
response_json_paths:
$.allocations.['fcfa516a-abbe-45d1-8152-d5225d82e596'].generation: 3
$.allocations.['fcfa516a-abbe-45d1-8152-d5225d82e596'].resources.DISK_GB: 10
$.allocations.['fcfa516a-abbe-45d1-8152-d5225d82e596'].resources.VCPU: 8
$.allocations.['9229b2fc-d556-4e38-9c18-443e4bc6ceae'].generation: 3
$.allocations.['9229b2fc-d556-4e38-9c18-443e4bc6ceae'].resources.DISK_GB: 5
$.allocations.['9229b2fc-d556-4e38-9c18-443e4bc6ceae'].resources.VCPU: 16
- name: get those allocations for resource provider
GET: /resource_providers/fcfa516a-abbe-45d1-8152-d5225d82e596/allocations
response_json_paths:
$.resource_provider_generation: 3
$.allocations.['75d0f5f7-75d9-458c-b204-f90ac91604ec'].resources.DISK_GB: 5
$.allocations.['75d0f5f7-75d9-458c-b204-f90ac91604ec'].resources.VCPU: 4
$.allocations.['1835b1c9-1c61-45af-9eb3-3e0e9f29487b'].resources.DISK_GB: 10
$.allocations.['1835b1c9-1c61-45af-9eb3-3e0e9f29487b'].resources.VCPU: 8