- used the syntax employed by the 'tree' tool, which is not so heavy on whitespace, and should render much better as a literal example on the web - merged the 'backends' example into the general example, since having at least one backend is common - flushed out an example 'migrate_repo' directory Change-Id: Idba9555f93caf2142e0d1becdc1afb0af50b94de
10 KiB
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 directory structure convention:
keystone/contrib/ └── my_extension ├── backends (optional) │ ├── __init__.py (mandatory) │ └── sql.py (optional) │ └── kvs.py (optional) ├── migrate_repo (optional) │ ├── __init__.py (mandatory) │ ├── migrate.cfg (mandatory) │ └── versions (mandatory) │ ├── 001_create_tables.py (mandatory) │ └── __init__.py (mandatory) ├── __init__.py (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
androuters.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, then 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 section.If configuration changes are required/introduced in the
keystone-paste.ini
, the new filter must be declared.The module may register to listen to events by declaring the corresponding callbacks in the
core.py
file.The new extension should be disabled by default (it should not affect the default application pipelines).
Modifying the 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 section.
Example:
[example]
driver = keystone.contrib.example.backends.sql.mySQLClass
[my_other_extension]
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
[my_other_extension]
#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 unnecessary
confusion.
Modifying the
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:
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:
@dependency.provider('example_api')
class Manager(manager.Manager):
Routers
routers.py
have the objective of routing the HTTP
requests and direct them to the correct method within the
Controllers
. Extension routers are extending the
wsgi.ExtensionRouter
.
Example:
from keystone.common import wsgi
from keystone.contrib.example import controllers
class ExampleRouter(wsgi.ExtensionRouter):
= '/OS-EXAMPLE'
PATH_PREFIX
def add_routes(self, mapper):
= controllers.ExampleV3Controller()
example_controller connect(self.PATH_PREFIX + '/example',
mapper.=example_controller,
controller='do_something',
action=dict(method=['GET'])) conditions
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:
@dependency.requires('identity_api', 'example_api')
class ExampleV3Controller(controller.V3Controller):
pass
Backends
The backends
folder provides the model implementations
for the different backends supported by the Extension. See General above
for an example directory structure.
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, refer to the Keystone Architecture documentation.
Example:
class ExampleSQLBackend(sql.ModelBase, sql.DictBase):
"""example table description."""
= 'example_table'
__tablename__ = ['id', 'type', 'extra']
attributes
= sql.Column(sql.String(64),
example_id =True,
primary_key=False)
nullable ...
SQL Migration Repository
In case the Extension is adding SQL data structures, these must be
stored in separate tables and must not be included in the
migrate_repo
of the core Keystone. Please refer to the
migrate.cfg
file to configure the Extension repository.
In order to create the Extension tables and their attributes, a
db_sync
command must be executed.
Example:
./bin/keystone-manage db_sync --extension example
Event Callbacks
Extensions may provide callbacks to Keystone (Identity) events. Extensions must provide the list of events of interest and the corresponding callbacks. Events are issued upon successful creation, modification, and deletion of the following Keystone resources:
group
project
role
user
The extension's Manager
class must contain the
event_callbacks
attribute. It is a dictionary listing as
keys those events that are of interest and the values should be the
respective callbacks. Event callback registration is done via the
dependency injection mechanism. During dependency provider registration,
the dependency.provider
decorator looks for the
event_callbacks
class attribute. If it exists the event
callbacks are registered accordingly. In order to enable event
callbacks, the extension's Manager
class must also be a
dependency provider.
Example:
# Since this is a dependency provider. Any code module using this or any
# other dependency provider (uses the dependency.provider decorator)
# will be enabled for the attribute based notification
@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):
self.event_callbacks = {
# Here we add the the event_callbacks class attribute that
# calls project_deleted_callback when a project is deleted.
'deleted': {
'project': [
self.project_deleted_callback]}}
super(ExampleManager, self).__init__(
'keystone.contrib.example.core.ExampleDriver')
def project_deleted_callback(self, context, message):
# cleanup data related to the deleted project here
A callback must accept the following parameters:
service
- the service information (e.g. identity)resource_type
- the resource type (e.g. project)operation
- the operation (updated, created, deleted)payload
- the actual payload info of the resource that was acted on
Current callback operations:
created
deleted
updated
Example:
def project_deleted_callback(self, service, resource_type, operation,
payload):