blob: 0de67916fe92bfb968bd002b6328acd1e710e0a5 [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 kdanilov0fdaaee2015-06-30 11:10:48 +03005import logging
6import argparse
7import functools
koder aka kdanilov39e449e2016-12-17 15:15:26 +02008import contextlib
9from typing import List, Tuple, Any, Callable, IO, cast, Optional, Iterator
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030010from yaml import load as _yaml_load
11
koder aka kdanilov22d134e2016-11-08 11:33:19 +020012
13YLoader = Callable[[IO], Any]
14yaml_load = None # type: YLoader
15
16
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030017try:
18 from yaml import CLoader
koder aka kdanilov22d134e2016-11-08 11:33:19 +020019 yaml_load = cast(YLoader, functools.partial(_yaml_load, Loader=CLoader))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030020except ImportError:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020021 yaml_load = cast(YLoader, _yaml_load)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030022
23
24import texttable
25
26try:
27 import faulthandler
28except ImportError:
29 faulthandler = None
30
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +020031from . import utils, node
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020032from .storage import make_storage, Storage
koder aka kdanilov22d134e2016-11-08 11:33:19 +020033from .config import Config
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030034from .logger import setup_loggers
koder aka kdanilov39e449e2016-12-17 15:15:26 +020035from .stage import Stage
koder aka kdanilov22d134e2016-11-08 11:33:19 +020036from .test_run_class import TestRun
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020037from .ssh import set_ssh_key_passwd
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030038
39
koder aka kdanilov39e449e2016-12-17 15:15:26 +020040# stages
41from .ceph import DiscoverCephStage
42from .openstack import DiscoverOSStage
43from .fuel import DiscoverFuelStage
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +020044from .run_test import (CollectInfoStage, ExplicitNodesStage, SaveNodesStage,
koder aka kdanilov7f59d562016-12-26 01:34:23 +020045 RunTestsStage, ConnectStage, SleepStage, PrepareNodes,
46 LoadStoredNodesStage)
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020047from .process_results import CalcStatisticStage
koder aka kdanilov39e449e2016-12-17 15:15:26 +020048from .report import ConsoleReportStage, HtmlReportStage
49from .sensors import StartSensorsStage, CollectSensorsStage
50
51
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030052logger = logging.getLogger("wally")
53
54
koder aka kdanilov39e449e2016-12-17 15:15:26 +020055@contextlib.contextmanager
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020056def log_stage(stage: Stage, cleanup: bool = False) -> Iterator[None]:
57 logger.info("Start " + stage.name() + ("::cleanup" if cleanup else ""))
koder aka kdanilov39e449e2016-12-17 15:15:26 +020058 try:
59 yield
60 except utils.StopTestError as exc:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020061 raise
koder aka kdanilov39e449e2016-12-17 15:15:26 +020062 except Exception:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020063 logger.exception("During %s", stage.name() + ("::cleanup" if cleanup else ""))
64 raise
koder aka kdanilov39e449e2016-12-17 15:15:26 +020065
66
koder aka kdanilov22d134e2016-11-08 11:33:19 +020067def list_results(path: str) -> List[Tuple[str, str, str, str]]:
koder aka kdanilov73084622016-11-16 21:51:08 +020068 results = [] # type: List[Tuple[float, str, str, str, str]]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030069
koder aka kdanilov22d134e2016-11-08 11:33:19 +020070 for dir_name in os.listdir(path):
71 full_path = os.path.join(path, dir_name)
72
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030073 try:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020074 stor = make_storage(full_path, existing=True)
75 except Exception as exc:
76 logger.warning("Can't load folder {}. Error {}".format(full_path, exc))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030077
koder aka kdanilov7f59d562016-12-26 01:34:23 +020078 comment = cast(str, stor.get('info/comment'))
79 run_uuid = cast(str, stor.get('info/run_uuid'))
80 run_time = cast(float, stor.get('info/run_time'))
koder aka kdanilov22d134e2016-11-08 11:33:19 +020081 test_types = ""
koder aka kdanilov73084622016-11-16 21:51:08 +020082 results.append((run_time,
koder aka kdanilov22d134e2016-11-08 11:33:19 +020083 run_uuid,
84 test_types,
koder aka kdanilov73084622016-11-16 21:51:08 +020085 time.ctime(run_time),
koder aka kdanilov22d134e2016-11-08 11:33:19 +020086 '-' if comment is None else comment))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030087
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030088 results.sort()
koder aka kdanilov22d134e2016-11-08 11:33:19 +020089 return [i[1:] for i in results]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030090
91
koder aka kdanilov22d134e2016-11-08 11:33:19 +020092def log_nodes_statistic_stage(ctx: TestRun) -> None:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030093 utils.log_nodes_statistic(ctx.nodes)
94
95
96def parse_args(argv):
97 descr = "Disk io performance test suite"
98 parser = argparse.ArgumentParser(prog='wally', description=descr)
koder aka kdanilov22d134e2016-11-08 11:33:19 +020099 parser.add_argument("-l", '--log-level', help="print some extra log info")
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200100 parser.add_argument("--ssh-key-passwd", default=None, help="Pass ssh key password")
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200101 parser.add_argument("-s", '--settings-dir', default=None,
102 help="Folder to store key/settings/history files")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300103
104 subparsers = parser.add_subparsers(dest='subparser_name')
105
106 # ---------------------------------------------------------------------
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200107 report_parser = subparsers.add_parser('ls', help='list all results')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300108 report_parser.add_argument("result_storage", help="Folder with test results")
109
110 # ---------------------------------------------------------------------
111 compare_help = 'compare two results'
112 report_parser = subparsers.add_parser('compare', help=compare_help)
113 report_parser.add_argument("data_path1", help="First folder with test results")
114 report_parser.add_argument("data_path2", help="Second folder with test results")
115
116 # ---------------------------------------------------------------------
117 report_help = 'run report on previously obtained results'
118 report_parser = subparsers.add_parser('report', help=report_help)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300119 report_parser.add_argument("data_dir", help="folder with rest results")
120
121 # ---------------------------------------------------------------------
122 test_parser = subparsers.add_parser('test', help='run tests')
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200123 test_parser.add_argument('--build-description', type=str, default="Build info")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300124 test_parser.add_argument('--build-id', type=str, default="id")
125 test_parser.add_argument('--build-type', type=str, default="GA")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200126 test_parser.add_argument('--dont-collect', action='store_true', help="Don't collect cluster info")
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200127 test_parser.add_argument('-n', '--no-tests', action='store_true', help="Don't run tests")
128 test_parser.add_argument('--load-report', action='store_true')
129 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 +0300130 test_parser.add_argument("-d", '--dont-discover-nodes', action='store_true',
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200131 help="Don't connect/discover fuel nodes")
132 test_parser.add_argument('--no-report', action='store_true', help="Skip report stages")
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200133 test_parser.add_argument('--result-dir', default=None, help="Save results to DIR", metavar="DIR")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300134 test_parser.add_argument("comment", help="Test information")
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200135 test_parser.add_argument("config_file", help="Yaml config file")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300136
137 # ---------------------------------------------------------------------
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200138 test_parser = subparsers.add_parser('resume', help='resume tests')
139 test_parser.add_argument("storage_dir", help="Path to test directory")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300140
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200141 # ---------------------------------------------------------------------
142 test_parser = subparsers.add_parser('db', help='resume tests')
143 test_parser.add_argument("cmd", choices=("show",), help="Command to execute")
144 test_parser.add_argument("params", nargs='*', help="Command params")
145 test_parser.add_argument("storage_dir", help="Storage path")
146
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300147 return parser.parse_args(argv[1:])
148
149
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200150def get_config_path(config: Config, opts_value: Optional[str]) -> str:
151 if opts_value is None and 'settings_dir' not in config:
152 val = "~/.wally"
153 elif opts_value is not None:
154 val = opts_value
155 else:
156 val = config.settings_dir
157
158 return os.path.abspath(os.path.expanduser(val))
159
160
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200161def find_cfg_file(name: str, included_from: str = None) -> str:
162 paths = [".", os.path.expanduser('~/.wally')]
163 if included_from is not None:
164 paths.append(os.path.dirname(included_from))
165
166 search_paths = set(os.path.abspath(path) for path in paths if os.path.isdir(path))
167
168 for folder in search_paths:
169 path = os.path.join(folder, name)
170 if os.path.exists(path):
171 return path
172
173 raise FileNotFoundError(name)
174
175
176def load_config(path: str) -> Config:
177 path = os.path.abspath(path)
178 cfg_dict = yaml_load(open(path).read())
179
180 while 'include' in cfg_dict:
181 inc = cfg_dict.pop('include')
182 if isinstance(inc, str):
183 inc = [inc]
184
185 for fname in inc:
186 inc_path = find_cfg_file(fname, path)
187 inc_dict = yaml_load(open(inc_path).read())
188 inc_dict.update(cfg_dict)
189 cfg_dict = inc_dict
190
191 return Config(cfg_dict)
192
193
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200194def get_run_stages() -> List[Stage]:
195 return [DiscoverCephStage(),
196 DiscoverOSStage(),
197 DiscoverFuelStage(),
198 ExplicitNodesStage(),
199 StartSensorsStage(),
200 RunTestsStage(),
201 CollectSensorsStage(),
202 ConnectStage(),
203 SleepStage(),
204 PrepareNodes()]
205
206
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200207def main(argv: List[str]) -> int:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300208 if faulthandler is not None:
209 faulthandler.register(signal.SIGUSR1, all_threads=True)
210
211 opts = parse_args(argv)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200212 stages = [] # type: List[Stage]
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200213
214 # stop mypy from telling that config & storage might be undeclared
215 config = None # type: Config
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200216 storage = None # type: Storage
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300217
218 if opts.subparser_name == 'test':
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200219 config = load_config(opts.config_file)
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200220 config.storage_url, config.run_uuid = utils.get_uniq_path_uuid(config.results_dir)
221 config.comment = opts.comment
222 config.keep_vm = opts.keep_vm
223 config.no_tests = opts.no_tests
224 config.dont_discover_nodes = opts.dont_discover_nodes
225 config.build_id = opts.build_id
226 config.build_description = opts.build_description
227 config.build_type = opts.build_type
228 config.settings_dir = get_config_path(config, opts.settings_dir)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300229
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200230 storage = make_storage(config.storage_url)
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200231 storage.put(config, 'config')
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200232
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200233 stages.extend(get_run_stages())
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200234 stages.append(SaveNodesStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300235
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200236 if not opts.dont_collect:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200237 stages.append(CollectInfoStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300238
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200239 argv2 = argv[:]
240 if '--ssh-key-passwd' in argv2:
241 # don't save ssh key password to storage
242 argv2[argv2.index("--ssh-key-passwd") + 1] = "<removed from output>"
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200243 storage.put(argv2, 'cli')
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200244
245 elif opts.subparser_name == 'resume':
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200246 opts.resumed = True
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200247 storage = make_storage(opts.storage_dir, existing=True)
248 config = storage.load(Config, 'config')
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200249 stages.extend(get_run_stages())
250 stages.append(LoadStoredNodesStage())
251 prev_opts = storage.get('cli')
252 if '--ssh-key-passwd' in prev_opts and opts.ssh_key_passwd:
253 prev_opts[prev_opts.index("--ssh-key-passwd") + 1] = opts.ssh_key_passwd
254
255 restored_opts = parse_args(prev_opts)
256 opts.__dict__.update(restored_opts.__dict__)
257 opts.subparser_name = 'resume'
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200258
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300259 elif opts.subparser_name == 'ls':
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200260 tab = texttable.Texttable(max_width=200)
261 tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER)
262 tab.set_cols_align(["l", "l", "l", "l"])
263 tab.header(["Name", "Tests", "Run at", "Comment"])
264 tab.add_rows(list_results(opts.result_storage))
265 print(tab.draw())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300266 return 0
267
268 elif opts.subparser_name == 'report':
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200269 if getattr(opts, "no_report", False):
270 print(" --no-report option can't be used with 'report' cmd")
271 return 1
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200272 storage = make_storage(opts.data_dir, existing=True)
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200273 config = storage.load(Config, 'config')
274 stages.append(LoadStoredNodesStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300275
276 elif opts.subparser_name == 'compare':
koder aka kdanilov73084622016-11-16 21:51:08 +0200277 # x = run_test.load_data_from_path(opts.data_path1)
278 # y = run_test.load_data_from_path(opts.data_path2)
279 # print(run_test.IOPerfTest.format_diff_for_console(
280 # [x['io'][0], y['io'][0]]))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300281 return 0
282
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200283 elif opts.subparser_name == 'db':
284 storage = make_storage(opts.storage_dir, existing=True)
285 if opts.cmd == 'show':
286 if len(opts.params) != 1:
287 print("'show' command requires parameter - key to show")
288 return 1
289 pprint.pprint(storage.get(opts.params[0]))
290 else:
291 print("Unknown/not_implemented command {!r}".format(opts.cmd))
292 return 1
293 return 0
294
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200295 report_stages = [] # type: List[Stage]
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200296 if not getattr(opts, "no_report", False):
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200297 report_stages.append(CalcStatisticStage())
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200298 report_stages.append(ConsoleReportStage())
299 report_stages.append(HtmlReportStage())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300300
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200301 # log level is not a part of config
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300302 if opts.log_level is not None:
303 str_level = opts.log_level
304 else:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200305 str_level = config.get('logging/log_level', 'INFO')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300306
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200307 log_config_file = config.get('logging/config', None)
308
309 if log_config_file is not None:
310 log_config_file = find_cfg_file(log_config_file, opts.config_file)
311
312 setup_loggers(getattr(logging, str_level),
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200313 log_fd=storage.get_fd('log', "w"),
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200314 config_file=log_config_file)
315
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200316 logger.info("All info would be stored into %r", config.storage_url)
317
318 ctx = TestRun(config, storage)
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200319 ctx.rpc_code, ctx.default_rpc_plugins = node.get_rpc_server_code()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300320
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200321 if opts.ssh_key_passwd is not None:
322 set_ssh_key_passwd(opts.ssh_key_passwd)
323
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200324 stages.sort(key=lambda x: x.priority)
325
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200326 # TODO: run only stages, which have config
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200327 failed = False
328 cleanup_stages = []
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200329
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300330 for stage in stages:
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200331 if stage.config_block is not None:
332 if stage.config_block not in ctx.config:
333 continue
334
335 cleanup_stages.append(stage)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200336 try:
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200337 with log_stage(stage):
338 stage.run(ctx)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200339 except (Exception, KeyboardInterrupt):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200340 failed = True
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300341 break
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200342 ctx.storage.sync()
343 ctx.storage.sync()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300344
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200345 logger.debug("Start cleanup")
346 cleanup_failed = False
347 for stage in cleanup_stages[::-1]:
348 try:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200349 with log_stage(stage, cleanup=True):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200350 stage.cleanup(ctx)
351 except:
352 cleanup_failed = True
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200353 ctx.storage.sync()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300354
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200355 if not failed:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300356 for report_stage in report_stages:
357 with log_stage(report_stage):
koder aka kdanilovffaf48d2016-12-27 02:25:29 +0200358 try:
359 report_stage.run(ctx)
360 except utils.StopTestError:
361 logger.error("Report stage %s requested stop execution", report_stage.name())
362 failed = True
363 break
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300364
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200365 ctx.storage.sync()
366
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200367 logger.info("All info is stored into %r", config.storage_url)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300368
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200369 if failed or cleanup_failed:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200370 logger.error("Tests are failed. See error details in log above")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300371 return 1
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200372 else:
373 logger.info("Tests finished successfully")
374 return 0