Add aggregate API
This addes the aggregate APIs and docs Partially Implements: bp node-aggregate Change-Id: Iaa8d1de301f09360f7aa36e3a8fe5829e65b55c9
This commit is contained in:
parent
9c76ae4467
commit
27f3b1832a
|
@ -0,0 +1,182 @@
|
|||
.. -*- rst -*-
|
||||
|
||||
============
|
||||
Aggregates
|
||||
============
|
||||
|
||||
Creates and manages node aggregates. An aggregate assigns metadata to
|
||||
groups of compute nodes. Aggregates are only visible to the cloud provider.
|
||||
|
||||
List Aggregates
|
||||
===============
|
||||
|
||||
.. rest_method:: GET /aggregates
|
||||
|
||||
Lists all aggregates.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: unauthorized(401), forbidden(403)
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- aggregates: aggregates
|
||||
- name: aggregate_name
|
||||
- links: links
|
||||
- metadata: aggregate_metadata
|
||||
- uuid: aggregate_uuid
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
**Example List aggregates: JSON response**
|
||||
|
||||
.. literalinclude:: samples/aggregates/aggregates-list-resp.json
|
||||
:language: javascript
|
||||
|
||||
Create Aggregate
|
||||
================
|
||||
|
||||
.. rest_method:: POST /aggregates
|
||||
|
||||
Creates an aggregate.
|
||||
|
||||
Normal response codes: 201
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
conflict(409)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name: aggregate_name
|
||||
- metadata: aggregate_metadata
|
||||
|
||||
**Example Create Aggregatei: JSON request**
|
||||
|
||||
.. literalinclude:: samples/aggregates/aggregate-create-post-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name: aggregate_name
|
||||
- links: links
|
||||
- metadata: aggregate_metadata
|
||||
- uuid: aggregate_uuid
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
**Example Create Aggregate: JSON response**
|
||||
|
||||
.. literalinclude:: samples/aggregates/aggregate-create-post-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Aggregate
|
||||
================
|
||||
|
||||
.. rest_method:: PATCH /aggregates/{aggregate_uuid}
|
||||
|
||||
Updates an aggregate.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
conflict(409)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
The BODY of the PATCH request must be a JSON PATCH document, adhering to
|
||||
`RFC 6902 <https://tools.ietf.org/html/rfc6902>`_.
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- aggregate_uuid: aggregate_uuid_path
|
||||
|
||||
**Example Update Aggregate: JSON request**
|
||||
|
||||
.. literalinclude:: samples/aggregates/aggregate-update-put-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name: aggregate_name
|
||||
- links: links
|
||||
- metadata: aggregate_metadata
|
||||
- uuid: aggregate_uuid
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
**Example Update Aggregate: JSON response**
|
||||
|
||||
.. literalinclude:: samples/aggregates/aggregate-update-put-resp.json
|
||||
:language: javascript
|
||||
|
||||
Show Aggregate Details
|
||||
======================
|
||||
|
||||
.. rest_method:: GET /aggregates/{aggregate_uuid}
|
||||
|
||||
Shows details for an aggregate.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- aggregate_uuid: aggregate_uuid_path
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name: aggregate_name
|
||||
- links: links
|
||||
- metadata: aggregate_metadata
|
||||
- uuid: aggregate_uuid
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
**Example Show Aggregate Details**
|
||||
|
||||
.. literalinclude:: samples/aggregates/aggregate-get-resp.json
|
||||
:language: javascript
|
||||
|
||||
|
||||
Delete Aggregate
|
||||
================
|
||||
|
||||
.. rest_method:: DELETE /aggregates/{aggregate_uuid}
|
||||
|
||||
Deletes an aggregate.
|
||||
|
||||
Normal response codes: 204
|
||||
|
||||
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- aggregate_uuid: aggregate_uuid_path
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE.
|
|
@ -14,3 +14,4 @@ Baremetal Compute API V1 (CURRENT)
|
|||
.. include:: flavors.inc
|
||||
.. include:: flavor_access.inc
|
||||
.. include:: availability_zones.inc
|
||||
.. include:: aggregates.inc
|
||||
|
|
|
@ -15,6 +15,12 @@ address_path:
|
|||
in: path
|
||||
required: true
|
||||
type: string
|
||||
aggregate_uuid_path:
|
||||
description: |
|
||||
The UUID of the aggregate.
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
api_version:
|
||||
in: path
|
||||
required: true
|
||||
|
@ -124,6 +130,30 @@ address:
|
|||
in: body
|
||||
required: true
|
||||
type: string
|
||||
aggregate_metadata:
|
||||
description: |
|
||||
Metadata key and value pairs associate with the aggregate.
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
aggregate_name:
|
||||
description: |
|
||||
The name of the node aggregate.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
aggregate_uuid:
|
||||
description: |
|
||||
The UUID of the node aggregate.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
aggregates:
|
||||
description: |
|
||||
The list of existing aggregates.
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
availability_zone:
|
||||
description: |
|
||||
The availability zone from which to launch the server. When you provision resources,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "test_aggregate",
|
||||
"metadata": {
|
||||
"k1": "v1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "test_aggregate",
|
||||
"uuid": "7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/v1/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"k1": "v1"
|
||||
},
|
||||
"created_at": "2016-09-27T02:37:21.966342+00:00",
|
||||
"updated_at": null
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "test_aggregate",
|
||||
"uuid": "7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/v1/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"k1": "v1"
|
||||
},
|
||||
"created_at": "2016-09-27T02:37:21.966342+00:00",
|
||||
"updated_at": null
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/metadata/k1",
|
||||
"value": "v2"
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/metadata/k2",
|
||||
"value": "v2"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "test_aggregate",
|
||||
"uuid": "7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/v1/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"k1": "v2",
|
||||
"k2": "v2"
|
||||
},
|
||||
"created_at": "2016-09-27T02:37:21.966342+00:00",
|
||||
"updated_at": null
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"aggregates": [
|
||||
{
|
||||
"name": "test_aggregate1",
|
||||
"uuid": "7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/v1/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"k1": "v1"
|
||||
},
|
||||
"created_at": "2016-09-27T02:37:21.966342+00:00",
|
||||
"updated_at": null
|
||||
},
|
||||
{
|
||||
"name": "test_aggregate2",
|
||||
"uuid": "7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/v1/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://10.3.150.17:6688/aggregates/7de2859d-ec6d-42c7-bb86-9d630ba5ac94",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"k2": "v2"
|
||||
},
|
||||
"created_at": "2016-09-27T02:37:21.966342+00:00",
|
||||
"updated_at": null
|
||||
}
|
||||
]
|
||||
}
|
|
@ -25,6 +25,7 @@ from wsme import types as wtypes
|
|||
|
||||
from mogan.api.controllers import base
|
||||
from mogan.api.controllers import link
|
||||
from mogan.api.controllers.v1 import aggregates
|
||||
from mogan.api.controllers.v1 import availability_zone
|
||||
from mogan.api.controllers.v1 import flavors
|
||||
from mogan.api.controllers.v1 import keypairs
|
||||
|
@ -50,6 +51,9 @@ class V1(base.APIBase):
|
|||
keypairs = [link.Link]
|
||||
"""Links to the keypairs resource"""
|
||||
|
||||
aggregates = [link.Link]
|
||||
"""Links to the aggregates resource"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
v1 = V1()
|
||||
|
@ -84,6 +88,14 @@ class V1(base.APIBase):
|
|||
'keypairs', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.aggregates = [link.Link.make_link('self',
|
||||
pecan.request.public_url,
|
||||
'aggregates', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.public_url,
|
||||
'aggregates', '',
|
||||
bookmark=True)
|
||||
]
|
||||
return v1
|
||||
|
||||
|
||||
|
@ -94,6 +106,7 @@ class Controller(rest.RestController):
|
|||
servers = servers.ServerController()
|
||||
availability_zones = availability_zone.AvailabilityZoneController()
|
||||
keypairs = keypairs.KeyPairController()
|
||||
aggregates = aggregates.AggregateController()
|
||||
|
||||
@expose.expose(V1)
|
||||
def get(self):
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
# Copyright 2017 Huawei Technologies Co.,LTD.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from six.moves import http_client
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from mogan.api.controllers import base
|
||||
from mogan.api.controllers import link
|
||||
from mogan.api.controllers.v1.schemas import aggregate as agg_schema
|
||||
from mogan.api.controllers.v1 import types
|
||||
from mogan.api.controllers.v1 import utils as api_utils
|
||||
from mogan.api import expose
|
||||
from mogan.api import validation
|
||||
from mogan.common import exception
|
||||
from mogan.common import policy
|
||||
from mogan import objects
|
||||
|
||||
|
||||
class Aggregate(base.APIBase):
|
||||
"""API representation of an aggregate.
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of
|
||||
an aggregate.
|
||||
"""
|
||||
uuid = types.uuid
|
||||
"""The UUID of the aggregate"""
|
||||
|
||||
name = wtypes.text
|
||||
"""The name of the aggregate"""
|
||||
|
||||
metadata = {wtypes.text: types.jsontype}
|
||||
"""The meta data of the aggregate"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
for field in objects.Aggregate.fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, field):
|
||||
continue
|
||||
self.fields.append(field)
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, db_aggregate):
|
||||
aggregate = Aggregate(**db_aggregate.as_dict())
|
||||
url = pecan.request.public_url
|
||||
aggregate.links = [link.Link.make_link('self', url,
|
||||
'aggregates',
|
||||
aggregate.uuid),
|
||||
link.Link.make_link('bookmark', url,
|
||||
'aggregates',
|
||||
aggregate.uuid,
|
||||
bookmark=True)
|
||||
]
|
||||
|
||||
return aggregate
|
||||
|
||||
|
||||
class AggregatePatchType(types.JsonPatchType):
|
||||
|
||||
_api_base = Aggregate
|
||||
|
||||
|
||||
class AggregateCollection(base.APIBase):
|
||||
"""API representation of a collection of aggregates."""
|
||||
|
||||
aggregates = [Aggregate]
|
||||
"""A list containing Aggregate objects"""
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(aggregates, url=None, **kwargs):
|
||||
collection = AggregateCollection()
|
||||
collection.aggregates = [Aggregate.convert_with_links(aggregate)
|
||||
for aggregate in aggregates]
|
||||
return collection
|
||||
|
||||
|
||||
class AggregateController(rest.RestController):
|
||||
"""REST controller for Aggregates."""
|
||||
|
||||
@policy.authorize_wsgi("mogan:aggregate", "get_all")
|
||||
@expose.expose(AggregateCollection)
|
||||
def get_all(self):
|
||||
"""Retrieve a list of aggregates."""
|
||||
|
||||
aggregates = objects.AggregateList.get_all(pecan.request.context)
|
||||
return AggregateCollection.convert_with_links(aggregates)
|
||||
|
||||
@policy.authorize_wsgi("mogan:aggregate", "get_one")
|
||||
@expose.expose(Aggregate, types.uuid)
|
||||
def get_one(self, aggregate_uuid):
|
||||
"""Retrieve information about the given aggregate.
|
||||
|
||||
:param aggregate_uuid: UUID of an aggregate.
|
||||
"""
|
||||
db_aggregate = objects.Aggregate.get(pecan.request.context,
|
||||
aggregate_uuid)
|
||||
return Aggregate.convert_with_links(db_aggregate)
|
||||
|
||||
@policy.authorize_wsgi("mogan:aggregate", "create")
|
||||
@expose.expose(Aggregate, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
def post(self, aggregate):
|
||||
"""Create an new aggregate.
|
||||
|
||||
:param aggregate: an aggregate within the request body.
|
||||
"""
|
||||
validation.check_schema(aggregate, agg_schema.create_aggregate)
|
||||
new_aggregate = objects.Aggregate(pecan.request.context, **aggregate)
|
||||
new_aggregate.create()
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('aggregates',
|
||||
new_aggregate.uuid)
|
||||
return Aggregate.convert_with_links(new_aggregate)
|
||||
|
||||
@policy.authorize_wsgi("mogan:aggregate", "update")
|
||||
@wsme.validate(types.uuid, [AggregatePatchType])
|
||||
@expose.expose(Aggregate, types.uuid, body=[AggregatePatchType])
|
||||
def patch(self, aggregate_uuid, patch):
|
||||
"""Update an aggregate.
|
||||
|
||||
:param aggregate_uuid: the uuid of the aggregate to be updated.
|
||||
:param aggregate: a json PATCH document to apply to this aggregate.
|
||||
"""
|
||||
|
||||
db_aggregate = objects.Aggregate.get(pecan.request.context,
|
||||
aggregate_uuid)
|
||||
|
||||
try:
|
||||
aggregate = Aggregate(
|
||||
**api_utils.apply_jsonpatch(db_aggregate.as_dict(), patch))
|
||||
|
||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
# Update only the fields that have changed
|
||||
for field in objects.Aggregate.fields:
|
||||
try:
|
||||
patch_val = getattr(aggregate, field)
|
||||
except AttributeError:
|
||||
# Ignore fields that aren't exposed in the API
|
||||
continue
|
||||
if patch_val == wtypes.Unset:
|
||||
patch_val = None
|
||||
if db_aggregate[field] != patch_val:
|
||||
db_aggregate[field] = patch_val
|
||||
|
||||
db_aggregate.save()
|
||||
|
||||
return Aggregate.convert_with_links(db_aggregate)
|
||||
|
||||
@policy.authorize_wsgi("mogan:aggregate", "delete")
|
||||
@expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT)
|
||||
def delete(self, aggregate_uuid):
|
||||
"""Delete an aggregate.
|
||||
|
||||
:param aggregate_uuid: UUID of an aggregate.
|
||||
"""
|
||||
db_aggregate = objects.Aggregate.get(pecan.request.context,
|
||||
aggregate_uuid)
|
||||
db_aggregate.destroy()
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2017 Huawei Technologies Co.,LTD.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from mogan.api.validation import parameter_types
|
||||
|
||||
|
||||
create_aggregate = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
'name': parameter_types.name,
|
||||
'metadata': parameter_types.metadata,
|
||||
},
|
||||
'required': ['name'],
|
||||
'additionalProperties': False,
|
||||
}
|
|
@ -144,6 +144,21 @@ server_policies = [
|
|||
policy.RuleDefault('mogan:server:detach_interface',
|
||||
'rule:default',
|
||||
description='Detach a network interface'),
|
||||
policy.RuleDefault('mogan:aggregate:create',
|
||||
'rule:admin_api',
|
||||
description='Create aggregate records'),
|
||||
policy.RuleDefault('mogan:aggregate:update',
|
||||
'rule:admin_api',
|
||||
description='Update aggregate records'),
|
||||
policy.RuleDefault('mogan:aggregate:delete',
|
||||
'rule:admin_api',
|
||||
description='Delete aggregate records'),
|
||||
policy.RuleDefault('mogan:aggregate:get_all',
|
||||
'rule:admin_api',
|
||||
description='Retrieve all aggregate records'),
|
||||
policy.RuleDefault('mogan:aggregate:get_one',
|
||||
'rule:admin_api',
|
||||
description='Show aggregate details'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from mogan.objects import base
|
|||
from mogan.objects import fields as object_fields
|
||||
|
||||
|
||||
def _get_nodes_from_cache(context, aggregate_id):
|
||||
def _get_nodes_from_cache(aggregate_id):
|
||||
return []
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue