Improved documentation (docstrings and sphinx)
Added docstrings to every controllers and types. Added samples for every types. Improved CloudKitty's developper documentation. Change-Id: Idcd25777f67c61c096fbc52962bc173a86d614ba
This commit is contained in:
@@ -28,50 +28,111 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APILink(wtypes.Base):
|
||||
"""API link description.
|
||||
|
||||
"""
|
||||
|
||||
type = wtypes.text
|
||||
"""Type of link."""
|
||||
|
||||
rel = wtypes.text
|
||||
"""Relationship with this link."""
|
||||
|
||||
href = wtypes.text
|
||||
"""URL of the link."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
version = 'v1'
|
||||
sample = cls(
|
||||
rel='self',
|
||||
type='text/html',
|
||||
href='http://127.0.0.1:8888/{id}'.format(
|
||||
id=version))
|
||||
return sample
|
||||
|
||||
|
||||
class APIMediaType(wtypes.Base):
|
||||
"""Media type description.
|
||||
|
||||
"""
|
||||
|
||||
base = wtypes.text
|
||||
"""Base type of this media type."""
|
||||
|
||||
type = wtypes.text
|
||||
"""Type of this media type."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls(
|
||||
base='application/json',
|
||||
type='application/vnd.openstack.cloudkitty-v1+json')
|
||||
return sample
|
||||
|
||||
|
||||
VERSION_STATUS = wtypes.Enum(wtypes.text, 'EXPERIMENTAL', 'STABLE')
|
||||
|
||||
|
||||
class APIVersion(wtypes.Base):
|
||||
"""API Version description.
|
||||
|
||||
"""
|
||||
|
||||
id = wtypes.text
|
||||
"""ID of the version."""
|
||||
|
||||
status = wtypes.text
|
||||
status = VERSION_STATUS
|
||||
"""Status of the version."""
|
||||
|
||||
updated = wtypes.text
|
||||
"Last update in iso8601 format."
|
||||
|
||||
links = [APILink]
|
||||
"""List of links to API resources."""
|
||||
|
||||
media_types = [APIMediaType]
|
||||
"""Types accepted by this API."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
version = 'v1'
|
||||
updated = '2014-08-11T16:00:00Z'
|
||||
links = [APILink.sample()]
|
||||
media_types = [APIMediaType.sample()]
|
||||
sample = cls(id=version,
|
||||
status='STABLE',
|
||||
updated=updated,
|
||||
links=links,
|
||||
media_types=media_types)
|
||||
return sample
|
||||
|
||||
|
||||
class RootController(rest.RestController):
|
||||
"""Root REST Controller exposing versions of the API.
|
||||
|
||||
"""
|
||||
|
||||
v1 = v1.V1Controller()
|
||||
|
||||
@wsme_pecan.wsexpose([APIVersion])
|
||||
def get(self):
|
||||
"""Return the version list
|
||||
|
||||
"""
|
||||
# TODO(sheeprine): Maybe we should store all the API version
|
||||
# informations in every API modules
|
||||
ver1 = APIVersion(
|
||||
id='v1',
|
||||
status='EXPERIMENTAL',
|
||||
updated='2014-06-02T00:00:00Z',
|
||||
updated='2014-08-11T16:00:00Z',
|
||||
links=[
|
||||
APILink(
|
||||
rel='self',
|
||||
href='{scheme}://{host}/v1'.format(
|
||||
href='{scheme}://{host}:{port}/v1'.format(
|
||||
scheme=pecan.request.scheme,
|
||||
host=pecan.request.host
|
||||
host=pecan.request.host,
|
||||
port=pecan.request.port
|
||||
)
|
||||
)
|
||||
],
|
||||
|
||||
@@ -33,14 +33,20 @@ CLOUDKITTY_SERVICES = wtypes.Enum(wtypes.text,
|
||||
|
||||
|
||||
class ResourceDescriptor(wtypes.Base):
|
||||
"""Type describing a resource in CloudKitty.
|
||||
|
||||
"""
|
||||
|
||||
service = CLOUDKITTY_SERVICES
|
||||
"""Name of the service."""
|
||||
|
||||
# FIXME(sheeprine): values should be dynamic
|
||||
# Testing with ironic dynamic type
|
||||
desc = {wtypes.text: cktypes.MultiType(wtypes.text, int, float, dict)}
|
||||
"""Description of the resources parameters."""
|
||||
|
||||
volume = int
|
||||
"""Number of resources."""
|
||||
|
||||
def to_json(self):
|
||||
res_dict = {}
|
||||
@@ -50,8 +56,20 @@ class ResourceDescriptor(wtypes.Base):
|
||||
}]
|
||||
return res_dict
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls(service='compute',
|
||||
desc={
|
||||
'image_id': 'a41fba37-2429-4f15-aa00-b5bc4bf557bf'
|
||||
},
|
||||
volume=1)
|
||||
return sample
|
||||
|
||||
|
||||
class ModulesController(rest.RestController):
|
||||
"""REST Controller managing billing modules.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.extensions = extension.ExtensionManager(
|
||||
@@ -72,6 +90,10 @@ class ModulesController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose([wtypes.text])
|
||||
def get(self):
|
||||
"""Return the list of loaded modules.
|
||||
|
||||
:return: Name of every loaded modules.
|
||||
"""
|
||||
return [ext for ext in self.extensions.names()]
|
||||
|
||||
|
||||
@@ -85,6 +107,12 @@ class BillingController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(float, body=[ResourceDescriptor])
|
||||
def quote(self, res_data):
|
||||
"""Get an instant quote based on multiple resource descriptions.
|
||||
|
||||
:param res_data: List of resource descriptions.
|
||||
:return: Total price for these descriptions.
|
||||
"""
|
||||
|
||||
# TODO(sheeprine): Send RPC request for quote
|
||||
from cloudkitty import extension_manager
|
||||
b_processors = {}
|
||||
@@ -115,6 +143,9 @@ class BillingController(rest.RestController):
|
||||
|
||||
|
||||
class ReportController(rest.RestController):
|
||||
"""REST Controller managing the reporting.
|
||||
|
||||
"""
|
||||
|
||||
_custom_actions = {
|
||||
'total': ['GET']
|
||||
@@ -122,11 +153,17 @@ class ReportController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(float)
|
||||
def total(self):
|
||||
"""Return the amount to pay for the current month.
|
||||
|
||||
"""
|
||||
# TODO(sheeprine): Get current total from DB
|
||||
return 10.0
|
||||
|
||||
|
||||
class V1Controller(rest.RestController):
|
||||
"""API version 1 controller.
|
||||
|
||||
"""
|
||||
|
||||
billing = BillingController()
|
||||
report = ReportController()
|
||||
|
||||
@@ -39,19 +39,37 @@ class ExtensionSummary(wtypes.Base):
|
||||
"""
|
||||
|
||||
name = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
"""Name of the extension."""
|
||||
|
||||
description = wtypes.text
|
||||
"""Short description of the extension."""
|
||||
|
||||
enabled = wtypes.wsattr(bool, default=False)
|
||||
"""Extension status."""
|
||||
|
||||
hot_config = wtypes.wsattr(bool, default=False, name='hot-config')
|
||||
"""On-the-fly configuration support."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls(name='example',
|
||||
description='Sample extension.',
|
||||
enabled=True,
|
||||
hot_config=False)
|
||||
return sample
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BillingEnableController(rest.RestController):
|
||||
"""REST Controller to enable or disable a billing module.
|
||||
|
||||
"""
|
||||
|
||||
@wsme_pecan.wsexpose(bool)
|
||||
def get(self):
|
||||
"""Get module status
|
||||
|
||||
"""
|
||||
api = db_api.get_instance()
|
||||
module = pecan.request.path.rsplit('/', 2)[-2]
|
||||
module_db = api.get_module_enable_state()
|
||||
@@ -59,6 +77,11 @@ class BillingEnableController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(bool, body=bool)
|
||||
def put(self, state):
|
||||
"""Set module status
|
||||
|
||||
:param state: State to set.
|
||||
:return: New state set for the module.
|
||||
"""
|
||||
api = db_api.get_instance()
|
||||
module = pecan.request.path.rsplit('/', 2)[-2]
|
||||
module_db = api.get_module_enable_state()
|
||||
@@ -67,18 +90,37 @@ class BillingEnableController(rest.RestController):
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BillingConfigController(rest.RestController):
|
||||
"""REST Controller managing internal configuration of billing modules.
|
||||
|
||||
@wsme_pecan.wsexpose()
|
||||
def get(self):
|
||||
"""
|
||||
|
||||
def _not_configurable(self):
|
||||
try:
|
||||
module = pecan.request.path.rsplit('/', 1)[-1]
|
||||
raise BillingModuleNotConfigurable(module)
|
||||
except BillingModuleNotConfigurable as e:
|
||||
pecan.abort(400, str(e))
|
||||
|
||||
@wsme_pecan.wsexpose()
|
||||
def get(self):
|
||||
"""Get current module configuration
|
||||
|
||||
"""
|
||||
self._not_configurable()
|
||||
|
||||
@wsme_pecan.wsexpose()
|
||||
def put(self):
|
||||
"""Set current module configuration
|
||||
|
||||
"""
|
||||
self._not_configurable()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BillingController(rest.RestController):
|
||||
"""REST Controller used to manage billing system.
|
||||
|
||||
"""
|
||||
|
||||
config = BillingConfigController()
|
||||
enabled = BillingEnableController()
|
||||
|
||||
@@ -34,8 +34,15 @@ MAP_TYPE = wtypes.Enum(wtypes.text, 'flat', 'rate')
|
||||
class Mapping(wtypes.Base):
|
||||
|
||||
map_type = wtypes.wsattr(MAP_TYPE, default='rate', name='type')
|
||||
"""Type of the mapping."""
|
||||
|
||||
value = wtypes.wsattr(float, mandatory=True)
|
||||
"""Value of the mapping."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls(value=4.2)
|
||||
return sample
|
||||
|
||||
|
||||
class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
@@ -56,7 +63,7 @@ class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
|
||||
@wsme_pecan.wsexpose(Mapping, wtypes.text, wtypes.text, wtypes.text)
|
||||
def get_mapping(self, service, field, key):
|
||||
"""Return the list of every mappings.
|
||||
"""Get a mapping from full path.
|
||||
|
||||
"""
|
||||
hashmap = api.get_instance()
|
||||
@@ -67,6 +74,10 @@ class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
|
||||
@wsme_pecan.wsexpose([wtypes.text])
|
||||
def get(self):
|
||||
"""Get the service list
|
||||
|
||||
:return: List of every services' name.
|
||||
"""
|
||||
hashmap = api.get_instance()
|
||||
return [service.name for service in hashmap.list_services()]
|
||||
|
||||
@@ -74,6 +85,8 @@ class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
def get_one(self, service=None, field=None):
|
||||
"""Return the list of every sub keys.
|
||||
|
||||
:param service: (Optional) Filter on this service.
|
||||
:param field: (Optional) Filter on this field.
|
||||
"""
|
||||
hashmap = api.get_instance()
|
||||
if field:
|
||||
@@ -95,6 +108,13 @@ class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, wtypes.text,
|
||||
body=Mapping)
|
||||
def post(self, service, field=None, key=None, mapping=None):
|
||||
"""Create hashmap fields.
|
||||
|
||||
:param service: Name of the service to create.
|
||||
:param field: (Optional) Name of the field to create.
|
||||
:param key: (Optional) Name of the key to create.
|
||||
:param mapping: (Optional) Mapping object to create.
|
||||
"""
|
||||
hashmap = api.get_instance()
|
||||
if field:
|
||||
if key:
|
||||
@@ -131,6 +151,13 @@ class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, wtypes.text,
|
||||
body=Mapping)
|
||||
def put(self, service, field, key, mapping):
|
||||
"""Modify hashmap fields
|
||||
|
||||
:param service: Filter on this service.
|
||||
:param field: Filter on this field.
|
||||
:param key: Modify the content of this key.
|
||||
:param mapping: Mapping object to update.
|
||||
"""
|
||||
hashmap = api.get_instance()
|
||||
try:
|
||||
hashmap.update_mapping(
|
||||
@@ -149,6 +176,9 @@ class BasicHashMapConfigController(billing.BillingConfigController):
|
||||
def delete(self, service, field=None, key=None):
|
||||
"""Delete the parent and all the sub keys recursively.
|
||||
|
||||
:param service: Name of the service to delete.
|
||||
:param field: (Optional) Name of the field to delete.
|
||||
:param key: (Optional) Name of the key to delete.
|
||||
"""
|
||||
hashmap = api.get_instance()
|
||||
try:
|
||||
|
||||
47
doc/source/arch.rst
Normal file
47
doc/source/arch.rst
Normal file
@@ -0,0 +1,47 @@
|
||||
=========================
|
||||
CloudKitty's Architecture
|
||||
=========================
|
||||
|
||||
CloudKitty can be cut in four big parts:
|
||||
|
||||
* API
|
||||
* collector
|
||||
* billing processor
|
||||
* writer pipeline
|
||||
|
||||
|
||||
Module loading and extensions
|
||||
=============================
|
||||
|
||||
Nearly every part of CloudKitty makes use of stevedore to load extensions
|
||||
dynamically.
|
||||
|
||||
Every billing module is loaded at runtime and can be enabled/disabled directly
|
||||
via CloudKitty's API. The billing module is responsible of its own API to ease
|
||||
the management of its configuration.
|
||||
|
||||
Collectors and writers are loaded with stevedore but configured in CloudKitty's
|
||||
configuration file.
|
||||
|
||||
|
||||
Collector
|
||||
=========
|
||||
|
||||
This part is responsible of the information gathering. It consists of a python
|
||||
module that load data from a backend and return them in a format that
|
||||
CloudKitty can handle.
|
||||
|
||||
Processor
|
||||
=========
|
||||
|
||||
This is where every pricing calculations is done. The data gathered by
|
||||
the collector is pushed in a pipeline of billing processors. Every
|
||||
processor does its calculations and updates the data.
|
||||
|
||||
|
||||
Writer
|
||||
======
|
||||
|
||||
In the same way as the processor pipeline, the writing is handled with a
|
||||
pipeline. The data is pushed to every writer in the pipeline which is
|
||||
responsible of the writing.
|
||||
@@ -12,8 +12,8 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
#import sys
|
||||
#import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -28,7 +28,18 @@ import os
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'wsmeext.sphinxext',
|
||||
'sphinxcontrib.docbookrestapi.setup',
|
||||
'sphinxcontrib.pecanwsme.rest',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'oslosphinx',
|
||||
]
|
||||
|
||||
wsme_protocols = ['restjson', 'restxml']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -74,11 +85,11 @@ exclude_patterns = []
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
@@ -88,7 +99,7 @@ exclude_patterns = []
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
modindex_common_prefix = ['cloudkitty.']
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
@@ -3,14 +3,44 @@
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to cloudkitty's documentation!
|
||||
======================================
|
||||
=================================================
|
||||
Welcome to CloudKitty's developper documentation!
|
||||
=================================================
|
||||
|
||||
Contents:
|
||||
Introduction
|
||||
============
|
||||
|
||||
CloudKitty is a PricingAsAService project aimed at translating Ceilometer
|
||||
metrics to prices.
|
||||
|
||||
|
||||
Architecture
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
arch
|
||||
|
||||
|
||||
API References
|
||||
==============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
webapi/root
|
||||
webapi/v1
|
||||
|
||||
|
||||
Modules
|
||||
=======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
webapi/billing/*
|
||||
|
||||
|
||||
Indices and tables
|
||||
@@ -19,4 +49,3 @@ Indices and tables
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
||||
30
doc/source/webapi/billing/hashmap.rst
Normal file
30
doc/source/webapi/billing/hashmap.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
=======================
|
||||
HashMap Module REST API
|
||||
=======================
|
||||
|
||||
.. rest-controller:: cloudkitty.billing.hash:BasicHashMapController
|
||||
:webprefix: /v1/billing/modules/hashmap
|
||||
|
||||
.. rest-controller:: cloudkitty.billing.hash:BasicHashMapConfigController
|
||||
:webprefix: /v1/billing/modules/hashmap/config
|
||||
|
||||
.. http:get:: /v1/billing/hashmap/modules/config/(service)/(field)/(key)
|
||||
|
||||
Get a mapping from full path
|
||||
|
||||
:param service: Filter on this service.
|
||||
:param field: Filter on this field.
|
||||
:param key: Filter on this key.
|
||||
:type service: :class:`unicode`
|
||||
:type field: :class:`unicode`
|
||||
:type key: :class:`unicode`
|
||||
:type mapping: :class:`Mapping`
|
||||
:return: A mapping
|
||||
|
||||
:return type: :class:`Mapping`
|
||||
|
||||
|
||||
.. autotype:: cloudkitty.billing.hash.Mapping
|
||||
:members:
|
||||
|
||||
|
||||
16
doc/source/webapi/root.rst
Normal file
16
doc/source/webapi/root.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
==========================
|
||||
CloudKitty REST API (root)
|
||||
==========================
|
||||
|
||||
.. rest-controller:: cloudkitty.api.controllers.root:RootController
|
||||
:webprefix: / /
|
||||
.. Dirty hack till the bug is fixed so we can specify root path
|
||||
|
||||
.. autotype:: cloudkitty.api.controllers.root.APILink
|
||||
:members:
|
||||
|
||||
.. autotype:: cloudkitty.api.controllers.root.APIMediaType
|
||||
:members:
|
||||
|
||||
.. autotype:: cloudkitty.api.controllers.root.APIVersion
|
||||
:members:
|
||||
31
doc/source/webapi/v1.rst
Normal file
31
doc/source/webapi/v1.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
========================
|
||||
CloudKitty REST API (v1)
|
||||
========================
|
||||
|
||||
Billing
|
||||
=======
|
||||
|
||||
.. rest-controller:: cloudkitty.billing:BillingEnableController
|
||||
:webprefix: /v1/billing/modules/(module)/enabled
|
||||
|
||||
.. rest-controller:: cloudkitty.billing:BillingConfigController
|
||||
:webprefix: /v1/billing/modules/(module)/config
|
||||
|
||||
.. rest-controller:: cloudkitty.api.controllers.v1:ModulesController
|
||||
:webprefix: /v1/billing/modules
|
||||
|
||||
.. rest-controller:: cloudkitty.api.controllers.v1:BillingController
|
||||
:webprefix: /v1/billing
|
||||
|
||||
.. autotype:: cloudkitty.billing.ExtensionSummary
|
||||
:members:
|
||||
|
||||
.. autotype:: cloudkitty.api.controllers.v1.ResourceDescriptor
|
||||
:members:
|
||||
|
||||
|
||||
Report
|
||||
======
|
||||
|
||||
.. rest-controller:: cloudkitty.api.controllers.v1:ReportController
|
||||
:webprefix: /v1/report
|
||||
@@ -6,7 +6,6 @@ mock>=1.0
|
||||
sphinx>=1.1.2,<1.2
|
||||
oslosphinx
|
||||
oslotest
|
||||
|
||||
sphinxcontrib-docbookrestapi
|
||||
sphinxcontrib-httpdomain
|
||||
sphinxcontrib-pecanwsme>=0.8
|
||||
|
||||
Reference in New Issue
Block a user