blob: 4cac0052eeefa1713c5f88e4ac01be6ac2129858 [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
60 orig = VMScenario.run_command_over_ssh
61
62 @functools.wraps(orig)
63 def closure(self, ssh, *args, **kwargs):
64 try:
65 sftp = ssh._client.open_sftp()
66 except AttributeError:
67 # rally code was changed
68 log("Prototype of VMScenario.run_command_over_ssh "
69 "was changed. Update patch code.")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020070 raise exceptions.ScriptError("monkeypatch code fails on "
koder aka kdanilov4af80852015-02-01 23:36:38 +020071 "ssh._client.open_sftp()")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020072 try:
73 for src, dst in paths.items():
74 try:
75 if os.path.isfile(src):
76 ssh_copy_file(sftp, src, dst)
77 elif os.path.isdir(src):
78 put_dir_recursively(sftp, src, dst)
79 else:
80 templ = "Can't copy {0!r} - " + \
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020081 "it neither a file not a directory"
koder aka kdanilov80cb6192015-02-02 03:06:08 +020082 msg = templ.format(src)
83 log(msg)
84 raise exceptions.ScriptError(msg)
85 except exceptions.ScriptError:
86 raise
87 except Exception as exc:
88 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
89 msg = tmpl.format(src, dst, exc)
90 log(msg)
91 raise exceptions.ScriptError(msg)
92 finally:
93 sftp.close()
koder aka kdanilov4af80852015-02-01 23:36:38 +020094
95 log("Start io test")
96
97 if barrier is not None:
98 if latest_start_time is not None:
99 timeout = latest_start_time - time.time()
100 else:
101 timeout = None
102
103 if timeout is not None and timeout > 0:
104 msg = "Ready and waiting on barrier. " + \
105 "Will wait at most {0} seconds"
106 log(msg.format(int(timeout)))
107
108 if not barrier(timeout):
109 log("Barrier timeouted")
110
111 try:
112 code, out, err = orig(self, ssh, *args, **kwargs)
113 except Exception as exc:
114 log("Rally raises exception {0}".format(exc.message))
115 raise
116
117 if 0 != code:
118 templ = "Script returns error! code={0}\n {1}"
119 log(templ.format(code, err.rstrip()))
120 else:
121 log("Test finished")
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200122
koder aka kdanilov4af80852015-02-01 23:36:38 +0200123 try:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200124 for line in out.split("\n"):
125 if line.strip() != "":
126 result = json.loads(line)
127 on_result_cb(result)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200128 except Exception as err:
129 log("Error during postprocessing results: {0!r}".format(err))
130
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200131 result = {"rally": 0, "srally": 1}
132 out = json.dumps(result)
133
koder aka kdanilov4af80852015-02-01 23:36:38 +0200134 return code, out, err
135
136 VMScenario.run_command_over_ssh = closure
137
138 try:
139 yield
140 finally:
141 VMScenario.run_command_over_ssh = orig
142
143
144def run_rally(rally_args):
145 return cliutils.run(['rally'] + rally_args, categories)
146
147
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200148def prepare_files(testtool_py_args_v, dst_testtool_path, files_dir):
koder aka kdanilov4af80852015-02-01 23:36:38 +0200149
150 # we do need temporary named files
151 with warnings.catch_warnings():
152 warnings.simplefilter("ignore")
153 py_file = os.tmpnam()
154 yaml_file = os.tmpnam()
155
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200156 testtool_py_inp_path = os.path.join(files_dir, "io.py")
157 py_src_cont = open(testtool_py_inp_path).read()
158 args_repl_rr = r'INSERT_TOOL_ARGS\(sys\.argv.*?\)'
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200159 py_dst_cont = re.sub(args_repl_rr, repr(testtool_py_args_v), py_src_cont)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200160
161 if py_dst_cont == args_repl_rr:
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200162 templ = "Can't find replace marker in file {0}"
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200163 msg = templ.format(testtool_py_inp_path)
164 log(msg)
165 raise ValueError(msg)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200166
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200167 yaml_src_cont = open(os.path.join(files_dir, "io.yaml")).read()
koder aka kdanilov4af80852015-02-01 23:36:38 +0200168 task_params = yaml.load(yaml_src_cont)
169 rcd_params = task_params['VMTasks.boot_runcommand_delete']
170 rcd_params[0]['args']['script'] = py_file
171 yaml_dst_cont = yaml.dump(task_params)
172
173 open(py_file, "w").write(py_dst_cont)
174 open(yaml_file, "w").write(yaml_dst_cont)
175
176 return yaml_file, py_file
177
178
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200179def run_test(tool, testtool_py_args_v, dst_testtool_path, files_dir,
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200180 rally_extra_opts, max_preparation_time=300):
181
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200182 path = 'iozone' if 'iozone' == tool else 'fio'
183 testtool_local = os.path.join(files_dir, path)
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200184 yaml_file, py_file = prepare_files(testtool_py_args_v,
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200185 dst_testtool_path,
koder aka kdanilov4af80852015-02-01 23:36:38 +0200186 files_dir)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200187 try:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200188 config = yaml.load(open(yaml_file).read())
189
190 vm_sec = 'VMTasks.boot_runcommand_delete'
191 concurrency = config[vm_sec][0]['runner']['concurrency']
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200192 copy_files = {testtool_local: dst_testtool_path}
koder aka kdanilov4af80852015-02-01 23:36:38 +0200193
194 result_queue = multiprocessing.Queue()
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200195 results_cb = result_queue.put
koder aka kdanilov4af80852015-02-01 23:36:38 +0200196
197 do_patch = patch_VMScenario_run_command_over_ssh
198
199 barrier = get_barrier(concurrency)
200 max_release_time = time.time() + max_preparation_time
201
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200202 with do_patch(copy_files, results_cb, barrier, max_release_time):
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200203 opts = ['task', 'start', yaml_file] + list(rally_extra_opts)
204 log("Start rally with opts '{0}'".format(" ".join(opts)))
205 run_rally(opts)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200206
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200207 rally_result = []
208 while not result_queue.empty():
209 rally_result.append(result_queue.get())
koder aka kdanilov4af80852015-02-01 23:36:38 +0200210
211 return rally_result
212
213 finally:
214 os.unlink(yaml_file)
215 os.unlink(py_file)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200216
217
218def parse_args(argv):
219 parser = argparse.ArgumentParser(
220 description="Run rally disk io performance test")
221 parser.add_argument("tool_type", help="test tool type",
222 choices=['iozone', 'fio'])
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200223 parser.add_argument("-l", dest='extra_logs',
224 action='store_true', default=False,
225 help="print some extra log info")
226 parser.add_argument("-o", "--io-opts", dest='io_opts',
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200227 nargs="*", default=[],
228 help="cmd line options for io.py")
229 parser.add_argument("-t", "--test-directory", help="directory with test",
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200230 dest="test_directory", required=True)
231 parser.add_argument("rally_extra_opts", nargs="*",
232 default=[], help="rally extra options")
233 parser.add_argument("--max-preparation-time", default=300,
234 type=int, dest="max_preparation_time")
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200235
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200236 return parser.parse_args(argv)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200237
238
239def main(argv):
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200240 opts = parse_args(argv)
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200241 dst_testtool_path = '/tmp/io_tool'
242
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200243 if not opts.extra_logs:
244 global log
245
246 def nolog(x):
247 pass
248
249 log = nolog
250
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200251 if opts.io_opts == []:
252 testtool_py_args_v = []
253
254 block_sizes = ["4k", "64k"]
255 ops = ['randwrite']
256 iodepths = ['8']
257 syncs = [True]
258
259 for block_size in block_sizes:
260 for op in ops:
261 for iodepth in iodepths:
262 for sync in syncs:
263 tt_argv = ['--type', opts.tool_type,
264 '-a', op,
265 '--iodepth', iodepth,
266 '--blocksize', block_size,
267 '--iosize', '20M',
268 '--binary-path', dst_testtool_path]
269 if sync:
270 tt_argv.append('-s')
271 testtool_py_args_v.append(tt_argv)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200272 else:
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200273 testtool_py_args_v = [o.split(" ") for o in opts.io_opts]
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200274
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200275 res = run_test(opts.tool_type,
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200276 testtool_py_args_v,
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200277 dst_testtool_path,
278 files_dir=opts.test_directory,
279 rally_extra_opts=opts.rally_extra_opts,
280 max_preparation_time=opts.max_preparation_time)
koder aka kdanilov78ba8952015-02-03 01:11:23 +0200281
282 print "Results =",
283 pprint.pprint(res)
284
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200285 return 0
koder aka kdanilov4af80852015-02-01 23:36:38 +0200286
287# ubuntu cloud image
288# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
289
290# glance image-create --name 'ubuntu' --disk-format qcow2
291# --container-format bare --is-public true --copy-from
292# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
293
294if __name__ == '__main__':
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200295 exit(main(sys.argv[1:]))