blob: 3787da41c87a8d82f2cdc19c06455ec390de16a2 [file] [log] [blame]
koder aka kdanilov4af80852015-02-01 23:36:38 +02001import os
2import re
3import sys
4import time
5import yaml
6import json
koder aka kdanilov78ba8952015-02-03 01:11:23 +02007import pprint
koder aka kdanilov4af80852015-02-01 23:36:38 +02008import os.path
koder aka kdanilov80cb6192015-02-02 03:06:08 +02009import argparse
koder aka kdanilov4af80852015-02-01 23:36:38 +020010import datetime
11import warnings
12import functools
13import contextlib
14import multiprocessing
15
16from rally import exceptions
17from rally.cmd import cliutils
18from rally.cmd.main import categories
19from rally.benchmark.scenarios.vm.utils import VMScenario
20
koder aka kdanilov80cb6192015-02-02 03:06:08 +020021from ssh_copy_directory import put_dir_recursively, ssh_copy_file
22
koder aka kdanilov4af80852015-02-01 23:36:38 +020023
24def log(x):
25 dt_str = datetime.datetime.now().strftime("%H:%M:%S")
26 pref = dt_str + " " + str(os.getpid()) + " >>>> "
27 sys.stderr.write(pref + x.replace("\n", "\n" + pref) + "\n")
28
29
30def get_barrier(count):
31 val = multiprocessing.Value('i', count)
32 cond = multiprocessing.Condition()
33
34 def closure(timeout):
koder aka kdanilov80cb6192015-02-02 03:06:08 +020035 me_released = False
koder aka kdanilov4af80852015-02-01 23:36:38 +020036 with cond:
37 val.value -= 1
koder aka kdanilov4af80852015-02-01 23:36:38 +020038 if val.value == 0:
koder aka kdanilov80cb6192015-02-02 03:06:08 +020039 me_released = True
koder aka kdanilov4af80852015-02-01 23:36:38 +020040 cond.notify_all()
41 else:
42 cond.wait(timeout)
43 return val.value == 0
44
koder aka kdanilov80cb6192015-02-02 03:06:08 +020045 if me_released:
46 log("Test begins!")
47
koder aka kdanilov4af80852015-02-01 23:36:38 +020048 return closure
49
50
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020051# should actually use mock module for this,
52# but don't wanna to add new dependency
53
koder aka kdanilov4af80852015-02-01 23:36:38 +020054@contextlib.contextmanager
55def patch_VMScenario_run_command_over_ssh(paths,
koder aka kdanilov80cb6192015-02-02 03:06:08 +020056 on_result_cb,
koder aka kdanilov4af80852015-02-01 23:36:38 +020057 barrier=None,
58 latest_start_time=None):
59
koder aka kdanilov1b0d3502015-02-03 21:32:31 +020060 try:
61 orig = VMScenario.run_action
62 except AttributeError:
63 # rally code was changed
64 log("VMScenario class was changed and have no run_action"
65 " method anymore. Update patch code.")
66 raise exceptions.ScriptError("monkeypatch code fails on "
67 "VMScenario.run_action")
koder aka kdanilov4af80852015-02-01 23:36:38 +020068
69 @functools.wraps(orig)
70 def closure(self, ssh, *args, **kwargs):
71 try:
72 sftp = ssh._client.open_sftp()
73 except AttributeError:
74 # rally code was changed
75 log("Prototype of VMScenario.run_command_over_ssh "
76 "was changed. Update patch code.")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020077 raise exceptions.ScriptError("monkeypatch code fails on "
koder aka kdanilov4af80852015-02-01 23:36:38 +020078 "ssh._client.open_sftp()")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020079 try:
80 for src, dst in paths.items():
81 try:
82 if os.path.isfile(src):
83 ssh_copy_file(sftp, src, dst)
84 elif os.path.isdir(src):
85 put_dir_recursively(sftp, src, dst)
86 else:
87 templ = "Can't copy {0!r} - " + \
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020088 "it neither a file not a directory"
koder aka kdanilov80cb6192015-02-02 03:06:08 +020089 msg = templ.format(src)
90 log(msg)
91 raise exceptions.ScriptError(msg)
92 except exceptions.ScriptError:
93 raise
94 except Exception as exc:
95 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
96 msg = tmpl.format(src, dst, exc)
97 log(msg)
98 raise exceptions.ScriptError(msg)
99 finally:
100 sftp.close()
koder aka kdanilov4af80852015-02-01 23:36:38 +0200101
102 log("Start io test")
103
104 if barrier is not None:
105 if latest_start_time is not None:
106 timeout = latest_start_time - time.time()
107 else:
108 timeout = None
109
110 if timeout is not None and timeout > 0:
111 msg = "Ready and waiting on barrier. " + \
112 "Will wait at most {0} seconds"
113 log(msg.format(int(timeout)))
114
115 if not barrier(timeout):
116 log("Barrier timeouted")
117
118 try:
119 code, out, err = orig(self, ssh, *args, **kwargs)
120 except Exception as exc:
121 log("Rally raises exception {0}".format(exc.message))
122 raise
123
124 if 0 != code:
125 templ = "Script returns error! code={0}\n {1}"
126 log(templ.format(code, err.rstrip()))
127 else:
128 log("Test finished")
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200129
koder aka kdanilov4af80852015-02-01 23:36:38 +0200130 try:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200131 for line in out.split("\n"):
132 if line.strip() != "":
133 result = json.loads(line)
134 on_result_cb(result)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200135 except Exception as err:
136 log("Error during postprocessing results: {0!r}".format(err))
137
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200138 # result = {"rally": 0, "srally": 1}
139 result = {"rally": 0}
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200140 out = json.dumps(result)
141
koder aka kdanilov4af80852015-02-01 23:36:38 +0200142 return code, out, err
143
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200144 VMScenario.run_action = closure
koder aka kdanilov4af80852015-02-01 23:36:38 +0200145
146 try:
147 yield
148 finally:
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200149 VMScenario.run_action = orig
koder aka kdanilov4af80852015-02-01 23:36:38 +0200150
151
152def run_rally(rally_args):
153 return cliutils.run(['rally'] + rally_args, categories)
154
155
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200156def prepare_files(testtool_py_args_v, dst_testtool_path, files_dir):
koder aka kdanilov4af80852015-02-01 23:36:38 +0200157
158 # we do need temporary named files
159 with warnings.catch_warnings():
160 warnings.simplefilter("ignore")
161 py_file = os.tmpnam()
162 yaml_file = os.tmpnam()
163
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200164 testtool_py_inp_path = os.path.join(files_dir, "io.py")
165 py_src_cont = open(testtool_py_inp_path).read()
166 args_repl_rr = r'INSERT_TOOL_ARGS\(sys\.argv.*?\)'
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200167 py_dst_cont = re.sub(args_repl_rr, repr(testtool_py_args_v), py_src_cont)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200168
169 if py_dst_cont == args_repl_rr:
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200170 templ = "Can't find replace marker in file {0}"
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200171 msg = templ.format(testtool_py_inp_path)
172 log(msg)
173 raise ValueError(msg)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200174
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200175 yaml_src_cont = open(os.path.join(files_dir, "io.yaml")).read()
koder aka kdanilov4af80852015-02-01 23:36:38 +0200176 task_params = yaml.load(yaml_src_cont)
177 rcd_params = task_params['VMTasks.boot_runcommand_delete']
178 rcd_params[0]['args']['script'] = py_file
179 yaml_dst_cont = yaml.dump(task_params)
180
181 open(py_file, "w").write(py_dst_cont)
182 open(yaml_file, "w").write(yaml_dst_cont)
183
184 return yaml_file, py_file
185
186
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200187def run_test(tool, testtool_py_args_v, dst_testtool_path, files_dir,
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200188 rally_extra_opts, max_preparation_time=300):
189
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200190 path = 'iozone' if 'iozone' == tool else 'fio'
191 testtool_local = os.path.join(files_dir, path)
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200192 yaml_file, py_file = prepare_files(testtool_py_args_v,
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200193 dst_testtool_path,
koder aka kdanilov4af80852015-02-01 23:36:38 +0200194 files_dir)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200195 try:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200196 config = yaml.load(open(yaml_file).read())
197
198 vm_sec = 'VMTasks.boot_runcommand_delete'
199 concurrency = config[vm_sec][0]['runner']['concurrency']
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200200 copy_files = {testtool_local: dst_testtool_path}
koder aka kdanilov4af80852015-02-01 23:36:38 +0200201
202 result_queue = multiprocessing.Queue()
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200203 results_cb = result_queue.put
koder aka kdanilov4af80852015-02-01 23:36:38 +0200204
205 do_patch = patch_VMScenario_run_command_over_ssh
206
207 barrier = get_barrier(concurrency)
208 max_release_time = time.time() + max_preparation_time
209
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200210 with do_patch(copy_files, results_cb, barrier, max_release_time):
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200211 opts = ['task', 'start', yaml_file] + list(rally_extra_opts)
212 log("Start rally with opts '{0}'".format(" ".join(opts)))
213 run_rally(opts)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200214
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200215 rally_result = []
216 while not result_queue.empty():
217 rally_result.append(result_queue.get())
koder aka kdanilov4af80852015-02-01 23:36:38 +0200218
219 return rally_result
220
221 finally:
222 os.unlink(yaml_file)
223 os.unlink(py_file)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200224
225
226def parse_args(argv):
227 parser = argparse.ArgumentParser(
228 description="Run rally disk io performance test")
229 parser.add_argument("tool_type", help="test tool type",
230 choices=['iozone', 'fio'])
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200231 parser.add_argument("-l", dest='extra_logs',
232 action='store_true', default=False,
233 help="print some extra log info")
234 parser.add_argument("-o", "--io-opts", dest='io_opts',
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200235 nargs="*", default=[],
236 help="cmd line options for io.py")
237 parser.add_argument("-t", "--test-directory", help="directory with test",
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200238 dest="test_directory", required=True)
239 parser.add_argument("rally_extra_opts", nargs="*",
240 default=[], help="rally extra options")
241 parser.add_argument("--max-preparation-time", default=300,
242 type=int, dest="max_preparation_time")
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200243
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200244 return parser.parse_args(argv)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200245
246
247def main(argv):
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200248 opts = parse_args(argv)
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200249 dst_testtool_path = '/tmp/io_tool'
250
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200251 if not opts.extra_logs:
252 global log
253
254 def nolog(x):
255 pass
256
257 log = nolog
258
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200259 if opts.io_opts == []:
260 testtool_py_args_v = []
261
262 block_sizes = ["4k", "64k"]
263 ops = ['randwrite']
264 iodepths = ['8']
265 syncs = [True]
266
267 for block_size in block_sizes:
268 for op in ops:
269 for iodepth in iodepths:
270 for sync in syncs:
271 tt_argv = ['--type', opts.tool_type,
272 '-a', op,
273 '--iodepth', iodepth,
274 '--blocksize', block_size,
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200275 '--iosize', '20M']
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200276 if sync:
277 tt_argv.append('-s')
278 testtool_py_args_v.append(tt_argv)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200279 else:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200280 testtool_py_args_v = [o.split(" ") for o in opts.io_opts]
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200281
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200282 for io_argv_list in testtool_py_args_v:
283 io_argv_list.extend(['--binary-path', dst_testtool_path])
284
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200285 res = run_test(opts.tool_type,
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200286 testtool_py_args_v,
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200287 dst_testtool_path,
288 files_dir=opts.test_directory,
289 rally_extra_opts=opts.rally_extra_opts,
290 max_preparation_time=opts.max_preparation_time)
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200291
koder aka kdanilov1b0d3502015-02-03 21:32:31 +0200292 print "Results = ",
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200293 pprint.pprint(res)
294
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200295 return 0
koder aka kdanilov4af80852015-02-01 23:36:38 +0200296
297# ubuntu cloud image
298# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
299
300# glance image-create --name 'ubuntu' --disk-format qcow2
301# --container-format bare --is-public true --copy-from
302# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
303
304if __name__ == '__main__':
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200305 exit(main(sys.argv[1:]))