blob: aa47b218f6c7e4bb22485d3c2604499d7c33c2f0 [file] [log] [blame]
gstepanov023c1e42015-04-08 15:50:19 +03001import os
2import pickle
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08003import sys
koder aka kdanilovda45e882015-04-06 02:24:42 +03004import json
koder aka kdanilov2c473092015-03-29 17:12:13 +03005import Queue
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08006import pprint
koder aka kdanilove21d7472015-02-14 19:02:04 -08007import logging
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08008import argparse
koder aka kdanilov1c2b5112015-04-10 16:53:51 +03009import traceback
koder aka kdanilov2c473092015-03-29 17:12:13 +030010import threading
11import collections
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080012
koder aka kdanilov2c473092015-03-29 17:12:13 +030013from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov6c491062015-04-09 22:33:13 +030014
Yulia Portnova407ca952015-04-10 10:38:15 +030015import report
koder aka kdanilov6c491062015-04-09 22:33:13 +030016# import formatters
koder aka kdanilov2c473092015-03-29 17:12:13 +030017
18import utils
koder aka kdanilove06762a2015-03-22 23:32:09 +020019import ssh_utils
koder aka kdanilovda45e882015-04-06 02:24:42 +030020import start_vms
koder aka kdanilove06762a2015-03-22 23:32:09 +020021from nodes import discover
koder aka kdanilov2c473092015-03-29 17:12:13 +030022from nodes.node import Node
gstepanovcd256d62015-04-07 17:47:32 +030023from config import cfg_dict, parse_config
koder aka kdanilovda45e882015-04-06 02:24:42 +030024from tests.itest import IOPerfTest, PgBenchTest
koder aka kdanilov2c473092015-03-29 17:12:13 +030025from sensors.api import start_monitoring
26
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 kdanilov2c473092015-03-29 17:12:13 +030031def setup_logger(logger, level=logging.DEBUG):
32 logger.setLevel(level)
33 ch = logging.StreamHandler()
34 ch.setLevel(level)
35 logger.addHandler(ch)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020036
koder aka kdanilov2c473092015-03-29 17:12:13 +030037 log_format = '%(asctime)s - %(levelname)-6s - %(name)s - %(message)s'
38 formatter = logging.Formatter(log_format,
39 "%H:%M:%S")
40 ch.setFormatter(formatter)
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080041
koder aka kdanilov6c491062015-04-09 22:33:13 +030042 # logger.setLevel(logging.INFO)
43 # logger.addHandler(logging.FileHandler('log.txt'))
44
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080045
Yulia Portnova7ddfa732015-02-24 17:32:58 +020046def format_result(res, formatter):
koder aka kdanilove21d7472015-02-14 19:02:04 -080047 data = "\n{0}\n".format("=" * 80)
48 data += pprint.pformat(res) + "\n"
49 data += "{0}\n".format("=" * 80)
koder aka kdanilovfe056622015-02-19 08:46:15 -080050 templ = "{0}\n\n====> {1}\n\n{2}\n\n"
Yulia Portnova7ddfa732015-02-24 17:32:58 +020051 return templ.format(data, formatter(res), "=" * 80)
koder aka kdanilove21d7472015-02-14 19:02:04 -080052
53
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030054class Context(object):
55 def __init__(self):
56 self.build_meta = {}
57 self.nodes = []
58 self.clear_calls_stack = []
59 self.openstack_nodes_ids = []
60
61
koder aka kdanilov5d589b42015-03-26 12:25:51 +020062def connect_one(node):
63 try:
koder aka kdanilov2c473092015-03-29 17:12:13 +030064 ssh_pref = "ssh://"
65 if node.conn_url.startswith(ssh_pref):
66 url = node.conn_url[len(ssh_pref):]
67 node.connection = ssh_utils.connect(url)
68 else:
69 raise ValueError("Unknown url type {0}".format(node.conn_url))
koder aka kdanilov3a6633e2015-03-26 18:20:00 +020070 except Exception:
koder aka kdanilov2c473092015-03-29 17:12:13 +030071 logger.exception("During connect to {0}".format(node))
koder aka kdanilov5d589b42015-03-26 12:25:51 +020072
73
74def connect_all(nodes):
koder aka kdanilov2c473092015-03-29 17:12:13 +030075 logger.info("Connecting to nodes")
76 with ThreadPoolExecutor(32) as pool:
77 list(pool.map(connect_one, nodes))
koder aka kdanilovda45e882015-04-06 02:24:42 +030078 logger.info("All nodes connected successfully")
koder aka kdanilov2c473092015-03-29 17:12:13 +030079
80
81def save_sensors_data(q):
82 logger.info("Start receiving sensors data")
koder aka kdanilovda45e882015-04-06 02:24:42 +030083 sensor_data = []
koder aka kdanilov2c473092015-03-29 17:12:13 +030084 while True:
85 val = q.get()
86 if val is None:
koder aka kdanilovda45e882015-04-06 02:24:42 +030087 print sensor_data
88 q.put(sensor_data)
koder aka kdanilov2c473092015-03-29 17:12:13 +030089 break
koder aka kdanilovda45e882015-04-06 02:24:42 +030090 sensor_data.append(val)
koder aka kdanilov2c473092015-03-29 17:12:13 +030091 logger.info("Sensors thread exits")
92
93
94def test_thread(test, node, barrier):
95 try:
96 logger.debug("Run preparation for {0}".format(node.conn_url))
97 test.pre_run(node.connection)
98 logger.debug("Run test for {0}".format(node.conn_url))
99 test.run(node.connection, barrier)
100 except:
101 logger.exception("In test {0} for node {1}".format(test, node))
102
103
104def run_tests(config, nodes):
105 tool_type_mapper = {
106 "io": IOPerfTest,
107 "pgbench": PgBenchTest,
108 }
109
110 test_nodes = [node for node in nodes
111 if 'testnode' in node.roles]
112
113 res_q = Queue.Queue()
114
koder aka kdanilovda45e882015-04-06 02:24:42 +0300115 for test in config['tests']:
gstepanov82489e72015-04-10 16:18:03 +0300116 for test in config['tests'][test]['tests']:
gstepanov023c1e42015-04-08 15:50:19 +0300117 for name, params in test.items():
118 logger.info("Starting {0} tests".format(name))
koder aka kdanilov2c473092015-03-29 17:12:13 +0300119
gstepanov023c1e42015-04-08 15:50:19 +0300120 threads = []
121 barrier = utils.Barrier(len(test_nodes))
122 for node in test_nodes:
123 msg = "Starting {0} test on {1} node"
124 logger.debug(msg.format(name, node.conn_url))
125 test = tool_type_mapper[name](params, res_q.put)
126 th = threading.Thread(None, test_thread, None,
127 (test, node, barrier))
128 threads.append(th)
129 th.daemon = True
130 th.start()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300131
gstepanov023c1e42015-04-08 15:50:19 +0300132 for th in threads:
133 th.join()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300134
gstepanov023c1e42015-04-08 15:50:19 +0300135 results = []
136 while not res_q.empty():
137 results.append(res_q.get())
138 # logger.info("Get test result {0!r}".format(results[-1]))
139 yield name, results
koder aka kdanilov2c473092015-03-29 17:12:13 +0300140
141
142def parse_args(argv):
143 parser = argparse.ArgumentParser(
144 description="Run disk io performance test")
145
146 parser.add_argument("-l", dest='extra_logs',
147 action='store_true', default=False,
148 help="print some extra log info")
149
gstepanov4861d712015-04-09 13:28:02 +0300150 parser.add_argument("-b", '--build_description',
151 type=str, default="Build info")
gstepanovaffcdb12015-04-07 17:18:29 +0300152 parser.add_argument("-i", '--build_id', type=str, default="id")
153 parser.add_argument("-t", '--build_type', type=str, default="GA")
154 parser.add_argument("-u", '--username', type=str, default="admin")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300155 parser.add_argument("-o", '--output-dest', nargs="*")
156 parser.add_argument("config_file", nargs="?", default="config.yaml")
koder aka kdanilov2c473092015-03-29 17:12:13 +0300157
158 return parser.parse_args(argv[1:])
159
160
koder aka kdanilovda45e882015-04-06 02:24:42 +0300161def log_nodes_statistic(_, ctx):
162 nodes = ctx.nodes
koder aka kdanilov2c473092015-03-29 17:12:13 +0300163 logger.info("Found {0} nodes total".format(len(nodes)))
164 per_role = collections.defaultdict(lambda: 0)
165 for node in nodes:
166 for role in node.roles:
167 per_role[role] += 1
168
169 for role, count in sorted(per_role.items()):
170 logger.debug("Found {0} nodes with role {1}".format(count, role))
171
172
173def log_sensors_config(cfg):
koder aka kdanilov5d589b42015-03-26 12:25:51 +0200174 pass
175
176
koder aka kdanilovda45e882015-04-06 02:24:42 +0300177def connect_stage(cfg, ctx):
178 ctx.clear_calls_stack.append(disconnect_stage)
179 connect_all(ctx.nodes)
180
181
182def discover_stage(cfg, ctx):
183 if 'discover' in cfg:
184 discover_objs = [i.strip() for i in cfg['discover'].strip().split(",")]
185 ctx.nodes.extend(discover.discover(discover_objs, cfg['clouds']))
186
187 for url, roles in cfg.get('explicit_nodes', {}).items():
188 ctx.nodes.append(Node(url, roles.split(",")))
189
190
191def deploy_sensors_stage(cfg_dict, ctx):
192 ctx.clear_calls_stack.append(remove_sensors_stage)
193 if 'sensors' not in cfg_dict:
194 return
195
196 cfg = cfg_dict.get('sensors')
197 sens_cfg = []
198
199 for role, sensors_str in cfg["roles_mapping"].items():
200 sensors = [sens.strip() for sens in sensors_str.split(",")]
201
202 collect_cfg = dict((sensor, {}) for sensor in sensors)
203
204 for node in ctx.nodes:
205 if role in node.roles:
206 sens_cfg.append((node.connection, collect_cfg))
207
208 log_sensors_config(sens_cfg)
209
210 ctx.sensor_cm = start_monitoring(cfg["receiver_uri"], None,
211 connected_config=sens_cfg)
212
213 ctx.sensors_control_queue = ctx.sensor_cm.__enter__()
214
215 th = threading.Thread(None, save_sensors_data, None,
216 (ctx.sensors_control_queue,))
217 th.daemon = True
218 th.start()
219 ctx.sensor_listen_thread = th
220
221
222def remove_sensors_stage(cfg, ctx):
223 ctx.sensors_control_queue.put(None)
224 ctx.sensor_listen_thread.join()
225 ctx.sensor_data = ctx.sensors_control_queue.get()
226
227
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300228def run_all_test(cfg, ctx):
koder aka kdanilovda45e882015-04-06 02:24:42 +0300229 ctx.results = []
230
gstepanov023c1e42015-04-08 15:50:19 +0300231 if 'start_test_nodes' in cfg['tests']:
232 params = cfg['tests']['start_test_nodes']['openstack']
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300233 os_nodes_ids = []
koder aka kdanilov6c491062015-04-09 22:33:13 +0300234
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300235 os_creds = params['creds']
236
237 if os_creds == 'fuel':
238 raise NotImplementedError()
239
240 elif os_creds == 'clouds':
241 os_cfg = cfg['clouds']['openstack']
242 tenant = os_cfg['OS_TENANT_NAME'].strip()
243 user = os_cfg['OS_USERNAME'].strip()
244 passwd = os_cfg['OS_PASSWORD'].strip()
245 auth_url = os_cfg['OS_AUTH_URL'].strip()
246
247 elif os_creds == 'ENV':
248 tenant = None
249 user = None
250 passwd = None
251 auth_url = None
252
253 else:
254 raise ValueError("Only 'ENV' creds are supported")
255
256 start_vms.nova_connect(user, passwd, tenant, auth_url)
257
258 new_nodes = []
259 for new_node, node_id in start_vms.launch_vms(params):
260 new_node.roles.append('testnode')
261 ctx.nodes.append(new_node)
262 os_nodes_ids.append(node_id)
263 new_nodes.append(new_node)
264
265 store_nodes_in_log(os_nodes_ids)
266 ctx.openstack_nodes_ids = os_nodes_ids
267
268 connect_all(new_nodes)
gstepanov023c1e42015-04-08 15:50:19 +0300269
koder aka kdanilovda45e882015-04-06 02:24:42 +0300270 if 'tests' in cfg:
271 ctx.results.extend(run_tests(cfg_dict, ctx.nodes))
272
gstepanov023c1e42015-04-08 15:50:19 +0300273
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300274def shut_down_vms_stage(cfg, ctx):
275 if ctx.openstack_nodes_ids is None:
276 data = open('vm_journal.log').read().strip()
gstepanov023c1e42015-04-08 15:50:19 +0300277
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300278 if data == "":
279 logger.info("Journal file is empty")
280 return
gstepanov023c1e42015-04-08 15:50:19 +0300281
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300282 try:
283 nodes_ids = pickle.loads(data)
284 except:
285 logger.error("File vm_journal.log corrupted")
286 return
287 else:
288 nodes_ids = ctx.openstack_nodes_ids
289
290 logger.info("Removing nodes")
291 start_vms.clear_nodes(nodes_ids)
292 logger.info("Nodes has been removed")
gstepanov023c1e42015-04-08 15:50:19 +0300293
294
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300295def store_nodes_in_log(nodes_ids):
gstepanov023c1e42015-04-08 15:50:19 +0300296 with open('vm_journal.log', 'w+') as f:
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300297 f.write(pickle.dumps([nodes_ids]))
gstepanov023c1e42015-04-08 15:50:19 +0300298
299
300def clear_enviroment(cfg, ctx):
301 if os.path.exists('vm_journal.log'):
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300302 shut_down_vms_stage(cfg, ctx)
gstepanov023c1e42015-04-08 15:50:19 +0300303 os.remove('vm_journal.log')
304
305
306def run_tests_stage(cfg, ctx):
307 # clear nodes that possible were created on previous test running
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300308 # clear_enviroment(cfg, ctx) << fix OS connection
309 ctx.clear_calls_stack.append(shut_down_vms_stage)
310 run_all_test(cfg, ctx)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300311
312
313def disconnect_stage(cfg, ctx):
314 for node in ctx.nodes:
315 if node.connection is not None:
316 node.connection.close()
317
318
319def report_stage(cfg, ctx):
320 output_dest = cfg.get('output_dest')
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300321
koder aka kdanilovda45e882015-04-06 02:24:42 +0300322 if output_dest is not None:
Yulia Portnova407ca952015-04-10 10:38:15 +0300323 if output_dest.endswith(".html"):
324 report.render_html_results(ctx, output_dest)
325 logger.info("Results were stored in %s" % output_dest)
326 else:
327 with open(output_dest, "w") as fd:
328 data = {"sensor_data": ctx.sensor_data,
329 "results": ctx.results}
330 fd.write(json.dumps(data))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300331 else:
332 print "=" * 20 + " RESULTS " + "=" * 20
333 pprint.pprint(ctx.results)
334 print "=" * 60
335
336
337def complete_log_nodes_statistic(cfg, ctx):
338 nodes = ctx.nodes
339 for node in nodes:
340 logger.debug(str(node))
341
342
gstepanovcd256d62015-04-07 17:47:32 +0300343def load_config(path):
344 global cfg_dict
345 cfg_dict = parse_config(path)
346
347
koder aka kdanilov3f356262015-02-13 08:06:14 -0800348def main(argv):
koder aka kdanilove06762a2015-03-22 23:32:09 +0200349 opts = parse_args(argv)
koder aka kdanilov2c473092015-03-29 17:12:13 +0300350
351 level = logging.DEBUG if opts.extra_logs else logging.WARNING
352 setup_logger(logger, level)
353
koder aka kdanilovda45e882015-04-06 02:24:42 +0300354 stages = [
355 discover_stage,
koder aka kdanilov6c491062015-04-09 22:33:13 +0300356 log_nodes_statistic,
koder aka kdanilovda45e882015-04-06 02:24:42 +0300357 complete_log_nodes_statistic,
koder aka kdanilov6c491062015-04-09 22:33:13 +0300358 connect_stage,
359 # complete_log_nodes_statistic,
360 deploy_sensors_stage,
koder aka kdanilovda45e882015-04-06 02:24:42 +0300361 run_tests_stage,
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300362 report_stage
koder aka kdanilovda45e882015-04-06 02:24:42 +0300363 ]
koder aka kdanilov2c473092015-03-29 17:12:13 +0300364
gstepanovcd256d62015-04-07 17:47:32 +0300365 load_config(opts.config_file)
366
koder aka kdanilovda45e882015-04-06 02:24:42 +0300367 ctx = Context()
gstepanovaffcdb12015-04-07 17:18:29 +0300368 ctx.build_meta['build_id'] = opts.build_id
369 ctx.build_meta['build_descrption'] = opts.build_description
370 ctx.build_meta['build_type'] = opts.build_type
371 ctx.build_meta['username'] = opts.username
koder aka kdanilov6c491062015-04-09 22:33:13 +0300372
koder aka kdanilovda45e882015-04-06 02:24:42 +0300373 try:
374 for stage in stages:
375 logger.info("Start {0.__name__} stage".format(stage))
gstepanov023c1e42015-04-08 15:50:19 +0300376 print "Start {0.__name__} stage".format(stage)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300377 stage(cfg_dict, ctx)
378 finally:
379 exc, cls, tb = sys.exc_info()
380 for stage in ctx.clear_calls_stack[::-1]:
381 try:
382 logger.info("Start {0.__name__} stage".format(stage))
383 stage(cfg_dict, ctx)
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300384 except Exception as exc:
385 logger.exception("During {0.__name__} stage".format(stage))
koder aka kdanilov2c473092015-03-29 17:12:13 +0300386
koder aka kdanilovda45e882015-04-06 02:24:42 +0300387 if exc is not None:
388 raise exc, cls, tb
koder aka kdanilov2c473092015-03-29 17:12:13 +0300389
koder aka kdanilove06762a2015-03-22 23:32:09 +0200390 return 0
koder aka kdanilov3f356262015-02-13 08:06:14 -0800391
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800392
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800393if __name__ == '__main__':
koder aka kdanilove06762a2015-03-22 23:32:09 +0200394 exit(main(sys.argv))