blob: 6b3082575587fe35d01e18ac9f9d1d4a65be7cb9 [file] [log] [blame]
Matthew Treinish3a851dc2015-07-30 11:34:03 -04001=============================
2Tempest Test Plugin Interface
3=============================
4
5Tempest has an external test plugin interface which enables anyone to integrate
6an external test suite as part of a tempest run. This will let any project
7leverage being run with the rest of the tempest suite while not requiring the
8tests live in the tempest tree.
9
10Creating a plugin
11=================
12
13Creating a plugin is fairly straightforward and doesn't require much additional
Andrea Frittoli (andreaf)1370baf2016-04-29 14:26:22 -050014effort on top of creating a test suite using tempest.lib. One thing to note with
Matthew Treinish3a851dc2015-07-30 11:34:03 -040015doing this is that the interfaces exposed by tempest are not considered stable
16(with the exception of configuration variables which ever effort goes into
17ensuring backwards compatibility). You should not need to import anything from
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010018tempest itself except where explicitly noted.
19
20Stable Tempest APIs plugins may use
21-----------------------------------
22
23As noted above, several tempest APIs are acceptable to use from plugins, while
24others are not. A list of stable APIs available to plugins is provided below:
25
26* tempest.lib.*
27* tempest.config
28* tempest.test_discover.plugins
29
30If there is an interface from tempest that you need to rely on in your plugin
31which is not listed above, it likely needs to be migrated to tempest.lib. In
32that situation, file a bug, push a migration patch, etc. to expedite providing
33the interface in a reliable manner.
Matthew Treinish3a851dc2015-07-30 11:34:03 -040034
Marc Koderer66210aa2015-10-26 10:52:32 +010035Plugin Cookiecutter
36-------------------
37
38In order to create the basic structure with base classes and test directories
39you can use the tempest-plugin-cookiecutter project::
40
Yuiko Takadaccb2bbf2015-11-17 10:09:44 +090041 > pip install -U cookiecutter && cookiecutter https://git.openstack.org/openstack/tempest-plugin-cookiecutter
Marc Koderer66210aa2015-10-26 10:52:32 +010042
43 Cloning into 'tempest-plugin-cookiecutter'...
44 remote: Counting objects: 17, done.
45 remote: Compressing objects: 100% (13/13), done.
46 remote: Total 17 (delta 1), reused 14 (delta 1)
47 Unpacking objects: 100% (17/17), done.
48 Checking connectivity... done.
49 project (default is "sample")? foo
50 testclass (default is "SampleTempestPlugin")? FooTempestPlugin
51
52This would create a folder called ``foo_tempest_plugin/`` with all necessary
53basic classes. You only need to move/create your test in
54``foo_tempest_plugin/tests``.
55
56Entry Point
57-----------
58
59Once you've created your plugin class you need to add an entry point to your
60project to enable tempest to find the plugin. The entry point must be added
61to the "tempest.test_plugins" namespace.
62
63If you are using pbr this is fairly straightforward, in the setup.cfg just add
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +090064something like the following:
65
66.. code-block:: ini
Marc Koderer66210aa2015-10-26 10:52:32 +010067
68 [entry_points]
69 tempest.test_plugins =
70 plugin_name = module.path:PluginClass
71
Matthew Treinish00686f22016-03-09 15:39:19 -050072Standalone Plugin vs In-repo Plugin
73-----------------------------------
74
75Since all that's required for a plugin to be detected by tempest is a valid
76setuptools entry point in the proper namespace there is no difference from the
77tempest perspective on either creating a separate python package to
78house the plugin or adding the code to an existing python project. However,
79there are tradeoffs to consider when deciding which approach to take when
80creating a new plugin.
81
82If you create a separate python project for your plugin this makes a lot of
83things much easier. Firstly it makes packaging and versioning much simpler, you
84can easily decouple the requirements for the plugin from the requirements for
85the other project. It lets you version the plugin independently and maintain a
86single version of the test code across project release boundaries (see the
87`Branchless Tempest Spec`_ for more details on this). It also greatly
88simplifies the install time story for external users. Instead of having to
89install the right version of a project in the same python namespace as tempest
90they simply need to pip install the plugin in that namespace. It also means
91that users don't have to worry about inadvertently installing a tempest plugin
92when they install another package.
93
94.. _Branchless Tempest Spec: http://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
95
96The sole advantage to integrating a plugin into an existing python project is
97that it enables you to land code changes at the same time you land test changes
98in the plugin. This reduces some of the burden on contributors by not having
99to land 2 changes to add a new API feature and then test it and doing it as a
100single combined commit.
101
102
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400103Plugin Class
Marc Koderer66210aa2015-10-26 10:52:32 +0100104============
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400105
106To provide tempest with all the required information it needs to be able to run
107your plugin you need to create a plugin class which tempest will load and call
108to get information when it needs. To simplify creating this tempest provides an
109abstract class that should be used as the parent for your plugin. To use this
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900110you would do something like the following:
111
112.. code-block:: python
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400113
YAMAMOTO Takashicb2ac6e2015-10-19 15:54:42 +0900114 from tempest.test_discover import plugins
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400115
YAMAMOTO Takashicb2ac6e2015-10-19 15:54:42 +0900116 class MyPlugin(plugins.TempestPlugin):
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400117
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100118Then you need to ensure you locally define all of the mandatory methods in the
119abstract class, you can refer to the api doc below for a reference of what that
120entails.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400121
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400122Abstract Plugin Class
Marc Koderer66210aa2015-10-26 10:52:32 +0100123---------------------
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400124
125.. autoclass:: tempest.test_discover.plugins.TempestPlugin
126 :members:
127
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400128Plugin Structure
Marc Koderer66210aa2015-10-26 10:52:32 +0100129================
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400130While there are no hard and fast rules for the structure a plugin, there are
131basically no constraints on what the plugin looks like as long as the 2 steps
132above are done. However, there are some recommended patterns to follow to make
133it easy for people to contribute and work with your plugin. For example, if you
134create a directory structure with something like::
135
136 plugin_dir/
137 config.py
138 plugin.py
139 tests/
140 api/
141 scenario/
142 services/
143 client.py
144
145That will mirror what people expect from tempest. The file
146
147* **config.py**: contains any plugin specific configuration variables
148* **plugin.py**: contains the plugin class used for the entry point
149* **tests**: the directory where test discovery will be run, all tests should
150 be under this dir
151* **services**: where the plugin specific service clients are
152
153Additionally, when you're creating the plugin you likely want to follow all
154of the tempest developer and reviewer documentation to ensure that the tests
155being added in the plugin act and behave like the rest of tempest.
156
Matthew Treinish9392a832015-08-24 10:00:49 -0400157Dealing with configuration options
Marc Koderer66210aa2015-10-26 10:52:32 +0100158----------------------------------
Matthew Treinish9392a832015-08-24 10:00:49 -0400159
160Historically Tempest didn't provide external guarantees on its configuration
161options. However, with the introduction of the plugin interface this is no
162longer the case. An external plugin can rely on using any configuration option
163coming from Tempest, there will be at least a full deprecation cycle for any
164option before it's removed. However, just the options provided by Tempest
165may not be sufficient for the plugin. If you need to add any plugin specific
166configuration options you should use the ``register_opts`` and
167``get_opt_lists`` methods to pass them to Tempest when the plugin is loaded.
168When adding configuration options the ``register_opts`` method gets passed the
169CONF object from tempest. This enables the plugin to add options to both
170existing sections and also create new configuration sections for new options.
171
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100172Service Clients
173---------------
174
175If a plugin defines a service client, it is beneficial for it to implement the
176``get_service_clients`` method in the plugin class. All service clients which
177are exposed via this interface will be automatically configured and be
178available in any instance of the service clients class, defined in
179``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are
180installed, all service clients from all plugins will be registered, making it
181easy to write tests which rely on multiple APIs whose service clients are in
182different plugins.
183
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900184Example implementation of ``get_service_clients``:
185
186.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100187
188 def get_service_clients(self):
189 # Example implementation with two service clients
190 my_service1_config = config.service_client_config('my_service')
191 params_my_service1 = {
192 'name': 'my_service_v1',
193 'service_version': 'my_service.v1',
194 'module_path': 'plugin_tempest_tests.services.my_service.v1',
195 'client_names': ['API1Client', 'API2Client'],
196 }
197 params_my_service1.update(my_service_config)
198 my_service2_config = config.service_client_config('my_service')
199 params_my_service2 = {
200 'name': 'my_service_v2',
201 'service_version': 'my_service.v2',
202 'module_path': 'plugin_tempest_tests.services.my_service.v2',
203 'client_names': ['API1Client', 'API2Client'],
204 }
205 params_my_service2.update(my_service2_config)
206 return [params_my_service1, params_my_service2]
207
208Parameters:
209
210* **name**: Name of the attribute used to access the ``ClientsFactory`` from
211 the ``ServiceClients`` instance. See example below.
212* **service_version**: Tempest enforces a single implementation for each
213 service client. Available service clients are held in a ``ClientsRegistry``
214 singleton, and registered with ``service_version``, which means that
215 ``service_version`` must be unique and it should represent the service API
216 and version implemented by the service client.
217* **module_path**: Relative to the service client module, from the root of the
218 plugin.
219* **client_names**: Name of the classes that implement service clients in the
220 service clients module.
221
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900222Example usage of the service clients in tests:
223
224.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100225
226 # my_creds is instance of tempest.lib.auth.Credentials
227 # identity_uri is v2 or v3 depending on the configuration
228 from tempest.lib.services import clients
229
230 my_clients = clients.ServiceClients(my_creds, identity_uri)
231 my_service1_api1_client = my_clients.my_service_v1.API1Client()
232 my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any')
233
234Automatic configuration and registration of service clients imposes some extra
235constraints on the structure of the configuration options exposed by the
236plugin.
237
238First ``service_version`` should be in the format `service_config[.version]`.
239The `.version` part is optional, and should only be used if there are multiple
240versions of the same API available. The `service_config` must match the name of
241a configuration options group defined by the plugin. Different versions of one
242API must share the same configuration group.
243
244Second the configuration options group `service_config` must contain the
245following options:
246
247* `catalog_type`: corresponds to `service` in the catalog
248* `endpoint_type`
249
250The following options will be honoured if defined, but they are not mandatory,
251as they do not necessarily apply to all service clients.
252
253* `region`: default to identity.region
254* `build_timeout` : default to compute.build_timeout
255* `build_interval`: default to compute.build_interval
256
257Third the service client classes should inherit from ``RestClient``, should
258accept generic keyword arguments, and should pass those arguments to the
259``__init__`` method of ``RestClient``. Extra arguments can be added. For
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900260instance:
261
262.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100263
264 class MyAPIClient(rest_client.RestClient):
265
266 def __init__(self, auth_provider, service, region,
267 my_arg, my_arg2=True, **kwargs):
268 super(MyAPIClient, self).__init__(
269 auth_provider, service, region, **kwargs)
270 self.my_arg = my_arg
271 self.my_args2 = my_arg
272
273Finally the service client should be structured in a python module, so that all
274service client classes are importable from it. Each major API version should
275have its own module.
276
277The following folder and module structure is recommended for a single major
278API version::
279
280 plugin_dir/
281 services/
282 __init__.py
283 client_api_1.py
284 client_api_2.py
285
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900286The content of __init__.py module should be:
287
288.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100289
290 from client_api_1.py import API1Client
291 from client_api_2.py import API2Client
292
293 __all__ = ['API1Client', 'API2Client']
294
295The following folder and module structure is recommended for multiple major
296API version::
297
298 plugin_dir/
299 services/
300 v1/
301 __init__.py
302 client_api_1.py
303 client_api_2.py
304 v2/
305 __init__.py
306 client_api_1.py
307 client_api_2.py
308
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900309The content each of __init__.py module under vN should be:
310
311.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100312
313 from client_api_1.py import API1Client
314 from client_api_2.py import API2Client
315
316 __all__ = ['API1Client', 'API2Client']
317
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400318Using Plugins
319=============
320
321Tempest will automatically discover any installed plugins when it is run. So by
322just installing the python packages which contain your plugin you'll be using
323them with tempest, nothing else is really required.
324
325However, you should take care when installing plugins. By their very nature
326there are no guarantees when running tempest with plugins enabled about the
327quality of the plugin. Additionally, while there is no limitation on running
328with multiple plugins it's worth noting that poorly written plugins might not
329properly isolate their tests which could cause unexpected cross interactions
330between plugins.
331
332Notes for using plugins with virtualenvs
333----------------------------------------
334
335When using a tempest inside a virtualenv (like when running under tox) you have
336to ensure that the package that contains your plugin is either installed in the
337venv too or that you have system site-packages enabled. The virtualenv will
338isolate the tempest install from the rest of your system so just installing the
339plugin package on your system and then running tempest inside a venv will not
340work.
341
342Tempest also exposes a tox job, all-plugin, which will setup a tox virtualenv
343with system site-packages enabled. This will let you leverage tox without
344requiring to manually install plugins in the tox venv before running tests.