blob: 5166fdd4445641f94f1a96867ffd11281311aab6 [file] [log] [blame]
koder aka kdanilov4643fd62015-02-10 16:20:13 -08001import abc
koder aka kdanilov66839a92015-04-11 13:22:31 +03002import time
koder aka kdanilov4d4771c2015-04-23 01:32:02 +03003import random
koder aka kdanilov4643fd62015-02-10 16:20:13 -08004import os.path
koder aka kdanilove21d7472015-02-14 19:02:04 -08005import logging
koder aka kdanilovea22c3d2015-04-21 03:42:22 +03006import datetime
koder aka kdanilove21d7472015-02-14 19:02:04 -08007
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03008from wally.utils import ssize_to_b, open_for_append_or_create, sec_to_str
9
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030010from wally.ssh_utils import (copy_paths, run_over_ssh,
11 save_to_remote, ssh_mkdir,
12 # delete_file,
13 connect, read_from_remote, Local)
14
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030015from . import postgres
16from .io import agent as io_agent
17from .io import formatter as io_formatter
18from .io.results_loader import parse_output
koder aka kdanilov652cd802015-04-13 12:21:07 +030019
koder aka kdanilov4643fd62015-02-10 16:20:13 -080020
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030021logger = logging.getLogger("wally")
koder aka kdanilove21d7472015-02-14 19:02:04 -080022
23
koder aka kdanilov4643fd62015-02-10 16:20:13 -080024class IPerfTest(object):
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030025 def __init__(self, on_result_cb, test_uuid, node, log_directory=None):
koder aka kdanilov4643fd62015-02-10 16:20:13 -080026 self.on_result_cb = on_result_cb
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030027 self.log_directory = log_directory
28 self.node = node
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030029 self.test_uuid = test_uuid
koder aka kdanilov4643fd62015-02-10 16:20:13 -080030
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030031 def pre_run(self):
koder aka kdanilov4643fd62015-02-10 16:20:13 -080032 pass
33
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030034 def cleanup(self):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030035 pass
36
koder aka kdanilov4643fd62015-02-10 16:20:13 -080037 @abc.abstractmethod
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030038 def run(self, barrier):
koder aka kdanilov4643fd62015-02-10 16:20:13 -080039 pass
40
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030041 @classmethod
42 def format_for_console(cls, data):
43 msg = "{0}.format_for_console".format(cls.__name__)
44 raise NotImplementedError(msg)
45
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030046 def run_over_ssh(self, cmd, **kwargs):
47 return run_over_ssh(self.node.connection, cmd,
48 node=self.node.get_conn_id(), **kwargs)
49
koder aka kdanilov4643fd62015-02-10 16:20:13 -080050
Yulia Portnova7ddfa732015-02-24 17:32:58 +020051class TwoScriptTest(IPerfTest):
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030052 remote_tmp_dir = '/tmp'
53
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030054 def __init__(self, opts, *dt, **mp):
55 IPerfTest.__init__(self, *dt, **mp)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020056 self.opts = opts
Yulia Portnova7ddfa732015-02-24 17:32:58 +020057
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030058 if 'run_script' in self.opts:
59 self.run_script = self.opts['run_script']
60 self.prepare_script = self.opts['prepare_script']
Yulia Portnova7ddfa732015-02-24 17:32:58 +020061
62 def get_remote_for_script(self, script):
63 return os.path.join(self.tmp_dir, script.rpartition('/')[2])
64
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030065 def copy_script(self, src):
Yulia Portnova7ddfa732015-02-24 17:32:58 +020066 remote_path = self.get_remote_for_script(src)
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030067 copy_paths(self.node.connection, {src: remote_path})
Yulia Portnova7ddfa732015-02-24 17:32:58 +020068 return remote_path
69
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030070 def pre_run(self):
71 remote_script = self.copy_script(self.node.connection,
72 self.pre_run_script)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020073 cmd = remote_script
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030074 self.run_over_ssh(cmd)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020075
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030076 def run(self, barrier):
77 remote_script = self.copy_script(self.node.connection, self.run_script)
Yulia Portnova886a2562015-04-07 11:16:13 +030078 cmd_opts = ' '.join(["%s %s" % (key, val) for key, val
79 in self.opts.items()])
80 cmd = remote_script + ' ' + cmd_opts
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030081 out_err = self.run_over_ssh(cmd)
koder aka kdanilov66839a92015-04-11 13:22:31 +030082 self.on_result(out_err, cmd)
Yulia Portnova7ddfa732015-02-24 17:32:58 +020083
84 def parse_results(self, out):
85 for line in out.split("\n"):
86 key, separator, value = line.partition(":")
87 if key and value:
88 self.on_result_cb((key, float(value)))
89
koder aka kdanilov66839a92015-04-11 13:22:31 +030090 def on_result(self, out_err, cmd):
91 try:
92 self.parse_results(out_err)
93 except Exception as exc:
94 msg_templ = "Error during postprocessing results: {0!r}. {1}"
95 raise RuntimeError(msg_templ.format(exc.message, out_err))
Yulia Portnova7ddfa732015-02-24 17:32:58 +020096
97
98class PgBenchTest(TwoScriptTest):
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030099 root = os.path.dirname(postgres.__file__)
100 prepare_script = os.path.join(root, "prepare.sh")
101 run_script = os.path.join(root, "run.sh")
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300102
103
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800104class IOPerfTest(IPerfTest):
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300105 io_py_remote_templ = "/tmp/test/{0}/io/disk_test_agent.py"
106 log_fl_templ = "/tmp/test/{0}/io/disk_test_agent_log.txt"
107 pid_file_templ = "/tmp/test/{0}/io/disk_test_agent_pid_file"
108 task_file_templ = "/tmp/test/{0}/io/io_task.cfg"
koder aka kdanilov2c473092015-03-29 17:12:13 +0300109
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300110 def __init__(self, test_options, *dt, **mp):
111 IPerfTest.__init__(self, *dt, **mp)
koder aka kdanilov2c473092015-03-29 17:12:13 +0300112 self.options = test_options
koder aka kdanilovda45e882015-04-06 02:24:42 +0300113 self.config_fname = test_options['cfg']
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300114 self.alive_check_interval = test_options.get('alive_check_interval')
koder aka kdanilovda45e882015-04-06 02:24:42 +0300115 self.config_params = test_options.get('params', {})
116 self.tool = test_options.get('tool', 'fio')
117 self.raw_cfg = open(self.config_fname).read()
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300118 self.configs = list(io_agent.parse_all_in_1(self.raw_cfg,
119 self.config_params))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800120
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300121 cmd_log = os.path.join(self.log_directory, "task_compiled.cfg")
122 raw_res = os.path.join(self.log_directory, "raw_results.txt")
koder aka kdanilovda45e882015-04-06 02:24:42 +0300123
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300124 self.io_py_remote = self.io_py_remote_templ.format(self.test_uuid)
125 self.log_fl = self.log_fl_templ.format(self.test_uuid)
126 self.pid_file = self.pid_file_templ.format(self.test_uuid)
127 self.task_file = self.task_file_templ.format(self.test_uuid)
128
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300129 fio_command_file = open_for_append_or_create(cmd_log)
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300130
131 cfg_s_it = io_agent.compile_all_in_1(self.raw_cfg, self.config_params)
132 splitter = "\n\n" + "-" * 60 + "\n\n"
133 fio_command_file.write(splitter.join(cfg_s_it))
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300134 self.fio_raw_results_file = open_for_append_or_create(raw_res)
135
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300136 def cleanup(self):
137 # delete_file(conn, self.io_py_remote)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300138 # Need to remove tempo files, used for testing
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300139 pass
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300140
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300141 def pre_run(self):
142 ssh_mkdir(self.node.connection.open_sftp(),
143 os.path.dirname(self.io_py_remote),
144 intermediate=True)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300145 try:
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300146 self.run_over_ssh('which fio')
koder aka kdanilov652cd802015-04-13 12:21:07 +0300147 except OSError:
148 # TODO: install fio, if not installed
koder aka kdanilov652cd802015-04-13 12:21:07 +0300149 for i in range(3):
150 try:
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300151 self.run_over_ssh("sudo apt-get -y install fio")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300152 break
153 except OSError as err:
154 time.sleep(3)
155 else:
156 raise OSError("Can't install fio - " + err.message)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300157
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300158 local_fname = os.path.splitext(io_agent.__file__)[0] + ".py"
159
160 self.files_to_copy = {
161 local_fname: self.io_py_remote,
162 }
163
164 copy_paths(self.node.connection, self.files_to_copy)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800165
koder aka kdanilove87ae652015-04-20 02:14:35 +0300166 if self.options.get('prefill_files', True):
167 files = {}
koder aka kdanilov652cd802015-04-13 12:21:07 +0300168
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300169 for section in self.configs:
170 sz = ssize_to_b(section.vals['size'])
koder aka kdanilove87ae652015-04-20 02:14:35 +0300171 msz = sz / (1024 ** 2)
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300172
koder aka kdanilove87ae652015-04-20 02:14:35 +0300173 if sz % (1024 ** 2) != 0:
174 msz += 1
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800175
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300176 fname = section.vals['filename']
koder aka kdanilov652cd802015-04-13 12:21:07 +0300177
koder aka kdanilove87ae652015-04-20 02:14:35 +0300178 # if already has other test with the same file name
179 # take largest size
180 files[fname] = max(files.get(fname, 0), msz)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300181
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300182 cmd_templ = "dd oflag=direct " + \
183 "if=/dev/zero of={0} bs={1} count={2}"
koder aka kdanilove87ae652015-04-20 02:14:35 +0300184
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300185 if self.options.get("use_sudo", True):
186 cmd_templ = "sudo " + cmd_templ
187
koder aka kdanilove87ae652015-04-20 02:14:35 +0300188 ssize = 0
189 stime = time.time()
190
191 for fname, curr_sz in files.items():
192 cmd = cmd_templ.format(fname, 1024 ** 2, curr_sz)
193 ssize += curr_sz
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300194 self.run_over_ssh(cmd, timeout=curr_sz)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300195
196 ddtime = time.time() - stime
197 if ddtime > 1E-3:
198 fill_bw = int(ssize / ddtime)
199 mess = "Initiall dd fill bw is {0} MiBps for this vm"
200 logger.info(mess.format(fill_bw))
201 else:
202 logger.warning("Test files prefill disabled")
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800203
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300204 def run(self, barrier):
205 cmd_templ = "screen -S {screen_name} -d -m " + \
206 "env python2 {0} -p {pid_file} -o {results_file} " + \
207 "--type {1} {2} --json {3}"
208
209 if self.options.get("use_sudo", True):
210 cmd_templ = "sudo " + cmd_templ
koder aka kdanilov66839a92015-04-11 13:22:31 +0300211
212 params = " ".join("{0}={1}".format(k, v)
213 for k, v in self.config_params.items())
214
215 if "" != params:
216 params = "--params " + params
217
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300218 save_to_remote(self.node.connection.open_sftp(),
219 self.task_file, self.raw_cfg)
220
221 screen_name = self.test_uuid
222 cmd = cmd_templ.format(self.io_py_remote,
223 self.tool,
224 params,
225 self.task_file,
226 pid_file=self.pid_file,
227 results_file=self.log_fl,
228 screen_name=screen_name)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300229 logger.debug("Waiting on barrier")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300230
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300231 exec_time = io_agent.calculate_execution_time(self.configs)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300232 exec_time_str = sec_to_str(exec_time)
233
koder aka kdanilov2c473092015-03-29 17:12:13 +0300234 try:
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300235 timeout = int(exec_time * 2 + 300)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300236 if barrier.wait():
koder aka kdanilovea22c3d2015-04-21 03:42:22 +0300237 templ = "Test should takes about {0}." + \
238 " Should finish at {1}," + \
239 " will wait at most till {2}"
240 now_dt = datetime.datetime.now()
241 end_dt = now_dt + datetime.timedelta(0, exec_time)
242 wait_till = now_dt + datetime.timedelta(0, timeout)
243
244 logger.info(templ.format(exec_time_str,
245 end_dt.strftime("%H:%M:%S"),
246 wait_till.strftime("%H:%M:%S")))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300247
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300248 self.run_over_ssh(cmd)
249 logger.debug("Test started in screen {0}".format(screen_name))
250
251 end_of_wait_time = timeout + time.time()
252
253 # time_till_check = random.randint(30, 90)
254 time_till_check = 1
255
256 pid = None
257 no_pid_file = True
258 tcp_conn_timeout = 30
259 pid_get_timeout = 30 + time.time()
260
261 # TODO: add monitoring socket
262 if self.node.connection is not Local:
263 self.node.connection.close()
264
265 while end_of_wait_time > time.time():
266 conn = None
267 time.sleep(time_till_check)
268
269 try:
270 if self.node.connection is not Local:
271 conn = connect(self.node.conn_url,
272 conn_timeout=tcp_conn_timeout)
273 else:
274 conn = self.node.connection
275 except:
276 logging.exception("During connect")
277 continue
278
279 try:
280 pid = read_from_remote(conn.open_sftp(), self.pid_file)
281 no_pid_file = False
282 except (NameError, IOError):
283 no_pid_file = True
284
285 if conn is not Local:
286 conn.close()
287
288 if no_pid_file:
289 if pid is None:
290 if time.time() > pid_get_timeout:
291 msg = "On node {0} pid file doesn't " + \
292 "appears in time"
293 logging.error(msg.format(self.node.get_conn_id()))
294 raise RuntimeError("Start timeout")
295 else:
296 # execution finished
297 break
298
299 logger.debug("Done")
300
301 if self.node.connection is not Local:
302 timeout = tcp_conn_timeout * 3
303 self.node.connection = connect(self.node.conn_url,
304 conn_timeout=timeout)
305
306 # try to reboot and then connect
307 out_err = read_from_remote(self.node.connection.open_sftp(),
308 self.log_fl)
koder aka kdanilov2c473092015-03-29 17:12:13 +0300309 finally:
310 barrier.exit()
311
koder aka kdanilov652cd802015-04-13 12:21:07 +0300312 self.on_result(out_err, cmd)
313
koder aka kdanilov66839a92015-04-11 13:22:31 +0300314 def on_result(self, out_err, cmd):
315 try:
316 for data in parse_output(out_err):
317 self.on_result_cb(data)
318 except Exception as exc:
319 msg_templ = "Error during postprocessing results: {0!r}"
320 raise RuntimeError(msg_templ.format(exc.message))
321
322 def merge_results(self, results):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300323 if len(results) == 0:
324 return None
325
koder aka kdanilov66839a92015-04-11 13:22:31 +0300326 merged_result = results[0]
327 merged_data = merged_result['res']
328 expected_keys = set(merged_data.keys())
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300329 mergable_fields = ['bw', 'clat', 'iops', 'lat', 'slat']
koder aka kdanilov66839a92015-04-11 13:22:31 +0300330
331 for res in results[1:]:
332 assert res['__meta__'] == merged_result['__meta__']
333
334 data = res['res']
335 diff = set(data.keys()).symmetric_difference(expected_keys)
336
337 msg = "Difference: {0}".format(",".join(diff))
338 assert len(diff) == 0, msg
339
340 for testname, test_data in data.items():
341 res_test_data = merged_data[testname]
342
343 diff = set(test_data.keys()).symmetric_difference(
344 res_test_data.keys())
345
346 msg = "Difference: {0}".format(",".join(diff))
347 assert len(diff) == 0, msg
348
349 for k, v in test_data.items():
350 if k in mergable_fields:
351 res_test_data[k].extend(v)
352 else:
353 msg = "{0!r} != {1!r}".format(res_test_data[k], v)
354 assert res_test_data[k] == v, msg
355
356 return merged_result
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300357
358 @classmethod
359 def format_for_console(cls, data):
360 return io_formatter.format_results_for_console(data)