Remove example extension

we will no longer be supporting extensions, everything is in keystone
proper, with varying degrees of stability.

Change-Id: I419df55ebe760cbd36a1c93ff7e712cfd3e8405f
Implements: bp move-extensions
Closes-Bug: #1519244
This commit is contained in:
Steve Martinelli 2015-10-15 16:28:14 -04:00
parent 2feb7be843
commit 6f012b2eb8
11 changed files with 0 additions and 564 deletions

View File

@ -1,303 +0,0 @@
..
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.
=====================================
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`` 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, 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 = sql
[my_other_extension]
extension_flag = False
The Extension parameters expressed should be commented out since, by default,
extensions are disabled.
Example::
[example]
#driver = sql
[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:
.. code-block:: 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-block:: 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 correct method within the ``Controllers``. Extension routers are extending
the ``wsgi.ExtensionRouter``.
Example:
.. code-block:: 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-block:: python
@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
<http://docs.openstack.org/developer/keystone/architecture.html>`_
documentation.
Example:
.. code-block:: 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)
...
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:
.. code-block:: bash
$ ./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:
.. code-block:: python
# 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 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:
.. code-block:: python
def project_deleted_callback(self, service, resource_type, operation,
payload):

View File

@ -1,31 +0,0 @@
..
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

@ -1,26 +0,0 @@
# 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

@ -1,98 +0,0 @@
# 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.
"""Main entry point into this Example service."""
from oslo_log import log
from keystone.common import dependency
from keystone.common import manager
from keystone import exception
from keystone.i18n import _LI
from keystone import notifications
LOG = log.getLogger(__name__)
@notifications.listener # NOTE(dstanek): only needed if using event_callbacks
@dependency.provider('example_api')
class ExampleManager(manager.Manager):
"""Default pivot point for this Example backend.
See :mod:`keystone.common.manager.Manager` for more details on
how this dynamically calls the backend.
"""
driver_namespace = 'keystone.example'
def __init__(self):
# The following is an example of event callbacks. In this setup,
# ExampleManager's data model is depended on project's data model.
# It must create additional aggregates when a new project is created,
# and it must cleanup data related to the project whenever a project
# has been deleted.
#
# In this example, the project_deleted_callback will be invoked
# whenever a project has been deleted. Similarly, the
# project_created_callback will be invoked whenever a new project is
# created.
# This information is used when the @notifications.listener decorator
# acts on the class.
self.event_callbacks = {
notifications.ACTIONS.deleted: {
'project': [self.project_deleted_callback],
},
notifications.ACTIONS.created: {
'project': [self.project_created_callback],
},
}
super(ExampleManager, self).__init__(
'keystone.contrib.example.core.ExampleDriver')
def project_deleted_callback(self, service, resource_type, operation,
payload):
# The code below is merely an example.
msg = _LI('Received the following notification: service %(service)s, '
'resource_type: %(resource_type)s, operation %(operation)s '
'payload %(payload)s')
LOG.info(msg, {'service': service, 'resource_type': resource_type,
'operation': operation, 'payload': payload})
def project_created_callback(self, service, resource_type, operation,
payload):
# The code below is merely an example.
msg = _LI('Received the following notification: service %(service)s, '
'resource_type: %(resource_type)s, operation %(operation)s '
'payload %(payload)s')
LOG.info(msg, {'service': service, 'resource_type': resource_type,
'operation': operation, 'payload': payload})
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.NotImplemented: If the operation was not
implemented.
:returns: None.
"""
raise exception.NotImplemented()

View File

@ -1,25 +0,0 @@
[db_settings]
# Used to identify which repository this database is versioned under.
# You can use the name of your project.
repository_id=example
# The name of the database table used to track the schema version.
# This name shouldn't already be used by your project.
# If this is changed once a database is under version control, you'll need to
# change the table name in each database too.
version_table=migrate_version
# When committing a change script, Migrate will attempt to generate the
# sql for all supported databases; normally, if one of them fails - probably
# because you don't have that database installed - it is ignored and the
# commit continues, perhaps ending successfully.
# Databases in this list MUST compile successfully during a commit, or the
# entire commit will fail. List the databases your application will actually
# be using to ensure your updates to that database work properly.
# This must be a list; example: ['postgres','sqlite']
required_dbs=[]
# When creating new change scripts, Migrate will stamp the new script with
# a version number. By default this is latest_version + 1. You can set this
# to 'true' to tell Migrate to use the UTC timestamp instead.
use_timestamp_numbering=False

View File

@ -1,32 +0,0 @@
# Copyright 2012 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.
import sqlalchemy as sql
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine; bind
# migrate_engine to your metadata
meta = sql.MetaData()
meta.bind = migrate_engine
# catalog
service_table = sql.Table(
'example',
meta,
sql.Column('id', sql.String(64), primary_key=True),
sql.Column('type', sql.String(255)),
sql.Column('extra', sql.Text()))
service_table.create(migrate_engine, checkfirst=True)

View File

@ -1,38 +0,0 @@
# 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.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib.example import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-EXAMPLE', extension_version='1.0')
class ExampleRouter(wsgi.V3ExtensionRouter):
PATH_PREFIX = '/OS-EXAMPLE'
def add_routes(self, mapper):
example_controller = controllers.ExampleV3Controller()
self._add_resource(
mapper, example_controller,
path=self.PATH_PREFIX + '/example',
get_action='do_something',
rel=build_resource_relation(resource_name='example'))

View File

@ -31,7 +31,6 @@ WARNING::
from keystone.contrib import endpoint_filter
from keystone.contrib import endpoint_policy
from keystone.contrib import example
from keystone.contrib import federation
from keystone.contrib import oauth1
from keystone.contrib import revoke
@ -39,16 +38,6 @@ from keystone import exception
from keystone.tests.unit import test_sql_upgrade
class SqlUpgradeExampleExtension(test_sql_upgrade.SqlMigrateBase):
def repo_package(self):
return example
def test_upgrade(self):
self.assertTableDoesNotExist('example')
self.upgrade(1, repository=self.repo_path)
self.assertTableColumns('example', ['id', 'type', 'extra'])
class SqlUpgradeOAuth1Extension(test_sql_upgrade.SqlMigrateBase):
OAUTH1_MIGRATIONS = 5