blob: a9e205928016ffb3e02e26b6b918d82d991384f7 [file] [log] [blame]
Matthew Treinishe8ab5f92017-03-01 15:25:39 -05001.. _tempest_plugin:
2
Matthew Treinish3a851dc2015-07-30 11:34:03 -04003=============================
4Tempest Test Plugin Interface
5=============================
6
7Tempest has an external test plugin interface which enables anyone to integrate
deepak_mouryae495cd22018-07-16 12:38:17 +05308an external test suite as part of a Tempest run. This will let any project
9leverage being run with the rest of the Tempest suite while not requiring the
10tests live in the Tempest tree.
Matthew Treinish3a851dc2015-07-30 11:34:03 -040011
12Creating a plugin
13=================
14
15Creating a plugin is fairly straightforward and doesn't require much additional
Andrea Frittoli (andreaf)1370baf2016-04-29 14:26:22 -050016effort on top of creating a test suite using tempest.lib. One thing to note with
deepak_mouryae495cd22018-07-16 12:38:17 +053017doing this is that the interfaces exposed by Tempest are not considered stable
18(with the exception of configuration variables whichever effort goes into
19ensuring backward compatibility). You should not need to import anything from
20Tempest itself except where explicitly noted.
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010021
22Stable Tempest APIs plugins may use
23-----------------------------------
24
deepak_mouryae495cd22018-07-16 12:38:17 +053025As noted above, several Tempest APIs are acceptable to use from plugins, while
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010026others are not. A list of stable APIs available to plugins is provided below:
27
28* tempest.lib.*
29* tempest.config
30* tempest.test_discover.plugins
Andrea Frittoli17347f02017-07-26 16:18:30 +010031* tempest.common.credentials_factory
Andrea Frittolibf142fc2017-10-23 17:30:18 +020032* tempest.clients
33* tempest.test
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010034
deepak_mouryae495cd22018-07-16 12:38:17 +053035If there is an interface from Tempest that you need to rely on in your plugin
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010036which is not listed above, it likely needs to be migrated to tempest.lib. In
37that situation, file a bug, push a migration patch, etc. to expedite providing
38the interface in a reliable manner.
Matthew Treinish3a851dc2015-07-30 11:34:03 -040039
Marc Koderer66210aa2015-10-26 10:52:32 +010040Plugin Cookiecutter
41-------------------
42
43In order to create the basic structure with base classes and test directories
44you can use the tempest-plugin-cookiecutter project::
45
caoyuan349ba752019-04-23 19:40:06 +080046 > pip install -U cookiecutter && cookiecutter https://opendev.org/openstack/tempest-plugin-cookiecutter
Marc Koderer66210aa2015-10-26 10:52:32 +010047
48 Cloning into 'tempest-plugin-cookiecutter'...
49 remote: Counting objects: 17, done.
50 remote: Compressing objects: 100% (13/13), done.
51 remote: Total 17 (delta 1), reused 14 (delta 1)
52 Unpacking objects: 100% (17/17), done.
53 Checking connectivity... done.
54 project (default is "sample")? foo
55 testclass (default is "SampleTempestPlugin")? FooTempestPlugin
56
57This would create a folder called ``foo_tempest_plugin/`` with all necessary
58basic classes. You only need to move/create your test in
59``foo_tempest_plugin/tests``.
60
61Entry Point
62-----------
63
64Once you've created your plugin class you need to add an entry point to your
deepak_mouryae495cd22018-07-16 12:38:17 +053065project to enable Tempest to find the plugin. The entry point must be added
Marc Koderer66210aa2015-10-26 10:52:32 +010066to the "tempest.test_plugins" namespace.
67
68If you are using pbr this is fairly straightforward, in the setup.cfg just add
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +090069something like the following:
70
71.. code-block:: ini
Marc Koderer66210aa2015-10-26 10:52:32 +010072
73 [entry_points]
74 tempest.test_plugins =
75 plugin_name = module.path:PluginClass
76
Matthew Treinish00686f22016-03-09 15:39:19 -050077Standalone Plugin vs In-repo Plugin
78-----------------------------------
79
deepak_mouryae495cd22018-07-16 12:38:17 +053080Since all that's required for a plugin to be detected by Tempest is a valid
Matthew Treinish00686f22016-03-09 15:39:19 -050081setuptools entry point in the proper namespace there is no difference from the
deepak_mouryae495cd22018-07-16 12:38:17 +053082Tempest perspective on either creating a separate python package to
Matthew Treinish00686f22016-03-09 15:39:19 -050083house the plugin or adding the code to an existing python project. However,
84there are tradeoffs to consider when deciding which approach to take when
85creating a new plugin.
86
87If you create a separate python project for your plugin this makes a lot of
88things much easier. Firstly it makes packaging and versioning much simpler, you
89can easily decouple the requirements for the plugin from the requirements for
90the other project. It lets you version the plugin independently and maintain a
91single version of the test code across project release boundaries (see the
92`Branchless Tempest Spec`_ for more details on this). It also greatly
93simplifies the install time story for external users. Instead of having to
deepak_mouryae495cd22018-07-16 12:38:17 +053094install the right version of a project in the same python namespace as Tempest
Matthew Treinish00686f22016-03-09 15:39:19 -050095they simply need to pip install the plugin in that namespace. It also means
deepak_mouryae495cd22018-07-16 12:38:17 +053096that users don't have to worry about inadvertently installing a Tempest plugin
Matthew Treinish00686f22016-03-09 15:39:19 -050097when they install another package.
98
sunqingliang68606c832018-11-09 14:25:17 +080099.. _Branchless Tempest Spec: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
Matthew Treinish00686f22016-03-09 15:39:19 -0500100
101The sole advantage to integrating a plugin into an existing python project is
102that it enables you to land code changes at the same time you land test changes
103in the plugin. This reduces some of the burden on contributors by not having
104to land 2 changes to add a new API feature and then test it and doing it as a
105single combined commit.
106
107
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400108Plugin Class
Marc Koderer66210aa2015-10-26 10:52:32 +0100109============
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400110
deepak_mouryae495cd22018-07-16 12:38:17 +0530111To provide Tempest with all the required information it needs to be able to run
112your plugin you need to create a plugin class which Tempest will load and call
113to get information when it needs. To simplify creating this Tempest provides an
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400114abstract class that should be used as the parent for your plugin. To use this
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900115you would do something like the following:
116
117.. code-block:: python
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400118
YAMAMOTO Takashicb2ac6e2015-10-19 15:54:42 +0900119 from tempest.test_discover import plugins
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400120
YAMAMOTO Takashicb2ac6e2015-10-19 15:54:42 +0900121 class MyPlugin(plugins.TempestPlugin):
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400122
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100123Then you need to ensure you locally define all of the mandatory methods in the
124abstract class, you can refer to the api doc below for a reference of what that
125entails.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400126
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400127Abstract Plugin Class
Marc Koderer66210aa2015-10-26 10:52:32 +0100128---------------------
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400129
130.. autoclass:: tempest.test_discover.plugins.TempestPlugin
131 :members:
132
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400133Plugin Structure
Marc Koderer66210aa2015-10-26 10:52:32 +0100134================
zhufle9241b52017-12-06 15:41:08 +0800135While there are no hard and fast rules for the structure of a plugin, there are
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400136basically no constraints on what the plugin looks like as long as the 2 steps
137above are done. However, there are some recommended patterns to follow to make
138it easy for people to contribute and work with your plugin. For example, if you
139create a directory structure with something like::
140
141 plugin_dir/
142 config.py
143 plugin.py
144 tests/
145 api/
146 scenario/
147 services/
148 client.py
149
deepak_mouryae495cd22018-07-16 12:38:17 +0530150That will mirror what people expect from Tempest. The file
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400151
152* **config.py**: contains any plugin specific configuration variables
153* **plugin.py**: contains the plugin class used for the entry point
154* **tests**: the directory where test discovery will be run, all tests should
155 be under this dir
156* **services**: where the plugin specific service clients are
157
158Additionally, when you're creating the plugin you likely want to follow all
deepak_mouryae495cd22018-07-16 12:38:17 +0530159of the Tempest developer and reviewer documentation to ensure that the tests
160being added in the plugin act and behave like the rest of Tempest.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400161
Matthew Treinish9392a832015-08-24 10:00:49 -0400162Dealing with configuration options
Marc Koderer66210aa2015-10-26 10:52:32 +0100163----------------------------------
Matthew Treinish9392a832015-08-24 10:00:49 -0400164
deepak_mouryae495cd22018-07-16 12:38:17 +0530165Historically, Tempest didn't provide external guarantees on its configuration
166options. However, with the introduction of the plugin interface, this is no
Matthew Treinish9392a832015-08-24 10:00:49 -0400167longer the case. An external plugin can rely on using any configuration option
168coming from Tempest, there will be at least a full deprecation cycle for any
169option before it's removed. However, just the options provided by Tempest
170may not be sufficient for the plugin. If you need to add any plugin specific
171configuration options you should use the ``register_opts`` and
172``get_opt_lists`` methods to pass them to Tempest when the plugin is loaded.
173When adding configuration options the ``register_opts`` method gets passed the
deepak_mouryae495cd22018-07-16 12:38:17 +0530174CONF object from Tempest. This enables the plugin to add options to both
Matthew Treinish9392a832015-08-24 10:00:49 -0400175existing sections and also create new configuration sections for new options.
176
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100177Service Clients
178---------------
179
180If a plugin defines a service client, it is beneficial for it to implement the
181``get_service_clients`` method in the plugin class. All service clients which
182are exposed via this interface will be automatically configured and be
183available in any instance of the service clients class, defined in
184``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are
185installed, all service clients from all plugins will be registered, making it
186easy to write tests which rely on multiple APIs whose service clients are in
187different plugins.
188
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900189Example implementation of ``get_service_clients``:
190
191.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100192
193 def get_service_clients(self):
194 # Example implementation with two service clients
195 my_service1_config = config.service_client_config('my_service')
196 params_my_service1 = {
197 'name': 'my_service_v1',
198 'service_version': 'my_service.v1',
199 'module_path': 'plugin_tempest_tests.services.my_service.v1',
200 'client_names': ['API1Client', 'API2Client'],
201 }
202 params_my_service1.update(my_service_config)
203 my_service2_config = config.service_client_config('my_service')
204 params_my_service2 = {
205 'name': 'my_service_v2',
206 'service_version': 'my_service.v2',
207 'module_path': 'plugin_tempest_tests.services.my_service.v2',
208 'client_names': ['API1Client', 'API2Client'],
209 }
210 params_my_service2.update(my_service2_config)
211 return [params_my_service1, params_my_service2]
212
213Parameters:
214
215* **name**: Name of the attribute used to access the ``ClientsFactory`` from
216 the ``ServiceClients`` instance. See example below.
217* **service_version**: Tempest enforces a single implementation for each
218 service client. Available service clients are held in a ``ClientsRegistry``
219 singleton, and registered with ``service_version``, which means that
220 ``service_version`` must be unique and it should represent the service API
221 and version implemented by the service client.
222* **module_path**: Relative to the service client module, from the root of the
223 plugin.
224* **client_names**: Name of the classes that implement service clients in the
225 service clients module.
226
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900227Example usage of the service clients in tests:
228
229.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100230
231 # my_creds is instance of tempest.lib.auth.Credentials
232 # identity_uri is v2 or v3 depending on the configuration
233 from tempest.lib.services import clients
234
235 my_clients = clients.ServiceClients(my_creds, identity_uri)
236 my_service1_api1_client = my_clients.my_service_v1.API1Client()
237 my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any')
238
239Automatic configuration and registration of service clients imposes some extra
240constraints on the structure of the configuration options exposed by the
241plugin.
242
243First ``service_version`` should be in the format `service_config[.version]`.
244The `.version` part is optional, and should only be used if there are multiple
245versions of the same API available. The `service_config` must match the name of
246a configuration options group defined by the plugin. Different versions of one
247API must share the same configuration group.
248
249Second the configuration options group `service_config` must contain the
250following options:
251
252* `catalog_type`: corresponds to `service` in the catalog
253* `endpoint_type`
254
255The following options will be honoured if defined, but they are not mandatory,
256as they do not necessarily apply to all service clients.
257
258* `region`: default to identity.region
259* `build_timeout` : default to compute.build_timeout
260* `build_interval`: default to compute.build_interval
261
262Third the service client classes should inherit from ``RestClient``, should
263accept generic keyword arguments, and should pass those arguments to the
264``__init__`` method of ``RestClient``. Extra arguments can be added. For
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900265instance:
266
267.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100268
269 class MyAPIClient(rest_client.RestClient):
270
271 def __init__(self, auth_provider, service, region,
272 my_arg, my_arg2=True, **kwargs):
273 super(MyAPIClient, self).__init__(
274 auth_provider, service, region, **kwargs)
275 self.my_arg = my_arg
276 self.my_args2 = my_arg
277
278Finally the service client should be structured in a python module, so that all
279service client classes are importable from it. Each major API version should
280have its own module.
281
282The following folder and module structure is recommended for a single major
283API version::
284
285 plugin_dir/
286 services/
287 __init__.py
288 client_api_1.py
289 client_api_2.py
290
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900291The content of __init__.py module should be:
292
293.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100294
295 from client_api_1.py import API1Client
296 from client_api_2.py import API2Client
297
298 __all__ = ['API1Client', 'API2Client']
299
300The following folder and module structure is recommended for multiple major
301API version::
302
303 plugin_dir/
304 services/
305 v1/
306 __init__.py
307 client_api_1.py
308 client_api_2.py
309 v2/
310 __init__.py
311 client_api_1.py
312 client_api_2.py
313
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900314The content each of __init__.py module under vN should be:
315
316.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100317
318 from client_api_1.py import API1Client
319 from client_api_2.py import API2Client
320
321 __all__ = ['API1Client', 'API2Client']
322
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400323Using Plugins
324=============
325
326Tempest will automatically discover any installed plugins when it is run. So by
327just installing the python packages which contain your plugin you'll be using
deepak_mouryae495cd22018-07-16 12:38:17 +0530328them with Tempest, nothing else is really required.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400329
330However, you should take care when installing plugins. By their very nature
deepak_mouryae495cd22018-07-16 12:38:17 +0530331there are no guarantees when running Tempest with plugins enabled about the
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400332quality of the plugin. Additionally, while there is no limitation on running
deepak_mouryae495cd22018-07-16 12:38:17 +0530333with multiple plugins, it's worth noting that poorly written plugins might not
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400334properly isolate their tests which could cause unexpected cross interactions
335between plugins.
336
337Notes for using plugins with virtualenvs
338----------------------------------------
339
deepak_mouryae495cd22018-07-16 12:38:17 +0530340When using a Tempest inside a virtualenv (like when running under tox) you have
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400341to ensure that the package that contains your plugin is either installed in the
342venv too or that you have system site-packages enabled. The virtualenv will
deepak_mouryae495cd22018-07-16 12:38:17 +0530343isolate the Tempest install from the rest of your system so just installing the
344plugin package on your system and then running Tempest inside a venv will not
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400345work.
346
347Tempest also exposes a tox job, all-plugin, which will setup a tox virtualenv
348with system site-packages enabled. This will let you leverage tox without
349requiring to manually install plugins in the tox venv before running tests.