Documentation on how-to develop Keystone Extensions

This file documents in details how to develop an
extension following the Keystone guidelines.
It also improves the already existing `example` extension
adding some boilerplate code.

bug #1218596

Change-Id: I559aa219f8552c00d326c1b766078ff306e94bb9
This commit is contained in:
Fabio Giannetti 2013-10-21 16:25:33 -07:00
parent 2df1b7cba4
commit eccc92d5cd
6 changed files with 387 additions and 0 deletions

View File

@ -0,0 +1,243 @@
Keystone Extensions Development Guide
=====================================
General
-------
This Extension Development Guide provides some mocked code to use as an
Extension code base in the `keystone/contrib/example` folder.
- All Extensions must be created in the `keystone/contrib` folder.
- The new Extension code must be contained in a new folder under `contrib`.
- Whenever possible an Extension should follow the following structure
convention:
keystone
\\\ contrib
\\\ my_extension
\\\ backends (optional)
\\\ migrate_repo (optional)
\\\ __init__.py (mandatory)
\\\ configuration.rst (mandatory)
\\\ core.py (mandatory)
\\\ controllers.py (mandatory for API Extension)
\\\ routers.py (mandatory for API Extension)
- If the Extension implements an API Extension the `controllers.py` and
`routers.py` must be present and correctly handle the API Extension requests
and responses.
- If the Extension implements backends a `backends` folder should exist.
Backends are defined to store data persistently and can use a variety of
technologies. Please see the Backends section in this document for more info.
- If the Extension adds data structures a `migrate_repo` folder should exist.
- If configuration changes are required/introduced in the `keystone.conf.sample`
file, these should be kept disabled as default and have their own element.
- If configuration changes are required/introduced in the `keystone-paste.ini`,
the new filter must be declared.
`keystone.conf.sample` File
---------------------------
In the case an Extension needs to change the `keystone.conf.sample` file, it
must follow the config file conventions and introduce a dedicated entry.
Example::
[example]
driver = keystone.contrib.example.backends.sql.mySQLClass
[myOtherExtension]
extension_flag = False
The Extension parameters expressed should be commented out since, by default,
extensions are disabled.
Example::
[example]
#driver = keystone.contrib.example.backends.sql.mySQLClass
[myOtherExtension]
#extension_flag = False
In case the Extension is overriding or re-implementing an existing portion of
Keystone the required change should be commented in the `configuration.rst` but
not placed in the `keystone.conf.sample` file to avoid unecessary confusion.
`keystone-paste.ini` File
--------------------------
In the case an Extension is augmenting a pipeline introducing a new `filter`
and/or APIs in the `OS` namespace, a corresponding `filter:` section is
necessary to be introduced in the `keystone-paste.ini` file.
The Extension should declare the filter factory constructor in the `ini` file.
Example::
[filter:example]
paste.filter_factory = keystone.contrib.example.routers:ExampleRouter.
factory
The `filter` must not be placed in the `pipeline` and treated as optional.
How to add the extension in the pipeline should be specified in detail in the
`configuration.rst` file.
Package Constructor File
------------------------
The `__init__.py` file represents the package constructor. Extension needs to
import what is necessary from the `core.py` module.
Example:
.. code:: python
from keystone.contrib.example.core import *
Core
----
The `core.py` file represents the main module defining the data structure and
interface. In the `Model View Control` (MVC) model it represents the `Model`
part and it delegates to the `Backends` the data layer implementation.
In case the `core.py` file contains a `Manager` and a `Driver` it must provide
the dependency injections for the `Controllers` and/or other modules using the
`Manager`. A good practice is to call the dependency `extension_name_api`.
Example:
.. code:: python
@dependency.provider('example_api')
class Manager(manager.Manager):
Routers
-------
`routers.py` have the objective of routing the HTTP requests and direct them to
the right method within the `Controllers`. Extension routers are extending the
`wsgi.ExtensionRouter`.
Example:
.. code:: python
from keystone.common import wsgi
from keystone.contrib.example import controllers
class ExampleRouter(wsgi.ExtensionRouter):
PATH_PREFIX = '/OS-EXAMPLE'
def add_routes(self, mapper):
example_controller = controllers.ExampleV3Controller()
mapper.connect(self.PATH_PREFIX + '/example',
controller=example_controller,
action='do_something',
conditions=dict(method=['GET']))
...
Controllers
-----------
`controllers.py` have the objective of handing requests and implement the
Extension logic. Controllers are consumers of 'Managers' API and must have all
the dependency injections required. `Controllers` are extending the
`V3Controller` class.
Example:
.. code:: python
@dependency.requires('identity_api', 'example_api')
class ExampleV3Controller(controller.V3Controller):
...
Backends
--------
The `backends` folder provides the model implementations for the different
backends supported by the Extension.
The folder structure must be the following:
keystone
\\\ contrib
\\\ my_extension
\\\ backends
\\\ __init__.py (required)
\\\ sql.py (optional)
\\\ kvs.py (optional)
If a SQL backend is provided, in the `sql.py` backend implementation it is
mandatory to define the new table(s) that the Extension introduces and the
attributes they are composed of.
For more information on Backends please consult the Keystone Architecture
documentation:
(http://docs.openstack.org/developer/keystone/architecture.html)
Example:
.. code:: python
class ExampleSQLBackend(sql.ModelBase, sql.DictBase):
"""example table description."""
__tablename__ = 'example_table'
attributes = ['id', 'type', 'extra']
example_id = sql.Column(sql.String(64),
primary_key=True,
nullable=False)
...
Migrate Repository
------------------
In case the Extension is adding data structures, these must be stored in
separate tables and must not be included in the `migrate_repo` of the core
Keystone. Please refere to the 'migrate.cfg' file to configure the Extension
repository.
In order to create the Extension tables and its attributes, a db_sync command
must be executed.
Example::
./bin/keystone-manage db_sync --extension example

View File

@ -68,6 +68,7 @@ Developers Documentation
apache-httpd
external-auth
event_notifications
EXTENSIONS_HOWTO
Code Documentation
==================

View File

@ -0,0 +1,31 @@
..
Copyright 2013 OpenStack, Foundation
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
=================
Extension Example
=================
Please describe here in details how to enable your extension:
1. Add the required fields and values in the ``[example]`` section
in ``keystone.conf``.
2. Optional: add the required ``filter`` to the ``pipeline`` in ``keystone-paste.ini``
3. Optional: create the extension tables if using the provided sql backend. Example::
./bin/keystone-manage db_sync --extension example

View File

@ -0,0 +1,28 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import controller
from keystone.common import dependency
@dependency.requires('example_api')
class ExampleV3Controller(controller.V3Controller):
@controller.protected()
def example_get(self, context):
"""Description of the controller logic."""
self.example_api.do_something(context)

View File

@ -0,0 +1,54 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import dependency
from keystone.common import manager
from keystone import config
from keystone import exception
from keystone.openstack.common import log as logging
CONF = config.CONF
LOG = logging.getLogger(__name__)
@dependency.provider('example_api')
class ExampleManager(manager.Manager):
"""Example Manager.
See :mod:`keystone.common.manager.Manager` for more details on
how this dynamically calls the backend.
"""
def __init__(self):
super(ExampleManager, self).__init__(CONF.ExampleDriver.driver)
class ExampleDriver(object):
"""Interface description for Example driver."""
def do_something(self, data):
"""Do something
:param data: example data
:type data: string
:raises: keystone.exception,
:returns: None.
"""
raise exception.NotImplemented()

View File

@ -0,0 +1,30 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import wsgi
from keystone.contrib.example import controllers
class ExampleRouter(wsgi.ExtensionRouter):
PATH_PREFIX = '/OS-EXAMPLE'
def add_routes(self, mapper):
example_controller = controllers.ExampleV3Controller()
mapper.connect(self.PATH_PREFIX + '/example',
controller=example_controller,
action='do_something',
conditions=dict(method=['GET']))