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