This fixes all build warnings in the docs, and makes them build with -W (consider warnings as errors). Work items: * Remove the useless "METRICS_CONF" global variable in the monasca collector. It was causing the "load_conf()" function to fail, as the /etc/cloudkitty/metrics.yml file couldn't be found. * Change the docstring indentation in cloudkitty/rating/hash/controllers/mapping.py. This was the cause for the "doc/source/api-reference/v1/rating/hashmap.rst:12: WARNING: Field list ends without a blank line; unexpected unindent." message. * Added a cloudkitty.conf sample to the admin documentation. * Deleted unused files. * Fixed the policy.yaml sample include path. * Sorted the v2 API parameters alphabetically. * Ignored the "wsmeext.sphinxext: directive 'autoattribute' is already registered" warning * Added :noindex: directives to functions documented twice. * Made sphinx always build with -W. Change-Id: I5f1ab04e18920f3352c2807878cad327692a225f
9.6 KiB
Tutorial: creating an API endpoint
This section of the document details how to create an endpoint for CloudKitty's v2 API. The v1 API is frozen, no endpoint should be added.
Setting up the layout for a new resource
In this section, we will create an example
endpoint.
Create the following files and subdirectories in
cloudkitty/api/v2/
:
cloudkitty/api/v2/
└── example
├── example.py
└── __init__.py
Creating a custom resource
Each v2 API endpoint is based on a Flask Blueprint and one
Flask-RESTful resource per sub-endpoint. This allows to have a logical
grouping of the resources. Let's take the /rating/hashmap
route as an example. Each of the hashmap module's resources should be a
Flask-RESTful resource (eg. /rating/hashmap/service
,
/rating/hashmap/field
etc...).
Note
There should be a distinction between endpoints refering to a single resource and to several ones. For example, if you want an endpoint allowing to list resources of some kind, you should implement the following:
- A
MyResource
resource with support forGET
,POST
andPUT
HTTP methods on the/myresource/<uuid:>
route. - A
MyResourceList
resource with support for theGET
HTTP method on the/myresource
route. - A blueprint containing these resources.
Basic resource
We'll create an /example/
endpoint, used to manipulate
fruits. We'll create an Example
resource, supporting
GET
and POST
HTTP methods. First of all, we'll
create a class with get
and post
methods in
cloudkitty/api/v2/example/example.py
:
from cloudkitty.api.v2 import base
class Example(base.BaseResource):
def get(self):
pass
def post(self):
pass
Validating a method's parameters and output
A GET
request on our resource will simply return
{"message": "This is an example endpoint"}. The
add_output_schema
decorator adds voluptuous validation to a
method's output. This allows to set defaults.
cloudkitty.api.v2.utils.add_output_schema
Let's update our get
method in order to use this
decorator:
import voluptuous
from cloudkitty.api.v2 import base
from cloudkitty import validation_utils
class Example(base.BaseResource):
@api_utils.add_output_schema({
voluptuous.Required('message',
='This is an example endpoint',
default
): validation_utils.get_string_type(),
})def get(self):
return {}
Note
In this snippet, get_string_type
returns
basestring
in python2 and str
in python3.
$ curl 'http://cloudkitty-api:8889/v2/example'
{"message": "This is an example endpoint"}
It is now time to implement the post
method. This
function will take a parameter. In order to validate it, we'll use the
add_input_schema
decorator:
cloudkitty.api.v2.utils.add_input_schema
Arguments validated by the input schema are passed as named arguments to the decorated function. Let's implement the post method. We'll use Werkzeug exceptions for HTTP return codes.
@api_utils.add_input_schema('body', {
'fruit'): validation_utils.get_string_type(),
voluptuous.Required(
})def post(self, fruit=None):
'example:submit_fruit', {})
policy.authorize(flask.request.context, if not fruit:
raise http_exceptions.BadRequest(
'You must submit a fruit',
)if fruit not in ['banana', 'strawberry']:
raise http_exceptions.Forbidden(
'You submitted a forbidden fruit',
)return {
'message': 'Your fruit is a ' + fruit,
}
Here, fruit
is expected to be found in the request
body:
$ curl -X POST -H 'Content-Type: application/json' 'http://cloudkitty-api:8889/v2/example' -d '{"fruit": "banana"}'
{"message": "Your fruit is a banana"}
In order to retrieve fruit
from the query, the function
should have been decorated like this:
@api_utils.add_input_schema('query', {
'fruit'): api_utils.SingleQueryParam(str),
voluptuous.Required(
})def post(self, fruit=None):
Note that a SingleQueryParam
is used here: given that
query parameters can be specified several times (eg
xxx?groupby=a&groupby=b
), Flask provides query
parameters as lists. The SingleQueryParam
helper checks
that a parameter is provided only once, and returns it.
cloudkitty.api.v2.utils.SingleQueryParam
Warning
SingleQueryParam
uses voluptuous.Coerce
internally for type checking. Thus,
validation_utils.get_string_type
cannot be used as
basestring
can't be instantiated.
Authorising methods
The Example
resource is still missing some
authorisations. We'll create a policy per method, configurable via the
policy.yaml
file. Create a
cloudkitty/common/policies/v2/example.py
file with the
following content:
from oslo_policy import policy
from cloudkitty.common.policies import base
= [
example_policies
policy.DocumentedRuleDefault(='example:get_example',
name=base.UNPROTECTED,
check_str='Get an example message',
description=[{'path': '/v2/example',
operations'method': 'GET'}]),
policy.DocumentedRuleDefault(='example:submit_fruit',
name=base.UNPROTECTED,
check_str='Submit a fruit',
description=[{'path': '/v2/example',
operations'method': 'POST'}]),
]
def list_rules():
return example_policies
Add the following lines to
cloudkitty/common/policies/__init__.py
:
# [...]
from cloudkitty.common.policies.v2 import example as v2_example
def list_rules():
return itertools.chain(
base.list_rules(),# [...]
v2_example.list_rules(), )
This registers two documented policies, get_example
and
submit_fruit
. They are unprotected by default, which means
that everybody can access them. However, they can be overriden in
policy.yaml
. Call them the following way:
# [...]
import flask
from cloudkitty.common import policy
from cloudkitty.api.v2 import base
class Example(base.BaseResource):
# [...]
def get(self):
'example:get_example', {})
policy.authorize(flask.request.context, return {}
# [...]
def post(self):
'example:submit_fruit', {})
policy.authorize(flask.request.context, # [...]
Loading drivers
Most of the time, resources need to load some drivers (storage, SQL...). As the instantiation of these drivers can take some time, this should be done only once.
Some drivers (like the storage driver) are loaded in
BaseResource
and are thus available to all resources.
Resources requiring some additional drivers should implement the
reload
function:
class BaseResource(flask_restful.Resource):
@classmethod
def reload(cls):
"""Reloads all required drivers"""
Here's an example taken from
cloudkitty.api.v2.scope.state.ScopeState
:
@classmethod
def reload(cls):
super(ScopeState, cls).reload()
= messaging.get_client()
cls._client = storage_state.StateManager() cls._storage_state
Registering resources
Each endpoint should provide an init
method taking a
Flask app as only parameter. This method should call
do_init
:
cloudkitty.api.v2.utils.do_init
Add the following to
cloudkitty/api/v2/example/__init__.py
:
from cloudkitty.api.v2 import utils as api_utils
def init(app):
'example', [
api_utils.do_init(app,
{'module': __name__ + '.' + 'example',
'resource_class': 'Example',
'url': '',
},
])return app
Here, we call do_init
with the flask app passed as
parameter, a blueprint name, and a list of resources. The blueprint name
will prefix the URLs of all resources. Each resource is represented by a
dict with the following attributes:
module
: name of the python module containing the resource classresource_class
: class of the resourceurl
: url suffix
In our case, the Example
resource will be served at
/example
(blueprint name + URL suffix).
Note
In case you need to add a resource to an existing endpoint, just add it to the list.
Warning
If you created a new module, you'll have to add it to
API_MODULES
in
cloudkitty/api/v2/__init__.py
:
= [
API_MODULES 'cloudkitty.api.v2.example',
]
Documenting your endpoint
The v2 API is documented with os_api_ref .
Each v2 API endpoint must be documented in
doc/source/api-reference/v2/<endpoint_name>/
.