blob: 07713180d9f2d584f7697e2d1e7aa0d68841b61b [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
Ghanshyam Mannc2ca52d2021-03-25 21:29:14 -050034* tempest.scenario.manager
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010035
deepak_mouryae495cd22018-07-16 12:38:17 +053036If there is an interface from Tempest that you need to rely on in your plugin
Kiall Mac Innes9e6f9742016-05-23 16:20:55 +010037which is not listed above, it likely needs to be migrated to tempest.lib. In
38that situation, file a bug, push a migration patch, etc. to expedite providing
39the interface in a reliable manner.
Matthew Treinish3a851dc2015-07-30 11:34:03 -040040
Marc Koderer66210aa2015-10-26 10:52:32 +010041Plugin Cookiecutter
42-------------------
43
44In order to create the basic structure with base classes and test directories
45you can use the tempest-plugin-cookiecutter project::
46
Ghanshyam Mann0aa06362020-07-15 13:38:42 -050047 > pip install -U cookiecutter && cookiecutter https://opendev.org/openstack/tempest-plugin-cookiecutter.git
Marc Koderer66210aa2015-10-26 10:52:32 +010048
49 Cloning into 'tempest-plugin-cookiecutter'...
50 remote: Counting objects: 17, done.
51 remote: Compressing objects: 100% (13/13), done.
52 remote: Total 17 (delta 1), reused 14 (delta 1)
53 Unpacking objects: 100% (17/17), done.
54 Checking connectivity... done.
55 project (default is "sample")? foo
56 testclass (default is "SampleTempestPlugin")? FooTempestPlugin
57
58This would create a folder called ``foo_tempest_plugin/`` with all necessary
59basic classes. You only need to move/create your test in
60``foo_tempest_plugin/tests``.
61
62Entry Point
63-----------
64
65Once you've created your plugin class you need to add an entry point to your
deepak_mouryae495cd22018-07-16 12:38:17 +053066project to enable Tempest to find the plugin. The entry point must be added
Marc Koderer66210aa2015-10-26 10:52:32 +010067to the "tempest.test_plugins" namespace.
68
69If you are using pbr this is fairly straightforward, in the setup.cfg just add
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +090070something like the following:
71
72.. code-block:: ini
Marc Koderer66210aa2015-10-26 10:52:32 +010073
74 [entry_points]
75 tempest.test_plugins =
76 plugin_name = module.path:PluginClass
77
Matthew Treinish00686f22016-03-09 15:39:19 -050078Standalone Plugin vs In-repo Plugin
79-----------------------------------
80
deepak_mouryae495cd22018-07-16 12:38:17 +053081Since all that's required for a plugin to be detected by Tempest is a valid
Matthew Treinish00686f22016-03-09 15:39:19 -050082setuptools entry point in the proper namespace there is no difference from the
deepak_mouryae495cd22018-07-16 12:38:17 +053083Tempest perspective on either creating a separate python package to
Matthew Treinish00686f22016-03-09 15:39:19 -050084house the plugin or adding the code to an existing python project. However,
85there are tradeoffs to consider when deciding which approach to take when
86creating a new plugin.
87
88If you create a separate python project for your plugin this makes a lot of
89things much easier. Firstly it makes packaging and versioning much simpler, you
90can easily decouple the requirements for the plugin from the requirements for
91the other project. It lets you version the plugin independently and maintain a
92single version of the test code across project release boundaries (see the
93`Branchless Tempest Spec`_ for more details on this). It also greatly
94simplifies the install time story for external users. Instead of having to
deepak_mouryae495cd22018-07-16 12:38:17 +053095install the right version of a project in the same python namespace as Tempest
Matthew Treinish00686f22016-03-09 15:39:19 -050096they simply need to pip install the plugin in that namespace. It also means
deepak_mouryae495cd22018-07-16 12:38:17 +053097that users don't have to worry about inadvertently installing a Tempest plugin
Matthew Treinish00686f22016-03-09 15:39:19 -050098when they install another package.
99
sunqingliang68606c832018-11-09 14:25:17 +0800100.. _Branchless Tempest Spec: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html
Matthew Treinish00686f22016-03-09 15:39:19 -0500101
102The sole advantage to integrating a plugin into an existing python project is
103that it enables you to land code changes at the same time you land test changes
104in the plugin. This reduces some of the burden on contributors by not having
105to land 2 changes to add a new API feature and then test it and doing it as a
106single combined commit.
107
108
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400109Plugin Class
Marc Koderer66210aa2015-10-26 10:52:32 +0100110============
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400111
deepak_mouryae495cd22018-07-16 12:38:17 +0530112To provide Tempest with all the required information it needs to be able to run
113your plugin you need to create a plugin class which Tempest will load and call
114to get information when it needs. To simplify creating this Tempest provides an
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400115abstract class that should be used as the parent for your plugin. To use this
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900116you would do something like the following:
117
118.. code-block:: python
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400119
YAMAMOTO Takashicb2ac6e2015-10-19 15:54:42 +0900120 from tempest.test_discover import plugins
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400121
YAMAMOTO Takashicb2ac6e2015-10-19 15:54:42 +0900122 class MyPlugin(plugins.TempestPlugin):
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400123
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100124Then you need to ensure you locally define all of the mandatory methods in the
125abstract class, you can refer to the api doc below for a reference of what that
126entails.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400127
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400128Abstract Plugin Class
Marc Koderer66210aa2015-10-26 10:52:32 +0100129---------------------
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400130
131.. autoclass:: tempest.test_discover.plugins.TempestPlugin
132 :members:
133
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400134Plugin Structure
Marc Koderer66210aa2015-10-26 10:52:32 +0100135================
zhufle9241b52017-12-06 15:41:08 +0800136While there are no hard and fast rules for the structure of a plugin, there are
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400137basically no constraints on what the plugin looks like as long as the 2 steps
138above are done. However, there are some recommended patterns to follow to make
139it easy for people to contribute and work with your plugin. For example, if you
140create a directory structure with something like::
141
142 plugin_dir/
143 config.py
144 plugin.py
145 tests/
146 api/
147 scenario/
148 services/
149 client.py
150
deepak_mouryae495cd22018-07-16 12:38:17 +0530151That will mirror what people expect from Tempest. The file
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400152
153* **config.py**: contains any plugin specific configuration variables
154* **plugin.py**: contains the plugin class used for the entry point
155* **tests**: the directory where test discovery will be run, all tests should
156 be under this dir
157* **services**: where the plugin specific service clients are
158
159Additionally, when you're creating the plugin you likely want to follow all
deepak_mouryae495cd22018-07-16 12:38:17 +0530160of the Tempest developer and reviewer documentation to ensure that the tests
161being added in the plugin act and behave like the rest of Tempest.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400162
Matthew Treinish9392a832015-08-24 10:00:49 -0400163Dealing with configuration options
Marc Koderer66210aa2015-10-26 10:52:32 +0100164----------------------------------
Matthew Treinish9392a832015-08-24 10:00:49 -0400165
deepak_mouryae495cd22018-07-16 12:38:17 +0530166Historically, Tempest didn't provide external guarantees on its configuration
167options. However, with the introduction of the plugin interface, this is no
Matthew Treinish9392a832015-08-24 10:00:49 -0400168longer the case. An external plugin can rely on using any configuration option
169coming from Tempest, there will be at least a full deprecation cycle for any
170option before it's removed. However, just the options provided by Tempest
171may not be sufficient for the plugin. If you need to add any plugin specific
172configuration options you should use the ``register_opts`` and
173``get_opt_lists`` methods to pass them to Tempest when the plugin is loaded.
174When adding configuration options the ``register_opts`` method gets passed the
deepak_mouryae495cd22018-07-16 12:38:17 +0530175CONF object from Tempest. This enables the plugin to add options to both
Matthew Treinish9392a832015-08-24 10:00:49 -0400176existing sections and also create new configuration sections for new options.
177
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100178Service Clients
179---------------
180
181If a plugin defines a service client, it is beneficial for it to implement the
182``get_service_clients`` method in the plugin class. All service clients which
183are exposed via this interface will be automatically configured and be
184available in any instance of the service clients class, defined in
185``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are
186installed, all service clients from all plugins will be registered, making it
187easy to write tests which rely on multiple APIs whose service clients are in
188different plugins.
189
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900190Example implementation of ``get_service_clients``:
191
192.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100193
194 def get_service_clients(self):
195 # Example implementation with two service clients
196 my_service1_config = config.service_client_config('my_service')
197 params_my_service1 = {
198 'name': 'my_service_v1',
199 'service_version': 'my_service.v1',
200 'module_path': 'plugin_tempest_tests.services.my_service.v1',
201 'client_names': ['API1Client', 'API2Client'],
202 }
203 params_my_service1.update(my_service_config)
204 my_service2_config = config.service_client_config('my_service')
205 params_my_service2 = {
206 'name': 'my_service_v2',
207 'service_version': 'my_service.v2',
208 'module_path': 'plugin_tempest_tests.services.my_service.v2',
209 'client_names': ['API1Client', 'API2Client'],
210 }
211 params_my_service2.update(my_service2_config)
212 return [params_my_service1, params_my_service2]
213
214Parameters:
215
216* **name**: Name of the attribute used to access the ``ClientsFactory`` from
217 the ``ServiceClients`` instance. See example below.
218* **service_version**: Tempest enforces a single implementation for each
219 service client. Available service clients are held in a ``ClientsRegistry``
220 singleton, and registered with ``service_version``, which means that
221 ``service_version`` must be unique and it should represent the service API
222 and version implemented by the service client.
223* **module_path**: Relative to the service client module, from the root of the
224 plugin.
225* **client_names**: Name of the classes that implement service clients in the
226 service clients module.
227
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900228Example usage of the service clients in tests:
229
230.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100231
232 # my_creds is instance of tempest.lib.auth.Credentials
233 # identity_uri is v2 or v3 depending on the configuration
234 from tempest.lib.services import clients
235
236 my_clients = clients.ServiceClients(my_creds, identity_uri)
237 my_service1_api1_client = my_clients.my_service_v1.API1Client()
238 my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any')
239
240Automatic configuration and registration of service clients imposes some extra
241constraints on the structure of the configuration options exposed by the
242plugin.
243
244First ``service_version`` should be in the format `service_config[.version]`.
245The `.version` part is optional, and should only be used if there are multiple
246versions of the same API available. The `service_config` must match the name of
247a configuration options group defined by the plugin. Different versions of one
248API must share the same configuration group.
249
250Second the configuration options group `service_config` must contain the
251following options:
252
253* `catalog_type`: corresponds to `service` in the catalog
254* `endpoint_type`
255
256The following options will be honoured if defined, but they are not mandatory,
257as they do not necessarily apply to all service clients.
258
259* `region`: default to identity.region
260* `build_timeout` : default to compute.build_timeout
261* `build_interval`: default to compute.build_interval
262
263Third the service client classes should inherit from ``RestClient``, should
264accept generic keyword arguments, and should pass those arguments to the
265``__init__`` method of ``RestClient``. Extra arguments can be added. For
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900266instance:
267
268.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100269
270 class MyAPIClient(rest_client.RestClient):
271
Roman Doboszedda62a2020-12-01 14:40:45 +0100272 def __init__(self, auth_provider, service, region,
273 my_arg, my_arg2=True, **kwargs):
274 super(MyAPIClient, self).__init__(
275 auth_provider, service, region, **kwargs)
276 self.my_arg = my_arg
277 self.my_args2 = my_arg
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100278
279Finally the service client should be structured in a python module, so that all
280service client classes are importable from it. Each major API version should
281have its own module.
282
283The following folder and module structure is recommended for a single major
284API version::
285
286 plugin_dir/
287 services/
288 __init__.py
289 client_api_1.py
290 client_api_2.py
291
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900292The content of __init__.py module should be:
293
294.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100295
296 from client_api_1.py import API1Client
297 from client_api_2.py import API2Client
298
299 __all__ = ['API1Client', 'API2Client']
300
301The following folder and module structure is recommended for multiple major
302API version::
303
304 plugin_dir/
305 services/
306 v1/
307 __init__.py
308 client_api_1.py
309 client_api_2.py
310 v2/
311 __init__.py
312 client_api_1.py
313 client_api_2.py
314
Yushiro FURUKAWA836361d2016-09-30 23:26:58 +0900315The content each of __init__.py module under vN should be:
316
317.. code-block:: python
Andrea Frittoli (andreaf)e07579c2016-08-05 07:27:02 +0100318
319 from client_api_1.py import API1Client
320 from client_api_2.py import API2Client
321
322 __all__ = ['API1Client', 'API2Client']
323
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400324Using Plugins
325=============
326
327Tempest will automatically discover any installed plugins when it is run. So by
328just installing the python packages which contain your plugin you'll be using
deepak_mouryae495cd22018-07-16 12:38:17 +0530329them with Tempest, nothing else is really required.
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400330
331However, you should take care when installing plugins. By their very nature
deepak_mouryae495cd22018-07-16 12:38:17 +0530332there are no guarantees when running Tempest with plugins enabled about the
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400333quality of the plugin. Additionally, while there is no limitation on running
deepak_mouryae495cd22018-07-16 12:38:17 +0530334with multiple plugins, it's worth noting that poorly written plugins might not
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400335properly isolate their tests which could cause unexpected cross interactions
336between plugins.
337
338Notes for using plugins with virtualenvs
339----------------------------------------
340
deepak_mouryae495cd22018-07-16 12:38:17 +0530341When using a Tempest inside a virtualenv (like when running under tox) you have
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400342to ensure that the package that contains your plugin is either installed in the
343venv too or that you have system site-packages enabled. The virtualenv will
deepak_mouryae495cd22018-07-16 12:38:17 +0530344isolate the Tempest install from the rest of your system so just installing the
345plugin package on your system and then running Tempest inside a venv will not
Matthew Treinish3a851dc2015-07-30 11:34:03 -0400346work.
347
Lukáš Piwowarskibe19f212022-10-07 13:47:11 +0200348For example, you can use tox to install and run tests from a tempest plugin like
349this::
350
351 [~/tempest] $ tox -e venv-tempest -- pip install (path to the plugin directory)
352 [~/tempest] $ tox -e all