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