Adding a v2 API to CloudKitty¶
https://storyboard.openstack.org/#!/story/2004208
CloudKitty has gone through a lot of changes recently (v2 storage, support for Prometheus, standalone mode…). This requires big changes on the API: given that the internal data format is evolving, we need to expose the data differently, in order to support the new features of the v2 storage: pagination, grouping, filtering…
Problem Description¶
Several distinct problems can be identified:
CloudKitty’s data needs to be exposed with more granularity. A non-exhaustive list of currently missing features:
Pagination
Filtering
Grouping
…
For its current API, CloudKitty uses WSME + pecan. Code written using these tools can be quite difficult to understand for new contributors. In addition to that, pecan’s development seems to have slowed down: https://github.com/pecan/pecan/commits/master .
Semantics. A lot of endpoints are still using OpenStack-related terms (tenant, service…).
Proposed Change¶
In consequence to the previously evoked reasons, the CloudKitty development team proposes to implement a new API, which will be based on Flask/Werkzeug and Flask-RESTful. These frameworks have been chosen for the following reasons:
Flask is a well-known and easy-to-use framework. This will make contributions easier for new contributors. It is solid and used by other OpenStack projects, like Keystone.
Flask-RESTful makes code easier to read and routing is also eased. The idea would be to have each important endpoint (for example
/rating/hashmap
) mapped to a blueprint, and each sub-endpoint (rating/hashmap/services
) mapped to a Flask-RESTful resource. This would make adding new endpoints easy, and leaves space for the v2 API to evolve. Below a working code sample, with voluptuous input and output validation:@api_utils.add_output_schema(GET_OUTPUT_EXAMPLE_SCHEMA) def get(self): policy.authorize(flask.request.context, 'example:get_example', {}) return {} @api_utils.add_input_schema(POST_INPUT_EXAMPLE_SCHEMA) def post(self, fruit='banana'): policy.authorize(flask.request.context, 'example:submit_fruit', {}) if fruit not in ['banana', 'strawberry']: raise http_exceptions.Forbidden( 'You submitted a forbidden fruit', ) return { 'msg': 'Your fruit is a ' + fruit }
Flask-RESTful forces (or at least encourages) contributors to have a restful approach to the API, whereas Flask leaves them too much freedom.
This new API will make it easy to add a new endpoint, and will be a container to new features. Any new endpoint will be added to this API.
This v2 API will only be compatible with the v2 storage, allowing new endpoints to completely exploit the capacities of the v2 storage, without having to handle backward compatibility. The v1 API is completely compatible with the v2 storage, so data will still be accessible through the v1 API, compatibilty with existing scripts etc, will be kept. The v2 API will be disabled when a v1 storage backend is used.
In order to limit the workload, not all v1 endpoints will be migrated at once.
The proposition will be to have one WSGI app per API version, and to
distinguish between them through routing. This could be done with Werkzeug’s
DispatcherMiddleware
: http://werkzeug.pocoo.org/docs/0.14/middlewares/#werkzeug.wsgi.DispatcherMiddleware.
In case a v1 storage backend is used, the v2 API will simply not be loaded.
V1 endpoints will be migrated one after another. Once the entire v1 API has
been migrated, the v2 API will be marked as CURRENT
, and the v1 API will
be marked as DEPRECATED
. Until that point is reached, v1 will be
CURRENT
and v2 will be EXPERIMENTAL
.
The proposed workflow for adding an endpoint is the following:
Write a spec detailing what the endpoint does, its impact on the client, the dashboard and the tempest plugin.
Once the spec is reviewed and merged, create a storyboard story linking to your spec and containing one task for each of the following points:
Adding the endpoint to the API
Adding support for the endpoint to the client
Adding tests for this endpoint to the tempest plugin
(Optional) Adding support for the endpoint to the dashboard
Each of these tasks must be the subject of a new patch. Each task which is not marked as optional is mandatory.
The patches adding client/dashboard support as well as the patch adding tests to the tempest plugin must depend on the patch adding the endpoint to the API.
Once the v2 API is stable and marked as the current API, it will be
microversioned in order to have an indicator of its capabilities. A
microversion increase will be indicating a new feature (new endpoint).
However, API endpoints in OpenStack’s service catalog will not be
microversioned. The exact version of the API will be available at the API root
(/
).
Versioning will be done using semantic versioning (https://semver.org/).
Once all v1 endpoint have been migrated, and the v2 API is considered stable,
its version will be 2.0
. Before that version, each version will be suffixed
with -beta.version
.
Example: v2.0-beta.1
-> … -> v2.0-beta.x
-> v2.0
.
Alternatives¶
Migrate the whole v1 API to Flask and extend existing endpoints in order to support the features listed above: This would imply to migrate the whole API codebase at once, which can’t be done. Also, this wouldn’t address the semantics problem.
Data model impact¶
This particular change (creating a v2 WSGI app and changing the way requests are routed) has no impact on the datamodel. Each v2 API endpoint will be the subject of a new spec. Potential data model impact will be detailed in these upcoming specs.
REST API impact¶
v1 API will go from
EXPERIMENTAL
toCURRENT
current state.A v2 API, marked as
EXPERIMENTAL
, will be available.A new route (
/v2
) will be available. It will contain a placeholder indicating that this will be the prefix for all upcoming v2 endpoints.
Security impact¶
None
Notifications Impact¶
None
Other end user impact¶
None. However, python-cloudkittyclient
will need to be compatible with the
new v2 endpoints.
Performance Impact¶
Pagination will allow to lower the load on the network.
Other deployer impact¶
None
Developer impact¶
For developers, adding a new enpoint should be way easier.
A dependency on Flask and Flask-RESTful will be added.
Implementation¶
Assignee(s)¶
- Primary assignee:
lukapeschke/peschk_l
- Gerrit topic:
cloudkitty-v2-api
Work Items¶
Mark the v1 API as
CURRENT
Route the API with Werkzeug instead of Pecan
Add an Example endpoint showing how to implement an endpoint, and how to document it. This example endpoint should be removed as soon as the first real endpoint is implemented.
Update the documentation: Update the developer documentation with an explanation about how to add an enpoint and generate the documentation of the v2 API.
Dependencies¶
Flask
Flask-RESTful
os-api-ref (for API reference generation)
Testing¶
This particular feature will be tested with unit tests only (gabbi and unittest). Endpoints to come will also add some tests to the tempest plugin.
Documentation Impact¶
The developer documentation will be updated to detail how requests are
routed and how to add a new endpoint. The user documentation will also be
updated in order to include the v2 API reference. The API reference will be
generated with os-api-ref
.
References¶
os-api-ref documentation: https://docs.openstack.org/os-api-ref/latest/
API WG wiki: https://wiki.openstack.org/wiki/API_Special_Interest_Group