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:
Stéphane Albert
2014-08-11 16:54:57 +02:00
parent 1467304878
commit 254b13af5e
11 changed files with 352 additions and 19 deletions

View File

@@ -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
)
)
],

View File

@@ -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()

View File

@@ -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()

View File

@@ -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
View 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.

View File

@@ -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

View File

@@ -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`

View 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:

View 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
View 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

View File

@@ -6,7 +6,6 @@ mock>=1.0
sphinx>=1.1.2,<1.2
oslosphinx
oslotest
sphinxcontrib-docbookrestapi
sphinxcontrib-httpdomain
sphinxcontrib-pecanwsme>=0.8