blob: 0553b4e7bd6b73ca71c4bcccd1806252e20d6475 [file] [log] [blame]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03001import os
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03002import time
3import signal
koder aka kdanilov7f59d562016-12-26 01:34:23 +02004import pprint
koder aka kdanilovf2865172016-12-30 03:35:11 +02005import getpass
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03006import logging
7import argparse
8import functools
koder aka kdanilov39e449e2016-12-17 15:15:26 +02009import contextlib
10from typing import List, Tuple, Any, Callable, IO, cast, Optional, Iterator
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030011from yaml import load as _yaml_load
12
koder aka kdanilov22d134e2016-11-08 11:33:19 +020013
14YLoader = Callable[[IO], Any]
15yaml_load = None # type: YLoader
16
17
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030018try:
19 from yaml import CLoader
koder aka kdanilov22d134e2016-11-08 11:33:19 +020020 yaml_load = cast(YLoader, functools.partial(_yaml_load, Loader=CLoader))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030021except ImportError:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020022 yaml_load = cast(YLoader, _yaml_load)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030023
24
25import texttable
26
27try:
28 import faulthandler
29except ImportError:
30 faulthandler = None
31
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +020032from . import utils, node
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020033from .storage import make_storage, Storage
koder aka kdanilov22d134e2016-11-08 11:33:19 +020034from .config import Config
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030035from .logger import setup_loggers
koder aka kdanilov39e449e2016-12-17 15:15:26 +020036from .stage import Stage
koder aka kdanilov22d134e2016-11-08 11:33:19 +020037from .test_run_class import TestRun
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020038from .ssh import set_ssh_key_passwd
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030039
40
koder aka kdanilov39e449e2016-12-17 15:15:26 +020041# stages
42from .ceph import DiscoverCephStage
43from .openstack import DiscoverOSStage
44from .fuel import DiscoverFuelStage
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +020045from .run_test import (CollectInfoStage, ExplicitNodesStage, SaveNodesStage,
koder aka kdanilov7f59d562016-12-26 01:34:23 +020046 RunTestsStage, ConnectStage, SleepStage, PrepareNodes,
47 LoadStoredNodesStage)
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020048from .process_results import CalcStatisticStage
koder aka kdanilov39e449e2016-12-17 15:15:26 +020049from .report import ConsoleReportStage, HtmlReportStage
50from .sensors import StartSensorsStage, CollectSensorsStage
51
52
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030053logger = logging.getLogger("wally")
54
55
koder aka kdanilov39e449e2016-12-17 15:15:26 +020056@contextlib.contextmanager
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020057def log_stage(stage: Stage, cleanup: bool = False) -> Iterator[None]:
58 logger.info("Start " + stage.name() + ("::cleanup" if cleanup else ""))
koder aka kdanilov39e449e2016-12-17 15:15:26 +020059 try:
60 yield
61 except utils.StopTestError as exc:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020062 raise
koder aka kdanilov39e449e2016-12-17 15:15:26 +020063 except Exception:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020064 logger.exception("During %s", stage.name() + ("::cleanup" if cleanup else ""))
65 raise
koder aka kdanilov39e449e2016-12-17 15:15:26 +020066
67
koder aka kdanilov22d134e2016-11-08 11:33:19 +020068def list_results(path: str) -> List[Tuple[str, str, str, str]]:
koder aka kdanilov73084622016-11-16 21:51:08 +020069 results = [] # type: List[Tuple[float, str, str, str, str]]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030070
koder aka kdanilov22d134e2016-11-08 11:33:19 +020071 for dir_name in os.listdir(path):
72 full_path = os.path.join(path, dir_name)
73
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030074 try:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020075 stor = make_storage(full_path, existing=True)
76 except Exception as exc:
77 logger.warning("Can't load folder {}. Error {}".format(full_path, exc))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030078
koder aka kdanilov7f59d562016-12-26 01:34:23 +020079 comment = cast(str, stor.get('info/comment'))
80 run_uuid = cast(str, stor.get('info/run_uuid'))
81 run_time = cast(float, stor.get('info/run_time'))
koder aka kdanilov22d134e2016-11-08 11:33:19 +020082 test_types = ""
koder aka kdanilov73084622016-11-16 21:51:08 +020083 results.append((run_time,
koder aka kdanilov22d134e2016-11-08 11:33:19 +020084 run_uuid,
85 test_types,
koder aka kdanilov73084622016-11-16 21:51:08 +020086 time.ctime(run_time),
koder aka kdanilov22d134e2016-11-08 11:33:19 +020087 '-' if comment is None else comment))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030088
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030089 results.sort()
koder aka kdanilov22d134e2016-11-08 11:33:19 +020090 return [i[1:] for i in results]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030091
92
koder aka kdanilov22d134e2016-11-08 11:33:19 +020093def log_nodes_statistic_stage(ctx: TestRun) -> None:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030094 utils.log_nodes_statistic(ctx.nodes)
95
96
97def parse_args(argv):
98 descr = "Disk io performance test suite"
99 parser = argparse.ArgumentParser(prog='wally', description=descr)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200100 parser.add_argument("-l", '--log-level', help="print some extra log info")
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200101 parser.add_argument("--ssh-key-passwd", default=None, help="Pass ssh key password")
koder aka kdanilovf2865172016-12-30 03:35:11 +0200102 parser.add_argument("--ssh-key-passwd-kbd", action="store_true", help="Enter ssh key password interactivelly")
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200103 parser.add_argument("-s", '--settings-dir', default=None,
104 help="Folder to store key/settings/history files")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300105
106 subparsers = parser.add_subparsers(dest='subparser_name')
107
108 # ---------------------------------------------------------------------
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200109 report_parser = subparsers.add_parser('ls', help='list all results')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300110 report_parser.add_argument("result_storage", help="Folder with test results")
111
112 # ---------------------------------------------------------------------
113 compare_help = 'compare two results'
114 report_parser = subparsers.add_parser('compare', help=compare_help)
115 report_parser.add_argument("data_path1", help="First folder with test results")
116 report_parser.add_argument("data_path2", help="Second folder with test results")
117
118 # ---------------------------------------------------------------------
119 report_help = 'run report on previously obtained results'
120 report_parser = subparsers.add_parser('report', help=report_help)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300121 report_parser.add_argument("data_dir", help="folder with rest results")
122
123 # ---------------------------------------------------------------------
124 test_parser = subparsers.add_parser('test', help='run tests')
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200125 test_parser.add_argument('--build-description', type=str, default="Build info")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300126 test_parser.add_argument('--build-id', type=str, default="id")
127 test_parser.add_argument('--build-type', type=str, default="GA")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200128 test_parser.add_argument('--dont-collect', action='store_true', help="Don't collect cluster info")
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200129 test_parser.add_argument('-n', '--no-tests', action='store_true', help="Don't run tests")
130 test_parser.add_argument('--load-report', action='store_true')
131 test_parser.add_argument("-k", '--keep-vm', action='store_true', help="Don't remove test vm's")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300132 test_parser.add_argument("-d", '--dont-discover-nodes', action='store_true',
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200133 help="Don't connect/discover fuel nodes")
134 test_parser.add_argument('--no-report', action='store_true', help="Skip report stages")
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200135 test_parser.add_argument('--result-dir', default=None, help="Save results to DIR", metavar="DIR")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300136 test_parser.add_argument("comment", help="Test information")
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200137 test_parser.add_argument("config_file", help="Yaml config file")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300138
139 # ---------------------------------------------------------------------
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200140 test_parser = subparsers.add_parser('resume', help='resume tests')
141 test_parser.add_argument("storage_dir", help="Path to test directory")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300142
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200143 # ---------------------------------------------------------------------
144 test_parser = subparsers.add_parser('db', help='resume tests')
145 test_parser.add_argument("cmd", choices=("show",), help="Command to execute")
146 test_parser.add_argument("params", nargs='*', help="Command params")
147 test_parser.add_argument("storage_dir", help="Storage path")
148
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300149 return parser.parse_args(argv[1:])
150
151
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200152def get_config_path(config: Config, opts_value: Optional[str]) -> str:
153 if opts_value is None and 'settings_dir' not in config:
154 val = "~/.wally"
155 elif opts_value is not None:
156 val = opts_value
157 else:
158 val = config.settings_dir
159
160 return os.path.abspath(os.path.expanduser(val))
161
162
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200163def find_cfg_file(name: str, included_from: str = None) -> str:
164 paths = [".", os.path.expanduser('~/.wally')]
165 if included_from is not None:
166 paths.append(os.path.dirname(included_from))
167
168 search_paths = set(os.path.abspath(path) for path in paths if os.path.isdir(path))
169
170 for folder in search_paths:
171 path = os.path.join(folder, name)
172 if os.path.exists(path):
173 return path
174
175 raise FileNotFoundError(name)
176
177
178def load_config(path: str) -> Config:
179 path = os.path.abspath(path)
180 cfg_dict = yaml_load(open(path).read())
181
182 while 'include' in cfg_dict:
183 inc = cfg_dict.pop('include')
184 if isinstance(inc, str):
185 inc = [inc]
186
187 for fname in inc:
188 inc_path = find_cfg_file(fname, path)
189 inc_dict = yaml_load(open(inc_path).read())
190 inc_dict.update(cfg_dict)
191 cfg_dict = inc_dict
192
193 return Config(cfg_dict)
194
195
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200196def get_run_stages() -> List[Stage]:
197 return [DiscoverCephStage(),
198 DiscoverOSStage(),
199 DiscoverFuelStage(),
200 ExplicitNodesStage(),
201 StartSensorsStage(),
202 RunTestsStage(),
203 CollectSensorsStage(),
204 ConnectStage(),
205 SleepStage(),
206 PrepareNodes()]
207
208
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200209def main(argv: List[str]) -> int:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300210 if faulthandler is not None:
211 faulthandler.register(signal.SIGUSR1, all_threads=True)
212
213 opts = parse_args(argv)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200214 stages = [] # type: List[Stage]
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200215
216 # stop mypy from telling that config & storage might be undeclared
217 config = None # type: Config
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200218 storage = None # type: Storage
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300219
220 if opts.subparser_name == 'test':
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200221 config = load_config(opts.config_file)
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200222 config.storage_url, config.run_uuid = utils.get_uniq_path_uuid(config.results_dir)
223 config.comment = opts.comment
224 config.keep_vm = opts.keep_vm
225 config.no_tests = opts.no_tests
226 config.dont_discover_nodes = opts.dont_discover_nodes
227 config.build_id = opts.build_id
228 config.build_description = opts.build_description
229 config.build_type = opts.build_type
230 config.settings_dir = get_config_path(config, opts.settings_dir)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300231
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200232 storage = make_storage(config.storage_url)
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200233 storage.put(config, 'config')
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200234
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200235 stages.extend(get_run_stages())
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200236 stages.append(SaveNodesStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300237
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200238 if not opts.dont_collect:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200239 stages.append(CollectInfoStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300240
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200241 argv2 = argv[:]
242 if '--ssh-key-passwd' in argv2:
243 # don't save ssh key password to storage
244 argv2[argv2.index("--ssh-key-passwd") + 1] = "<removed from output>"
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200245 storage.put(argv2, 'cli')
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200246
247 elif opts.subparser_name == 'resume':
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200248 opts.resumed = True
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200249 storage = make_storage(opts.storage_dir, existing=True)
250 config = storage.load(Config, 'config')
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200251 stages.extend(get_run_stages())
252 stages.append(LoadStoredNodesStage())
253 prev_opts = storage.get('cli')
254 if '--ssh-key-passwd' in prev_opts and opts.ssh_key_passwd:
255 prev_opts[prev_opts.index("--ssh-key-passwd") + 1] = opts.ssh_key_passwd
256
257 restored_opts = parse_args(prev_opts)
258 opts.__dict__.update(restored_opts.__dict__)
259 opts.subparser_name = 'resume'
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200260
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300261 elif opts.subparser_name == 'ls':
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200262 tab = texttable.Texttable(max_width=200)
263 tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER)
264 tab.set_cols_align(["l", "l", "l", "l"])
265 tab.header(["Name", "Tests", "Run at", "Comment"])
266 tab.add_rows(list_results(opts.result_storage))
267 print(tab.draw())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300268 return 0
269
270 elif opts.subparser_name == 'report':
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200271 if getattr(opts, "no_report", False):
272 print(" --no-report option can't be used with 'report' cmd")
273 return 1
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200274 storage = make_storage(opts.data_dir, existing=True)
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200275 config = storage.load(Config, 'config')
276 stages.append(LoadStoredNodesStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300277
278 elif opts.subparser_name == 'compare':
koder aka kdanilov73084622016-11-16 21:51:08 +0200279 # x = run_test.load_data_from_path(opts.data_path1)
280 # y = run_test.load_data_from_path(opts.data_path2)
281 # print(run_test.IOPerfTest.format_diff_for_console(
282 # [x['io'][0], y['io'][0]]))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300283 return 0
284
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200285 elif opts.subparser_name == 'db':
286 storage = make_storage(opts.storage_dir, existing=True)
287 if opts.cmd == 'show':
288 if len(opts.params) != 1:
289 print("'show' command requires parameter - key to show")
290 return 1
291 pprint.pprint(storage.get(opts.params[0]))
292 else:
293 print("Unknown/not_implemented command {!r}".format(opts.cmd))
294 return 1
295 return 0
296
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200297 report_stages = [] # type: List[Stage]
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200298 if not getattr(opts, "no_report", False):
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200299 report_stages.append(CalcStatisticStage())
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200300 report_stages.append(ConsoleReportStage())
301 report_stages.append(HtmlReportStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300302
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200303 # log level is not a part of config
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300304 if opts.log_level is not None:
305 str_level = opts.log_level
306 else:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200307 str_level = config.get('logging/log_level', 'INFO')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300308
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200309 log_config_file = config.get('logging/config', None)
310
311 if log_config_file is not None:
312 log_config_file = find_cfg_file(log_config_file, opts.config_file)
313
314 setup_loggers(getattr(logging, str_level),
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200315 log_fd=storage.get_fd('log', "w"),
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200316 config_file=log_config_file)
317
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200318 logger.info("All info would be stored into %r", config.storage_url)
319
320 ctx = TestRun(config, storage)
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200321 ctx.rpc_code, ctx.default_rpc_plugins = node.get_rpc_server_code()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300322
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200323 if opts.ssh_key_passwd is not None:
324 set_ssh_key_passwd(opts.ssh_key_passwd)
koder aka kdanilovf2865172016-12-30 03:35:11 +0200325 elif opts.ssh_key_passwd_kbd:
326 set_ssh_key_passwd(getpass.getpass("Ssh key password: ").strip())
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200327
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200328 stages.sort(key=lambda x: x.priority)
329
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200330 # TODO: run only stages, which have config
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200331 failed = False
332 cleanup_stages = []
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200333
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300334 for stage in stages:
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200335 if stage.config_block is not None:
336 if stage.config_block not in ctx.config:
337 continue
338
339 cleanup_stages.append(stage)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200340 try:
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200341 with log_stage(stage):
342 stage.run(ctx)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200343 except (Exception, KeyboardInterrupt):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200344 failed = True
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300345 break
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200346 ctx.storage.sync()
347 ctx.storage.sync()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300348
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200349 logger.debug("Start cleanup")
350 cleanup_failed = False
351 for stage in cleanup_stages[::-1]:
352 try:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200353 with log_stage(stage, cleanup=True):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200354 stage.cleanup(ctx)
355 except:
356 cleanup_failed = True
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200357 ctx.storage.sync()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300358
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200359 if not failed:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300360 for report_stage in report_stages:
361 with log_stage(report_stage):
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200362 try:
363 report_stage.run(ctx)
364 except utils.StopTestError:
365 logger.error("Report stage %s requested stop execution", report_stage.name())
366 failed = True
367 break
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300368
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200369 ctx.storage.sync()
370
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200371 logger.info("All info is stored into %r", config.storage_url)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300372
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200373 if failed or cleanup_failed:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200374 logger.error("Tests are failed. See error details in log above")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300375 return 1
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200376 else:
377 logger.info("Tests finished successfully")
378 return 0