blob: da144dd34be39ebecfd544803372c709b267c51f [file] [log] [blame]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03001import os
2import sys
3import time
4import signal
5import logging
6import argparse
7import functools
8import contextlib
9
10from yaml import load as _yaml_load
11
12try:
13 from yaml import CLoader
14 yaml_load = functools.partial(_yaml_load, Loader=CLoader)
15except ImportError:
16 yaml_load = _yaml_load
17
18
19import texttable
20
21try:
22 import faulthandler
23except ImportError:
24 faulthandler = None
25
26
27from wally.timeseries import SensorDatastore
28from wally import utils, run_test, pretty_yaml
29from wally.config import (load_config, setup_loggers,
30 get_test_files, save_run_params, load_run_params)
31
32
33logger = logging.getLogger("wally")
34
35
36class Context(object):
37 def __init__(self):
38 self.build_meta = {}
39 self.nodes = []
40 self.clear_calls_stack = []
41 self.openstack_nodes_ids = []
42 self.sensors_mon_q = None
43 self.hw_info = []
koder aka kdanilov05e15b92016-02-07 19:32:46 +020044 self.fuel_openstack_creds = None
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030045
46
47def get_stage_name(func):
48 nm = get_func_name(func)
49 if nm.endswith("stage"):
50 return nm
51 else:
52 return nm + " stage"
53
54
55def get_test_names(raw_res):
56 res = []
57 for tp, data in raw_res:
58 if not isinstance(data, list):
59 raise ValueError()
60
61 keys = []
62 for dt in data:
63 if not isinstance(dt, dict):
64 raise ValueError()
65
66 keys.append(",".join(dt.keys()))
67
68 res.append(tp + "(" + ",".join(keys) + ")")
69 return res
70
71
72def list_results(path):
73 results = []
74
75 for dname in os.listdir(path):
76 try:
77 files_cfg = get_test_files(os.path.join(path, dname))
78
79 if not os.path.isfile(files_cfg['raw_results']):
80 continue
81
82 mt = os.path.getmtime(files_cfg['raw_results'])
83 res_mtime = time.ctime(mt)
84
85 raw_res = yaml_load(open(files_cfg['raw_results']).read())
86 test_names = ",".join(sorted(get_test_names(raw_res)))
87
88 params = load_run_params(files_cfg['run_params_file'])
89
90 comm = params.get('comment')
91 results.append((mt, dname, test_names, res_mtime,
92 '-' if comm is None else comm))
93 except ValueError:
94 pass
95
96 tab = texttable.Texttable(max_width=200)
97 tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER)
98 tab.set_cols_align(["l", "l", "l", "l"])
99 results.sort()
100
101 for data in results[::-1]:
102 tab.add_row(data[1:])
103
104 tab.header(["Name", "Tests", "etime", "Comment"])
105
106 print(tab.draw())
107
108
109def get_func_name(obj):
110 if hasattr(obj, '__name__'):
111 return obj.__name__
112 if hasattr(obj, 'func_name'):
113 return obj.func_name
114 return obj.func.func_name
115
116
117@contextlib.contextmanager
118def log_stage(func):
119 msg_templ = "Exception during {0}: {1!s}"
120 msg_templ_no_exc = "During {0}"
121
122 logger.info("Start " + get_stage_name(func))
123
124 try:
125 yield
126 except utils.StopTestError as exc:
127 logger.error(msg_templ.format(
128 get_func_name(func), exc))
129 except Exception:
130 logger.exception(msg_templ_no_exc.format(
131 get_func_name(func)))
132
133
134def make_storage_dir_struct(cfg):
135 utils.mkdirs_if_unxists(cfg.results_dir)
136 utils.mkdirs_if_unxists(cfg.sensor_storage)
137 utils.mkdirs_if_unxists(cfg.hwinfo_directory)
138 utils.mkdirs_if_unxists(cfg.results_storage)
139
140
141def log_nodes_statistic_stage(_, ctx):
142 utils.log_nodes_statistic(ctx.nodes)
143
144
145def parse_args(argv):
146 descr = "Disk io performance test suite"
147 parser = argparse.ArgumentParser(prog='wally', description=descr)
148 parser.add_argument("-l", '--log-level',
149 help="print some extra log info")
150
151 subparsers = parser.add_subparsers(dest='subparser_name')
152
153 # ---------------------------------------------------------------------
154 compare_help = 'list all results'
155 report_parser = subparsers.add_parser('ls', help=compare_help)
156 report_parser.add_argument("result_storage", help="Folder with test results")
157
158 # ---------------------------------------------------------------------
159 compare_help = 'compare two results'
160 report_parser = subparsers.add_parser('compare', help=compare_help)
161 report_parser.add_argument("data_path1", help="First folder with test results")
162 report_parser.add_argument("data_path2", help="Second folder with test results")
163
164 # ---------------------------------------------------------------------
165 report_help = 'run report on previously obtained results'
166 report_parser = subparsers.add_parser('report', help=report_help)
koder aka kdanilov49977e12016-10-13 22:54:24 +0300167 report_parser.add_argument('--load_report', action='store_true')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300168 report_parser.add_argument("data_dir", help="folder with rest results")
169
170 # ---------------------------------------------------------------------
171 test_parser = subparsers.add_parser('test', help='run tests')
172 test_parser.add_argument('--build-description',
173 type=str, default="Build info")
174 test_parser.add_argument('--build-id', type=str, default="id")
175 test_parser.add_argument('--build-type', type=str, default="GA")
176 test_parser.add_argument('-n', '--no-tests', action='store_true',
177 help="Don't run tests", default=False)
178 test_parser.add_argument('--load_report', action='store_true')
179 test_parser.add_argument("-k", '--keep-vm', action='store_true',
180 help="Don't remove test vm's", default=False)
181 test_parser.add_argument("-d", '--dont-discover-nodes', action='store_true',
182 help="Don't connect/discover fuel nodes",
183 default=False)
184 test_parser.add_argument('--no-report', action='store_true',
185 help="Skip report stages", default=False)
186 test_parser.add_argument("comment", help="Test information")
187 test_parser.add_argument("config_file", help="Yaml config file")
188
189 # ---------------------------------------------------------------------
190
191 return parser.parse_args(argv[1:])
192
193
194def main(argv):
195 if faulthandler is not None:
196 faulthandler.register(signal.SIGUSR1, all_threads=True)
197
198 opts = parse_args(argv)
199 stages = []
200 report_stages = []
201
202 ctx = Context()
203 ctx.results = {}
204 ctx.sensors_data = SensorDatastore()
205
206 if opts.subparser_name == 'test':
207 cfg = load_config(opts.config_file)
208 make_storage_dir_struct(cfg)
209 cfg.comment = opts.comment
210 save_run_params(cfg)
211
212 with open(cfg.saved_config_file, 'w') as fd:
213 fd.write(pretty_yaml.dumps(cfg.__dict__))
214
215 stages = [
216 run_test.discover_stage
217 ]
218
219 stages.extend([
220 run_test.reuse_vms_stage,
221 log_nodes_statistic_stage,
222 run_test.save_nodes_stage,
223 run_test.connect_stage])
224
225 if cfg.settings.get('collect_info', True):
226 stages.append(run_test.collect_hw_info_stage)
227
228 stages.extend([
229 # deploy_sensors_stage,
230 run_test.run_tests_stage,
231 run_test.store_raw_results_stage,
232 # gather_sensors_stage
233 ])
234
235 cfg.keep_vm = opts.keep_vm
236 cfg.no_tests = opts.no_tests
237 cfg.dont_discover_nodes = opts.dont_discover_nodes
238
239 ctx.build_meta['build_id'] = opts.build_id
240 ctx.build_meta['build_descrption'] = opts.build_description
241 ctx.build_meta['build_type'] = opts.build_type
242
243 elif opts.subparser_name == 'ls':
244 list_results(opts.result_storage)
245 return 0
246
247 elif opts.subparser_name == 'report':
248 cfg = load_config(get_test_files(opts.data_dir)['saved_config_file'])
249 stages.append(run_test.load_data_from(opts.data_dir))
250 opts.no_report = False
251 # load build meta
252
253 elif opts.subparser_name == 'compare':
254 x = run_test.load_data_from_path(opts.data_path1)
255 y = run_test.load_data_from_path(opts.data_path2)
256 print(run_test.IOPerfTest.format_diff_for_console(
257 [x['io'][0], y['io'][0]]))
258 return 0
259
260 if not opts.no_report:
261 report_stages.append(run_test.console_report_stage)
262 if opts.load_report:
263 report_stages.append(run_test.test_load_report_stage)
264 report_stages.append(run_test.html_report_stage)
265
266 if opts.log_level is not None:
267 str_level = opts.log_level
268 else:
269 str_level = cfg.settings.get('log_level', 'INFO')
270
271 setup_loggers(getattr(logging, str_level), cfg.log_file)
272 logger.info("All info would be stored into " + cfg.results_dir)
273
274 for stage in stages:
275 ok = False
276 with log_stage(stage):
277 stage(cfg, ctx)
278 ok = True
279 if not ok:
280 break
281
282 exc, cls, tb = sys.exc_info()
283 for stage in ctx.clear_calls_stack[::-1]:
284 with log_stage(stage):
285 stage(cfg, ctx)
286
287 logger.debug("Start utils.cleanup")
288 for clean_func, args, kwargs in utils.iter_clean_func():
289 with log_stage(clean_func):
290 clean_func(*args, **kwargs)
291
292 if exc is None:
293 for report_stage in report_stages:
294 with log_stage(report_stage):
295 report_stage(cfg, ctx)
296
297 logger.info("All info stored into " + cfg.results_dir)
298
299 if exc is None:
300 logger.info("Tests finished successfully")
301 return 0
302 else:
303 logger.error("Tests are failed. See detailed error above")
304 return 1