From eccc92d5cdc4ef8aac5f0fab484ebdf7077649d8 Mon Sep 17 00:00:00 2001 From: Fabio Giannetti Date: Mon, 21 Oct 2013 16:25:33 -0700 Subject: [PATCH] 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 --- doc/source/EXTENSIONS_HOWTO.rst | 243 +++++++++++++++++++++ doc/source/index.rst | 1 + keystone/contrib/example/configuration.rst | 31 +++ keystone/contrib/example/controllers.py | 28 +++ keystone/contrib/example/core.py | 54 +++++ keystone/contrib/example/routers.py | 30 +++ 6 files changed, 387 insertions(+) create mode 100644 doc/source/EXTENSIONS_HOWTO.rst create mode 100644 keystone/contrib/example/configuration.rst create mode 100644 keystone/contrib/example/controllers.py create mode 100644 keystone/contrib/example/core.py create mode 100644 keystone/contrib/example/routers.py diff --git a/doc/source/EXTENSIONS_HOWTO.rst b/doc/source/EXTENSIONS_HOWTO.rst new file mode 100644 index 000000000..70cb2f666 --- /dev/null +++ b/doc/source/EXTENSIONS_HOWTO.rst @@ -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 diff --git a/doc/source/index.rst b/doc/source/index.rst index ebaddc454..111c8a42b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -68,6 +68,7 @@ Developers Documentation apache-httpd external-auth event_notifications + EXTENSIONS_HOWTO Code Documentation ================== diff --git a/keystone/contrib/example/configuration.rst b/keystone/contrib/example/configuration.rst new file mode 100644 index 000000000..979d34577 --- /dev/null +++ b/keystone/contrib/example/configuration.rst @@ -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 \ No newline at end of file diff --git a/keystone/contrib/example/controllers.py b/keystone/contrib/example/controllers.py new file mode 100644 index 000000000..204f88429 --- /dev/null +++ b/keystone/contrib/example/controllers.py @@ -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) diff --git a/keystone/contrib/example/core.py b/keystone/contrib/example/core.py new file mode 100644 index 000000000..cd1494503 --- /dev/null +++ b/keystone/contrib/example/core.py @@ -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() diff --git a/keystone/contrib/example/routers.py b/keystone/contrib/example/routers.py new file mode 100644 index 000000000..d7f502bc2 --- /dev/null +++ b/keystone/contrib/example/routers.py @@ -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']))