CloudKitty’s Architecture

CloudKitty’s Architecture

CloudKitty can be cut in five big parts:

  • API
  • Data collection (collector)
  • Rating processing
  • Storage
  • Report writer

Module loading and extensions

Nearly every part of CloudKitty makes use of stevedore to load extensions dynamically.

Every rating module is loaded at runtime and can be enabled/disabled directly via CloudKitty’s API. The module is responsible of its own API to ease the management of its configuration.

Collectors and storage backends are loaded with stevedore but configured in CloudKitty’s configuration file.


Loaded with stevedore

The name of the collector to use is specified in the configuration, only one collector can be loaded at once. This part is responsible of information gathering. It consists of a python class that loads data from a backend and return it in a format that CloudKitty can handle.

The data format of CloudKitty is the following:

    "myservice": [
            "rating": {
                "price": 0.1
            "desc": {
                "sugar": "25",
                "fiber": "10",
                "name": "apples",
            "vol": {
                "qty": 1,
                "unit": "banana"

Example code of a basic collector:

class MyCollector(BaseCollector):
    def __init__(self, **kwargs):
        super(MyCollector, self).__init__(**kwargs)

    def get_mydata(self, start, end=None, project_id=None, q_filter=None):
        # Do stuff
        return ck_data

You’ll now be able to add the gathering of mydata in CloudKitty by modifying the configuration and specifying the new service in collect/services.

If you need to load multiple collectors, you can use the meta collector and use its API to enable/disable collector loading, and set priority.


Loaded with stevedore

This is where every rating calculations is done. The data gathered by the collector is pushed in a pipeline of rating processors. Every processor does its calculations and updates the data.

Example of minimal rating module (taken from the Noop module):

class Noop(rating.RatingProcessorBase):

    controller = NoopController
    description = 'Dummy test module'

    def enabled(self):
        """Check if the module is enabled

        :returns: bool if module is enabled
        return True

    def priority(self):
        return 1

    def reload_config(self):

    def process(self, data):
        for cur_data in data:
            cur_usage = cur_data['usage']
            for service in cur_usage:
                for entry in cur_usage[service]:
                    if 'rating' not in entry:
                        entry['rating'] = {'price': decimal.Decimal(0)}
        return data


Loaded with stevedore

The storage module is responsible of storing the data in a backend. It implements an API on top of the storage to be able to query the data without the need of knowing the type of backend used.

You can use the API to create reports on the fly for example.


Loaded with stevedore

In the same way as the rating pipeline, the writing is handled with a pipeline. The data is pushed to write orchestrator that will store the data in a transient DB (in case of output file invalidation). And then to every writer in the pipeline which is responsible of the writing.

Creative Commons Attribution 3.0 License

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.