blob: 2669ff7740a7a834dc79e17bfaaffb002008ace9 [file] [log] [blame]
Matthew Treinisha051c222016-05-23 15:48:22 -04001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13"""
14Runs tempest tests
15
16This command is used for running the tempest tests
17
18Test Selection
19==============
20Tempest run has several options:
21
Masayuki Igawa9c25b1d2019-07-05 16:32:49 +090022* ``--regex/-r``: This is a selection regex like what stestr uses. It will run
23 any tests that match on re.match() with the regex
24* ``--smoke/-s``: Run all the tests tagged as smoke
Martin Kopecdc844232020-12-24 15:57:53 +000025* ``--exclude-regex``: It allows to do simple test exclusion via passing a
26 rejection/exclude regexp
Matthew Treinisha051c222016-05-23 15:48:22 -040027
Martin Kopecdc844232020-12-24 15:57:53 +000028There are also the ``--exclude-list`` and ``--include-list`` options that
Matthew Treinisha6b4da92016-05-23 17:24:12 -040029let you pass a filepath to tempest run with the file format being a line
zhangyanxian68d31b82016-07-13 01:48:33 +000030separated regex, with '#' used to signify the start of a comment on a line.
Matthew Treinisha6b4da92016-05-23 17:24:12 -040031For example::
32
33 # Regex file
34 ^regex1 # Match these tests
35 .*regex2 # Match those tests
36
Matthew Treinish3e97aae2018-02-22 13:39:59 -050037These arguments are just passed into stestr, you can refer to the stestr
38selection docs for more details on how these operate:
39http://stestr.readthedocs.io/en/latest/MANUAL.html#test-selection
Matthew Treinisha6b4da92016-05-23 17:24:12 -040040
Masayuki Igawabbbaad62017-11-21 16:04:03 +090041You can also use the ``--list-tests`` option in conjunction with selection
Matthew Treinisha051c222016-05-23 15:48:22 -040042arguments to list which tests will be run.
43
Masayuki Igawabbbaad62017-11-21 16:04:03 +090044You can also use the ``--load-list`` option that lets you pass a filepath to
ubuntu0dba54c2017-07-25 15:25:22 -050045tempest run with the file format being in a non-regex format, similar to the
Masayuki Igawabbbaad62017-11-21 16:04:03 +090046tests generated by the ``--list-tests`` option. You can specify target tests
ubuntu0dba54c2017-07-25 15:25:22 -050047by removing unnecessary tests from a list file which is generated from
Masayuki Igawabbbaad62017-11-21 16:04:03 +090048``--list-tests`` option.
ubuntu0dba54c2017-07-25 15:25:22 -050049
Arx Cruzc06c3712020-02-20 11:03:52 +010050You can also use ``--worker-file`` option that let you pass a filepath to a
51worker yaml file, allowing you to manually schedule the tests run.
52For example, you can setup a tempest run with
53different concurrences to be used with different regexps.
54An example of worker file is showed below::
55
56 # YAML Worker file
57 - worker:
58 # you can have more than one regex per worker
59 - tempest.api.*
60 - neutron_tempest_tests
61 - worker:
62 - tempest.scenario.*
63
64This will run test matching with 'tempest.api.*' and 'neutron_tempest_tests'
65against worker 1. Run tests matching with 'tempest.scenario.*' under worker 2.
66
67You can mix manual scheduling with the standard scheduling mechanisms by
68concurrency field on a worker. For example::
69
70 # YAML Worker file
71 - worker:
72 # you can have more than one regex per worker
73 - tempest.api.*
74 - neutron_tempest_tests
75 concurrency: 3
76 - worker:
77 - tempest.scenario.*
78 concurrency: 2
79
80This will run tests matching with 'tempest.scenario.*' against 2 workers.
81
82This worker file is passed into stestr. For some more details on how it
83operates please refer to the stestr scheduling docs:
84https://stestr.readthedocs.io/en/stable/MANUAL.html#test-scheduling
85
Matthew Treinisha051c222016-05-23 15:48:22 -040086Test Execution
87==============
88There are several options to control how the tests are executed. By default
89tempest will run in parallel with a worker for each CPU present on the machine.
Masayuki Igawabbbaad62017-11-21 16:04:03 +090090If you want to adjust the number of workers use the ``--concurrency`` option
91and if you want to run tests serially use ``--serial/-t``
Matthew Treinisha051c222016-05-23 15:48:22 -040092
Matthew Treinishc89a9512016-06-09 17:43:35 -040093Running with Workspaces
94-----------------------
95Tempest run enables you to run your tempest tests from any setup tempest
96workspace it relies on you having setup a tempest workspace with either the
97``tempest init`` or ``tempest workspace`` commands. Then using the
98``--workspace`` CLI option you can specify which one of your workspaces you
99want to run tempest from. Using this option you don't have to run Tempest
100directly with you current working directory being the workspace, Tempest will
101take care of managing everything to be executed from there.
102
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400103Running from Anywhere
104---------------------
105Tempest run provides you with an option to execute tempest from anywhere on
106your system. You are required to provide a config file in this case with the
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530107``--config-file`` option. When run tempest will create a .stestr
108directory and a .stestr.conf file in your current working directory. This way
109you can use stestr commands directly to inspect the state of the previous run.
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400110
Matthew Treinisha051c222016-05-23 15:48:22 -0400111Test Output
112===========
113By default tempest run's output to STDOUT will be generated using the
114subunit-trace output filter. But, if you would prefer a subunit v2 stream be
Masayuki Igawabbbaad62017-11-21 16:04:03 +0900115output to STDOUT use the ``--subunit`` flag
Matthew Treinisha051c222016-05-23 15:48:22 -0400116
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500117Combining Runs
118==============
119
120There are certain situations in which you want to split a single run of tempest
121across 2 executions of tempest run. (for example to run part of the tests
122serially and others in parallel) To accomplish this but still treat the results
Masayuki Igawabbbaad62017-11-21 16:04:03 +0900123as a single run you can leverage the ``--combine`` option which will append
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500124the current run's results with the previous runs.
Matthew Treinisha051c222016-05-23 15:48:22 -0400125"""
126
Matthew Treinisha051c222016-05-23 15:48:22 -0400127import os
128import sys
Matthew Treinisha051c222016-05-23 15:48:22 -0400129
130from cliff import command
Martin Kopecdc844232020-12-24 15:57:53 +0000131from oslo_log import log
Prateek Aroraa028de12017-03-14 09:01:03 -0400132from oslo_serialization import jsonutils as json
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530133from stestr import commands
Matthew Treinisha051c222016-05-23 15:48:22 -0400134
ghanshyam009a1f62017-08-08 10:22:57 +0300135from tempest import clients
Prateek Aroraa028de12017-03-14 09:01:03 -0400136from tempest.cmd import cleanup_service
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400137from tempest.cmd import init
Matthew Treinishc89a9512016-06-09 17:43:35 -0400138from tempest.cmd import workspace
Prateek Aroraa028de12017-03-14 09:01:03 -0400139from tempest.common import credentials_factory as credentials
Matthew Treinisha051c222016-05-23 15:48:22 -0400140from tempest import config
141
Matthew Treinisha051c222016-05-23 15:48:22 -0400142CONF = config.CONF
Prateek Aroraa028de12017-03-14 09:01:03 -0400143SAVED_STATE_JSON = "saved_state.json"
Matthew Treinisha051c222016-05-23 15:48:22 -0400144
Martin Kopecdc844232020-12-24 15:57:53 +0000145LOG = log.getLogger(__name__)
146
Matthew Treinisha051c222016-05-23 15:48:22 -0400147
148class TempestRun(command.Command):
149
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400150 def _set_env(self, config_file=None):
151 if config_file:
Manik Bindlish21491df2018-12-14 06:58:42 +0000152 if os.path.exists(os.path.abspath(config_file)):
153 CONF.set_config_path(os.path.abspath(config_file))
154 else:
155 raise FileNotFoundError(
156 "Config file: %s doesn't exist" % config_file)
157
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530158 # NOTE(mtreinish): This is needed so that stestr doesn't gobble up any
Matthew Treinisha051c222016-05-23 15:48:22 -0400159 # stacktraces on failure.
160 if 'TESTR_PDB' in os.environ:
161 return
162 else:
163 os.environ["TESTR_PDB"] = ""
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530164 # NOTE(dims): most of our .stestr.conf try to test for PYTHON
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500165 # environment variable and fall back to "python", under python3
166 # if it does not exist. we should set it to the python3 executable
167 # to deal with this situation better for now.
likui7d91c872020-09-22 12:29:16 +0800168 if 'PYTHON' not in os.environ:
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500169 os.environ['PYTHON'] = sys.executable
Matthew Treinisha051c222016-05-23 15:48:22 -0400170
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530171 def _create_stestr_conf(self):
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400172 top_level_path = os.path.dirname(os.path.dirname(__file__))
173 discover_path = os.path.join(top_level_path, 'test_discover')
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530174 file_contents = init.STESTR_CONF % (discover_path, top_level_path)
175 with open('.stestr.conf', 'w+') as stestr_conf_file:
176 stestr_conf_file.write(file_contents)
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400177
Matthew Treinisha051c222016-05-23 15:48:22 -0400178 def take_action(self, parsed_args):
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400179 if parsed_args.config_file:
180 self._set_env(parsed_args.config_file)
181 else:
182 self._set_env()
Matthew Treinishc89a9512016-06-09 17:43:35 -0400183 # Workspace execution mode
184 if parsed_args.workspace:
185 workspace_mgr = workspace.WorkspaceManager(
186 parsed_args.workspace_path)
187 path = workspace_mgr.get_workspace(parsed_args.workspace)
Brant Knudson6a090f42016-10-13 12:51:49 -0500188 if not path:
189 sys.exit(
190 "The %r workspace isn't registered in "
191 "%r. Use 'tempest init' to "
192 "register the workspace." %
193 (parsed_args.workspace, workspace_mgr.path))
Matthew Treinishc89a9512016-06-09 17:43:35 -0400194 os.chdir(path)
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530195 if not os.path.isfile('.stestr.conf'):
196 self._create_stestr_conf()
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400197 # local execution with config file mode
Masayuki Igawaff07eac2018-02-22 16:53:09 +0900198 elif parsed_args.config_file and not os.path.isfile('.stestr.conf'):
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530199 self._create_stestr_conf()
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530200 elif not os.path.isfile('.stestr.conf'):
201 print("No .stestr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400202 sys.exit(2)
Prateek Aroraa028de12017-03-14 09:01:03 -0400203 if parsed_args.state:
204 self._init_state()
Prateek Aroraa028de12017-03-14 09:01:03 -0400205
Matthew Treinishf9902ec2018-02-22 12:11:46 -0500206 regex = self._build_regex(parsed_args)
Martin Kopecdc844232020-12-24 15:57:53 +0000207
208 # temporary method for parsing deprecated and new stestr options
209 # and showing warning messages in order to make the transition
210 # smoother for all tempest consumers
211 # TODO(kopecmartin) remove this after stestr>=3.1.0 is used
212 # in all supported OpenStack releases
213 def parse_dep(old_o, old_v, new_o, new_v):
214 ret = ''
215 if old_v:
216 LOG.warning("'%s' option is deprecated, use '%s' instead "
217 "which is functionally equivalent. Right now "
218 "Tempest still supports this option for "
219 "backward compatibility, however, it will be "
220 "removed soon.",
221 old_o, new_o)
222 ret = old_v
223 if old_v and new_v:
224 # both options are specified
225 LOG.warning("'%s' and '%s' are specified at the same time, "
226 "'%s' takes precedence over '%s'",
227 new_o, old_o, new_o, old_o)
228 if new_v:
229 ret = new_v
230 return ret
231 ex_regex = parse_dep('--black-regex', parsed_args.black_regex,
232 '--exclude-regex', parsed_args.exclude_regex)
233 ex_list = parse_dep('--blacklist-file', parsed_args.blacklist_file,
234 '--exclude-list', parsed_args.exclude_list)
235 in_list = parse_dep('--whitelist-file', parsed_args.whitelist_file,
236 '--include-list', parsed_args.include_list)
237
Masayuki Igawaff07eac2018-02-22 16:53:09 +0900238 return_code = 0
Matthew Treinishf9902ec2018-02-22 12:11:46 -0500239 if parsed_args.list_tests:
Martin Kopecdc844232020-12-24 15:57:53 +0000240 try:
241 return_code = commands.list_command(
242 filters=regex, include_list=in_list,
243 exclude_list=ex_list, exclude_regex=ex_regex)
244 except TypeError:
245 # exclude_list, include_list and exclude_regex are defined only
246 # in stestr >= 3.1.0, this except block catches the case when
247 # tempest is executed with an older stestr
248 return_code = commands.list_command(
249 filters=regex, whitelist_file=in_list,
250 blacklist_file=ex_list, black_regex=ex_regex)
Matthew Treinishf9902ec2018-02-22 12:11:46 -0500251
ghanshyam29981172018-02-23 05:25:28 +0000252 else:
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530253 serial = not parsed_args.parallel
Martin Kopecdc844232020-12-24 15:57:53 +0000254 params = {
255 'filters': regex, 'subunit_out': parsed_args.subunit,
256 'serial': serial, 'concurrency': parsed_args.concurrency,
257 'worker_path': parsed_args.worker_file,
258 'load_list': parsed_args.load_list,
259 'combine': parsed_args.combine
260 }
261 try:
262 return_code = commands.run_command(
263 **params, exclude_list=ex_list,
264 include_list=in_list, exclude_regex=ex_regex)
265 except TypeError:
266 # exclude_list, include_list and exclude_regex are defined only
267 # in stestr >= 3.1.0, this except block catches the case when
268 # tempest is executed with an older stestr
269 return_code = commands.run_command(
270 **params, blacklist_file=ex_list,
271 whitelist_file=in_list, black_regex=ex_regex)
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500272 if return_code > 0:
273 sys.exit(return_code)
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530274 return return_code
Matthew Treinisha051c222016-05-23 15:48:22 -0400275
276 def get_description(self):
277 return 'Run tempest'
278
Prateek Aroraa028de12017-03-14 09:01:03 -0400279 def _init_state(self):
280 print("Initializing saved state.")
281 data = {}
282 self.global_services = cleanup_service.get_global_cleanup_services()
ghanshyam009a1f62017-08-08 10:22:57 +0300283 self.admin_mgr = clients.Manager(
284 credentials.get_configured_admin_credentials())
Prateek Aroraa028de12017-03-14 09:01:03 -0400285 admin_mgr = self.admin_mgr
286 kwargs = {'data': data,
287 'is_dry_run': False,
288 'saved_state_json': data,
289 'is_preserve': False,
290 'is_save_state': True}
291 for service in self.global_services:
292 svc = service(admin_mgr, **kwargs)
293 svc.run()
294
295 with open(SAVED_STATE_JSON, 'w+') as f:
afazekas40fcb9b2019-03-08 11:25:11 +0100296 f.write(json.dumps(data, sort_keys=True,
297 indent=2, separators=(',', ': ')))
Prateek Aroraa028de12017-03-14 09:01:03 -0400298
Matthew Treinisha051c222016-05-23 15:48:22 -0400299 def get_parser(self, prog_name):
300 parser = super(TempestRun, self).get_parser(prog_name)
301 parser = self._add_args(parser)
302 return parser
303
304 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400305 # workspace args
306 parser.add_argument('--workspace', default=None,
307 help='Name of tempest workspace to use for running'
308 ' tests. You can see a list of workspaces '
309 'with tempest workspace list')
310 parser.add_argument('--workspace-path', default=None,
311 dest='workspace_path',
312 help="The path to the workspace file, the default "
313 "is ~/.tempest/workspace.yaml")
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400314 # Configuration flags
315 parser.add_argument('--config-file', default=None, dest='config_file',
316 help='Configuration file to run tempest with')
Matthew Treinisha051c222016-05-23 15:48:22 -0400317 # test selection args
318 regex = parser.add_mutually_exclusive_group()
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700319 regex.add_argument('--smoke', '-s', action='store_true',
Matthew Treinisha051c222016-05-23 15:48:22 -0400320 help="Run the smoke tests only")
321 regex.add_argument('--regex', '-r', default='',
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530322 help='A normal stestr selection regex used to '
Matthew Treinisha051c222016-05-23 15:48:22 -0400323 'specify a subset of tests to run')
Matthew Treinish13869202018-02-22 13:46:02 -0500324 parser.add_argument('--black-regex', dest='black_regex',
Martin Kopecdc844232020-12-24 15:57:53 +0000325 help='DEPRECATED: This option is deprecated and '
326 'will be removed soon, use --exclude-regex '
327 'which is functionally equivalent. If this '
328 'is specified at the same time as '
329 '--exclude-regex, this flag will be ignored '
330 'and --exclude-regex will be used')
331 parser.add_argument('--exclude-regex', dest='exclude_regex',
Matthew Treinish13869202018-02-22 13:46:02 -0500332 help='A regex to exclude tests that match it')
Matthew Treinish3e97aae2018-02-22 13:39:59 -0500333 parser.add_argument('--whitelist-file', '--whitelist_file',
Martin Kopecdc844232020-12-24 15:57:53 +0000334 help='DEPRECATED: This option is deprecated and '
335 'will be removed soon, use --include-list '
336 'which is functionally equivalent. If this '
337 'is specified at the same time as '
338 '--include-list, this flag will be ignored '
339 'and --include-list will be used')
340 parser.add_argument('--include-list', '--include_list',
341 help="Path to an include file which contains the "
342 "regex for tests to be included in tempest "
343 "run, this file contains a separate regex on "
344 "each newline.")
Matthew Treinish3e97aae2018-02-22 13:39:59 -0500345 parser.add_argument('--blacklist-file', '--blacklist_file',
Martin Kopecdc844232020-12-24 15:57:53 +0000346 help='DEPRECATED: This option is deprecated and '
347 'will be removed soon, use --exclude-list '
348 'which is functionally equivalent. If this '
349 'is specified at the same time as '
350 '--exclude-list, this flag will be ignored '
351 'and --exclude-list will be used')
352 parser.add_argument('--exclude-list', '--exclude_list',
353 help='Path to an exclude file which contains the '
354 'regex for tests to be excluded in tempest '
355 'run, this file contains a separate regex on '
356 'each newline.')
Matthew Treinish3e97aae2018-02-22 13:39:59 -0500357 parser.add_argument('--load-list', '--load_list',
358 help='Path to a non-regex whitelist file, '
baiwenteng781fe072018-06-11 17:39:20 +0800359 'this file contains a separate test '
zhuflde676372018-11-16 15:34:56 +0800360 'on each newline. This command '
361 'supports files created by the tempest '
Matthew Treinish3e97aae2018-02-22 13:39:59 -0500362 'run ``--list-tests`` command')
Arx Cruzc06c3712020-02-20 11:03:52 +0100363 parser.add_argument('--worker-file', '--worker_file',
364 help='Optional path to a worker file. This file '
365 'contains each worker configuration to be '
366 'used to schedule the tests run')
Matthew Treinisha051c222016-05-23 15:48:22 -0400367 # list only args
368 parser.add_argument('--list-tests', '-l', action='store_true',
369 help='List tests',
370 default=False)
Puneet Arora9ed41042016-07-05 19:46:06 +0000371 # execution args
Matthew Treinisha051c222016-05-23 15:48:22 -0400372 parser.add_argument('--concurrency', '-w',
Masayuki Igawa13c58832019-03-06 22:01:37 +0900373 type=int, default=0,
Matthew Treinisha051c222016-05-23 15:48:22 -0400374 help="The number of workers to use, defaults to "
375 "the number of cpus")
376 parallel = parser.add_mutually_exclusive_group()
377 parallel.add_argument('--parallel', dest='parallel',
378 action='store_true',
379 help='Run tests in parallel (this is the'
380 ' default)')
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700381 parallel.add_argument('--serial', '-t', dest='parallel',
Matthew Treinisha051c222016-05-23 15:48:22 -0400382 action='store_false',
383 help='Run tests serially')
Prateek Aroraa028de12017-03-14 09:01:03 -0400384 parser.add_argument('--save-state', dest='state',
385 action='store_true',
386 help="To save the state of the cloud before "
387 "running tempest.")
Matthew Treinisha051c222016-05-23 15:48:22 -0400388 # output args
389 parser.add_argument("--subunit", action='store_true',
390 help='Enable subunit v2 output')
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500391 parser.add_argument("--combine", action='store_true',
392 help='Combine the output of this run with the '
393 "previous run's as a combined stream in the "
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530394 "stestr repository after it finish")
Matthew Treinisha051c222016-05-23 15:48:22 -0400395
396 parser.set_defaults(parallel=True)
397 return parser
398
399 def _build_regex(self, parsed_args):
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530400 regex = None
Matthew Treinisha051c222016-05-23 15:48:22 -0400401 if parsed_args.smoke:
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530402 regex = ['smoke']
Matthew Treinisha051c222016-05-23 15:48:22 -0400403 elif parsed_args.regex:
Chandan Kumar8a4396e2017-09-15 12:18:10 +0530404 regex = parsed_args.regex.split()
Matthew Treinisha051c222016-05-23 15:48:22 -0400405 return regex