blob: 1f02e8d050e78a1c31efe699d5f09b1886f3aa04 [file] [log] [blame]
koder aka kdanilov4af80852015-02-01 23:36:38 +02001import os
2import re
3import sys
4import time
5import yaml
6import json
7import os.path
koder aka kdanilov80cb6192015-02-02 03:06:08 +02008import argparse
koder aka kdanilov4af80852015-02-01 23:36:38 +02009import datetime
10import warnings
11import functools
12import contextlib
13import multiprocessing
14
15from rally import exceptions
16from rally.cmd import cliutils
17from rally.cmd.main import categories
18from rally.benchmark.scenarios.vm.utils import VMScenario
19
koder aka kdanilov80cb6192015-02-02 03:06:08 +020020from ssh_copy_directory import put_dir_recursively, ssh_copy_file
21
koder aka kdanilov4af80852015-02-01 23:36:38 +020022
23def log(x):
24 dt_str = datetime.datetime.now().strftime("%H:%M:%S")
25 pref = dt_str + " " + str(os.getpid()) + " >>>> "
26 sys.stderr.write(pref + x.replace("\n", "\n" + pref) + "\n")
27
28
29def get_barrier(count):
30 val = multiprocessing.Value('i', count)
31 cond = multiprocessing.Condition()
32
33 def closure(timeout):
koder aka kdanilov80cb6192015-02-02 03:06:08 +020034 me_released = False
koder aka kdanilov4af80852015-02-01 23:36:38 +020035 with cond:
36 val.value -= 1
koder aka kdanilov4af80852015-02-01 23:36:38 +020037 if val.value == 0:
koder aka kdanilov80cb6192015-02-02 03:06:08 +020038 me_released = True
koder aka kdanilov4af80852015-02-01 23:36:38 +020039 cond.notify_all()
40 else:
41 cond.wait(timeout)
42 return val.value == 0
43
koder aka kdanilov80cb6192015-02-02 03:06:08 +020044 if me_released:
45 log("Test begins!")
46
koder aka kdanilov4af80852015-02-01 23:36:38 +020047 return closure
48
49
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020050# should actually use mock module for this,
51# but don't wanna to add new dependency
52
koder aka kdanilov4af80852015-02-01 23:36:38 +020053@contextlib.contextmanager
54def patch_VMScenario_run_command_over_ssh(paths,
koder aka kdanilov80cb6192015-02-02 03:06:08 +020055 on_result_cb,
koder aka kdanilov4af80852015-02-01 23:36:38 +020056 barrier=None,
57 latest_start_time=None):
58
59 orig = VMScenario.run_command_over_ssh
60
61 @functools.wraps(orig)
62 def closure(self, ssh, *args, **kwargs):
63 try:
64 sftp = ssh._client.open_sftp()
65 except AttributeError:
66 # rally code was changed
67 log("Prototype of VMScenario.run_command_over_ssh "
68 "was changed. Update patch code.")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020069 raise exceptions.ScriptError("monkeypatch code fails on "
koder aka kdanilov4af80852015-02-01 23:36:38 +020070 "ssh._client.open_sftp()")
koder aka kdanilov80cb6192015-02-02 03:06:08 +020071 try:
72 for src, dst in paths.items():
73 try:
74 if os.path.isfile(src):
75 ssh_copy_file(sftp, src, dst)
76 elif os.path.isdir(src):
77 put_dir_recursively(sftp, src, dst)
78 else:
79 templ = "Can't copy {0!r} - " + \
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +020080 "it neither a file not a directory"
koder aka kdanilov80cb6192015-02-02 03:06:08 +020081 msg = templ.format(src)
82 log(msg)
83 raise exceptions.ScriptError(msg)
84 except exceptions.ScriptError:
85 raise
86 except Exception as exc:
87 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
88 msg = tmpl.format(src, dst, exc)
89 log(msg)
90 raise exceptions.ScriptError(msg)
91 finally:
92 sftp.close()
koder aka kdanilov4af80852015-02-01 23:36:38 +020093
94 log("Start io test")
95
96 if barrier is not None:
97 if latest_start_time is not None:
98 timeout = latest_start_time - time.time()
99 else:
100 timeout = None
101
102 if timeout is not None and timeout > 0:
103 msg = "Ready and waiting on barrier. " + \
104 "Will wait at most {0} seconds"
105 log(msg.format(int(timeout)))
106
107 if not barrier(timeout):
108 log("Barrier timeouted")
109
110 try:
111 code, out, err = orig(self, ssh, *args, **kwargs)
112 except Exception as exc:
113 log("Rally raises exception {0}".format(exc.message))
114 raise
115
116 if 0 != code:
117 templ = "Script returns error! code={0}\n {1}"
118 log(templ.format(code, err.rstrip()))
119 else:
120 log("Test finished")
121 try:
122 try:
123 result = json.loads(out)
124 except:
125 pass
126 else:
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200127 on_result_cb(result)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200128 if '__meta__' in result:
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200129 result.pop('__meta__')
koder aka kdanilov4af80852015-02-01 23:36:38 +0200130 out = json.dumps(result)
131 except Exception as err:
132 log("Error during postprocessing results: {0!r}".format(err))
133
134 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 kdanilov98615bf2015-02-02 00:59:07 +0200148def prepare_files(testtool_py_argv, 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.*?\)'
159 py_dst_cont = re.sub(args_repl_rr, repr(testtool_py_argv), 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 kdanilov56ee3ed2015-02-02 19:00:31 +0200179def run_test(tool, testtool_py_argv, dst_testtool_path, files_dir,
180 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 kdanilov4af80852015-02-01 23:36:38 +0200184
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200185 yaml_file, py_file = prepare_files(testtool_py_argv,
186 dst_testtool_path,
koder aka kdanilov4af80852015-02-01 23:36:38 +0200187 files_dir)
188
189 config = yaml.load(open(yaml_file).read())
190
191 vm_sec = 'VMTasks.boot_runcommand_delete'
192 concurrency = config[vm_sec][0]['runner']['concurrency']
193
koder aka kdanilov4af80852015-02-01 23:36:38 +0200194 try:
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200195 copy_files = {testtool_local: dst_testtool_path}
koder aka kdanilov4af80852015-02-01 23:36:38 +0200196
197 result_queue = multiprocessing.Queue()
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200198 results_cb = result_queue.put
koder aka kdanilov4af80852015-02-01 23:36:38 +0200199
200 do_patch = patch_VMScenario_run_command_over_ssh
201
202 barrier = get_barrier(concurrency)
203 max_release_time = time.time() + max_preparation_time
204
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200205 with do_patch(copy_files, results_cb, barrier, max_release_time):
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200206 opts = ['task', 'start', yaml_file] + list(rally_extra_opts)
207 log("Start rally with opts '{0}'".format(" ".join(opts)))
208 run_rally(opts)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200209
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200210 rally_result = []
211 while not result_queue.empty():
212 rally_result.append(result_queue.get())
koder aka kdanilov4af80852015-02-01 23:36:38 +0200213
214 return rally_result
215
216 finally:
217 os.unlink(yaml_file)
218 os.unlink(py_file)
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200219
220
221def parse_args(argv):
222 parser = argparse.ArgumentParser(
223 description="Run rally disk io performance test")
224 parser.add_argument("tool_type", help="test tool type",
225 choices=['iozone', 'fio'])
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200226 parser.add_argument("-l", dest='extra_logs',
227 action='store_true', default=False,
228 help="print some extra log info")
229 parser.add_argument("-o", "--io-opts", dest='io_opts',
230 default=None, help="cmd line options for io.py")
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200231 parser.add_argument("--test-directory", help="directory with test",
232 dest="test_directory", required=True)
233 parser.add_argument("rally_extra_opts", nargs="*",
234 default=[], help="rally extra options")
235 parser.add_argument("--max-preparation-time", default=300,
236 type=int, dest="max_preparation_time")
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200237 return parser.parse_args(argv)
koder aka kdanilov4af80852015-02-01 23:36:38 +0200238
239
240def main(argv):
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200241 opts = parse_args(argv)
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200242 dst_testtool_path = '/tmp/io_tool'
243
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200244 if not opts.extra_logs:
245 global log
246
247 def nolog(x):
248 pass
249
250 log = nolog
251
252 if opts.io_opts is None:
253 testtool_py_argv = ['--type', opts.tool_type,
254 '-a', 'randwrite',
255 '--iodepth', '2',
256 '--blocksize', '4k',
257 '--iosize', '20M',
258 '--binary-path', dst_testtool_path,
259 '-d']
260 else:
261 testtool_py_argv = opts.io_opts.split(" ")
262
koder aka kdanilov56ee3ed2015-02-02 19:00:31 +0200263 res = run_test(opts.tool_type,
264 testtool_py_argv,
265 dst_testtool_path,
266 files_dir=opts.test_directory,
267 rally_extra_opts=opts.rally_extra_opts,
268 max_preparation_time=opts.max_preparation_time)
269 res
koder aka kdanilov80cb6192015-02-02 03:06:08 +0200270 return 0
koder aka kdanilov4af80852015-02-01 23:36:38 +0200271
272# ubuntu cloud image
273# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
274
275# glance image-create --name 'ubuntu' --disk-format qcow2
276# --container-format bare --is-public true --copy-from
277# https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
278
279if __name__ == '__main__':
koder aka kdanilov98615bf2015-02-02 00:59:07 +0200280 exit(main(sys.argv[1:]))