blob: 2e01394cf578feb3fa5818df451b904f82d87b94 [file] [log] [blame]
gstepanov023c1e42015-04-08 15:50:19 +03001import os
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08002import sys
koder aka kdanilovda45e882015-04-06 02:24:42 +03003import json
koder aka kdanilov2c473092015-03-29 17:12:13 +03004import Queue
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08005import pprint
koder aka kdanilove21d7472015-02-14 19:02:04 -08006import logging
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08007import argparse
koder aka kdanilov1c2b5112015-04-10 16:53:51 +03008import traceback
koder aka kdanilov2c473092015-03-29 17:12:13 +03009import threading
10import collections
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080011
koder aka kdanilov66839a92015-04-11 13:22:31 +030012import yaml
koder aka kdanilov2c473092015-03-29 17:12:13 +030013from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov6c491062015-04-09 22:33:13 +030014
koder aka kdanilov2c473092015-03-29 17:12:13 +030015import utils
koder aka kdanilov66839a92015-04-11 13:22:31 +030016import report
koder aka kdanilove06762a2015-03-22 23:32:09 +020017import ssh_utils
koder aka kdanilovda45e882015-04-06 02:24:42 +030018import start_vms
koder aka kdanilov66839a92015-04-11 13:22:31 +030019import pretty_yaml
koder aka kdanilove06762a2015-03-22 23:32:09 +020020from nodes import discover
koder aka kdanilov2c473092015-03-29 17:12:13 +030021from nodes.node import Node
koder aka kdanilov66839a92015-04-11 13:22:31 +030022from config import cfg_dict, load_config
koder aka kdanilovda45e882015-04-06 02:24:42 +030023from tests.itest import IOPerfTest, PgBenchTest
koder aka kdanilov2c473092015-03-29 17:12:13 +030024from sensors.api import start_monitoring
koder aka kdanilov66839a92015-04-11 13:22:31 +030025from formatters import format_results_for_console
koder aka kdanilov2c473092015-03-29 17:12:13 +030026
27
koder aka kdanilove21d7472015-02-14 19:02:04 -080028logger = logging.getLogger("io-perf-tool")
koder aka kdanilove21d7472015-02-14 19:02:04 -080029
30
koder aka kdanilovf4b82c22015-04-11 13:35:25 +030031def setup_logger(logger, level=logging.DEBUG, log_fname=None):
32 # logger.setLevel(level)
33 sh = logging.StreamHandler()
34 sh.setLevel(level)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020035
koder aka kdanilov2c473092015-03-29 17:12:13 +030036 log_format = '%(asctime)s - %(levelname)-6s - %(name)s - %(message)s'
37 formatter = logging.Formatter(log_format,
38 "%H:%M:%S")
koder aka kdanilovf4b82c22015-04-11 13:35:25 +030039 sh.setFormatter(formatter)
40 logger.addHandler(sh)
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080041
koder aka kdanilovf4b82c22015-04-11 13:35:25 +030042 if log_fname is not None:
43 fh = logging.FileHandler(log_fname)
44 fh.setLevel(logging.DEBUG)
45 logger.addHandler(fh)
koder aka kdanilov6c491062015-04-09 22:33:13 +030046
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080047
Yulia Portnova7ddfa732015-02-24 17:32:58 +020048def format_result(res, formatter):
koder aka kdanilove21d7472015-02-14 19:02:04 -080049 data = "\n{0}\n".format("=" * 80)
50 data += pprint.pformat(res) + "\n"
51 data += "{0}\n".format("=" * 80)
koder aka kdanilovfe056622015-02-19 08:46:15 -080052 templ = "{0}\n\n====> {1}\n\n{2}\n\n"
Yulia Portnova7ddfa732015-02-24 17:32:58 +020053 return templ.format(data, formatter(res), "=" * 80)
koder aka kdanilove21d7472015-02-14 19:02:04 -080054
55
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030056class Context(object):
57 def __init__(self):
58 self.build_meta = {}
59 self.nodes = []
60 self.clear_calls_stack = []
61 self.openstack_nodes_ids = []
62
63
koder aka kdanilov5d589b42015-03-26 12:25:51 +020064def connect_one(node):
65 try:
koder aka kdanilov2c473092015-03-29 17:12:13 +030066 ssh_pref = "ssh://"
67 if node.conn_url.startswith(ssh_pref):
68 url = node.conn_url[len(ssh_pref):]
69 node.connection = ssh_utils.connect(url)
70 else:
71 raise ValueError("Unknown url type {0}".format(node.conn_url))
koder aka kdanilov3a6633e2015-03-26 18:20:00 +020072 except Exception:
koder aka kdanilov2c473092015-03-29 17:12:13 +030073 logger.exception("During connect to {0}".format(node))
koder aka kdanilov5d589b42015-03-26 12:25:51 +020074
75
76def connect_all(nodes):
koder aka kdanilov2c473092015-03-29 17:12:13 +030077 logger.info("Connecting to nodes")
78 with ThreadPoolExecutor(32) as pool:
79 list(pool.map(connect_one, nodes))
koder aka kdanilovda45e882015-04-06 02:24:42 +030080 logger.info("All nodes connected successfully")
koder aka kdanilov2c473092015-03-29 17:12:13 +030081
82
83def save_sensors_data(q):
84 logger.info("Start receiving sensors data")
koder aka kdanilovda45e882015-04-06 02:24:42 +030085 sensor_data = []
koder aka kdanilov2c473092015-03-29 17:12:13 +030086 while True:
87 val = q.get()
88 if val is None:
koder aka kdanilovda45e882015-04-06 02:24:42 +030089 print sensor_data
90 q.put(sensor_data)
koder aka kdanilov2c473092015-03-29 17:12:13 +030091 break
koder aka kdanilovda45e882015-04-06 02:24:42 +030092 sensor_data.append(val)
koder aka kdanilov2c473092015-03-29 17:12:13 +030093 logger.info("Sensors thread exits")
94
95
96def test_thread(test, node, barrier):
97 try:
98 logger.debug("Run preparation for {0}".format(node.conn_url))
99 test.pre_run(node.connection)
100 logger.debug("Run test for {0}".format(node.conn_url))
101 test.run(node.connection, barrier)
102 except:
103 logger.exception("In test {0} for node {1}".format(test, node))
104
105
106def run_tests(config, nodes):
107 tool_type_mapper = {
108 "io": IOPerfTest,
109 "pgbench": PgBenchTest,
110 }
111
112 test_nodes = [node for node in nodes
113 if 'testnode' in node.roles]
114
115 res_q = Queue.Queue()
116
koder aka kdanilovda45e882015-04-06 02:24:42 +0300117 for test in config['tests']:
gstepanov82489e72015-04-10 16:18:03 +0300118 for test in config['tests'][test]['tests']:
gstepanov023c1e42015-04-08 15:50:19 +0300119 for name, params in test.items():
120 logger.info("Starting {0} tests".format(name))
koder aka kdanilov2c473092015-03-29 17:12:13 +0300121
gstepanov023c1e42015-04-08 15:50:19 +0300122 threads = []
123 barrier = utils.Barrier(len(test_nodes))
124 for node in test_nodes:
125 msg = "Starting {0} test on {1} node"
126 logger.debug(msg.format(name, node.conn_url))
127 test = tool_type_mapper[name](params, res_q.put)
128 th = threading.Thread(None, test_thread, None,
129 (test, node, barrier))
130 threads.append(th)
131 th.daemon = True
132 th.start()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300133
gstepanov023c1e42015-04-08 15:50:19 +0300134 for th in threads:
135 th.join()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300136
gstepanov023c1e42015-04-08 15:50:19 +0300137 results = []
koder aka kdanilov66839a92015-04-11 13:22:31 +0300138
gstepanov023c1e42015-04-08 15:50:19 +0300139 while not res_q.empty():
140 results.append(res_q.get())
koder aka kdanilov66839a92015-04-11 13:22:31 +0300141
142 yield name, test.merge_results(results)
koder aka kdanilov2c473092015-03-29 17:12:13 +0300143
144
145def parse_args(argv):
146 parser = argparse.ArgumentParser(
147 description="Run disk io performance test")
148
149 parser.add_argument("-l", dest='extra_logs',
150 action='store_true', default=False,
151 help="print some extra log info")
152
gstepanov4861d712015-04-09 13:28:02 +0300153 parser.add_argument("-b", '--build_description',
154 type=str, default="Build info")
gstepanovaffcdb12015-04-07 17:18:29 +0300155 parser.add_argument("-i", '--build_id', type=str, default="id")
156 parser.add_argument("-t", '--build_type', type=str, default="GA")
157 parser.add_argument("-u", '--username', type=str, default="admin")
koder aka kdanilov66839a92015-04-11 13:22:31 +0300158 parser.add_argument("-p", '--post-process-only', default=None)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300159 parser.add_argument("-o", '--output-dest', nargs="*")
160 parser.add_argument("config_file", nargs="?", default="config.yaml")
koder aka kdanilov2c473092015-03-29 17:12:13 +0300161
162 return parser.parse_args(argv[1:])
163
164
koder aka kdanilovda45e882015-04-06 02:24:42 +0300165def log_nodes_statistic(_, ctx):
166 nodes = ctx.nodes
koder aka kdanilov2c473092015-03-29 17:12:13 +0300167 logger.info("Found {0} nodes total".format(len(nodes)))
168 per_role = collections.defaultdict(lambda: 0)
169 for node in nodes:
170 for role in node.roles:
171 per_role[role] += 1
172
173 for role, count in sorted(per_role.items()):
174 logger.debug("Found {0} nodes with role {1}".format(count, role))
175
176
177def log_sensors_config(cfg):
koder aka kdanilov5d589b42015-03-26 12:25:51 +0200178 pass
179
180
koder aka kdanilovda45e882015-04-06 02:24:42 +0300181def connect_stage(cfg, ctx):
182 ctx.clear_calls_stack.append(disconnect_stage)
183 connect_all(ctx.nodes)
184
185
186def discover_stage(cfg, ctx):
187 if 'discover' in cfg:
188 discover_objs = [i.strip() for i in cfg['discover'].strip().split(",")]
189 ctx.nodes.extend(discover.discover(discover_objs, cfg['clouds']))
190
191 for url, roles in cfg.get('explicit_nodes', {}).items():
192 ctx.nodes.append(Node(url, roles.split(",")))
193
194
195def deploy_sensors_stage(cfg_dict, ctx):
196 ctx.clear_calls_stack.append(remove_sensors_stage)
197 if 'sensors' not in cfg_dict:
198 return
199
200 cfg = cfg_dict.get('sensors')
201 sens_cfg = []
202
203 for role, sensors_str in cfg["roles_mapping"].items():
204 sensors = [sens.strip() for sens in sensors_str.split(",")]
205
206 collect_cfg = dict((sensor, {}) for sensor in sensors)
207
208 for node in ctx.nodes:
209 if role in node.roles:
210 sens_cfg.append((node.connection, collect_cfg))
211
212 log_sensors_config(sens_cfg)
213
214 ctx.sensor_cm = start_monitoring(cfg["receiver_uri"], None,
215 connected_config=sens_cfg)
216
217 ctx.sensors_control_queue = ctx.sensor_cm.__enter__()
218
219 th = threading.Thread(None, save_sensors_data, None,
220 (ctx.sensors_control_queue,))
221 th.daemon = True
222 th.start()
223 ctx.sensor_listen_thread = th
224
225
226def remove_sensors_stage(cfg, ctx):
227 ctx.sensors_control_queue.put(None)
228 ctx.sensor_listen_thread.join()
229 ctx.sensor_data = ctx.sensors_control_queue.get()
230
231
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300232def run_all_test(cfg, ctx):
koder aka kdanilovda45e882015-04-06 02:24:42 +0300233 ctx.results = []
234
gstepanov023c1e42015-04-08 15:50:19 +0300235 if 'start_test_nodes' in cfg['tests']:
236 params = cfg['tests']['start_test_nodes']['openstack']
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300237 os_nodes_ids = []
koder aka kdanilov6c491062015-04-09 22:33:13 +0300238
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300239 os_creds = params['creds']
240
241 if os_creds == 'fuel':
242 raise NotImplementedError()
243
244 elif os_creds == 'clouds':
245 os_cfg = cfg['clouds']['openstack']
246 tenant = os_cfg['OS_TENANT_NAME'].strip()
247 user = os_cfg['OS_USERNAME'].strip()
248 passwd = os_cfg['OS_PASSWORD'].strip()
249 auth_url = os_cfg['OS_AUTH_URL'].strip()
250
251 elif os_creds == 'ENV':
252 tenant = None
253 user = None
254 passwd = None
255 auth_url = None
256
257 else:
258 raise ValueError("Only 'ENV' creds are supported")
259
260 start_vms.nova_connect(user, passwd, tenant, auth_url)
261
262 new_nodes = []
263 for new_node, node_id in start_vms.launch_vms(params):
264 new_node.roles.append('testnode')
265 ctx.nodes.append(new_node)
266 os_nodes_ids.append(node_id)
267 new_nodes.append(new_node)
268
koder aka kdanilov66839a92015-04-11 13:22:31 +0300269 store_nodes_in_log(cfg, os_nodes_ids)
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300270 ctx.openstack_nodes_ids = os_nodes_ids
271
272 connect_all(new_nodes)
gstepanov023c1e42015-04-08 15:50:19 +0300273
koder aka kdanilovda45e882015-04-06 02:24:42 +0300274 if 'tests' in cfg:
275 ctx.results.extend(run_tests(cfg_dict, ctx.nodes))
276
gstepanov023c1e42015-04-08 15:50:19 +0300277
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300278def shut_down_vms_stage(cfg, ctx):
koder aka kdanilov66839a92015-04-11 13:22:31 +0300279 vm_ids_fname = cfg_dict['vm_ids_fname']
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300280 if ctx.openstack_nodes_ids is None:
koder aka kdanilov66839a92015-04-11 13:22:31 +0300281 nodes_ids = open(vm_ids_fname).read().split()
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300282 else:
283 nodes_ids = ctx.openstack_nodes_ids
284
285 logger.info("Removing nodes")
286 start_vms.clear_nodes(nodes_ids)
287 logger.info("Nodes has been removed")
gstepanov023c1e42015-04-08 15:50:19 +0300288
koder aka kdanilov66839a92015-04-11 13:22:31 +0300289 if os.path.exists(vm_ids_fname):
290 os.remove(vm_ids_fname)
gstepanov023c1e42015-04-08 15:50:19 +0300291
koder aka kdanilov66839a92015-04-11 13:22:31 +0300292
293def store_nodes_in_log(cfg, nodes_ids):
294 with open(cfg['vm_ids_fname'], 'w') as fd:
295 fd.write("\n".join(nodes_ids))
gstepanov023c1e42015-04-08 15:50:19 +0300296
297
298def clear_enviroment(cfg, ctx):
koder aka kdanilov66839a92015-04-11 13:22:31 +0300299 if os.path.exists(cfg_dict['vm_ids_fname']):
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300300 shut_down_vms_stage(cfg, ctx)
gstepanov023c1e42015-04-08 15:50:19 +0300301
302
303def run_tests_stage(cfg, ctx):
304 # clear nodes that possible were created on previous test running
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300305 # clear_enviroment(cfg, ctx) << fix OS connection
306 ctx.clear_calls_stack.append(shut_down_vms_stage)
307 run_all_test(cfg, ctx)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300308
309
310def disconnect_stage(cfg, ctx):
311 for node in ctx.nodes:
312 if node.connection is not None:
313 node.connection.close()
314
315
koder aka kdanilov66839a92015-04-11 13:22:31 +0300316def yamable(data):
317 if isinstance(data, (tuple, list)):
318 return map(yamable, data)
319
320 if isinstance(data, unicode):
321 return str(data)
322
323 if isinstance(data, dict):
324 res = {}
325 for k, v in data.items():
326 res[yamable(k)] = yamable(v)
327 return res
328
329 return data
330
331
332def store_raw_results_stage(cfg, ctx):
333
334 raw_results = os.path.join(cfg_dict['var_dir'], 'raw_results.yaml')
335
336 if os.path.exists(raw_results):
337 cont = yaml.load(open(raw_results).read())
338 else:
339 cont = []
340
341 cont.extend(yamable(ctx.results))
342 raw_data = pretty_yaml.dumps(cont)
343
344 with open(raw_results, "w") as fd:
345 fd.write(raw_data)
346
347
348def console_report_stage(cfg, ctx):
349 for tp, data in ctx.results:
350 if 'io' == tp:
351 print format_results_for_console(data)
352
353
koder aka kdanilovda45e882015-04-06 02:24:42 +0300354def report_stage(cfg, ctx):
355 output_dest = cfg.get('output_dest')
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300356
koder aka kdanilovda45e882015-04-06 02:24:42 +0300357 if output_dest is not None:
Yulia Portnova407ca952015-04-10 10:38:15 +0300358 if output_dest.endswith(".html"):
359 report.render_html_results(ctx, output_dest)
360 logger.info("Results were stored in %s" % output_dest)
361 else:
362 with open(output_dest, "w") as fd:
363 data = {"sensor_data": ctx.sensor_data,
364 "results": ctx.results}
365 fd.write(json.dumps(data))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300366 else:
367 print "=" * 20 + " RESULTS " + "=" * 20
368 pprint.pprint(ctx.results)
369 print "=" * 60
370
371
372def complete_log_nodes_statistic(cfg, ctx):
373 nodes = ctx.nodes
374 for node in nodes:
375 logger.debug(str(node))
376
377
koder aka kdanilov66839a92015-04-11 13:22:31 +0300378def load_data_from(var_dir):
379 def closure(cfg, ctx):
380 raw_results = os.path.join(var_dir, 'raw_results.yaml')
381 ctx.results = yaml.load(open(raw_results).read())
382 return closure
gstepanovcd256d62015-04-07 17:47:32 +0300383
384
koder aka kdanilov3f356262015-02-13 08:06:14 -0800385def main(argv):
koder aka kdanilove06762a2015-03-22 23:32:09 +0200386 opts = parse_args(argv)
koder aka kdanilov2c473092015-03-29 17:12:13 +0300387
koder aka kdanilov66839a92015-04-11 13:22:31 +0300388 if opts.post_process_only is not None:
389 stages = [
390 load_data_from(opts.post_process_only),
391 console_report_stage,
392 # report_stage
393 ]
394 else:
395 stages = [
396 discover_stage,
397 log_nodes_statistic,
398 complete_log_nodes_statistic,
399 connect_stage,
400 complete_log_nodes_statistic,
401 deploy_sensors_stage,
402 run_tests_stage,
403 store_raw_results_stage,
404 console_report_stage,
405 report_stage
406 ]
407
gstepanovcd256d62015-04-07 17:47:32 +0300408 load_config(opts.config_file)
koder aka kdanilovf4b82c22015-04-11 13:35:25 +0300409
410 level = logging.DEBUG if opts.extra_logs else logging.WARNING
411 setup_logger(logger, level, cfg_dict['log_file'])
412
koder aka kdanilov66839a92015-04-11 13:22:31 +0300413 logger.info("Store all info into {0}".format(cfg_dict['var_dir']))
gstepanovcd256d62015-04-07 17:47:32 +0300414
koder aka kdanilovda45e882015-04-06 02:24:42 +0300415 ctx = Context()
gstepanovaffcdb12015-04-07 17:18:29 +0300416 ctx.build_meta['build_id'] = opts.build_id
417 ctx.build_meta['build_descrption'] = opts.build_description
418 ctx.build_meta['build_type'] = opts.build_type
419 ctx.build_meta['username'] = opts.username
koder aka kdanilov6c491062015-04-09 22:33:13 +0300420
koder aka kdanilovda45e882015-04-06 02:24:42 +0300421 try:
422 for stage in stages:
423 logger.info("Start {0.__name__} stage".format(stage))
424 stage(cfg_dict, ctx)
425 finally:
426 exc, cls, tb = sys.exc_info()
427 for stage in ctx.clear_calls_stack[::-1]:
428 try:
429 logger.info("Start {0.__name__} stage".format(stage))
430 stage(cfg_dict, ctx)
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300431 except Exception as exc:
432 logger.exception("During {0.__name__} stage".format(stage))
koder aka kdanilov2c473092015-03-29 17:12:13 +0300433
koder aka kdanilovda45e882015-04-06 02:24:42 +0300434 if exc is not None:
435 raise exc, cls, tb
koder aka kdanilov2c473092015-03-29 17:12:13 +0300436
koder aka kdanilove06762a2015-03-22 23:32:09 +0200437 return 0
koder aka kdanilov3f356262015-02-13 08:06:14 -0800438
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800439
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800440if __name__ == '__main__':
koder aka kdanilove06762a2015-03-22 23:32:09 +0200441 exit(main(sys.argv))