Add support for allocations in placement API
A new endpoint supporting PUT and DELETE methods with a path of /allocations/{consumer_id} calls set_allocations and delete_allocations to set or delete allocations for one or more resource classes on one or more resource providers. An example JSON body in PUT request takes the following form: {"allocations": [ { "resource_provider": { "uuid": "d4b87fe1-30ca-4a65-8947-dfa3d85f10be" }, "resources": { "MEMORY_MB": 64, "VCPU": 2 } }, { "resource_provider": { "uuid": "3cbe1001-bba9-4ef6-ae9c-feac1c3f9626" }, "resources": { "DISK_GB": 28 } } ]} The body is validated against the JSON schema defined in the ALLOCATION_SCHEMA constant. The API code calls create_all and delete_all on the AllocationList object. create_all() allows an existing allocation to be replaced. Change-Id: Ic1e90636bad44b7dc76b45c5f93c67f3fe3297f7 Partially-Implements: blueprint generic-resource-pools
This commit is contained in:
parent
8ed5120829
commit
f441ee55c6
|
@ -28,6 +28,7 @@ import webob
|
|||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.api.openstack.placement.handlers import allocation
|
||||
from nova.api.openstack.placement.handlers import inventory
|
||||
from nova.api.openstack.placement.handlers import resource_provider
|
||||
from nova.api.openstack.placement.handlers import root
|
||||
|
@ -69,6 +70,10 @@ ROUTE_DECLARATIONS = {
|
|||
'/resource_providers/{uuid}/usages': {
|
||||
'GET': usage.list_usages
|
||||
},
|
||||
'/allocations/{consumer_uuid}': {
|
||||
'PUT': allocation.set_allocations,
|
||||
'DELETE': allocation.delete_allocations,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Placement API handlers for setting and deleting allocations."""
|
||||
|
||||
import jsonschema
|
||||
from oslo_serialization import jsonutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.placement import util
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
|
||||
|
||||
ALLOCATION_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allocations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"resource_provider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["uuid"]
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[0-9A-Z_]+$": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": False
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"resource_provider",
|
||||
"resources"
|
||||
],
|
||||
"additionalProperties": False
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["allocations"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
|
||||
def _extract_allocations(body, schema):
|
||||
"""Extract allocation data from a JSON body."""
|
||||
try:
|
||||
data = jsonutils.loads(body)
|
||||
except ValueError as exc:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
'Malformed JSON: %s' % exc,
|
||||
json_formatter=util.json_error_formatter)
|
||||
try:
|
||||
jsonschema.validate(data, schema,
|
||||
format_checker=jsonschema.FormatChecker())
|
||||
except jsonschema.ValidationError as exc:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
'JSON does not validate: %s' % exc,
|
||||
json_formatter=util.json_error_formatter)
|
||||
return data
|
||||
|
||||
|
||||
@webob.dec.wsgify
|
||||
@util.require_content('application/json')
|
||||
def set_allocations(req):
|
||||
context = req.environ['placement.context']
|
||||
consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
|
||||
data = _extract_allocations(req.body, ALLOCATION_SCHEMA)
|
||||
allocation_data = data['allocations']
|
||||
|
||||
# If the body includes an allocation for a resource provider
|
||||
# that does not exist, raise a 400.
|
||||
allocation_objects = []
|
||||
for allocation in allocation_data:
|
||||
resource_provider_uuid = allocation['resource_provider']['uuid']
|
||||
|
||||
try:
|
||||
resource_provider = objects.ResourceProvider.get_by_uuid(
|
||||
context, resource_provider_uuid)
|
||||
except exception.NotFound:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
"Allocation for resource provider '%s' "
|
||||
"that does not exist." % resource_provider_uuid,
|
||||
json_formatter=util.json_error_formatter)
|
||||
|
||||
resources = allocation['resources']
|
||||
for resource_class in resources:
|
||||
try:
|
||||
allocation = objects.Allocation(
|
||||
resource_provider=resource_provider,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=resource_class,
|
||||
used=resources[resource_class])
|
||||
except ValueError as exc:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
"Allocation of class '%s' for "
|
||||
"resource provider '%s' invalid: "
|
||||
"%s" % (resource_class, resource_provider_uuid, exc))
|
||||
allocation_objects.append(allocation)
|
||||
|
||||
allocations = objects.AllocationList(context, objects=allocation_objects)
|
||||
try:
|
||||
allocations.create_all()
|
||||
# InvalidInventory is a parent for several exceptions that
|
||||
# indicate either that Inventory is not present, or that
|
||||
# capacity limits have been exceeded.
|
||||
except exception.InvalidInventory as exc:
|
||||
raise webob.exc.HTTPConflict(
|
||||
'Unable to allocate inventory: %s' % exc,
|
||||
json_formatter=util.json_error_formatter)
|
||||
except exception.ConcurrentUpdateDetected as exc:
|
||||
raise webob.exc.HTTPConflict(
|
||||
'Inventory changed while attempting to allocate: %s' % exc,
|
||||
json_formatter=util.json_error_formatter)
|
||||
|
||||
req.response.status = 204
|
||||
req.response.content_type = None
|
||||
return req.response
|
||||
|
||||
|
||||
@webob.dec.wsgify
|
||||
def delete_allocations(req):
|
||||
context = req.environ['placement.context']
|
||||
consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
|
||||
|
||||
allocations = objects.AllocationList.get_all_by_consumer_id(
|
||||
context, consumer_uuid)
|
||||
if not allocations:
|
||||
raise webob.exc.HTTPNotFound(
|
||||
"No allocations for consumer '%s'" % consumer_uuid,
|
||||
json_formatter=util.json_error_formatter)
|
||||
allocations.delete_all()
|
||||
|
||||
req.response.status = 204
|
||||
req.response.content_type = None
|
||||
return req.response
|
|
@ -0,0 +1,202 @@
|
|||
# Tests of allocations API
|
||||
#
|
||||
# TODO(cdent): Where in the process is the consumer id being
|
||||
# validated?
|
||||
|
||||
|
||||
fixtures:
|
||||
- APIFixture
|
||||
|
||||
defaults:
|
||||
request_headers:
|
||||
x-auth-token: admin
|
||||
accept: application/json
|
||||
|
||||
tests:
|
||||
|
||||
- name: get allocations no consumer is 404
|
||||
GET: /allocations
|
||||
status: 404
|
||||
|
||||
- name: get allocations is 405
|
||||
GET: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
status: 405
|
||||
response_headers:
|
||||
allow: /(PUT, DELETE|DELETE, PUT)/
|
||||
|
||||
- name: put an allocation no resource provider
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resources:
|
||||
DISK_GB: 10
|
||||
status: 400
|
||||
|
||||
- name: create the resource provider
|
||||
POST: /resource_providers
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
name: $ENVIRON['RP_NAME']
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
status: 201
|
||||
|
||||
- name: put an allocation no data
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
status: 400
|
||||
|
||||
- name: put an allocation violate schema
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
cow: 10
|
||||
status: 400
|
||||
|
||||
- name: put an allocation no inventory
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
status: 409
|
||||
|
||||
- name: post some inventory
|
||||
POST: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
resource_class: DISK_GB
|
||||
total: 2048
|
||||
min_unit: 10
|
||||
max_unit: 1024
|
||||
status: 201
|
||||
|
||||
- name: put an allocation
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
status: 204
|
||||
|
||||
- name: put an allocation different consumer
|
||||
PUT: /allocations/39715579-2167-4c63-8247-301311cc6703
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
status: 204
|
||||
|
||||
- name: check usages after another 10
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/usages
|
||||
response_json_paths:
|
||||
$.usages.DISK_GB: 20
|
||||
|
||||
# NOTE(cdent): Contravening the spec, we decided that it is
|
||||
# important to be able to update an existing allocation, so this
|
||||
# should work but it is important to check the usage.
|
||||
- name: put allocation again
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 12
|
||||
status: 204
|
||||
|
||||
- name: check usages after 12
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/usages
|
||||
response_json_paths:
|
||||
$.usages.DISK_GB: 22
|
||||
|
||||
- name: put allocation bad resource class
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
COWS: 12
|
||||
status: 400
|
||||
response_strings:
|
||||
- Field value COWS is invalid
|
||||
|
||||
- name: delete allocation
|
||||
DELETE: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
status: 204
|
||||
|
||||
- name: delete allocation again
|
||||
DELETE: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
status: 404
|
||||
|
||||
- name: delete allocation of unknown consumer id
|
||||
DELETE: /allocations/da78521f-bf7e-4e6e-9901-3f79bd94d55d
|
||||
status: 404
|
||||
|
||||
- name: redo an allocation
|
||||
PUT: /allocations/599ffd2d-526a-4b2e-8683-f13ad25f9958
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 10
|
||||
status: 204
|
||||
|
||||
- name: add other inventory
|
||||
POST: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
resource_class: VCPU
|
||||
total: 32
|
||||
min_unit: 1
|
||||
max_unit: 8
|
||||
status: 201
|
||||
|
||||
- name: multiple allocations
|
||||
PUT: /allocations/833f0885-f78c-4788-bb2b-3607b0656be7
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
allocations:
|
||||
- resource_provider:
|
||||
uuid: $ENVIRON['RP_UUID']
|
||||
resources:
|
||||
DISK_GB: 20
|
||||
VCPU: 4
|
||||
status: 204
|
||||
|
||||
- name: check usages
|
||||
GET: /resource_providers/$ENVIRON['RP_UUID']/usages
|
||||
response_json_paths:
|
||||
$.resource_provider_generation: 7
|
||||
$.usages.DISK_GB: 40
|
Loading…
Reference in New Issue