placement: adds REST API for nested providers

Adds a new microversion (1.14) to the placement REST API for supporting
nested resource providers.

For POST /resource_providers and PUT /resource_providers/{uuid}, a new
optional 'parent_provider_uuid' field is added to the request payload.

For GET /resource_providers/{uuid} responses, the
'parent_provider_uuid' field and a convenience field called
'root_provider_uuid' are provided.

For GET /resource_providers, a new '?in_tree=<rp_uuid>' parameter is
supported. This parameter accepts a UUID of a resource provider. This
will cause the resulting list of resource providers to be only the
providers within the same "provider tree" as the provider identified by
<rp_uuid>

Clients for the placement REST API can specify either
'OpenStack-API-Version: placement 1.14' or 'placement latest' to handle
the new 'parent_provider_uuid' attribute and to query for resource
providers in a provider tree.

Change-Id: I4db74e4dc682bc03df6ec94cd1c3a5f5dc927a7b
blueprint: nested-resource-providers
APIImpact
This commit is contained in:
Stephen Finucane 2017-07-06 09:32:49 +01:00 committed by Eric Fried
parent 6c043b7ea6
commit 5734d078fc
14 changed files with 335 additions and 29 deletions

View File

@ -41,13 +41,30 @@ POST_RESOURCE_PROVIDER_SCHEMA = {
},
"required": [
"name"
],
],
"additionalProperties": False,
}
# Remove uuid to create the schema for PUTting a resource provider
PUT_RESOURCE_PROVIDER_SCHEMA = copy.deepcopy(POST_RESOURCE_PROVIDER_SCHEMA)
PUT_RESOURCE_PROVIDER_SCHEMA['properties'].pop('uuid')
# Placement API microversion 1.14 adds an optional parent_provider_uuid field
# to the POST and PUT request schemas
POST_RP_SCHEMA_V1_14 = copy.deepcopy(POST_RESOURCE_PROVIDER_SCHEMA)
POST_RP_SCHEMA_V1_14["properties"]["parent_provider_uuid"] = {
"anyOf": [
{
"type": "string",
"format": "uuid",
},
{
"type": "null",
}
]
}
PUT_RP_SCHEMA_V1_14 = copy.deepcopy(POST_RP_SCHEMA_V1_14)
PUT_RP_SCHEMA_V1_14['properties'].pop('uuid')
# Represents the allowed query string parameters to the GET /resource_providers
# API call
GET_RPS_SCHEMA_1_0 = {
@ -80,6 +97,17 @@ GET_RPS_SCHEMA_1_4['properties']['resources'] = {
"type": "string"
}
# Placement API microversion 1.14 adds support for requesting resource
# providers within a tree of providers. The 'in_tree' query string parameter
# should be the UUID of a resource provider. The result of the GET call will
# include only those resource providers in the same "provider tree" as the
# provider with the UUID represented by 'in_tree'
GET_RPS_SCHEMA_1_14 = copy.deepcopy(GET_RPS_SCHEMA_1_4)
GET_RPS_SCHEMA_1_14['properties']['in_tree'] = {
"type": "string",
"format": "uuid",
}
def _serialize_links(environ, resource_provider):
url = util.resource_provider_url(environ, resource_provider)
@ -97,20 +125,23 @@ def _serialize_links(environ, resource_provider):
return links
def _serialize_provider(environ, resource_provider):
def _serialize_provider(environ, resource_provider, want_version):
data = {
'uuid': resource_provider.uuid,
'name': resource_provider.name,
'generation': resource_provider.generation,
'links': _serialize_links(environ, resource_provider)
}
if want_version.matches((1, 14)):
data['parent_provider_uuid'] = resource_provider.parent_provider_uuid
data['root_provider_uuid'] = resource_provider.root_provider_uuid
return data
def _serialize_providers(environ, resource_providers):
def _serialize_providers(environ, resource_providers, want_version):
output = []
for provider in resource_providers:
provider_data = _serialize_provider(environ, provider)
provider_data = _serialize_provider(environ, provider, want_version)
output.append(provider_data)
return {"resource_providers": output}
@ -124,12 +155,15 @@ def create_resource_provider(req):
header pointing to the newly created resource provider.
"""
context = req.environ['placement.context']
data = util.extract_json(req.body, POST_RESOURCE_PROVIDER_SCHEMA)
schema = POST_RESOURCE_PROVIDER_SCHEMA
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
if want_version.matches((1, 14)):
schema = POST_RP_SCHEMA_V1_14
data = util.extract_json(req.body, schema)
try:
uuid = data.get('uuid', uuidutils.generate_uuid())
resource_provider = rp_obj.ResourceProvider(
context, name=data['name'], uuid=uuid)
uuid = data.setdefault('uuid', uuidutils.generate_uuid())
resource_provider = rp_obj.ResourceProvider(context, **data)
resource_provider.create()
except db_exc.DBDuplicateEntry as exc:
# Whether exc.columns has one or two entries (in the event
@ -192,8 +226,9 @@ def get_resource_provider(req):
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
context, uuid)
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
req.response.body = encodeutils.to_utf8(jsonutils.dumps(
_serialize_provider(req.environ, resource_provider)))
_serialize_provider(req.environ, resource_provider, want_version)))
req.response.content_type = 'application/json'
return req.response
@ -210,15 +245,17 @@ def list_resource_providers(req):
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
schema = GET_RPS_SCHEMA_1_0
if want_version == (1, 3):
schema = GET_RPS_SCHEMA_1_3
if want_version >= (1, 4):
if want_version.matches((1, 14)):
schema = GET_RPS_SCHEMA_1_14
elif want_version.matches((1, 4)):
schema = GET_RPS_SCHEMA_1_4
elif want_version.matches((1, 3)):
schema = GET_RPS_SCHEMA_1_3
util.validate_query_params(req, schema)
filters = {}
for attr in ['uuid', 'name', 'member_of']:
for attr in ['uuid', 'name', 'member_of', 'in_tree']:
if attr in req.GET:
value = req.GET[attr]
# special case member_of to always make its value a
@ -250,8 +287,8 @@ def list_resource_providers(req):
{'error': exc})
response = req.response
response.body = encodeutils.to_utf8(
jsonutils.dumps(_serialize_providers(req.environ, resource_providers)))
response.body = encodeutils.to_utf8(jsonutils.dumps(
_serialize_providers(req.environ, resource_providers, want_version)))
response.content_type = 'application/json'
return response
@ -271,9 +308,16 @@ def update_resource_provider(req):
resource_provider = rp_obj.ResourceProvider.get_by_uuid(
context, uuid)
data = util.extract_json(req.body, PUT_RESOURCE_PROVIDER_SCHEMA)
schema = PUT_RESOURCE_PROVIDER_SCHEMA
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
if want_version.matches((1, 14)):
schema = PUT_RP_SCHEMA_V1_14
resource_provider.name = data['name']
data = util.extract_json(req.body, schema)
for field in rp_obj.ResourceProvider.SETTABLE_FIELDS:
if field in data:
setattr(resource_provider, field, data[field])
try:
resource_provider.save()
@ -287,7 +331,7 @@ def update_resource_provider(req):
{'rp_uuid': uuid, 'error': exc})
req.response.body = encodeutils.to_utf8(jsonutils.dumps(
_serialize_provider(req.environ, resource_provider)))
_serialize_provider(req.environ, resource_provider, want_version)))
req.response.status = 200
req.response.content_type = 'application/json'
return req.response

View File

@ -54,6 +54,9 @@ VERSIONS = [
# as GET. The 'allocation_requests' format in GET
# /allocation_candidates is updated to be the same as well.
'1.13', # Adds POST /allocations to set allocations for multiple consumers
# as GET
'1.14', # Adds parent and root provider UUID on resource provider
# representation and 'in_tree' filter on GET /resource_providers
]

View File

@ -178,3 +178,22 @@ with the new `PUT` format.
Version 1.13 gives the ability to set or clear allocations for more than
one consumer uuid with a request to ``POST /allocations``.
1.14 Add nested resource providers
----------------------------------
The 1.14 version introduces the concept of nested resource providers. The
resource provider resource now contains two new attributes:
* ``parent_provider_uuid`` indicates the provider's direct parent, or null if
there is no parent. This attribute can be set in the call to ``POST
/resource_providers`` and ``PUT /resource_providers/{uuid}`` if the attribute
has not already been set to a non-NULL value (i.e. we do not support
"reparenting" a provider)
* ``root_provider_uuid`` indicates the UUID of the root resource provider in
the provider's tree. This is a read-only attribute
A new ``in_tree=<UUID>`` parameter is now available in the ``GET
/resource-providers`` API call. Supplying a UUID value for the ``in_tree``
parameter will cause all resource providers within the "provider tree" of the
provider matching ``<UUID>`` to be returned.

View File

@ -85,6 +85,8 @@ class APIFixture(fixture.GabbiFixture):
os.environ['INSTANCE_UUID'] = uuidutils.generate_uuid()
os.environ['MIGRATION_UUID'] = uuidutils.generate_uuid()
os.environ['CONSUMER_UUID'] = uuidutils.generate_uuid()
os.environ['PARENT_PROVIDER_UUID'] = uuidutils.generate_uuid()
os.environ['ALT_PARENT_PROVIDER_UUID'] = uuidutils.generate_uuid()
def stop_fixture(self):
self.api_db_fixture.cleanup()

View File

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

View File

@ -6,6 +6,7 @@ defaults:
request_headers:
x-auth-token: admin
accept: application/json
openstack-api-version: placement latest
tests:
@ -80,6 +81,7 @@ tests:
response_json_paths:
$.uuid: $ENVIRON['RP_UUID']
$.name: $ENVIRON['RP_NAME']
$.parent_provider_uuid: null
$.generation: 0
$.links[?rel = "self"].href: /resource_providers/$ENVIRON['RP_UUID']
$.links[?rel = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
@ -107,6 +109,7 @@ tests:
$.resource_providers[0].uuid: $ENVIRON['RP_UUID']
$.resource_providers[0].name: $ENVIRON['RP_NAME']
$.resource_providers[0].generation: 0
$.resource_providers[0].parent_provider_uuid: null
$.resource_providers[0].links[?rel = "self"].href: /resource_providers/$ENVIRON['RP_UUID']
$.resource_providers[0].links[?rel = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
$.resource_providers[0].links[?rel = "usages"].href: /resource_providers/$ENVIRON['RP_UUID']/usages
@ -174,7 +177,7 @@ tests:
$.resource_providers[0].links[?rel = "inventories"].href: /resource_providers/$ENVIRON['RP_UUID']/inventories
$.resource_providers[0].links[?rel = "usages"].href: /resource_providers/$ENVIRON['RP_UUID']/usages
- name: update a resource provider
- name: update a resource provider's name
PUT: /resource_providers/$RESPONSE['$.resource_providers[0].uuid']
request_headers:
content-type: application/json
@ -208,6 +211,174 @@ tests:
response_json_paths:
$.errors[0].title: Bad Request
# This section of tests validate nested resource provider relationships and
# constraints. We attempt to set the parent provider UUID for the primary
# resource provider to a UUID value of a provider we have not yet created and
# expect a failure. We then create that parent provider record and attempt to
# set the same parent provider UUID without also setting the root provider UUID
# to the same value, with an expected failure. Finally, we set the primary
# provider's root AND parent to the new provider UUID and verify success.
- name: test POST microversion limits nested providers
POST: /resource_providers
request_headers:
openstack-api-version: placement 1.13
content-type: application/json
data:
name: child
parent_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 400
response_strings:
- 'JSON does not validate'
- name: test PUT microversion limits nested providers
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
openstack-api-version: placement 1.13
content-type: application/json
data:
name: child
parent_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 400
response_strings:
- 'JSON does not validate'
- name: fail trying to set a root provider UUID
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
content-type: application/json
data:
root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 400
response_strings:
- 'JSON does not validate'
- name: fail trying to self-parent
POST: /resource_providers
request_headers:
content-type: application/json
data:
name: child
uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
parent_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
status: 400
response_strings:
- 'parent provider UUID cannot be same as UUID'
- name: update a parent provider UUID to non-existing provider
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
content-type: application/json
data:
name: parent
parent_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 400
response_strings:
- 'parent provider UUID does not exist'
- name: now create the parent provider
POST: /resource_providers
request_headers:
content-type: application/json
data:
name: parent
uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 201
- name: get provider with old microversion no root provider UUID field
GET: /resource_providers/$ENVIRON['PARENT_PROVIDER_UUID']
request_headers:
openstack-api-version: placement 1.13
content-type: application/json
response_json_paths:
$.`len`: 4
name: parent
status: 200
- name: get provider has root provider UUID field
GET: /resource_providers/$ENVIRON['PARENT_PROVIDER_UUID']
request_headers:
content-type: application/json
response_json_paths:
$.`len`: 6
name: parent
root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
parent_provider_uuid: null
status: 200
- name: update a parent
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
content-type: application/json
data:
name: child
parent_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 200
- name: get provider has new parent and root provider UUID field
GET: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
content-type: application/json
response_json_paths:
name: child
root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
parent_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
status: 200
- name: fail trying to un-parent
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
content-type: application/json
data:
name: child
parent_provider_uuid: null
status: 400
response_strings:
- 'un-parenting a provider is not currently allowed'
- name: list all resource providers in a tree that does not exist
GET: /resource_providers?in_tree=$ENVIRON['ALT_PARENT_PROVIDER_UUID']
response_json_paths:
$.resource_providers.`len`: 0
- name: list all resource providers in a tree with multiple providers in tree
GET: /resource_providers?in_tree=$ENVIRON['RP_UUID']
response_json_paths:
$.resource_providers.`len`: 2
# Verify that we have both the parent and child in the list
$.resource_providers[?uuid="$ENVIRON['PARENT_PROVIDER_UUID']"].root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
$.resource_providers[?uuid="$ENVIRON['RP_UUID']"].root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
- name: create a new parent provider
POST: /resource_providers
request_headers:
content-type: application/json
data:
name: altwparent
uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
status: 201
response_headers:
location: //resource_providers/[a-f0-9-]+/
response_forbidden_headers:
- content-type
- name: list all resource providers in a tree
GET: /resource_providers?in_tree=$ENVIRON['ALT_PARENT_PROVIDER_UUID']
response_json_paths:
$.resource_providers.`len`: 1
$.resource_providers[?uuid="$ENVIRON['ALT_PARENT_PROVIDER_UUID']"].root_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
- name: fail trying to re-parent to a different provider
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:
content-type: application/json
data:
name: child
parent_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
status: 400
response_strings:
- 're-parenting a provider is not currently allowed'
- name: create a new provider
POST: /resource_providers
request_headers:
@ -221,7 +392,7 @@ tests:
request_headers:
content-type: application/json
data:
name: new name
name: child
status: 409
response_json_paths:
$.errors[0].title: Conflict

View File

@ -1,4 +1,5 @@
{
"name": "NFS Share",
"uuid": "7d2590ae-fb85-4080-9306-058b4c915e3f"
"uuid": "7d2590ae-fb85-4080-9306-058b4c915e3f",
"parent_provider_uuid": "542df8ed-9be2-49b9-b4db-6d3183ff8ec8"
}

View File

@ -27,5 +27,7 @@
}
],
"name": "Ceph Storage Pool",
"uuid": "3b4005be-d64b-456f-ba36-0ffd02718868"
"uuid": "3b4005be-d64b-456f-ba36-0ffd02718868",
"parent_provider_uuid": "542df8ed-9be2-49b9-b4db-6d3183ff8ec8",
"root_provider_uuid": "542df8ed-9be2-49b9-b4db-6d3183ff8ec8"
}

View File

@ -29,7 +29,9 @@
"rel": "allocations"
}
],
"name": "vgr.localdomain"
"name": "vgr.localdomain",
"parent_provider_uuid": "542df8ed-9be2-49b9-b4db-6d3183ff8ec8",
"root_provider_uuid": "542df8ed-9be2-49b9-b4db-6d3183ff8ec8"
},
{
"generation": 2,
@ -60,7 +62,9 @@
"rel": "allocations"
}
],
"name": "pony1"
"name": "pony1",
"parent_provider_uuid": null,
"root_provider_uuid": "d0b381e9-8761-42de-8e6c-bba99a96d5f5"
}
]
}

View File

@ -58,6 +58,14 @@ resource_provider_name_query:
required: false
description: >
The name of a resource provider to filter the list.
resource_provider_tree_query:
type: string
in: query
required: false
description: >
A UUID of a resource provider. The returned resource providers will be in
the same "provider tree" as the specified provider.
min_version: 1.14
resource_provider_uuid_query:
<<: *resource_provider_uuid_path
in: query
@ -290,6 +298,34 @@ resource_provider_object:
required: true
description: >
A dictionary which contains the UUID of the resource provider.
resource_provider_parent_provider_uuid:
type: string
in: body
required: false
description: >
The UUID of the immediate parent of the resource provider.
min_version: 1.14
resource_provider_parent_provider_uuid_required:
type: string
in: body
required: true
description: >
The UUID of the immediate parent of the resource provider.
min_version: 1.14
resource_provider_root_provider_uuid:
type: string
in: body
required: false
description: >
Read-only UUID of the top-most provider in this provider tree.
min_version: 1.14
resource_provider_root_provider_uuid_required:
type: string
in: body
required: true
description: >
Read-only UUID of the top-most provider in this provider tree.
min_version: 1.14
resource_provider_usages:
type: object
in: body

View File

@ -34,7 +34,8 @@ Response
- uuid: resource_provider_uuid
- links: resource_provider_links
- name: resource_provider_name
- parent_provider_uuid: resource_provider_parent_provider_uuid_required
- root_provider_uuid: resource_provider_root_provider_uuid_required
Response Example
----------------
@ -63,6 +64,7 @@ Request
- uuid: resource_provider_uuid_path
- name: resource_provider_name
- parent_provider_uuid: resource_provider_parent_provider_uuid
Request example
---------------
@ -79,6 +81,8 @@ Response
- uuid: resource_provider_uuid
- links: resource_provider_links
- name: resource_provider_name
- parent_provider_uuid: resource_provider_parent_provider_uuid_required
- root_provider_uuid: resource_provider_root_provider_uuid_required
Response Example
----------------

View File

@ -28,6 +28,7 @@ of all filters are merged with a boolean `AND`.
- uuid: resource_provider_uuid_query
- member_of: member_of
- resources: resources_query
- in_tree: resource_provider_tree_query
Response
--------
@ -39,7 +40,8 @@ Response
- uuid: resource_provider_uuid
- links: resource_provider_links
- name: resource_provider_name
- parent_provider_uuid: resource_provider_parent_provider_uuid
- root_provider_uuid: resource_provider_root_provider_uuid
Response Example
----------------
@ -69,6 +71,8 @@ Request
- name: resource_provider_name
- uuid: resource_provider_uuid_opt
- parent_provider_uuid: resource_provider_parent_provider_uuid
- root_provider_uuid: resource_provider_root_provider_uuid
Request example
---------------

View File

@ -1 +1,4 @@
{"name": "Shared storage"}
{
"name": "Shared storage",
"parent_provider_uuid": "542df8ed-9be2-49b9-b4db-6d3183ff8ec8"
}

View File

@ -0,0 +1,13 @@
---
features:
- New placement REST API microversion 1.14 is added to support nested
resource providers. Users of the placement REST API can now pass a
``in_tree=<UUID>`` parameter to the ``GET /resource_providers`` REST API
call. This will trigger the placement service to return all resource
provider records within the "provider tree" of the resource provider with
the supplied UUID value. The resource provider representation now includes
a ``parent_provider_uuid`` value that indicates the UUID of the immediate
parent resource provider, or ``null`` if the provider has no parent. For
convenience, the resource provider resource also contains a
``root_provider_uuid`` field that is populated with the UUID of the
top-most resource provider in the provider tree.