blob: f85213f70deb70e64cd3f00add54b3483165ecdb [file] [log] [blame]
mbligh63073c92008-03-31 16:49:32 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. Released under the GPL v2
4
mbligh849a0f62008-08-28 20:12:19 +00005import os, pickle, random, re, resource, select, shutil, signal, StringIO
6import socket, struct, subprocess, sys, time, textwrap, urllib, urlparse
7import warnings
mbligh76a42932008-10-09 20:35:38 +00008from autotest_lib.client.common_lib import error, barrier, debug
mbligh81edd792008-08-26 16:54:02 +00009
mbligh849a0f62008-08-28 20:12:19 +000010def deprecated(func):
11 """This is a decorator which can be used to mark functions as deprecated.
12 It will result in a warning being emmitted when the function is used."""
13 def new_func(*args, **dargs):
14 warnings.warn("Call to deprecated function %s." % func.__name__,
15 category=DeprecationWarning)
16 return func(*args, **dargs)
17 new_func.__name__ = func.__name__
18 new_func.__doc__ = func.__doc__
19 new_func.__dict__.update(func.__dict__)
20 return new_func
21
22
23class BgJob(object):
mblighbd96b452008-09-03 23:14:27 +000024 def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True):
mbligh849a0f62008-08-28 20:12:19 +000025 self.command = command
26 self.stdout_tee = stdout_tee
27 self.stderr_tee = stderr_tee
28 self.result = CmdResult(command)
mbligh76a42932008-10-09 20:35:38 +000029 self.log = debug.get_logger()
mblighbd96b452008-09-03 23:14:27 +000030 if verbose:
mbligh76a42932008-10-09 20:35:38 +000031 self.log.debug("running: %s" % command)
mbligh849a0f62008-08-28 20:12:19 +000032 self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
33 stderr=subprocess.PIPE,
34 preexec_fn=self._reset_sigpipe, shell=True,
35 executable="/bin/bash")
36
37
38 def output_prepare(self, stdout_file=None, stderr_file=None):
39 self.stdout_file = stdout_file
40 self.stderr_file = stderr_file
41
42 def process_output(self, stdout=True, final_read=False):
43 """output_prepare must be called prior to calling this"""
44 if stdout:
45 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
46 else:
47 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
48
49 if final_read:
50 # read in all the data we can from pipe and then stop
51 data = []
52 while select.select([pipe], [], [], 0)[0]:
53 data.append(os.read(pipe.fileno(), 1024))
54 if len(data[-1]) == 0:
55 break
56 data = "".join(data)
57 else:
58 # perform a single read
59 data = os.read(pipe.fileno(), 1024)
60 buf.write(data)
61 if tee:
62 tee.write(data)
63 tee.flush()
64
65
66 def cleanup(self):
67 self.sp.stdout.close()
68 self.sp.stderr.close()
69 self.result.stdout = self.stdout_file.getvalue()
70 self.result.stderr = self.stderr_file.getvalue()
71
72
73 def _reset_sigpipe(self):
74 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
75
mbligh81edd792008-08-26 16:54:02 +000076
77def ip_to_long(ip):
78 # !L is a long in network byte order
79 return struct.unpack('!L', socket.inet_aton(ip))[0]
80
81
82def long_to_ip(number):
83 # See above comment.
84 return socket.inet_ntoa(struct.pack('!L', number))
85
86
87def create_subnet_mask(bits):
88 # ~ does weird things in python...but this does work
89 return (1 << 32) - (1 << 32-bits)
90
91
92def format_ip_with_mask(ip, mask_bits):
93 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
94 return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
mbligh6231cd62008-02-02 19:18:33 +000095
mblighde0d47e2008-03-28 14:37:18 +000096
jadmanskie80d4712008-10-03 16:15:59 +000097def normalize_hostname(alias):
98 ip = socket.gethostbyname(alias)
99 return socket.gethostbyaddr(ip)[0]
100
101
mblighd6d043c2008-09-27 21:00:45 +0000102def get_ip_local_port_range():
103 match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
104 read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
105 return (int(match.group(1)), int(match.group(2)))
106
107
108def set_ip_local_port_range(lower, upper):
109 write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
110 '%d %d\n' % (lower, upper))
111
mbligh315b9412008-10-01 03:34:11 +0000112
jadmanski5182e162008-05-13 21:48:16 +0000113def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +0000114 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +0000115
116
mblighb9d05512008-10-18 13:53:27 +0000117def write_one_line(filename, line):
118 open_write_close(filename, line.rstrip('\n') + '\n')
119
120
121def open_write_close(filename, data):
mbligh618ac9e2008-10-06 17:14:32 +0000122 f = open(filename, 'w')
mblighb9d05512008-10-18 13:53:27 +0000123 try:
124 f.write(data)
125 finally:
126 f.close()
jadmanski5182e162008-05-13 21:48:16 +0000127
128
mblighde0d47e2008-03-28 14:37:18 +0000129def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000130 """
131 Read a key-value pair format file into a dictionary, and return it.
132 Takes either a filename or directory name as input. If it's a
133 directory name, we assume you want the file to be called keyval.
134 """
135 if os.path.isdir(path):
136 path = os.path.join(path, 'keyval')
137 keyval = {}
138 for line in open(path):
jadmanskia6014a02008-07-14 19:41:54 +0000139 line = re.sub('#.*', '', line).rstrip()
jadmanski0afbb632008-06-06 21:10:57 +0000140 if not re.search(r'^[-\w]+=', line):
141 raise ValueError('Invalid format line: %s' % line)
142 key, value = line.split('=', 1)
143 if re.search('^\d+$', value):
144 value = int(value)
145 elif re.search('^(\d+\.)?\d+$', value):
146 value = float(value)
147 keyval[key] = value
148 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000149
150
jadmanskicc549172008-05-21 18:11:51 +0000151def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000152 """
153 Write a key-value pair format file out to a file. This uses append
154 mode to open the file, so existing text will not be overwritten or
155 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000156
jadmanski0afbb632008-06-06 21:10:57 +0000157 If type_tag is None, then the key must be composed of alphanumeric
158 characters (or dashes+underscores). However, if type-tag is not
159 null then the keys must also have "{type_tag}" as a suffix. At
160 the moment the only valid values of type_tag are "attr" and "perf".
161 """
162 if os.path.isdir(path):
163 path = os.path.join(path, 'keyval')
164 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000165
jadmanski0afbb632008-06-06 21:10:57 +0000166 if type_tag is None:
167 key_regex = re.compile(r'^[-\w]+$')
168 else:
169 if type_tag not in ('attr', 'perf'):
170 raise ValueError('Invalid type tag: %s' % type_tag)
171 escaped_tag = re.escape(type_tag)
172 key_regex = re.compile(r'^[-\w]+\{%s\}$' % escaped_tag)
173 try:
174 for key, value in dictionary.iteritems():
175 if not key_regex.search(key):
176 raise ValueError('Invalid key: %s' % key)
177 keyval.write('%s=%s\n' % (key, value))
178 finally:
179 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000180
181
182def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000183 """Return true if path looks like a URL"""
184 # for now, just handle http and ftp
185 url_parts = urlparse.urlparse(path)
186 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000187
188
jadmanskied91ba92008-09-30 17:19:27 +0000189def urlopen(url, data=None, proxies=None, timeout=5):
jadmanski0afbb632008-06-06 21:10:57 +0000190 """Wrapper to urllib.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000191
jadmanski0afbb632008-06-06 21:10:57 +0000192 # Save old timeout
193 old_timeout = socket.getdefaulttimeout()
194 socket.setdefaulttimeout(timeout)
195 try:
196 return urllib.urlopen(url, data=data, proxies=proxies)
197 finally:
198 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000199
200
201def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +0000202 """Wrapper to urllib.urlretrieve with timeout addition."""
203 old_timeout = socket.getdefaulttimeout()
204 socket.setdefaulttimeout(timeout)
205 try:
206 return urllib.urlretrieve(url, filename=filename,
207 reporthook=reporthook, data=data)
208 finally:
209 socket.setdefaulttimeout(old_timeout)
210
mbligh02ff2d52008-06-03 15:00:21 +0000211
mbligh6231cd62008-02-02 19:18:33 +0000212def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000213 """Get a file from src, which can be local or a remote URL"""
214 if (src == dest):
215 return
216 if (is_url(src)):
217 print 'PWD: ' + os.getcwd()
218 print 'Fetching \n\t', src, '\n\t->', dest
219 try:
220 urllib.urlretrieve(src, dest)
221 except IOError, e:
222 raise error.AutotestError('Unable to retrieve %s (to %s)'
223 % (src, dest), e)
224 else:
225 shutil.copyfile(src, dest)
226 if permissions:
227 os.chmod(dest, permissions)
228 return dest
mbligh6231cd62008-02-02 19:18:33 +0000229
230
231def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000232 """
233 Receives either a path to a local file or a URL.
234 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000235
jadmanski0afbb632008-06-06 21:10:57 +0000236 unmap_url('/usr/src', 'foo.tar', '/tmp')
237 = '/usr/src/foo.tar'
238 unmap_url('/usr/src', 'http://site/file', '/tmp')
239 = '/tmp/file'
240 (after retrieving it)
241 """
242 if is_url(src):
243 url_parts = urlparse.urlparse(src)
244 filename = os.path.basename(url_parts[2])
245 dest = os.path.join(destdir, filename)
246 return get_file(src, dest)
247 else:
248 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000249
250
251def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000252 *args, **dargs):
253 """
254 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000255
jadmanski0afbb632008-06-06 21:10:57 +0000256 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000257
jadmanski0afbb632008-06-06 21:10:57 +0000258 In the preserve_srcdir case, we just check it's up to date,
259 and if not, we rerun install, without removing srcdir
260 """
261 versionfile = os.path.join(srcdir, '.version')
262 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000263
jadmanski0afbb632008-06-06 21:10:57 +0000264 if os.path.exists(versionfile):
265 old_version = pickle.load(open(versionfile))
266 if old_version == new_version:
267 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000268
jadmanski0afbb632008-06-06 21:10:57 +0000269 if install_needed:
270 if not preserve_srcdir and os.path.exists(srcdir):
271 shutil.rmtree(srcdir)
272 install(*args, **dargs)
273 if os.path.exists(srcdir):
274 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000275
276
mbligh63073c92008-03-31 16:49:32 +0000277def run(command, timeout=None, ignore_status=False,
mblighbd96b452008-09-03 23:14:27 +0000278 stdout_tee=None, stderr_tee=None, verbose=True):
jadmanski0afbb632008-06-06 21:10:57 +0000279 """
280 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000281
jadmanski0afbb632008-06-06 21:10:57 +0000282 Args:
283 command: the command line string
284 timeout: time limit in seconds before attempting to
285 kill the running process. The run() function
286 will take a few seconds longer than 'timeout'
287 to complete if it has to kill the process.
288 ignore_status: do not raise an exception, no matter what
289 the exit code of the command is.
290 stdout_tee: optional file-like object to which stdout data
291 will be written as it is generated (data will still
292 be stored in result.stdout)
293 stderr_tee: likewise for stderr
mbligh63073c92008-03-31 16:49:32 +0000294
jadmanski0afbb632008-06-06 21:10:57 +0000295 Returns:
296 a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000297
jadmanski0afbb632008-06-06 21:10:57 +0000298 Raises:
299 CmdError: the exit code of the command
300 execution was not 0
301 """
mblighbd96b452008-09-03 23:14:27 +0000302 bg_job = join_bg_jobs((BgJob(command, stdout_tee, stderr_tee, verbose),),
mbligh849a0f62008-08-28 20:12:19 +0000303 timeout)[0]
304 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000305 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000306 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000307
mbligh849a0f62008-08-28 20:12:19 +0000308 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000309
mbligha5630a52008-09-03 22:09:50 +0000310def run_parallel(commands, timeout=None, ignore_status=False,
311 stdout_tee=None, stderr_tee=None):
312 """Beahves the same as run with the following exceptions:
313
314 - commands is a list of commands to run in parallel.
315 - ignore_status toggles whether or not an exception should be raised
316 on any error.
317
318 returns a list of CmdResult objects
319 """
320 bg_jobs = []
321 for command in commands:
322 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee))
323
324 # Updates objects in bg_jobs list with their process information
325 join_bg_jobs(bg_jobs, timeout)
326
327 for bg_job in bg_jobs:
328 if not ignore_status and bg_job.result.exit_status:
329 raise error.CmdError(command, bg_job.result,
330 "Command returned non-zero exit status")
331
332 return [bg_job.result for bg_job in bg_jobs]
333
334
mbligh849a0f62008-08-28 20:12:19 +0000335@deprecated
mbligh63073c92008-03-31 16:49:32 +0000336def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000337 """Function deprecated. Please use BgJob class instead."""
338 bg_job = BgJob(command)
339 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000340
341
mbligh849a0f62008-08-28 20:12:19 +0000342def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000343 """Joins the bg_jobs with the current thread.
344
345 Returns the same list of bg_jobs objects that was passed in.
346 """
mbligh849a0f62008-08-28 20:12:19 +0000347 ret, timeouterr = 0, False
348 for bg_job in bg_jobs:
349 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000350
jadmanski0afbb632008-06-06 21:10:57 +0000351 try:
352 # We are holding ends to stdin, stdout pipes
353 # hence we need to be sure to close those fds no mater what
354 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000355 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
356
357 for bg_job in bg_jobs:
358 # Process stdout and stderr
359 bg_job.process_output(stdout=True,final_read=True)
360 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000361 finally:
362 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000363 for bg_job in bg_jobs:
364 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000365
mbligh849a0f62008-08-28 20:12:19 +0000366 if timeout_error:
367 # TODO: This needs to be fixed to better represent what happens when
368 # running in parallel. However this is backwards compatable, so it will
369 # do for the time being.
370 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
371 "Command(s) did not complete within %d seconds"
372 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000373
mbligh63073c92008-03-31 16:49:32 +0000374
mbligh849a0f62008-08-28 20:12:19 +0000375 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000376
mbligh849a0f62008-08-28 20:12:19 +0000377
378def _wait_for_commands(bg_jobs, start_time, timeout):
379 # This returns True if it must return due to a timeout, otherwise False.
380
mblighf0b4a0a2008-09-03 20:46:16 +0000381 # To check for processes which terminate without producing any output
382 # a 1 second timeout is used in select.
383 SELECT_TIMEOUT = 1
384
mbligh849a0f62008-08-28 20:12:19 +0000385 select_list = []
386 reverse_dict = {}
387 for bg_job in bg_jobs:
388 select_list.append(bg_job.sp.stdout)
389 select_list.append(bg_job.sp.stderr)
390 reverse_dict[bg_job.sp.stdout] = (bg_job,True)
391 reverse_dict[bg_job.sp.stderr] = (bg_job,False)
392
jadmanski0afbb632008-06-06 21:10:57 +0000393 if timeout:
394 stop_time = start_time + timeout
395 time_left = stop_time - time.time()
396 else:
397 time_left = None # so that select never times out
398 while not timeout or time_left > 0:
399 # select will return when stdout is ready (including when it is
400 # EOF, that is the process has terminated).
mblighf0b4a0a2008-09-03 20:46:16 +0000401 ready, _, _ = select.select(select_list, [], [], SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000402
jadmanski0afbb632008-06-06 21:10:57 +0000403 # os.read() has to be used instead of
404 # subproc.stdout.read() which will otherwise block
mbligh849a0f62008-08-28 20:12:19 +0000405 for fileno in ready:
406 bg_job,stdout = reverse_dict[fileno]
407 bg_job.process_output(stdout)
mbligh63073c92008-03-31 16:49:32 +0000408
mbligh849a0f62008-08-28 20:12:19 +0000409 remaining_jobs = [x for x in bg_jobs if x.result.exit_status is None]
410 if len(remaining_jobs) == 0:
411 return False
412 for bg_job in remaining_jobs:
413 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000414
jadmanski0afbb632008-06-06 21:10:57 +0000415 if timeout:
416 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000417
mbligh849a0f62008-08-28 20:12:19 +0000418 # Kill all processes which did not complete prior to timeout
419 for bg_job in [x for x in bg_jobs if x.result.exit_status is None]:
mbligh7afc3a62008-11-27 00:35:44 +0000420 print '* Warning: run process timeout (%s) fired' % timeout
mbligh849a0f62008-08-28 20:12:19 +0000421 nuke_subprocess(bg_job.sp)
mbligh095dc642008-10-01 03:41:35 +0000422 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000423
mbligh849a0f62008-08-28 20:12:19 +0000424 return True
mbligh63073c92008-03-31 16:49:32 +0000425
426
mbligh63073c92008-03-31 16:49:32 +0000427def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000428 # check if the subprocess is still alive, first
429 if subproc.poll() is not None:
430 return subproc.poll()
431
jadmanski0afbb632008-06-06 21:10:57 +0000432 # the process has not terminated within timeout,
433 # kill it via an escalating series of signals.
434 signal_queue = [signal.SIGTERM, signal.SIGKILL]
435 for sig in signal_queue:
436 try:
437 os.kill(subproc.pid, sig)
438 # The process may have died before we could kill it.
439 except OSError:
440 pass
mbligh63073c92008-03-31 16:49:32 +0000441
jadmanski0afbb632008-06-06 21:10:57 +0000442 for i in range(5):
443 rc = subproc.poll()
444 if rc != None:
445 return rc
446 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000447
448
449def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000450 # the process has not terminated within timeout,
451 # kill it via an escalating series of signals.
452 signal_queue = [signal.SIGTERM, signal.SIGKILL]
453 for sig in signal_queue:
454 try:
455 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000456
jadmanski0afbb632008-06-06 21:10:57 +0000457 # The process may have died before we could kill it.
458 except OSError:
459 pass
mbligh63073c92008-03-31 16:49:32 +0000460
jadmanski0afbb632008-06-06 21:10:57 +0000461 try:
462 for i in range(5):
463 status = os.waitpid(pid, os.WNOHANG)[0]
464 if status == pid:
465 return
466 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000467
jadmanski0afbb632008-06-06 21:10:57 +0000468 if status != pid:
469 raise error.AutoservRunError('Could not kill %d'
470 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000471
jadmanski0afbb632008-06-06 21:10:57 +0000472 # the process died before we join it.
473 except OSError:
474 pass
mbligh63073c92008-03-31 16:49:32 +0000475
476
mbligh63073c92008-03-31 16:49:32 +0000477
478def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000479 """This function returns the exit status of command."""
mblighf8dffb12008-10-29 16:45:26 +0000480 return run(command, timeout=timeout, ignore_status=ignore_status,
mbligha5630a52008-09-03 22:09:50 +0000481 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
mbligh63073c92008-03-31 16:49:32 +0000482
483
mbligha5630a52008-09-03 22:09:50 +0000484def system_parallel(commands, timeout=None, ignore_status=False):
485 """This function returns a list of exit statuses for the respective
486 list of commands."""
487 return [bg_jobs.exit_status for bg_jobs in
mblighf8dffb12008-10-29 16:45:26 +0000488 run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
mbligha5630a52008-09-03 22:09:50 +0000489 stdout_tee=sys.stdout, stderr_tee=sys.stderr)]
mbligh849a0f62008-08-28 20:12:19 +0000490
491
mbligh8ea61e22008-05-09 18:09:37 +0000492def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000493 retain_output=False):
494 if retain_output:
mblighf8dffb12008-10-29 16:45:26 +0000495 out = run(command, timeout=timeout, ignore_status=ignore_status,
jadmanski0afbb632008-06-06 21:10:57 +0000496 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
497 else:
mblighf8dffb12008-10-29 16:45:26 +0000498 out = run(command, timeout=timeout, ignore_status=ignore_status).stdout
jadmanski0afbb632008-06-06 21:10:57 +0000499 if out[-1:] == '\n': out = out[:-1]
500 return out
mbligh63073c92008-03-31 16:49:32 +0000501
mbligh849a0f62008-08-28 20:12:19 +0000502
mbligha5630a52008-09-03 22:09:50 +0000503def system_output_parallel(commands, timeout=None, ignore_status=False,
504 retain_output=False):
505 if retain_output:
mblighf8dffb12008-10-29 16:45:26 +0000506 out = [bg_job.stdout for bg_job in run_parallel(commands,
507 timeout=timeout, ignore_status=ignore_status,
508 stdout_tee=sys.stdout, stderr_tee=sys.stderr)]
mbligha5630a52008-09-03 22:09:50 +0000509 else:
mblighf8dffb12008-10-29 16:45:26 +0000510 out = [bg_job.stdout for bg_job in run_parallel(commands,
511 timeout=timeout, ignore_status=ignore_status)]
mbligha5630a52008-09-03 22:09:50 +0000512 for x in out:
513 if out[-1:] == '\n': out = out[:-1]
514 return out
515
516
mbligh98467952008-11-19 00:25:45 +0000517def strip_unicode(input):
518 if type(input) == list:
519 return [strip_unicode(i) for i in input]
520 elif type(input) == dict:
521 output = {}
522 for key in input.keys():
523 output[str(key)] = strip_unicode(input[key])
524 return output
525 elif type(input) == unicode:
526 return str(input)
527 else:
528 return input
529
530
mbligha5630a52008-09-03 22:09:50 +0000531def get_cpu_percentage(function, *args, **dargs):
532 """Returns a tuple containing the CPU% and return value from function call.
533
534 This function calculates the usage time by taking the difference of
535 the user and system times both before and after the function call.
536 """
537 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
538 self_pre = resource.getrusage(resource.RUSAGE_SELF)
539 start = time.time()
540 to_return = function(*args, **dargs)
541 elapsed = time.time() - start
542 self_post = resource.getrusage(resource.RUSAGE_SELF)
543 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
544
545 # Calculate CPU Percentage
546 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
547 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
548 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
549
550 return cpu_percent, to_return
551
552
mblighc1cbc992008-05-27 20:01:45 +0000553"""
554This function is used when there is a need to run more than one
555job simultaneously starting exactly at the same time. It basically returns
556a modified control file (containing the synchronization code prepended)
557whenever it is ready to run the control file. The synchronization
558is done using barriers to make sure that the jobs start at the same time.
559
560Here is how the synchronization is done to make sure that the tests
561start at exactly the same time on the client.
562sc_bar is a server barrier and s_bar, c_bar are the normal barriers
563
564 Job1 Job2 ...... JobN
565 Server: | sc_bar
566 Server: | s_bar ...... s_bar
567 Server: | at.run() at.run() ...... at.run()
568 ----------|------------------------------------------------------
569 Client | sc_bar
570 Client | c_bar c_bar ...... c_bar
571 Client | <run test> <run test> ...... <run test>
572
573
574PARAMS:
575 control_file : The control file which to which the above synchronization
576 code would be prepended to
577 host_name : The host name on which the job is going to run
578 host_num (non negative) : A number to identify the machine so that we have
579 different sets of s_bar_ports for each of the machines.
580 instance : The number of the job
581 num_jobs : Total number of jobs that are going to run in parallel with
582 this job starting at the same time
583 port_base : Port number that is used to derive the actual barrier ports.
584
585RETURN VALUE:
586 The modified control file.
587
588"""
589def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000590 instance, num_jobs, port_base=63100):
591 sc_bar_port = port_base
592 c_bar_port = port_base
593 if host_num < 0:
594 print "Please provide a non negative number for the host"
595 return None
596 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
597 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000598
jadmanski0afbb632008-06-06 21:10:57 +0000599 sc_bar_timeout = 180
600 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000601
jadmanski0afbb632008-06-06 21:10:57 +0000602 # The barrier code snippet is prepended into the conrol file
603 # dynamically before at.run() is called finally.
604 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000605
jadmanski0afbb632008-06-06 21:10:57 +0000606 # jobid is the unique name used to identify the processes
607 # trying to reach the barriers
608 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000609
jadmanski0afbb632008-06-06 21:10:57 +0000610 rendv = []
611 # rendvstr is a temp holder for the rendezvous list of the processes
612 for n in range(num_jobs):
613 rendv.append("'%s#%d'" % (host_name, n))
614 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000615
jadmanski0afbb632008-06-06 21:10:57 +0000616 if instance == 0:
617 # Do the setup and wait at the server barrier
618 # Clean up the tmp and the control dirs for the first instance
619 control_new.append('if os.path.exists(job.tmpdir):')
620 control_new.append("\t system('umount -f %s > /dev/null"
621 "2> /dev/null' % job.tmpdir,"
622 "ignore_status=True)")
623 control_new.append("\t system('rm -rf ' + job.tmpdir)")
624 control_new.append(
625 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
626 % (jobid, sc_bar_timeout, sc_bar_port))
627 control_new.append(
628 'b0.rendevous_servers("PARALLEL_MASTER", "%s")'
629 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000630
jadmanski0afbb632008-06-06 21:10:57 +0000631 elif instance == 1:
632 # Wait at the server barrier to wait for instance=0
633 # process to complete setup
634 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
635 port=sc_bar_port)
636 b0.rendevous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000637
jadmanski0afbb632008-06-06 21:10:57 +0000638 if(num_jobs > 2):
639 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
640 port=s_bar_port)
641 b1.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000642
jadmanski0afbb632008-06-06 21:10:57 +0000643 else:
644 # For the rest of the clients
645 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
646 b2.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000647
jadmanski0afbb632008-06-06 21:10:57 +0000648 # Client side barrier for all the tests to start at the same time
649 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
650 % (jobid, c_bar_timeout, c_bar_port))
651 control_new.append("b1.rendevous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000652
jadmanski0afbb632008-06-06 21:10:57 +0000653 # Stick in the rest of the control file
654 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000655
jadmanski0afbb632008-06-06 21:10:57 +0000656 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000657
mbligh63073c92008-03-31 16:49:32 +0000658
mblighc5ddfd12008-08-04 17:15:00 +0000659def get_arch(run_function=run):
660 """
661 Get the hardware architecture of the machine.
662 run_function is used to execute the commands. It defaults to
663 utils.run() but a custom method (if provided) should be of the
664 same schema as utils.run. It should return a CmdResult object and
665 throw a CmdError exception.
666 """
667 arch = run_function('/bin/uname -m').stdout.rstrip()
668 if re.match(r'i\d86$', arch):
669 arch = 'i386'
670 return arch
671
672
jadmanski4f909252008-12-01 20:47:10 +0000673def merge_trees(src, dest):
674 """
675 Merges a source directory tree at 'src' into a destination tree at
676 'dest'. If a path is a file in both trees than the file in the source
677 tree is APPENDED to the one in the destination tree. If a path is
678 a directory in both trees then the directories are recursively merged
679 with this function. In any other case, the function will skip the
680 paths that cannot be merged (instead of failing).
681 """
682 if not os.path.exists(src):
683 return # exists only in dest
684 elif not os.path.exists(dest):
685 if os.path.isfile(src):
686 shutil.copy2(src, dest) # file only in src
687 else:
688 shutil.copytree(src, dest, symlinks=True) # dir only in src
689 return
690 elif os.path.isfile(src) and os.path.isfile(dest):
691 # src & dest are files in both trees, append src to dest
692 destfile = open(dest, "a")
693 try:
694 srcfile = open(src)
695 try:
696 destfile.write(srcfile.read())
697 finally:
698 srcfile.close()
699 finally:
700 destfile.close()
701 elif os.path.isdir(src) and os.path.isdir(dest):
702 # src & dest are directories in both trees, so recursively merge
703 for name in os.listdir(src):
704 merge_trees(os.path.join(src, name), os.path.join(dest, name))
705 else:
706 # src & dest both exist, but are incompatible
707 return
708
709
mbligh63073c92008-03-31 16:49:32 +0000710class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000711 """
712 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000713
jadmanski0afbb632008-06-06 21:10:57 +0000714 command: String containing the command line itself
715 exit_status: Integer exit code of the process
716 stdout: String containing stdout of the process
717 stderr: String containing stderr of the process
718 duration: Elapsed wall clock time running the process
719 """
mbligh63073c92008-03-31 16:49:32 +0000720
721
jadmanski0afbb632008-06-06 21:10:57 +0000722 def __init__(self, command=None, stdout="", stderr="",
723 exit_status=None, duration=0):
724 self.command = command
725 self.exit_status = exit_status
726 self.stdout = stdout
727 self.stderr = stderr
728 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000729
730
jadmanski0afbb632008-06-06 21:10:57 +0000731 def __repr__(self):
732 wrapper = textwrap.TextWrapper(width = 78,
733 initial_indent="\n ",
734 subsequent_indent=" ")
735
736 stdout = self.stdout.rstrip()
737 if stdout:
738 stdout = "\nstdout:\n%s" % stdout
739
740 stderr = self.stderr.rstrip()
741 if stderr:
742 stderr = "\nstderr:\n%s" % stderr
743
744 return ("* Command: %s\n"
745 "Exit status: %s\n"
746 "Duration: %s\n"
747 "%s"
748 "%s"
749 % (wrapper.fill(self.command), self.exit_status,
750 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000751
752
mbligh462c0152008-03-13 15:37:10 +0000753class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000754 def __init__(self, run_sequentially=False):
755 # Run sequentially is for debugging control files
756 self.test_list = []
757 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000758
759
jadmanski0afbb632008-06-06 21:10:57 +0000760 def add(self, *args, **dargs):
761 test = (args, dargs)
762 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000763
764
jadmanski0afbb632008-06-06 21:10:57 +0000765 def run(self, fn):
766 while self.test_list:
767 test_index = random.randint(0, len(self.test_list)-1)
768 if self.run_sequentially:
769 test_index = 0
770 (args, dargs) = self.test_list.pop(test_index)
771 fn(*args, **dargs)