blob: 8132e9f1de157e6b989bee91d1f064dbb6f89b67 [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
mblighb931b682008-10-09 20:25:28 +00008from autotest_lib.client.common_lib import error, barrier
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)
mblighbd96b452008-09-03 23:14:27 +000029 if verbose:
mblighb931b682008-10-09 20:25:28 +000030 print "running: %s" % command
mbligh849a0f62008-08-28 20:12:19 +000031 self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
32 stderr=subprocess.PIPE,
33 preexec_fn=self._reset_sigpipe, shell=True,
34 executable="/bin/bash")
35
36
37 def output_prepare(self, stdout_file=None, stderr_file=None):
38 self.stdout_file = stdout_file
39 self.stderr_file = stderr_file
40
41 def process_output(self, stdout=True, final_read=False):
42 """output_prepare must be called prior to calling this"""
43 if stdout:
44 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
45 else:
46 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
47
48 if final_read:
49 # read in all the data we can from pipe and then stop
50 data = []
51 while select.select([pipe], [], [], 0)[0]:
52 data.append(os.read(pipe.fileno(), 1024))
53 if len(data[-1]) == 0:
54 break
55 data = "".join(data)
56 else:
57 # perform a single read
58 data = os.read(pipe.fileno(), 1024)
59 buf.write(data)
60 if tee:
61 tee.write(data)
62 tee.flush()
63
64
65 def cleanup(self):
66 self.sp.stdout.close()
67 self.sp.stderr.close()
68 self.result.stdout = self.stdout_file.getvalue()
69 self.result.stderr = self.stderr_file.getvalue()
70
71
72 def _reset_sigpipe(self):
73 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
74
mbligh81edd792008-08-26 16:54:02 +000075
76def ip_to_long(ip):
77 # !L is a long in network byte order
78 return struct.unpack('!L', socket.inet_aton(ip))[0]
79
80
81def long_to_ip(number):
82 # See above comment.
83 return socket.inet_ntoa(struct.pack('!L', number))
84
85
86def create_subnet_mask(bits):
87 # ~ does weird things in python...but this does work
88 return (1 << 32) - (1 << 32-bits)
89
90
91def format_ip_with_mask(ip, mask_bits):
92 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
93 return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
mbligh6231cd62008-02-02 19:18:33 +000094
mblighde0d47e2008-03-28 14:37:18 +000095
jadmanskie80d4712008-10-03 16:15:59 +000096def normalize_hostname(alias):
97 ip = socket.gethostbyname(alias)
98 return socket.gethostbyaddr(ip)[0]
99
100
mblighd6d043c2008-09-27 21:00:45 +0000101def get_ip_local_port_range():
102 match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
103 read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
104 return (int(match.group(1)), int(match.group(2)))
105
106
107def set_ip_local_port_range(lower, upper):
108 write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
109 '%d %d\n' % (lower, upper))
110
mbligh315b9412008-10-01 03:34:11 +0000111
jadmanski5182e162008-05-13 21:48:16 +0000112def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +0000113 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +0000114
115
116def write_one_line(filename, str):
mbligh618ac9e2008-10-06 17:14:32 +0000117 f = open(filename, 'w')
118 f.write(str.rstrip('\n') + '\n')
119 f.flush()
jadmanski5182e162008-05-13 21:48:16 +0000120
121
mblighde0d47e2008-03-28 14:37:18 +0000122def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000123 """
124 Read a key-value pair format file into a dictionary, and return it.
125 Takes either a filename or directory name as input. If it's a
126 directory name, we assume you want the file to be called keyval.
127 """
128 if os.path.isdir(path):
129 path = os.path.join(path, 'keyval')
130 keyval = {}
131 for line in open(path):
jadmanskia6014a02008-07-14 19:41:54 +0000132 line = re.sub('#.*', '', line).rstrip()
jadmanski0afbb632008-06-06 21:10:57 +0000133 if not re.search(r'^[-\w]+=', line):
134 raise ValueError('Invalid format line: %s' % line)
135 key, value = line.split('=', 1)
136 if re.search('^\d+$', value):
137 value = int(value)
138 elif re.search('^(\d+\.)?\d+$', value):
139 value = float(value)
140 keyval[key] = value
141 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000142
143
jadmanskicc549172008-05-21 18:11:51 +0000144def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000145 """
146 Write a key-value pair format file out to a file. This uses append
147 mode to open the file, so existing text will not be overwritten or
148 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000149
jadmanski0afbb632008-06-06 21:10:57 +0000150 If type_tag is None, then the key must be composed of alphanumeric
151 characters (or dashes+underscores). However, if type-tag is not
152 null then the keys must also have "{type_tag}" as a suffix. At
153 the moment the only valid values of type_tag are "attr" and "perf".
154 """
155 if os.path.isdir(path):
156 path = os.path.join(path, 'keyval')
157 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000158
jadmanski0afbb632008-06-06 21:10:57 +0000159 if type_tag is None:
160 key_regex = re.compile(r'^[-\w]+$')
161 else:
162 if type_tag not in ('attr', 'perf'):
163 raise ValueError('Invalid type tag: %s' % type_tag)
164 escaped_tag = re.escape(type_tag)
165 key_regex = re.compile(r'^[-\w]+\{%s\}$' % escaped_tag)
166 try:
167 for key, value in dictionary.iteritems():
168 if not key_regex.search(key):
169 raise ValueError('Invalid key: %s' % key)
170 keyval.write('%s=%s\n' % (key, value))
171 finally:
172 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000173
174
175def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000176 """Return true if path looks like a URL"""
177 # for now, just handle http and ftp
178 url_parts = urlparse.urlparse(path)
179 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000180
181
jadmanskied91ba92008-09-30 17:19:27 +0000182def urlopen(url, data=None, proxies=None, timeout=5):
jadmanski0afbb632008-06-06 21:10:57 +0000183 """Wrapper to urllib.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000184
jadmanski0afbb632008-06-06 21:10:57 +0000185 # Save old timeout
186 old_timeout = socket.getdefaulttimeout()
187 socket.setdefaulttimeout(timeout)
188 try:
189 return urllib.urlopen(url, data=data, proxies=proxies)
190 finally:
191 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000192
193
194def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +0000195 """Wrapper to urllib.urlretrieve with timeout addition."""
196 old_timeout = socket.getdefaulttimeout()
197 socket.setdefaulttimeout(timeout)
198 try:
199 return urllib.urlretrieve(url, filename=filename,
200 reporthook=reporthook, data=data)
201 finally:
202 socket.setdefaulttimeout(old_timeout)
203
mbligh02ff2d52008-06-03 15:00:21 +0000204
mbligh6231cd62008-02-02 19:18:33 +0000205def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000206 """Get a file from src, which can be local or a remote URL"""
207 if (src == dest):
208 return
209 if (is_url(src)):
210 print 'PWD: ' + os.getcwd()
211 print 'Fetching \n\t', src, '\n\t->', dest
212 try:
213 urllib.urlretrieve(src, dest)
214 except IOError, e:
215 raise error.AutotestError('Unable to retrieve %s (to %s)'
216 % (src, dest), e)
217 else:
218 shutil.copyfile(src, dest)
219 if permissions:
220 os.chmod(dest, permissions)
221 return dest
mbligh6231cd62008-02-02 19:18:33 +0000222
223
224def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000225 """
226 Receives either a path to a local file or a URL.
227 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000228
jadmanski0afbb632008-06-06 21:10:57 +0000229 unmap_url('/usr/src', 'foo.tar', '/tmp')
230 = '/usr/src/foo.tar'
231 unmap_url('/usr/src', 'http://site/file', '/tmp')
232 = '/tmp/file'
233 (after retrieving it)
234 """
235 if is_url(src):
236 url_parts = urlparse.urlparse(src)
237 filename = os.path.basename(url_parts[2])
238 dest = os.path.join(destdir, filename)
239 return get_file(src, dest)
240 else:
241 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000242
243
244def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000245 *args, **dargs):
246 """
247 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000248
jadmanski0afbb632008-06-06 21:10:57 +0000249 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000250
jadmanski0afbb632008-06-06 21:10:57 +0000251 In the preserve_srcdir case, we just check it's up to date,
252 and if not, we rerun install, without removing srcdir
253 """
254 versionfile = os.path.join(srcdir, '.version')
255 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000256
jadmanski0afbb632008-06-06 21:10:57 +0000257 if os.path.exists(versionfile):
258 old_version = pickle.load(open(versionfile))
259 if old_version == new_version:
260 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000261
jadmanski0afbb632008-06-06 21:10:57 +0000262 if install_needed:
263 if not preserve_srcdir and os.path.exists(srcdir):
264 shutil.rmtree(srcdir)
265 install(*args, **dargs)
266 if os.path.exists(srcdir):
267 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000268
269
mbligh63073c92008-03-31 16:49:32 +0000270def run(command, timeout=None, ignore_status=False,
mblighbd96b452008-09-03 23:14:27 +0000271 stdout_tee=None, stderr_tee=None, verbose=True):
jadmanski0afbb632008-06-06 21:10:57 +0000272 """
273 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000274
jadmanski0afbb632008-06-06 21:10:57 +0000275 Args:
276 command: the command line string
277 timeout: time limit in seconds before attempting to
278 kill the running process. The run() function
279 will take a few seconds longer than 'timeout'
280 to complete if it has to kill the process.
281 ignore_status: do not raise an exception, no matter what
282 the exit code of the command is.
283 stdout_tee: optional file-like object to which stdout data
284 will be written as it is generated (data will still
285 be stored in result.stdout)
286 stderr_tee: likewise for stderr
mbligh63073c92008-03-31 16:49:32 +0000287
jadmanski0afbb632008-06-06 21:10:57 +0000288 Returns:
289 a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000290
jadmanski0afbb632008-06-06 21:10:57 +0000291 Raises:
292 CmdError: the exit code of the command
293 execution was not 0
294 """
mblighbd96b452008-09-03 23:14:27 +0000295 bg_job = join_bg_jobs((BgJob(command, stdout_tee, stderr_tee, verbose),),
mbligh849a0f62008-08-28 20:12:19 +0000296 timeout)[0]
297 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000298 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000299 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000300
mbligh849a0f62008-08-28 20:12:19 +0000301 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000302
mbligha5630a52008-09-03 22:09:50 +0000303def run_parallel(commands, timeout=None, ignore_status=False,
304 stdout_tee=None, stderr_tee=None):
305 """Beahves the same as run with the following exceptions:
306
307 - commands is a list of commands to run in parallel.
308 - ignore_status toggles whether or not an exception should be raised
309 on any error.
310
311 returns a list of CmdResult objects
312 """
313 bg_jobs = []
314 for command in commands:
315 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee))
316
317 # Updates objects in bg_jobs list with their process information
318 join_bg_jobs(bg_jobs, timeout)
319
320 for bg_job in bg_jobs:
321 if not ignore_status and bg_job.result.exit_status:
322 raise error.CmdError(command, bg_job.result,
323 "Command returned non-zero exit status")
324
325 return [bg_job.result for bg_job in bg_jobs]
326
327
mbligh849a0f62008-08-28 20:12:19 +0000328@deprecated
mbligh63073c92008-03-31 16:49:32 +0000329def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000330 """Function deprecated. Please use BgJob class instead."""
331 bg_job = BgJob(command)
332 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000333
334
mbligh849a0f62008-08-28 20:12:19 +0000335def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000336 """Joins the bg_jobs with the current thread.
337
338 Returns the same list of bg_jobs objects that was passed in.
339 """
mbligh849a0f62008-08-28 20:12:19 +0000340 ret, timeouterr = 0, False
341 for bg_job in bg_jobs:
342 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000343
jadmanski0afbb632008-06-06 21:10:57 +0000344 try:
345 # We are holding ends to stdin, stdout pipes
346 # hence we need to be sure to close those fds no mater what
347 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000348 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
349
350 for bg_job in bg_jobs:
351 # Process stdout and stderr
352 bg_job.process_output(stdout=True,final_read=True)
353 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000354 finally:
355 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000356 for bg_job in bg_jobs:
357 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000358
mbligh849a0f62008-08-28 20:12:19 +0000359 if timeout_error:
360 # TODO: This needs to be fixed to better represent what happens when
361 # running in parallel. However this is backwards compatable, so it will
362 # do for the time being.
363 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
364 "Command(s) did not complete within %d seconds"
365 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000366
mbligh63073c92008-03-31 16:49:32 +0000367
mbligh849a0f62008-08-28 20:12:19 +0000368 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000369
mbligh849a0f62008-08-28 20:12:19 +0000370
371def _wait_for_commands(bg_jobs, start_time, timeout):
372 # This returns True if it must return due to a timeout, otherwise False.
373
mblighf0b4a0a2008-09-03 20:46:16 +0000374 # To check for processes which terminate without producing any output
375 # a 1 second timeout is used in select.
376 SELECT_TIMEOUT = 1
377
mbligh849a0f62008-08-28 20:12:19 +0000378 select_list = []
379 reverse_dict = {}
380 for bg_job in bg_jobs:
381 select_list.append(bg_job.sp.stdout)
382 select_list.append(bg_job.sp.stderr)
383 reverse_dict[bg_job.sp.stdout] = (bg_job,True)
384 reverse_dict[bg_job.sp.stderr] = (bg_job,False)
385
jadmanski0afbb632008-06-06 21:10:57 +0000386 if timeout:
387 stop_time = start_time + timeout
388 time_left = stop_time - time.time()
389 else:
390 time_left = None # so that select never times out
391 while not timeout or time_left > 0:
392 # select will return when stdout is ready (including when it is
393 # EOF, that is the process has terminated).
mblighf0b4a0a2008-09-03 20:46:16 +0000394 ready, _, _ = select.select(select_list, [], [], SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000395
jadmanski0afbb632008-06-06 21:10:57 +0000396 # os.read() has to be used instead of
397 # subproc.stdout.read() which will otherwise block
mbligh849a0f62008-08-28 20:12:19 +0000398 for fileno in ready:
399 bg_job,stdout = reverse_dict[fileno]
400 bg_job.process_output(stdout)
mbligh63073c92008-03-31 16:49:32 +0000401
mbligh849a0f62008-08-28 20:12:19 +0000402 remaining_jobs = [x for x in bg_jobs if x.result.exit_status is None]
403 if len(remaining_jobs) == 0:
404 return False
405 for bg_job in remaining_jobs:
406 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000407
jadmanski0afbb632008-06-06 21:10:57 +0000408 if timeout:
409 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000410
mbligh849a0f62008-08-28 20:12:19 +0000411 # Kill all processes which did not complete prior to timeout
412 for bg_job in [x for x in bg_jobs if x.result.exit_status is None]:
413 nuke_subprocess(bg_job.sp)
mbligh095dc642008-10-01 03:41:35 +0000414 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000415
mbligh849a0f62008-08-28 20:12:19 +0000416 return True
mbligh63073c92008-03-31 16:49:32 +0000417
418
mbligh63073c92008-03-31 16:49:32 +0000419def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000420 # check if the subprocess is still alive, first
421 if subproc.poll() is not None:
422 return subproc.poll()
423
jadmanski0afbb632008-06-06 21:10:57 +0000424 # the process has not terminated within timeout,
425 # kill it via an escalating series of signals.
426 signal_queue = [signal.SIGTERM, signal.SIGKILL]
427 for sig in signal_queue:
428 try:
429 os.kill(subproc.pid, sig)
430 # The process may have died before we could kill it.
431 except OSError:
432 pass
mbligh63073c92008-03-31 16:49:32 +0000433
jadmanski0afbb632008-06-06 21:10:57 +0000434 for i in range(5):
435 rc = subproc.poll()
436 if rc != None:
437 return rc
438 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000439
440
441def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000442 # the process has not terminated within timeout,
443 # kill it via an escalating series of signals.
444 signal_queue = [signal.SIGTERM, signal.SIGKILL]
445 for sig in signal_queue:
446 try:
447 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000448
jadmanski0afbb632008-06-06 21:10:57 +0000449 # The process may have died before we could kill it.
450 except OSError:
451 pass
mbligh63073c92008-03-31 16:49:32 +0000452
jadmanski0afbb632008-06-06 21:10:57 +0000453 try:
454 for i in range(5):
455 status = os.waitpid(pid, os.WNOHANG)[0]
456 if status == pid:
457 return
458 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000459
jadmanski0afbb632008-06-06 21:10:57 +0000460 if status != pid:
461 raise error.AutoservRunError('Could not kill %d'
462 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000463
jadmanski0afbb632008-06-06 21:10:57 +0000464 # the process died before we join it.
465 except OSError:
466 pass
mbligh63073c92008-03-31 16:49:32 +0000467
468
mbligh63073c92008-03-31 16:49:32 +0000469
470def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000471 """This function returns the exit status of command."""
jadmanski0afbb632008-06-06 21:10:57 +0000472 return run(command, timeout, ignore_status,
mbligha5630a52008-09-03 22:09:50 +0000473 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
mbligh63073c92008-03-31 16:49:32 +0000474
475
mbligha5630a52008-09-03 22:09:50 +0000476def system_parallel(commands, timeout=None, ignore_status=False):
477 """This function returns a list of exit statuses for the respective
478 list of commands."""
479 return [bg_jobs.exit_status for bg_jobs in
480 run_parallel(commands, timeout, ignore_status,
481 stdout_tee=sys.stdout, stderr_tee=sys.stderr)]
mbligh849a0f62008-08-28 20:12:19 +0000482
483
mbligh8ea61e22008-05-09 18:09:37 +0000484def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000485 retain_output=False):
486 if retain_output:
487 out = run(command, timeout, ignore_status,
488 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
489 else:
490 out = run(command, timeout, ignore_status).stdout
491 if out[-1:] == '\n': out = out[:-1]
492 return out
mbligh63073c92008-03-31 16:49:32 +0000493
mbligh849a0f62008-08-28 20:12:19 +0000494
mbligha5630a52008-09-03 22:09:50 +0000495def system_output_parallel(commands, timeout=None, ignore_status=False,
496 retain_output=False):
497 if retain_output:
498 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
499 ignore_status,
500 stdout_tee=sys.stdout,
501 stderr_tee=sys.stderr)]
502 else:
503 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
504 ignore_status)]
505 for x in out:
506 if out[-1:] == '\n': out = out[:-1]
507 return out
508
509
510def get_cpu_percentage(function, *args, **dargs):
511 """Returns a tuple containing the CPU% and return value from function call.
512
513 This function calculates the usage time by taking the difference of
514 the user and system times both before and after the function call.
515 """
516 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
517 self_pre = resource.getrusage(resource.RUSAGE_SELF)
518 start = time.time()
519 to_return = function(*args, **dargs)
520 elapsed = time.time() - start
521 self_post = resource.getrusage(resource.RUSAGE_SELF)
522 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
523
524 # Calculate CPU Percentage
525 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
526 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
527 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
528
529 return cpu_percent, to_return
530
531
mblighc1cbc992008-05-27 20:01:45 +0000532"""
533This function is used when there is a need to run more than one
534job simultaneously starting exactly at the same time. It basically returns
535a modified control file (containing the synchronization code prepended)
536whenever it is ready to run the control file. The synchronization
537is done using barriers to make sure that the jobs start at the same time.
538
539Here is how the synchronization is done to make sure that the tests
540start at exactly the same time on the client.
541sc_bar is a server barrier and s_bar, c_bar are the normal barriers
542
543 Job1 Job2 ...... JobN
544 Server: | sc_bar
545 Server: | s_bar ...... s_bar
546 Server: | at.run() at.run() ...... at.run()
547 ----------|------------------------------------------------------
548 Client | sc_bar
549 Client | c_bar c_bar ...... c_bar
550 Client | <run test> <run test> ...... <run test>
551
552
553PARAMS:
554 control_file : The control file which to which the above synchronization
555 code would be prepended to
556 host_name : The host name on which the job is going to run
557 host_num (non negative) : A number to identify the machine so that we have
558 different sets of s_bar_ports for each of the machines.
559 instance : The number of the job
560 num_jobs : Total number of jobs that are going to run in parallel with
561 this job starting at the same time
562 port_base : Port number that is used to derive the actual barrier ports.
563
564RETURN VALUE:
565 The modified control file.
566
567"""
568def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000569 instance, num_jobs, port_base=63100):
570 sc_bar_port = port_base
571 c_bar_port = port_base
572 if host_num < 0:
573 print "Please provide a non negative number for the host"
574 return None
575 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
576 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000577
jadmanski0afbb632008-06-06 21:10:57 +0000578 sc_bar_timeout = 180
579 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000580
jadmanski0afbb632008-06-06 21:10:57 +0000581 # The barrier code snippet is prepended into the conrol file
582 # dynamically before at.run() is called finally.
583 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000584
jadmanski0afbb632008-06-06 21:10:57 +0000585 # jobid is the unique name used to identify the processes
586 # trying to reach the barriers
587 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000588
jadmanski0afbb632008-06-06 21:10:57 +0000589 rendv = []
590 # rendvstr is a temp holder for the rendezvous list of the processes
591 for n in range(num_jobs):
592 rendv.append("'%s#%d'" % (host_name, n))
593 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000594
jadmanski0afbb632008-06-06 21:10:57 +0000595 if instance == 0:
596 # Do the setup and wait at the server barrier
597 # Clean up the tmp and the control dirs for the first instance
598 control_new.append('if os.path.exists(job.tmpdir):')
599 control_new.append("\t system('umount -f %s > /dev/null"
600 "2> /dev/null' % job.tmpdir,"
601 "ignore_status=True)")
602 control_new.append("\t system('rm -rf ' + job.tmpdir)")
603 control_new.append(
604 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
605 % (jobid, sc_bar_timeout, sc_bar_port))
606 control_new.append(
607 'b0.rendevous_servers("PARALLEL_MASTER", "%s")'
608 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000609
jadmanski0afbb632008-06-06 21:10:57 +0000610 elif instance == 1:
611 # Wait at the server barrier to wait for instance=0
612 # process to complete setup
613 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
614 port=sc_bar_port)
615 b0.rendevous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000616
jadmanski0afbb632008-06-06 21:10:57 +0000617 if(num_jobs > 2):
618 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
619 port=s_bar_port)
620 b1.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000621
jadmanski0afbb632008-06-06 21:10:57 +0000622 else:
623 # For the rest of the clients
624 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
625 b2.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000626
jadmanski0afbb632008-06-06 21:10:57 +0000627 # Client side barrier for all the tests to start at the same time
628 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
629 % (jobid, c_bar_timeout, c_bar_port))
630 control_new.append("b1.rendevous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000631
jadmanski0afbb632008-06-06 21:10:57 +0000632 # Stick in the rest of the control file
633 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000634
jadmanski0afbb632008-06-06 21:10:57 +0000635 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000636
mbligh63073c92008-03-31 16:49:32 +0000637
mblighc5ddfd12008-08-04 17:15:00 +0000638def get_arch(run_function=run):
639 """
640 Get the hardware architecture of the machine.
641 run_function is used to execute the commands. It defaults to
642 utils.run() but a custom method (if provided) should be of the
643 same schema as utils.run. It should return a CmdResult object and
644 throw a CmdError exception.
645 """
646 arch = run_function('/bin/uname -m').stdout.rstrip()
647 if re.match(r'i\d86$', arch):
648 arch = 'i386'
649 return arch
650
651
mbligh63073c92008-03-31 16:49:32 +0000652class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000653 """
654 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000655
jadmanski0afbb632008-06-06 21:10:57 +0000656 command: String containing the command line itself
657 exit_status: Integer exit code of the process
658 stdout: String containing stdout of the process
659 stderr: String containing stderr of the process
660 duration: Elapsed wall clock time running the process
661 """
mbligh63073c92008-03-31 16:49:32 +0000662
663
jadmanski0afbb632008-06-06 21:10:57 +0000664 def __init__(self, command=None, stdout="", stderr="",
665 exit_status=None, duration=0):
666 self.command = command
667 self.exit_status = exit_status
668 self.stdout = stdout
669 self.stderr = stderr
670 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000671
672
jadmanski0afbb632008-06-06 21:10:57 +0000673 def __repr__(self):
674 wrapper = textwrap.TextWrapper(width = 78,
675 initial_indent="\n ",
676 subsequent_indent=" ")
677
678 stdout = self.stdout.rstrip()
679 if stdout:
680 stdout = "\nstdout:\n%s" % stdout
681
682 stderr = self.stderr.rstrip()
683 if stderr:
684 stderr = "\nstderr:\n%s" % stderr
685
686 return ("* Command: %s\n"
687 "Exit status: %s\n"
688 "Duration: %s\n"
689 "%s"
690 "%s"
691 % (wrapper.fill(self.command), self.exit_status,
692 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000693
694
mbligh462c0152008-03-13 15:37:10 +0000695class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000696 def __init__(self, run_sequentially=False):
697 # Run sequentially is for debugging control files
698 self.test_list = []
699 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000700
701
jadmanski0afbb632008-06-06 21:10:57 +0000702 def add(self, *args, **dargs):
703 test = (args, dargs)
704 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000705
706
jadmanski0afbb632008-06-06 21:10:57 +0000707 def run(self, fn):
708 while self.test_list:
709 test_index = random.randint(0, len(self.test_list)-1)
710 if self.run_sequentially:
711 test_index = 0
712 (args, dargs) = self.test_list.pop(test_index)
713 fn(*args, **dargs)