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:
parent
2df1b7cba4
commit
eccc92d5cd
243
doc/source/EXTENSIONS_HOWTO.rst
Normal file
243
doc/source/EXTENSIONS_HOWTO.rst
Normal 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
|
@ -68,6 +68,7 @@ Developers Documentation
|
||||
apache-httpd
|
||||
external-auth
|
||||
event_notifications
|
||||
EXTENSIONS_HOWTO
|
||||
|
||||
Code Documentation
|
||||
==================
|
||||
|
31
keystone/contrib/example/configuration.rst
Normal file
31
keystone/contrib/example/configuration.rst
Normal 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
|
28
keystone/contrib/example/controllers.py
Normal file
28
keystone/contrib/example/controllers.py
Normal 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)
|
54
keystone/contrib/example/core.py
Normal file
54
keystone/contrib/example/core.py
Normal 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()
|
30
keystone/contrib/example/routers.py
Normal file
30
keystone/contrib/example/routers.py
Normal 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']))
|
Loading…
Reference in New Issue
Block a user