summaryrefslogtreecommitdiffstats
path: root/tests/unit/ansible_library/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/ansible_library/plugins')
-rw-r--r--tests/unit/ansible_library/plugins/action/calculate_test.py22
1 files changed, 14 insertions, 8 deletions
diff --git a/tests/unit/ansible_library/plugins/action/calculate_test.py b/tests/unit/ansible_library/plugins/action/calculate_test.py
index 68a03e2a..31d72120 100644
--- a/tests/unit/ansible_library/plugins/action/calculate_test.py
+++ b/tests/unit/ansible_library/plugins/action/calculate_test.py
@@ -45,8 +45,8 @@ def section_spec(metric_spec):
@pytest.fixture
def qpi_spec(section_spec):
return {
- "description": "QTIP Performance Index of compute",
"name": "compute",
+ "description": "QTIP Performance Index of compute",
"sections": [section_spec]
}
@@ -54,23 +54,29 @@ def qpi_spec(section_spec):
@pytest.fixture()
def metric_result():
return {'score': 1.0,
- 'workload_results': [
- {'name': 'rsa_sign', 'score': 1.0},
- {'name': 'rsa_verify', 'score': 1.0}]}
+ 'name': 'ssl_rsa',
+ 'description': 'metric',
+ 'children': [{'description': 'workload', 'name': 'rsa_sign', 'score': 1.0},
+ {'description': 'workload', 'name': 'rsa_verify', 'score': 1.0}]}
@pytest.fixture()
def section_result(metric_result):
return {'score': 1.0,
- 'metric_results': [{'name': 'ssl_rsa', 'result': metric_result}]}
+ 'name': 'ssl',
+ 'description': 'cryptography and SSL/TLS performance',
+ 'children': [metric_result]}
@pytest.fixture()
def qpi_result(qpi_spec, section_result, metrics):
return {'score': 2048,
- 'spec': qpi_spec,
- 'metrics': metrics,
- 'section_results': [{'name': 'ssl', 'result': section_result}]}
+ 'name': 'compute',
+ 'description': 'QTIP Performance Index of compute',
+ 'children': [section_result],
+ 'details': {
+ 'spec': qpi_spec,
+ 'metrics': metrics}}
def test_calc_metric(metric_spec, metrics, metric_result):
'n211' href='#n211'>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):