Nailgun extensions docs

What extensions are
Required properties
Available events
REST API handlers
Database interaction
Extension Data Pipelines
How to install and plug in Extensions
Example Extension with Pipeline - additional logging

Change-Id: Iaa6f926fe24bea4300b2fb215180cff6b1639812
implements: blueprint stevedore-extensions-discovery
implements: blueprint data-pipeline
This commit is contained in:
Sylwester Brzeczkowski 2016-03-22 15:05:52 +01:00
parent 42ace6e5d9
commit c5db3ed6a4
2 changed files with 314 additions and 0 deletions

View File

@ -0,0 +1,313 @@
Nailgun Extensions
__________________
Overview of extensions
======================
Nailgun extensions provide a capability to extend Fuel features.
Extensions were introduced to provide *pythonic way* for adding integrations
with external services, extending existing features, or adding new features
**without** changing the Nailgun source code.
A Nailgun extension can execute its method on specific events
such as ``on_node_create`` or ``on_cluster_delete`` (more about event handlers
in the `Available Events`_ section) and also to change deployment and
provisioning data just before it is sent to orchestrator by the means of
Data Pipelines classes.
Required properties
===================
All Nailgun extensions must populate the following class variables:
* ``name`` - a string which will be used inside Nailgun to identify the
extension. It should consist only of lowercase letters with "_" (underscore)
separator and digits.
* ``version`` - a string which should look like *X.Y.Z* where X is a major
version, Y is minor, and Z is bug fix update or build number.
* ``description`` - a short text which briefly describes the actions that the
extension performs.
Available events
================
Extension can execute event handlers on specific events. There is
a list of available handlers:
.. list-table::
:widths: 10 10
:header-rows: 1
* - Method
- Event
* - ``on_node_create``
- Node has been created
* - ``on_node_update``
- Node has been updated
* - ``on_node_reset``
- Node has been reseted
* - ``on_node_delete``
- Node has been deleted
* - ``on_node_collection_delete``
- Collection of nodes has been deleted
* - ``on_cluster_delete``
- Cluster has been deleted
* - ``on_before_deployment_check``
- Called right before running "before deployment check task"
REST API handlers
=================
Nailgun Extensions also provide a way to add additional API endpoints.
To add an extension-specific handler sub-class from::
nailgun.api.v1.handlers.base.BaseHandler
The second step is to register the handler by providing the ``urls`` list in
Extension class:
.. code-block:: python
urls = [
{'uri': r'/example_extension/(?P<node_id>\d+)/?$',
'handler': ExampleNodeHandler},
{'uri': r'/example_extension/(?P<node_id>\d+)/properties/',
'handler': NodeDefaultsDisksHandler},
]
As you can see you need to provide a list of dicts with keys:
.. list-table::
:widths: 10 10
:header-rows: 1
* - key
- value
* - ``uri``
- a regular expression (string) for the URL path
* - ``handler``
- handler class
Database interaction
====================
There is a possibility to use the Nailgun database to store the data needed by
a Nailgun extension. To use it, provide alembic migration scripts which should
be placed in::
extension_module/alembic_migrations/migrations/
Where ``extension_module`` is the one where the file with your extension class
is placed.
You can also change this directory by overriding the classmethod::
alembic_migrations_path
It should return an absolute path (string) to alembic migrations
directory.
.. important::
Do not use the direct db calls to Nailgun core tables in the extension
class. Use the ``nailgun.objects`` module which ensures compatibility
between the Nailgun DB and the configuration implemented in your extension.
Extension Data Pipelines
========================
If you want to change the deployment or provisioning data just before it is
sent to an orchestrator use Extension Data Pipelines.
Data Pipeline is a class which inherits from::
nailgun.extensions.BasePipeline
BasePipeline provides two methods which you can override:
* ``process_provisioning``
* ``process_deployment``
Both methods take the following parameters:
* ``data`` - serialized data which will be sent to orchestrator. Data
**does not include** nodes data which was defined by User in
``replaced_deployment_info`` or in ``replaced_provisioning_info``.
* ``cluster`` - a cluster instance for which the data was serialized.
* ``nodes`` - nodes instances for which the data was serialized. Nodes list
**does not include** node instances which were filtered out in ``data``
parameter.
* ``**kwargs`` - additional kwargs - must be in method definition to provide
backwards-compatibility for future (small) changes in extensions API.
Both methods must return the ``data`` dict so it can be processed by other
pipelines.
To enable pipelines, add the ``data_pipelines`` variable in your extensions
class:
.. code-block:: python
class ExamplePipelineOne(BasePipeline):
@classmethod
def process_provisioning(cls, data, cluster, nodes, **kwargs):
data['new_field'] = 'example_value'
return data
@classmethod
def process_deployment(cls, data, cluster, nodes, **kwargs):
data['new_field'] = 'example_value'
return data
class ExamplePipelineTwo(BasePipeline):
@classmethod
def process_deployment(cls, data, cluster, nodes, **kwargs):
data['new_field2'] = 'example_value2'
return data
class ExampleExtension(BaseExtension):
...
data_pipelines = [
ExamplePipelineOne,
ExamplePipelineTwo,
]
...
Pipeline classes will be executed **in the order they are defined** in the
``data_pipelines`` variable.
How to install and plug in extensions
=====================================
To use extensions system in Nailgun, implement an extension class which will
be the subclass of::
nailgun.extensions.BaseExtension
The class must be placed in a separate module which defines ``entry_points`` in
its ``setup.py`` file.
Extension entry point should use Nailgun extensions namespace which is::
nailgun.extensions
Example ``setup.py`` file with ``ExampleExtension`` may look like this:
.. code-block:: python
from setuptools import setup, find_packages
setup(
name='example_package',
version='1.0',
description='Demonstration package for Nailgun Extensions',
author='Fuel Nailgman',
author_email='fuel@nailgman.com',
url='http://example.com',
classifiers=['Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Environment :: Console',
],
packages=find_packages(),
entry_points={
'nailgun.extensions': [
'ExampleExtension = example_package.nailgun_extensions.ExampleExtension',
],
},
)
Now to enable the extension it is enough to run::
python setup.py install
or::
pip install .
Now extension will be discovered by Nailgun automatically after restart.
Example Extension with Pipeline - additional logging
====================================================
.. code-block:: python
import datetime
import logging
from nailgun.extensions import BaseExtension
from nailgun.extensions import BasePipeline
logger = logging.getLogger(__name__)
class TimeStartedPipeline(BasePipeline):
@classmethod
def process_provisioning(cls, data, cluster, nodes, **kwargs):
now = datetime.datetime.now()
data['time_started'] = 'provisioning started at {}'.format(now)
return data
@classmethod
def process_deployment(cls, data, cluster, nodes, **kwargs):
now = datetime.datetime.now()
data['time_started'] = 'deployment started at {}'.format(now)
return data
class ExampleExtension(BaseExtension):
name = 'additional_logger'
version = '1.0.0'
description = 'Additional Logging Extension '
data_pipelines = [
TimeStartedPipeline,
]
@classmethod
def on_node_create(cls, node):
logging.debug('Node %s has been created', node.id)
@classmethod
def on_node_update(cls, node):
logging.debug('Node %s has been updated', node.id)
@classmethod
def on_node_reset(cls, node):
logging.debug('Node %s has been reseted', node.id)
@classmethod
def on_node_delete(cls, node):
logging.debug('Node %s has been deleted', node.id)
@classmethod
def on_node_collection_delete(cls, node_ids):
logging.debug('Nodes %s have been deleted', ', '.join(node_ids))
@classmethod
def on_cluster_delete(cls, cluster):
logging.debug('Cluster %s has been deleted', cluster.id)
@classmethod
def on_before_deployment_check(cls, cluster):
logging.debug('Cluster %s will be deployed soon', cluster.id)

View File

@ -23,3 +23,4 @@ Nailgun Customization Instructions
customization/roles
customization/settings
customization/bonding_in_ui
customization/extensions