| 16 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,73 @@ |
| 0 |
+.. |
|
| 1 |
+ Note to reviewers: the intent of this file is to be easy for |
|
| 2 |
+ community members to update. As such fast approving (single core +2) |
|
| 3 |
+ is fine as long as you've identified that the plugin listed actually exists. |
|
| 4 |
+ |
|
| 5 |
+========================== |
|
| 6 |
+ DevStack Plugin Registry |
|
| 7 |
+========================== |
|
| 8 |
+ |
|
| 9 |
+Since we've created the external plugin mechanism, it's gotten used by |
|
| 10 |
+a lot of projects. The following is a list of plugins that currently |
|
| 11 |
+exist. Any project that wishes to list their plugin here is welcomed |
|
| 12 |
+to. |
|
| 13 |
+ |
|
| 14 |
+Official OpenStack Projects |
|
| 15 |
+=========================== |
|
| 16 |
+ |
|
| 17 |
+The following are plugins that exist for official OpenStack projects. |
|
| 18 |
+ |
|
| 19 |
++--------------------+-------------------------------------------+--------------------+ |
|
| 20 |
+|Plugin Name |URL |Comments | |
|
| 21 |
++--------------------+-------------------------------------------+--------------------+ |
|
| 22 |
+|magnum |git://git.openstack.org/openstack/magnum | | |
|
| 23 |
++--------------------+-------------------------------------------+--------------------+ |
|
| 24 |
+|trove |git://git.openstack.org/openstack/trove | | |
|
| 25 |
++--------------------+-------------------------------------------+--------------------+ |
|
| 26 |
+|zaqar |git://git.openstack.org/openstack/zarar | | |
|
| 27 |
++--------------------+-------------------------------------------+--------------------+ |
|
| 28 |
+ |
|
| 29 |
+ |
|
| 30 |
+ |
|
| 31 |
+Drivers |
|
| 32 |
+======= |
|
| 33 |
+ |
|
| 34 |
++--------------------+-------------------------------------------------+------------------+ |
|
| 35 |
+|Plugin Name |URL |Comments | |
|
| 36 |
++--------------------+-------------------------------------------------+------------------+ |
|
| 37 |
+|dragonflow |git://git.openstack.org/openstack/dragonflow |[d1]_ | |
|
| 38 |
++--------------------+-------------------------------------------------+------------------+ |
|
| 39 |
+|odl |git://git.openstack.org/openstack/networking-odl |[d2]_ | |
|
| 40 |
++--------------------+-------------------------------------------------+------------------+ |
|
| 41 |
+ |
|
| 42 |
+.. [d1] demonstrates example of installing 3rd party SDN controller |
|
| 43 |
+.. [d2] demonstrates a pretty advanced set of modes that that allow |
|
| 44 |
+ one to run OpenDayLight either from a pre-existing install, or |
|
| 45 |
+ also from source |
|
| 46 |
+ |
|
| 47 |
+Alternate Configs |
|
| 48 |
+================= |
|
| 49 |
+ |
|
| 50 |
++-------------+------------------------------------------------------------+------------+ |
|
| 51 |
+| Plugin Name | URL | Comments | |
|
| 52 |
+| | | | |
|
| 53 |
++-------------+------------------------------------------------------------+------------+ |
|
| 54 |
+|glusterfs |git://git.openstack.org/stackforge/devstack-plugin-glusterfs| | |
|
| 55 |
++-------------+------------------------------------------------------------+------------+ |
|
| 56 |
+| | | | |
|
| 57 |
++-------------+------------------------------------------------------------+------------+ |
|
| 58 |
+ |
|
| 59 |
+Additional Services |
|
| 60 |
+=================== |
|
| 61 |
+ |
|
| 62 |
++-------------+------------------------------------------+------------+ |
|
| 63 |
+| Plugin Name | URL | Comments | |
|
| 64 |
+| | | | |
|
| 65 |
++-------------+------------------------------------------+------------+ |
|
| 66 |
+|ec2-api |git://git.openstack.org/stackforge/ec2api |[as1]_ | |
|
| 67 |
++-------------+------------------------------------------+------------+ |
|
| 68 |
+| | | | |
|
| 69 |
++-------------+------------------------------------------+------------+ |
|
| 70 |
+ |
|
| 71 |
+.. [as1] first functional devstack plugin, hence why used in most of |
|
| 72 |
+ the examples. |
| ... | ... |
@@ -2,78 +2,83 @@ |
| 2 | 2 |
Plugins |
| 3 | 3 |
======= |
| 4 | 4 |
|
| 5 |
-DevStack has a couple of plugin mechanisms to allow easily adding |
|
| 6 |
-support for additional projects and features. |
|
| 5 |
+The OpenStack ecosystem is wide and deep, and only growing more so |
|
| 6 |
+every day. The value of DevStack is that it's simple enough to |
|
| 7 |
+understand what it's doing clearly. And yet we'd like to support as |
|
| 8 |
+much of the OpenStack Ecosystem as possible. We do that with plugins. |
|
| 7 | 9 |
|
| 8 |
-Extras.d Hooks |
|
| 9 |
-============== |
|
| 10 |
+DevStack plugins are bits of bash code that live outside the DevStack |
|
| 11 |
+tree. They are called through a strong contract, so these plugins can |
|
| 12 |
+be sure that they will continue to work in the future as DevStack |
|
| 13 |
+evolves. |
|
| 10 | 14 |
|
| 11 |
-These hooks are an extension of the service calls in |
|
| 12 |
-``stack.sh`` at specific points in its run, plus ``unstack.sh`` and |
|
| 13 |
-``clean.sh``. A number of the higher-layer projects are implemented in |
|
| 14 |
-DevStack using this mechanism. |
|
| 15 |
+Plugin Interface |
|
| 16 |
+================ |
|
| 15 | 17 |
|
| 16 |
-The script in ``extras.d`` is expected to be mostly a dispatcher to |
|
| 17 |
-functions in a ``lib/*`` script. The scripts are named with a |
|
| 18 |
-zero-padded two digits sequence number prefix to control the order that |
|
| 19 |
-the scripts are called, and with a suffix of ``.sh``. DevStack reserves |
|
| 20 |
-for itself the sequence numbers 00 through 09 and 90 through 99. |
|
| 18 |
+DevStack supports a standard mechansim for including plugins from |
|
| 19 |
+external repositories. The plugin interface assumes the following: |
|
| 21 | 20 |
|
| 22 |
-Below is a template that shows handlers for the possible command-line |
|
| 23 |
-arguments: |
|
| 21 |
+An external git repository that includes a ``devstack/`` top level |
|
| 22 |
+directory. Inside this directory there can be 2 files. |
|
| 24 | 23 |
|
| 25 |
-:: |
|
| 24 |
+- ``settings`` - a file containing global variables that will be |
|
| 25 |
+ sourced very early in the process. This is helpful if other plugins |
|
| 26 |
+ might depend on this one, and need access to global variables to do |
|
| 27 |
+ their work. |
|
| 26 | 28 |
|
| 27 |
- # template.sh - DevStack extras.d dispatch script template |
|
| 29 |
+ Your settings should include any ``enable_service`` lines required |
|
| 30 |
+ by your plugin. This is especially important if you are kicking off |
|
| 31 |
+ services using ``run_process`` as it only works with enabled |
|
| 32 |
+ services. |
|
| 28 | 33 |
|
| 29 |
- # check for service enabled |
|
| 30 |
- if is_service_enabled template; then |
|
| 34 |
+ Be careful to allow users to override global-variables for |
|
| 35 |
+ customizing their environment. Usually it is best to provide a |
|
| 36 |
+ default value only if the variable is unset or empty; e.g. in bash |
|
| 37 |
+ syntax ``FOO=${FOO:-default}``.
|
|
| 31 | 38 |
|
| 32 |
- if [[ "$1" == "source" ]]; then |
|
| 33 |
- # Initial source of lib script |
|
| 34 |
- source $TOP_DIR/lib/template |
|
| 35 |
- fi |
|
| 39 |
+- ``plugin.sh`` - the actual plugin. It is executed by devstack at |
|
| 40 |
+ well defined points during a ``stack.sh`` run. The plugin.sh |
|
| 41 |
+ internal structure is discussed bellow. |
|
| 36 | 42 |
|
| 37 |
- if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then |
|
| 38 |
- # Set up system services |
|
| 39 |
- echo_summary "Configuring system services Template" |
|
| 40 |
- install_package cowsay |
|
| 41 | 43 |
|
| 42 |
- elif [[ "$1" == "stack" && "$2" == "install" ]]; then |
|
| 43 |
- # Perform installation of service source |
|
| 44 |
- echo_summary "Installing Template" |
|
| 45 |
- install_template |
|
| 44 |
+Plugins are registered by adding the following to the localrc section |
|
| 45 |
+of ``local.conf``. |
|
| 46 | 46 |
|
| 47 |
- elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then |
|
| 48 |
- # Configure after the other layer 1 and 2 services have been configured |
|
| 49 |
- echo_summary "Configuring Template" |
|
| 50 |
- configure_template |
|
| 47 |
+They are added in the following format:: |
|
| 51 | 48 |
|
| 52 |
- elif [[ "$1" == "stack" && "$2" == "extra" ]]; then |
|
| 53 |
- # Initialize and start the template service |
|
| 54 |
- echo_summary "Initializing Template" |
|
| 55 |
- ##init_template |
|
| 56 |
- fi |
|
| 49 |
+ [[local|localrc]] |
|
| 50 |
+ enable_plugin <NAME> <GITURL> [GITREF] |
|
| 57 | 51 |
|
| 58 |
- if [[ "$1" == "unstack" ]]; then |
|
| 59 |
- # Shut down template services |
|
| 60 |
- # no-op |
|
| 61 |
- : |
|
| 62 |
- fi |
|
| 52 |
+- ``name`` - an arbitrary name. (ex: glustfs, docker, zaqar, congress) |
|
| 53 |
+- ``giturl`` - a valid git url that can be cloned |
|
| 54 |
+- ``gitref`` - an optional git ref (branch / ref / tag) that will be |
|
| 55 |
+ cloned. Defaults to master. |
|
| 63 | 56 |
|
| 64 |
- if [[ "$1" == "clean" ]]; then |
|
| 65 |
- # Remove state and transient data |
|
| 66 |
- # Remember clean.sh first calls unstack.sh |
|
| 67 |
- # no-op |
|
| 68 |
- : |
|
| 69 |
- fi |
|
| 70 |
- fi |
|
| 57 |
+An example would be as follows:: |
|
| 71 | 58 |
|
| 72 |
-The arguments are: |
|
| 59 |
+ enable_plugin ec2api git://git.openstack.org/stackforge/ec2api |
|
| 60 |
+ |
|
| 61 |
+plugin.sh contract |
|
| 62 |
+================== |
|
| 63 |
+ |
|
| 64 |
+``plugin.sh`` is a bash script that will be called at specific points |
|
| 65 |
+during ``stack.sh``, ``unstack.sh``, and ``clean.sh``. It will be |
|
| 66 |
+called in the following way:: |
|
| 67 |
+ |
|
| 68 |
+ source $PATH/TO/plugin.sh <mode> [phase] |
|
| 73 | 69 |
|
| 74 |
-- **source** - Called by each script that utilizes ``extras.d`` hooks; |
|
| 75 |
- this replaces directly sourcing the ``lib/*`` script. |
|
| 76 |
-- **stack** - Called by ``stack.sh`` three times for different phases |
|
| 70 |
+``mode`` can be thought of as the major mode being called, currently |
|
| 71 |
+one of: ``stack``, ``unstack``, ``clean``. ``phase`` is used by modes |
|
| 72 |
+which have multiple points during their run where it's necessary to |
|
| 73 |
+be able to execute code. All existing ``mode`` and ``phase`` points |
|
| 74 |
+are considered **strong contracts** and won't be removed without a |
|
| 75 |
+reasonable deprecation period. Additional new ``mode`` or ``phase`` |
|
| 76 |
+points may be added at any time if we discover we need them to support |
|
| 77 |
+additional kinds of plugins in devstack. |
|
| 78 |
+ |
|
| 79 |
+The current full list of ``mode`` and ``phase`` are: |
|
| 80 |
+ |
|
| 81 |
+- **stack** - Called by ``stack.sh`` four times for different phases |
|
| 77 | 82 |
of its run: |
| 78 | 83 |
|
| 79 | 84 |
- **pre-install** - Called after system (OS) setup is complete and |
| ... | ... |
@@ -84,106 +89,90 @@ The arguments are: |
| 84 | 84 |
been configured. All configuration files for enabled services |
| 85 | 85 |
should exist at this point. |
| 86 | 86 |
- **extra** - Called near the end after layer 1 and 2 services have |
| 87 |
- been started. This is the existing hook and has not otherwise |
|
| 88 |
- changed. |
|
| 87 |
+ been started. |
|
| 89 | 88 |
|
| 90 | 89 |
- **unstack** - Called by ``unstack.sh`` before other services are shut |
| 91 | 90 |
down. |
| 92 | 91 |
- **clean** - Called by ``clean.sh`` before other services are cleaned, |
| 93 | 92 |
but after ``unstack.sh`` has been called. |
| 94 | 93 |
|
| 94 |
+Example plugin |
|
| 95 |
+==================== |
|
| 95 | 96 |
|
| 96 |
-Externally Hosted Plugins |
|
| 97 |
-========================= |
|
| 97 |
+An example plugin would look something as follows. |
|
| 98 | 98 |
|
| 99 |
-Based on the extras.d hooks, DevStack supports a standard mechansim |
|
| 100 |
-for including plugins from external repositories. The plugin interface |
|
| 101 |
-assumes the following: |
|
| 99 |
+``devstack/settings``:: |
|
| 102 | 100 |
|
| 103 |
-An external git repository that includes a ``devstack/`` top level |
|
| 104 |
-directory. Inside this directory there can be 2 files. |
|
| 101 |
+ # settings file for template |
|
| 102 |
+ enable_service template |
|
| 105 | 103 |
|
| 106 |
-- ``settings`` - a file containing global variables that will be |
|
| 107 |
- sourced very early in the process. This is helpful if other plugins |
|
| 108 |
- might depend on this one, and need access to global variables to do |
|
| 109 |
- their work. |
|
| 110 | 104 |
|
| 111 |
- Your settings should include any ``enable_service`` lines required |
|
| 112 |
- by your plugin. This is especially important if you are kicking off |
|
| 113 |
- services using ``run_process`` as it only works with enabled |
|
| 114 |
- services. |
|
| 105 |
+``devstack/plugin.sh``:: |
|
| 115 | 106 |
|
| 116 |
- Be careful to allow users to override global-variables for |
|
| 117 |
- customizing their environment. Usually it is best to provide a |
|
| 118 |
- default value only if the variable is unset or empty; e.g. in bash |
|
| 119 |
- syntax ``FOO=${FOO:-default}``.
|
|
| 107 |
+ # plugin.sh - DevStack plugin.sh dispatch script template |
|
| 120 | 108 |
|
| 121 |
-- ``plugin.sh`` - the actual plugin. It will be executed by devstack |
|
| 122 |
- during it's run. The run order will be done in the registration |
|
| 123 |
- order for these plugins, and will occur immediately after all in |
|
| 124 |
- tree extras.d dispatch at the phase in question. The plugin.sh |
|
| 125 |
- looks like the extras.d dispatcher above. |
|
| 109 |
+ function install_template {
|
|
| 110 |
+ ... |
|
| 111 |
+ } |
|
| 126 | 112 |
|
| 127 |
-Plugins are registered by adding the following to the localrc section |
|
| 128 |
-of ``local.conf``. |
|
| 113 |
+ function init_template {
|
|
| 114 |
+ ... |
|
| 115 |
+ } |
|
| 129 | 116 |
|
| 130 |
-They are added in the following format:: |
|
| 117 |
+ function configure_template {
|
|
| 118 |
+ ... |
|
| 119 |
+ } |
|
| 131 | 120 |
|
| 132 |
- [[local|localrc]] |
|
| 133 |
- enable_plugin <NAME> <GITURL> [GITREF] |
|
| 121 |
+ # check for service enabled |
|
| 122 |
+ if is_service_enabled template; then |
|
| 134 | 123 |
|
| 135 |
-- ``name`` - an arbitrary name. (ex: glustfs, docker, zaqar, congress) |
|
| 136 |
-- ``giturl`` - a valid git url that can be cloned |
|
| 137 |
-- ``gitref`` - an optional git ref (branch / ref / tag) that will be |
|
| 138 |
- cloned. Defaults to master. |
|
| 124 |
+ if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then |
|
| 125 |
+ # Set up system services |
|
| 126 |
+ echo_summary "Configuring system services Template" |
|
| 127 |
+ install_package cowsay |
|
| 139 | 128 |
|
| 140 |
-An example would be as follows:: |
|
| 129 |
+ elif [[ "$1" == "stack" && "$2" == "install" ]]; then |
|
| 130 |
+ # Perform installation of service source |
|
| 131 |
+ echo_summary "Installing Template" |
|
| 132 |
+ install_template |
|
| 141 | 133 |
|
| 142 |
- enable_plugin ec2api git://git.openstack.org/stackforge/ec2api |
|
| 134 |
+ elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then |
|
| 135 |
+ # Configure after the other layer 1 and 2 services have been configured |
|
| 136 |
+ echo_summary "Configuring Template" |
|
| 137 |
+ configure_template |
|
| 138 |
+ |
|
| 139 |
+ elif [[ "$1" == "stack" && "$2" == "extra" ]]; then |
|
| 140 |
+ # Initialize and start the template service |
|
| 141 |
+ echo_summary "Initializing Template" |
|
| 142 |
+ init_template |
|
| 143 |
+ fi |
|
| 144 |
+ |
|
| 145 |
+ if [[ "$1" == "unstack" ]]; then |
|
| 146 |
+ # Shut down template services |
|
| 147 |
+ # no-op |
|
| 148 |
+ : |
|
| 149 |
+ fi |
|
| 143 | 150 |
|
| 144 |
-Plugins for gate jobs |
|
| 145 |
- |
|
| 146 |
-All OpenStack plugins that wish to be used as gate jobs need to exist |
|
| 147 |
-in OpenStack's gerrit. Both ``openstack`` namespace and ``stackforge`` |
|
| 148 |
-namespace are fine. This allows testing of the plugin as well as |
|
| 149 |
-provides network isolation against upstream git repository failures |
|
| 150 |
-(which we see often enough to be an issue). |
|
| 151 |
- |
|
| 152 |
-Ideally plugins will be implemented as ``devstack`` directory inside |
|
| 153 |
-the project they are testing. For example, the stackforge/ec2-api |
|
| 154 |
-project has it's pluggin support in it's tree. |
|
| 155 |
- |
|
| 156 |
-In the cases where there is no "project tree" per say (like |
|
| 157 |
-integrating a backend storage configuration such as ceph or glusterfs) |
|
| 158 |
-it's also allowed to build a dedicated |
|
| 159 |
-``stackforge/devstack-plugin-FOO`` project to house the plugin. |
|
| 160 |
- |
|
| 161 |
-Note jobs must not require cloning of repositories during tests. |
|
| 162 |
-Tests must list their repository in the ``PROJECTS`` variable for |
|
| 163 |
-`devstack-gate |
|
| 164 |
-<https://git.openstack.org/cgit/openstack-infra/devstack-gate/tree/devstack-vm-gate-wrap.sh>`_ |
|
| 165 |
-for the repository to be available to the test. Further information |
|
| 166 |
-is provided in the project creator's guide. |
|
| 167 |
- |
|
| 168 |
-Hypervisor |
|
| 169 |
-========== |
|
| 170 |
- |
|
| 171 |
-Hypervisor plugins are fairly new and condense most hypervisor |
|
| 172 |
-configuration into one place. |
|
| 173 |
- |
|
| 174 |
-The initial plugin implemented was for Docker support and is a useful |
|
| 175 |
-template for the required support. Plugins are placed in |
|
| 176 |
-``lib/nova_plugins`` and named ``hypervisor-<name>`` where ``<name>`` is |
|
| 177 |
-the value of ``VIRT_DRIVER``. Plugins must define the following |
|
| 178 |
-functions: |
|
| 179 |
- |
|
| 180 |
-- ``install_nova_hypervisor`` - install any external requirements |
|
| 181 |
-- ``configure_nova_hypervisor`` - make configuration changes, including |
|
| 182 |
- those to other services |
|
| 183 |
-- ``start_nova_hypervisor`` - start any external services |
|
| 184 |
-- ``stop_nova_hypervisor`` - stop any external services |
|
| 185 |
-- ``cleanup_nova_hypervisor`` - remove transient data and cache |
|
| 151 |
+ if [[ "$1" == "clean" ]]; then |
|
| 152 |
+ # Remove state and transient data |
|
| 153 |
+ # Remember clean.sh first calls unstack.sh |
|
| 154 |
+ # no-op |
|
| 155 |
+ : |
|
| 156 |
+ fi |
|
| 157 |
+ fi |
|
| 158 |
+ |
|
| 159 |
+Plugin Execution Order |
|
| 160 |
+====================== |
|
| 161 |
+ |
|
| 162 |
+Plugins are run after in tree services at each of the stages |
|
| 163 |
+above. For example, if you need something to happen before Keystone |
|
| 164 |
+starts, you should do that at the ``post-config`` phase. |
|
| 165 |
+ |
|
| 166 |
+Multiple plugins can be specified in your ``local.conf``. When that |
|
| 167 |
+happens the plugins will be executed **in order** at each phase. This |
|
| 168 |
+allows plugins to conceptually depend on each other through |
|
| 169 |
+documenting to the user the order they must be declared. A formal |
|
| 170 |
+dependency mechanism is beyond the scope of the current work. |
|
| 186 | 171 |
|
| 187 | 172 |
System Packages |
| 188 | 173 |
=============== |
| ... | ... |
@@ -205,3 +194,47 @@ repository: |
| 205 | 205 |
|
| 206 | 206 |
- ``./devstack/files/rpms-suse/$plugin_name`` - Packages to install when |
| 207 | 207 |
running on SUSE Linux or openSUSE. |
| 208 |
+ |
|
| 209 |
+ |
|
| 210 |
+Using Plugins in the OpenStack Gate |
|
| 211 |
+=================================== |
|
| 212 |
+ |
|
| 213 |
+For everyday use, DevStack plugins can exist in any git tree that's |
|
| 214 |
+accessible on the internet. However, when using DevStack plugins in |
|
| 215 |
+the OpenStack gate, they must live in projects in OpenStack's |
|
| 216 |
+gerrit. Both ``openstack`` namespace and ``stackforge`` namespace are |
|
| 217 |
+fine. This allows testing of the plugin as well as provides network |
|
| 218 |
+isolation against upstream git repository failures (which we see often |
|
| 219 |
+enough to be an issue). |
|
| 220 |
+ |
|
| 221 |
+Ideally a plugin will be included within the ``devstack`` directory of |
|
| 222 |
+the project they are being tested. For example, the stackforge/ec2-api |
|
| 223 |
+project has its pluggin support in its own tree. |
|
| 224 |
+ |
|
| 225 |
+However, some times a DevStack plugin might be used solely to |
|
| 226 |
+configure a backend service that will be used by the rest of |
|
| 227 |
+OpenStack, so there is no "project tree" per say. Good examples |
|
| 228 |
+include: integration of back end storage (e.g. ceph or glusterfs), |
|
| 229 |
+integration of SDN controllers (e.g. ovn, OpenDayLight), or |
|
| 230 |
+integration of alternate RPC systems (e.g. zmq, qpid). In these cases |
|
| 231 |
+the best practice is to build a dedicated |
|
| 232 |
+``stackforge/devstack-plugin-FOO`` project. |
|
| 233 |
+ |
|
| 234 |
+To enable a plugin to be used in a gate job, the following lines will |
|
| 235 |
+be needed in your project.yaml definition:: |
|
| 236 |
+ |
|
| 237 |
+ # Because we are testing a non standard project, add the |
|
| 238 |
+ # our project repository. This makes zuul do the right |
|
| 239 |
+ # reference magic for testing changes. |
|
| 240 |
+ export PROJECTS="stackforge/ec2-api $PROJECTS" |
|
| 241 |
+ |
|
| 242 |
+ # note the actual url here is somewhat irrelevant because it |
|
| 243 |
+ # caches in nodepool, however make it a valid url for |
|
| 244 |
+ # documentation purposes. |
|
| 245 |
+ export DEVSTACK_LOCAL_CONFIG="enable_plugin ec2-api git://git.openstack.org/stackforge/ec2-api" |
|
| 246 |
+ |
|
| 247 |
+See Also |
|
| 248 |
+======== |
|
| 249 |
+ |
|
| 250 |
+For additional inspiration on devstack plugins you can check out the |
|
| 251 |
+`Plugin Registry <plugin-registry.html>`_. |