Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 1 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | # not use this file except in compliance with the License. You may obtain |
| 5 | # a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 12 | # License for the specific language governing permissions and limitations |
| 13 | # under the License. |
| 14 | |
| 15 | import abc |
| 16 | |
Anusha Ramineni | f3eb947 | 2017-01-13 08:54:01 +0530 | [diff] [blame] | 17 | from oslo_log import log as logging |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 18 | import stevedore |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 19 | |
Andrea Frittoli (andreaf) | db9672e | 2016-02-23 14:07:24 -0500 | [diff] [blame] | 20 | from tempest.lib.common.utils import misc |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 21 | from tempest.lib.services import clients |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 22 | |
Marc Koderer | 25319f6 | 2015-07-15 11:28:38 +0200 | [diff] [blame] | 23 | LOG = logging.getLogger(__name__) |
| 24 | |
| 25 | |
songwenping | 4c3bf8f | 2021-01-05 03:27:09 +0000 | [diff] [blame] | 26 | class TempestPlugin(object, metaclass=abc.ABCMeta): |
Ken'ichi Ohmichi | 2e2ee19 | 2015-11-19 09:48:27 +0000 | [diff] [blame] | 27 | """Provide basic hooks for an external plugin |
| 28 | |
| 29 | To provide tempest the necessary information to run the plugin. |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 30 | """ |
| 31 | |
| 32 | @abc.abstractmethod |
| 33 | def load_tests(self): |
Ken'ichi Ohmichi | 2e2ee19 | 2015-11-19 09:48:27 +0000 | [diff] [blame] | 34 | """Return the information necessary to load the tests in the plugin. |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 35 | |
| 36 | :return: a tuple with the first value being the test_dir and the second |
| 37 | being the top_level |
| 38 | :rtype: tuple |
| 39 | """ |
| 40 | return |
| 41 | |
Matthew Treinish | a966d0f | 2015-07-01 17:37:31 -0400 | [diff] [blame] | 42 | @abc.abstractmethod |
| 43 | def register_opts(self, conf): |
Ken'ichi Ohmichi | 2e2ee19 | 2015-11-19 09:48:27 +0000 | [diff] [blame] | 44 | """Add additional configuration options to tempest. |
| 45 | |
| 46 | This method will be run for the plugin during the register_opts() |
Andrea Frittoli | 382a6f1 | 2017-03-09 11:52:17 +0000 | [diff] [blame] | 47 | function in tempest.config. |
Matthew Treinish | a966d0f | 2015-07-01 17:37:31 -0400 | [diff] [blame] | 48 | |
| 49 | :param ConfigOpts conf: The conf object that can be used to register |
| 50 | additional options on. |
Andrea Frittoli | 382a6f1 | 2017-03-09 11:52:17 +0000 | [diff] [blame] | 51 | |
Masayuki Igawa | 683abe2 | 2017-04-11 16:06:46 +0900 | [diff] [blame] | 52 | Example:: |
| 53 | |
| 54 | # Config options are defined in a config.py module |
| 55 | service_option = cfg.BoolOpt( |
| 56 | "my_service", |
| 57 | default=True, |
| 58 | help="Whether or not my service is available") |
| 59 | |
| 60 | # Note: as long as the group is listed in get_opt_lists, |
| 61 | # it will be possible to access its optins in the plugin code |
| 62 | # via ("-" in the group name are replaces with "_"): |
| 63 | # CONF.my_service.<option_name> |
| 64 | my_service_group = cfg.OptGroup(name="my-service", |
| 65 | title="My service options") |
| 66 | |
| 67 | MyServiceGroup = [<list of options>] |
| 68 | # (...) More groups and options... |
| 69 | |
| 70 | # Plugin is implemented in a plugin.py module |
| 71 | from my_plugin import config as my_config |
| 72 | |
| 73 | def register_opts(self, conf): |
| 74 | conf.register_opt(my_config.service_option, |
| 75 | group='service_available') |
| 76 | conf.register_group(my_config.my_service_group) |
Andrea Frittoli (andreaf) | 17e96e1 | 2017-11-05 21:41:33 +1100 | [diff] [blame] | 77 | conf.register_opts(my_config.MyServiceGroup, |
Masayuki Igawa | 683abe2 | 2017-04-11 16:06:46 +0900 | [diff] [blame] | 78 | my_config.my_service_group) |
| 79 | |
| 80 | conf.register_group(my_config.my_service_feature_group) |
| 81 | conf.register_opts(my_config.MyServiceFeaturesGroup, |
| 82 | my_config.my_service_feature_group) |
Matthew Treinish | a966d0f | 2015-07-01 17:37:31 -0400 | [diff] [blame] | 83 | """ |
| 84 | return |
| 85 | |
Matthew Treinish | 83a19aa | 2015-07-23 13:06:13 -0400 | [diff] [blame] | 86 | @abc.abstractmethod |
| 87 | def get_opt_lists(self): |
Ken'ichi Ohmichi | 2e2ee19 | 2015-11-19 09:48:27 +0000 | [diff] [blame] | 88 | """Get a list of options for sample config generation |
Matthew Treinish | 83a19aa | 2015-07-23 13:06:13 -0400 | [diff] [blame] | 89 | |
| 90 | :return option_list: A list of tuples with the group name and options |
| 91 | in that group. |
| 92 | :rtype: list |
Andrea Frittoli | f1c6825 | 2017-04-24 11:02:39 +0100 | [diff] [blame] | 93 | |
| 94 | Example:: |
| 95 | |
| 96 | # Config options are defined in a config.py module |
| 97 | service_option = cfg.BoolOpt( |
| 98 | "my_service", default=True, |
| 99 | help="Whether or not my service is available") |
| 100 | |
| 101 | my_service_group = cfg.OptGroup(name="my-service", |
| 102 | title="My service options") |
| 103 | my_service_features_group = cfg.OptGroup( |
| 104 | name="my-service-features", |
| 105 | title="My service available features") |
| 106 | |
| 107 | MyServiceGroup = [<list of options>] |
| 108 | MyServiceFeaturesGroup = [<list of options>] |
| 109 | |
| 110 | # Plugin is implemented in a plugin.py module |
| 111 | from my_plugin import config as my_config |
| 112 | |
| 113 | def get_opt_lists(self, conf): |
| 114 | return [ |
| 115 | (my_service_group.name, MyServiceGroup), |
| 116 | (my_service_features_group.name, MyServiceFeaturesGroup) |
| 117 | ] |
Matthew Treinish | 83a19aa | 2015-07-23 13:06:13 -0400 | [diff] [blame] | 118 | """ |
| 119 | return [] |
| 120 | |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 121 | def get_service_clients(self): |
| 122 | """Get a list of the service clients for registration |
| 123 | |
| 124 | If the plugin implements service clients for one or more APIs, it |
| 125 | may return their details by this method for automatic registration |
| 126 | in any ServiceClients object instantiated by tests. |
| 127 | The default implementation returns an empty list. |
| 128 | |
Stephen Finucane | d114804 | 2017-03-22 12:35:10 +0000 | [diff] [blame] | 129 | :returns: Each element of the list represents the service client for an |
| 130 | API. Each dict must define all parameters required for the invocation |
| 131 | of `service_clients.ServiceClients.register_service_client_module`. |
| 132 | :rtype: list of dictionaries |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 133 | |
Masayuki Igawa | 683abe2 | 2017-04-11 16:06:46 +0900 | [diff] [blame] | 134 | Example implementation with one service client:: |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 135 | |
Masayuki Igawa | 683abe2 | 2017-04-11 16:06:46 +0900 | [diff] [blame] | 136 | def get_service_clients(self): |
| 137 | # Example implementation with one service client |
| 138 | myservice_config = config.service_client_config('myservice') |
| 139 | params = { |
| 140 | 'name': 'myservice', |
| 141 | 'service_version': 'myservice', |
| 142 | 'module_path': 'myservice_tempest_tests.services', |
| 143 | 'client_names': ['API1Client', 'API2Client'], |
| 144 | } |
| 145 | params.update(myservice_config) |
| 146 | return [params] |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 147 | |
Masayuki Igawa | 683abe2 | 2017-04-11 16:06:46 +0900 | [diff] [blame] | 148 | Example implementation with two service clients:: |
| 149 | |
| 150 | def get_service_clients(self): |
| 151 | # Example implementation with two service clients |
| 152 | foo1_config = config.service_client_config('foo') |
| 153 | params_foo1 = { |
| 154 | 'name': 'foo_v1', |
| 155 | 'service_version': 'foo.v1', |
| 156 | 'module_path': 'bar_tempest_tests.services.foo.v1', |
| 157 | 'client_names': ['API1Client', 'API2Client'], |
| 158 | } |
| 159 | params_foo1.update(foo_config) |
| 160 | foo2_config = config.service_client_config('foo') |
| 161 | params_foo2 = { |
| 162 | 'name': 'foo_v2', |
| 163 | 'service_version': 'foo.v2', |
| 164 | 'module_path': 'bar_tempest_tests.services.foo.v2', |
| 165 | 'client_names': ['API1Client', 'API2Client'], |
| 166 | } |
| 167 | params_foo2.update(foo2_config) |
| 168 | return [params_foo1, params_foo2] |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 169 | """ |
| 170 | return [] |
| 171 | |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 172 | |
| 173 | @misc.singleton |
| 174 | class TempestTestPluginManager(object): |
| 175 | """Tempest test plugin manager class |
| 176 | |
| 177 | This class is used to manage the lifecycle of external tempest test |
| 178 | plugins. It provides functions for getting set |
| 179 | """ |
afazekas | 40fcb9b | 2019-03-08 11:25:11 +0100 | [diff] [blame] | 180 | |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 181 | def __init__(self): |
| 182 | self.ext_plugins = stevedore.ExtensionManager( |
Marc Koderer | 191419c | 2015-07-14 14:30:45 +0200 | [diff] [blame] | 183 | 'tempest.test_plugins', invoke_on_load=True, |
Marc Koderer | 25319f6 | 2015-07-15 11:28:38 +0200 | [diff] [blame] | 184 | propagate_map_exceptions=True, |
| 185 | on_load_failure_callback=self.failure_hook) |
| 186 | |
| 187 | @staticmethod |
| 188 | def failure_hook(_, ep, err): |
| 189 | LOG.error('Could not load %r: %s', ep.name, err) |
| 190 | raise err |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 191 | |
| 192 | def get_plugin_load_tests_tuple(self): |
| 193 | load_tests_dict = {} |
| 194 | for plug in self.ext_plugins: |
Ghanshyam Mann | aa7c147 | 2020-03-10 14:52:22 -0500 | [diff] [blame] | 195 | LOG.info('Loading tests from Tempest plugin: %s', plug.name) |
Matthew Treinish | 7a51877 | 2015-07-01 12:46:41 -0400 | [diff] [blame] | 196 | load_tests_dict[plug.name] = plug.obj.load_tests() |
| 197 | return load_tests_dict |
Matthew Treinish | a966d0f | 2015-07-01 17:37:31 -0400 | [diff] [blame] | 198 | |
| 199 | def register_plugin_opts(self, conf): |
| 200 | for plug in self.ext_plugins: |
Ghanshyam Mann | aa7c147 | 2020-03-10 14:52:22 -0500 | [diff] [blame] | 201 | LOG.info('Register additional config options from Tempest ' |
| 202 | 'plugin: %s', plug.name) |
Matthew Treinish | 1bc49b9 | 2015-10-08 11:10:05 -0400 | [diff] [blame] | 203 | try: |
| 204 | plug.obj.register_opts(conf) |
| 205 | except Exception: |
| 206 | LOG.exception('Plugin %s raised an exception trying to run ' |
Jordan Pittier | 525ec71 | 2016-12-07 17:51:26 +0100 | [diff] [blame] | 207 | 'register_opts', plug.name) |
Matthew Treinish | 83a19aa | 2015-07-23 13:06:13 -0400 | [diff] [blame] | 208 | |
| 209 | def get_plugin_options_list(self): |
| 210 | plugin_options = [] |
| 211 | for plug in self.ext_plugins: |
| 212 | opt_list = plug.obj.get_opt_lists() |
Ghanshyam Mann | aa7c147 | 2020-03-10 14:52:22 -0500 | [diff] [blame] | 213 | LOG.info('List additional config options registered by ' |
| 214 | 'Tempest plugin: %s', plug.name) |
| 215 | |
Matthew Treinish | 83a19aa | 2015-07-23 13:06:13 -0400 | [diff] [blame] | 216 | if opt_list: |
| 217 | plugin_options.extend(opt_list) |
| 218 | return plugin_options |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 219 | |
| 220 | def _register_service_clients(self): |
| 221 | registry = clients.ClientsRegistry() |
| 222 | for plug in self.ext_plugins: |
| 223 | try: |
Matthew Treinish | 00c72b9 | 2016-10-04 13:04:52 -0400 | [diff] [blame] | 224 | service_clients = plug.obj.get_service_clients() |
| 225 | if service_clients: |
| 226 | registry.register_service_client( |
| 227 | plug.name, service_clients) |
Andrea Frittoli (andreaf) | 6d4d85a | 2016-06-21 17:20:31 +0100 | [diff] [blame] | 228 | except Exception: |
| 229 | LOG.exception('Plugin %s raised an exception trying to run ' |
Jordan Pittier | 525ec71 | 2016-12-07 17:51:26 +0100 | [diff] [blame] | 230 | 'get_service_clients', plug.name) |