1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
..
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):
|