blob: 48a944c9098979e7e5d34d001b257e01c246113b [file] [log] [blame]
mbligh63073c92008-03-31 16:49:32 +00001#
2# Copyright 2008 Google Inc. Released under the GPL v2
3
mbligh849a0f62008-08-28 20:12:19 +00004import os, pickle, random, re, resource, select, shutil, signal, StringIO
mblighb2896192009-07-11 00:12:37 +00005import socket, struct, subprocess, sys, time, textwrap, urlparse
mbligh25284cd2009-06-08 16:17:24 +00006import warnings, smtplib, logging, urllib2
showard108d73e2009-06-22 18:14:41 +00007from autotest_lib.client.common_lib import error, barrier, logging_manager
mbligh81edd792008-08-26 16:54:02 +00008
mbligh849a0f62008-08-28 20:12:19 +00009def deprecated(func):
10 """This is a decorator which can be used to mark functions as deprecated.
11 It will result in a warning being emmitted when the function is used."""
12 def new_func(*args, **dargs):
13 warnings.warn("Call to deprecated function %s." % func.__name__,
14 category=DeprecationWarning)
15 return func(*args, **dargs)
16 new_func.__name__ = func.__name__
17 new_func.__doc__ = func.__doc__
18 new_func.__dict__.update(func.__dict__)
19 return new_func
20
21
showard108d73e2009-06-22 18:14:41 +000022class _NullStream(object):
23 def write(self, data):
24 pass
25
26
27 def flush(self):
28 pass
29
30
31TEE_TO_LOGS = object()
32_the_null_stream = _NullStream()
33
showardb45a4662009-07-15 14:27:56 +000034DEFAULT_STDOUT_LEVEL = logging.DEBUG
35DEFAULT_STDERR_LEVEL = logging.ERROR
36
showard108d73e2009-06-22 18:14:41 +000037def get_stream_tee_file(stream, level):
38 if stream is None:
39 return _the_null_stream
40 if stream is TEE_TO_LOGS:
41 return logging_manager.LoggingFile(level=level)
42 return stream
43
44
mbligh849a0f62008-08-28 20:12:19 +000045class BgJob(object):
showard170873e2009-01-07 00:22:26 +000046 def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
showardb45a4662009-07-15 14:27:56 +000047 stdin=None, stderr_level=DEFAULT_STDERR_LEVEL):
mbligh849a0f62008-08-28 20:12:19 +000048 self.command = command
showardb45a4662009-07-15 14:27:56 +000049 self.stdout_tee = get_stream_tee_file(stdout_tee, DEFAULT_STDOUT_LEVEL)
50 self.stderr_tee = get_stream_tee_file(stderr_tee, stderr_level)
mbligh849a0f62008-08-28 20:12:19 +000051 self.result = CmdResult(command)
mblighbd96b452008-09-03 23:14:27 +000052 if verbose:
showardb18134f2009-03-20 20:52:18 +000053 logging.debug("Running '%s'" % command)
mbligh849a0f62008-08-28 20:12:19 +000054 self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
55 stderr=subprocess.PIPE,
56 preexec_fn=self._reset_sigpipe, shell=True,
showard170873e2009-01-07 00:22:26 +000057 executable="/bin/bash",
58 stdin=stdin)
mbligh849a0f62008-08-28 20:12:19 +000059
60
61 def output_prepare(self, stdout_file=None, stderr_file=None):
62 self.stdout_file = stdout_file
63 self.stderr_file = stderr_file
64
mbligh45ffc432008-12-09 23:35:17 +000065
mbligh849a0f62008-08-28 20:12:19 +000066 def process_output(self, stdout=True, final_read=False):
67 """output_prepare must be called prior to calling this"""
68 if stdout:
69 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
70 else:
71 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
72
73 if final_read:
74 # read in all the data we can from pipe and then stop
75 data = []
76 while select.select([pipe], [], [], 0)[0]:
77 data.append(os.read(pipe.fileno(), 1024))
78 if len(data[-1]) == 0:
79 break
80 data = "".join(data)
81 else:
82 # perform a single read
83 data = os.read(pipe.fileno(), 1024)
84 buf.write(data)
showard108d73e2009-06-22 18:14:41 +000085 tee.write(data)
mbligh849a0f62008-08-28 20:12:19 +000086
87
88 def cleanup(self):
showard108d73e2009-06-22 18:14:41 +000089 self.stdout_tee.flush()
90 self.stderr_tee.flush()
mbligh849a0f62008-08-28 20:12:19 +000091 self.sp.stdout.close()
92 self.sp.stderr.close()
93 self.result.stdout = self.stdout_file.getvalue()
94 self.result.stderr = self.stderr_file.getvalue()
95
96
97 def _reset_sigpipe(self):
98 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
99
mbligh81edd792008-08-26 16:54:02 +0000100
101def ip_to_long(ip):
102 # !L is a long in network byte order
103 return struct.unpack('!L', socket.inet_aton(ip))[0]
104
105
106def long_to_ip(number):
107 # See above comment.
108 return socket.inet_ntoa(struct.pack('!L', number))
109
110
111def create_subnet_mask(bits):
mbligh81edd792008-08-26 16:54:02 +0000112 return (1 << 32) - (1 << 32-bits)
113
114
115def format_ip_with_mask(ip, mask_bits):
116 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
117 return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
mbligh6231cd62008-02-02 19:18:33 +0000118
mblighde0d47e2008-03-28 14:37:18 +0000119
jadmanskie80d4712008-10-03 16:15:59 +0000120def normalize_hostname(alias):
121 ip = socket.gethostbyname(alias)
122 return socket.gethostbyaddr(ip)[0]
123
124
mblighd6d043c2008-09-27 21:00:45 +0000125def get_ip_local_port_range():
126 match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
127 read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
128 return (int(match.group(1)), int(match.group(2)))
129
130
131def set_ip_local_port_range(lower, upper):
132 write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
133 '%d %d\n' % (lower, upper))
134
mbligh315b9412008-10-01 03:34:11 +0000135
mbligh45ffc432008-12-09 23:35:17 +0000136
137def send_email(mail_from, mail_to, subject, body):
138 """
139 Sends an email via smtp
140
141 mail_from: string with email address of sender
142 mail_to: string or list with email address(es) of recipients
143 subject: string with subject of email
144 body: (multi-line) string with body of email
145 """
146 if isinstance(mail_to, str):
147 mail_to = [mail_to]
148 msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to),
149 subject, body)
150 try:
151 mailer = smtplib.SMTP('localhost')
152 try:
153 mailer.sendmail(mail_from, mail_to, msg)
154 finally:
155 mailer.quit()
156 except Exception, e:
157 # Emails are non-critical, not errors, but don't raise them
158 print "Sending email failed. Reason: %s" % repr(e)
159
160
jadmanski5182e162008-05-13 21:48:16 +0000161def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +0000162 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +0000163
164
mblighb9d05512008-10-18 13:53:27 +0000165def write_one_line(filename, line):
166 open_write_close(filename, line.rstrip('\n') + '\n')
167
168
169def open_write_close(filename, data):
mbligh618ac9e2008-10-06 17:14:32 +0000170 f = open(filename, 'w')
mblighb9d05512008-10-18 13:53:27 +0000171 try:
172 f.write(data)
173 finally:
174 f.close()
jadmanski5182e162008-05-13 21:48:16 +0000175
176
mblighde0d47e2008-03-28 14:37:18 +0000177def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000178 """
179 Read a key-value pair format file into a dictionary, and return it.
180 Takes either a filename or directory name as input. If it's a
181 directory name, we assume you want the file to be called keyval.
182 """
183 if os.path.isdir(path):
184 path = os.path.join(path, 'keyval')
185 keyval = {}
jadmanski58962982009-04-21 19:54:34 +0000186 if os.path.exists(path):
187 for line in open(path):
188 line = re.sub('#.*', '', line).rstrip()
189 if not re.search(r'^[-\.\w]+=', line):
190 raise ValueError('Invalid format line: %s' % line)
191 key, value = line.split('=', 1)
192 if re.search('^\d+$', value):
193 value = int(value)
194 elif re.search('^(\d+\.)?\d+$', value):
195 value = float(value)
196 keyval[key] = value
jadmanski0afbb632008-06-06 21:10:57 +0000197 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000198
199
jadmanskicc549172008-05-21 18:11:51 +0000200def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000201 """
202 Write a key-value pair format file out to a file. This uses append
203 mode to open the file, so existing text will not be overwritten or
204 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000205
jadmanski0afbb632008-06-06 21:10:57 +0000206 If type_tag is None, then the key must be composed of alphanumeric
207 characters (or dashes+underscores). However, if type-tag is not
208 null then the keys must also have "{type_tag}" as a suffix. At
209 the moment the only valid values of type_tag are "attr" and "perf".
210 """
211 if os.path.isdir(path):
212 path = os.path.join(path, 'keyval')
213 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000214
jadmanski0afbb632008-06-06 21:10:57 +0000215 if type_tag is None:
mbligh97227ea2009-03-11 17:09:50 +0000216 key_regex = re.compile(r'^[-\.\w]+$')
jadmanski0afbb632008-06-06 21:10:57 +0000217 else:
218 if type_tag not in ('attr', 'perf'):
219 raise ValueError('Invalid type tag: %s' % type_tag)
220 escaped_tag = re.escape(type_tag)
mbligh97227ea2009-03-11 17:09:50 +0000221 key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
jadmanski0afbb632008-06-06 21:10:57 +0000222 try:
mbligh6955e232009-07-11 00:58:47 +0000223 for key in sorted(dictionary.keys()):
jadmanski0afbb632008-06-06 21:10:57 +0000224 if not key_regex.search(key):
225 raise ValueError('Invalid key: %s' % key)
mbligh6955e232009-07-11 00:58:47 +0000226 keyval.write('%s=%s\n' % (key, dictionary[key]))
jadmanski0afbb632008-06-06 21:10:57 +0000227 finally:
228 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000229
230
231def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000232 """Return true if path looks like a URL"""
233 # for now, just handle http and ftp
234 url_parts = urlparse.urlparse(path)
235 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000236
237
mblighb2896192009-07-11 00:12:37 +0000238def urlopen(url, data=None, timeout=5):
239 """Wrapper to urllib2.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000240
jadmanski0afbb632008-06-06 21:10:57 +0000241 # Save old timeout
242 old_timeout = socket.getdefaulttimeout()
243 socket.setdefaulttimeout(timeout)
244 try:
mblighb2896192009-07-11 00:12:37 +0000245 return urllib2.urlopen(url, data=data)
jadmanski0afbb632008-06-06 21:10:57 +0000246 finally:
247 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000248
249
mblighb2896192009-07-11 00:12:37 +0000250def urlretrieve(url, filename, data=None, timeout=300):
251 """Retrieve a file from given url."""
252 logging.debug('Fetching %s -> %s', url, filename)
253
254 src_file = urlopen(url, data=data, timeout=timeout)
jadmanski0afbb632008-06-06 21:10:57 +0000255 try:
mblighb2896192009-07-11 00:12:37 +0000256 dest_file = open(filename, 'wb')
257 try:
258 shutil.copyfileobj(src_file, dest_file)
259 finally:
260 dest_file.close()
jadmanski0afbb632008-06-06 21:10:57 +0000261 finally:
mblighb2896192009-07-11 00:12:37 +0000262 src_file.close()
jadmanski0afbb632008-06-06 21:10:57 +0000263
mbligh02ff2d52008-06-03 15:00:21 +0000264
mbligh6231cd62008-02-02 19:18:33 +0000265def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000266 """Get a file from src, which can be local or a remote URL"""
mbligh25284cd2009-06-08 16:17:24 +0000267 if src == dest:
jadmanski0afbb632008-06-06 21:10:57 +0000268 return
mbligh25284cd2009-06-08 16:17:24 +0000269
270 if is_url(src):
mblighb2896192009-07-11 00:12:37 +0000271 urlretrieve(src, dest)
jadmanski0afbb632008-06-06 21:10:57 +0000272 else:
273 shutil.copyfile(src, dest)
mbligh25284cd2009-06-08 16:17:24 +0000274
jadmanski0afbb632008-06-06 21:10:57 +0000275 if permissions:
276 os.chmod(dest, permissions)
277 return dest
mbligh6231cd62008-02-02 19:18:33 +0000278
279
280def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000281 """
282 Receives either a path to a local file or a URL.
283 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000284
jadmanski0afbb632008-06-06 21:10:57 +0000285 unmap_url('/usr/src', 'foo.tar', '/tmp')
286 = '/usr/src/foo.tar'
287 unmap_url('/usr/src', 'http://site/file', '/tmp')
288 = '/tmp/file'
289 (after retrieving it)
290 """
291 if is_url(src):
292 url_parts = urlparse.urlparse(src)
293 filename = os.path.basename(url_parts[2])
294 dest = os.path.join(destdir, filename)
295 return get_file(src, dest)
296 else:
297 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000298
299
300def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000301 *args, **dargs):
302 """
303 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000304
jadmanski0afbb632008-06-06 21:10:57 +0000305 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000306
jadmanski0afbb632008-06-06 21:10:57 +0000307 In the preserve_srcdir case, we just check it's up to date,
308 and if not, we rerun install, without removing srcdir
309 """
310 versionfile = os.path.join(srcdir, '.version')
311 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000312
jadmanski0afbb632008-06-06 21:10:57 +0000313 if os.path.exists(versionfile):
314 old_version = pickle.load(open(versionfile))
315 if old_version == new_version:
316 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000317
jadmanski0afbb632008-06-06 21:10:57 +0000318 if install_needed:
319 if not preserve_srcdir and os.path.exists(srcdir):
320 shutil.rmtree(srcdir)
321 install(*args, **dargs)
322 if os.path.exists(srcdir):
323 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000324
325
showardb45a4662009-07-15 14:27:56 +0000326def get_stderr_level(stderr_is_expected):
327 if stderr_is_expected:
328 return DEFAULT_STDOUT_LEVEL
329 return DEFAULT_STDERR_LEVEL
330
331
mbligh63073c92008-03-31 16:49:32 +0000332def run(command, timeout=None, ignore_status=False,
showardb45a4662009-07-15 14:27:56 +0000333 stdout_tee=None, stderr_tee=None, verbose=True, stdin=None,
mblighc823e8d2009-10-02 00:01:35 +0000334 stderr_is_expected=None, args=()):
jadmanski0afbb632008-06-06 21:10:57 +0000335 """
336 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000337
mblighc823e8d2009-10-02 00:01:35 +0000338 @param command: the command line string.
339 @param timeout: time limit in seconds before attempting to kill the
340 running process. The run() function will take a few seconds
341 longer than 'timeout' to complete if it has to kill the process.
342 @param ignore_status: do not raise an exception, no matter what the exit
343 code of the command is.
344 @param stdout_tee: optional file-like object to which stdout data
345 will be written as it is generated (data will still be stored
346 in result.stdout).
347 @param stderr_tee: likewise for stderr.
348 @param verbose: if True, log the command being run.
349 @param stdin: stdin to pass to the executed process.
350 @param args: sequence of strings of arguments to be given to the command
351 inside " quotes after they have been escaped for that; each
352 element in the sequence will be given as a separate command
353 argument
mbligh63073c92008-03-31 16:49:32 +0000354
mblighc823e8d2009-10-02 00:01:35 +0000355 @return a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000356
mblighc823e8d2009-10-02 00:01:35 +0000357 @raise CmdError: the exit code of the command execution was not 0
jadmanski0afbb632008-06-06 21:10:57 +0000358 """
mblighc823e8d2009-10-02 00:01:35 +0000359 for arg in args:
360 command += ' "%s"' % sh_escape(arg)
showardb45a4662009-07-15 14:27:56 +0000361 if stderr_is_expected is None:
362 stderr_is_expected = ignore_status
mblighc823e8d2009-10-02 00:01:35 +0000363
showard170873e2009-01-07 00:22:26 +0000364 bg_job = join_bg_jobs(
showardb45a4662009-07-15 14:27:56 +0000365 (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
366 stderr_level=get_stderr_level(stderr_is_expected)),),
showard170873e2009-01-07 00:22:26 +0000367 timeout)[0]
mbligh849a0f62008-08-28 20:12:19 +0000368 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000369 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000370 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000371
mbligh849a0f62008-08-28 20:12:19 +0000372 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000373
mbligh45ffc432008-12-09 23:35:17 +0000374
mbligha5630a52008-09-03 22:09:50 +0000375def run_parallel(commands, timeout=None, ignore_status=False,
376 stdout_tee=None, stderr_tee=None):
377 """Beahves the same as run with the following exceptions:
378
379 - commands is a list of commands to run in parallel.
380 - ignore_status toggles whether or not an exception should be raised
381 on any error.
382
383 returns a list of CmdResult objects
384 """
385 bg_jobs = []
386 for command in commands:
showardb45a4662009-07-15 14:27:56 +0000387 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
388 stderr_level=get_stderr_level(ignore_status)))
mbligha5630a52008-09-03 22:09:50 +0000389
390 # Updates objects in bg_jobs list with their process information
391 join_bg_jobs(bg_jobs, timeout)
392
393 for bg_job in bg_jobs:
394 if not ignore_status and bg_job.result.exit_status:
395 raise error.CmdError(command, bg_job.result,
396 "Command returned non-zero exit status")
397
398 return [bg_job.result for bg_job in bg_jobs]
399
400
mbligh849a0f62008-08-28 20:12:19 +0000401@deprecated
mbligh63073c92008-03-31 16:49:32 +0000402def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000403 """Function deprecated. Please use BgJob class instead."""
404 bg_job = BgJob(command)
405 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000406
407
mbligh849a0f62008-08-28 20:12:19 +0000408def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000409 """Joins the bg_jobs with the current thread.
410
411 Returns the same list of bg_jobs objects that was passed in.
412 """
mblighae69f262009-04-17 20:14:56 +0000413 ret, timeout_error = 0, False
mbligh849a0f62008-08-28 20:12:19 +0000414 for bg_job in bg_jobs:
415 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000416
jadmanski0afbb632008-06-06 21:10:57 +0000417 try:
418 # We are holding ends to stdin, stdout pipes
419 # hence we need to be sure to close those fds no mater what
420 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000421 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
422
423 for bg_job in bg_jobs:
424 # Process stdout and stderr
425 bg_job.process_output(stdout=True,final_read=True)
426 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000427 finally:
428 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000429 for bg_job in bg_jobs:
430 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000431
mbligh849a0f62008-08-28 20:12:19 +0000432 if timeout_error:
433 # TODO: This needs to be fixed to better represent what happens when
434 # running in parallel. However this is backwards compatable, so it will
435 # do for the time being.
436 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
437 "Command(s) did not complete within %d seconds"
438 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000439
mbligh63073c92008-03-31 16:49:32 +0000440
mbligh849a0f62008-08-28 20:12:19 +0000441 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000442
mbligh849a0f62008-08-28 20:12:19 +0000443
444def _wait_for_commands(bg_jobs, start_time, timeout):
445 # This returns True if it must return due to a timeout, otherwise False.
446
mblighf0b4a0a2008-09-03 20:46:16 +0000447 # To check for processes which terminate without producing any output
448 # a 1 second timeout is used in select.
449 SELECT_TIMEOUT = 1
450
mbligh849a0f62008-08-28 20:12:19 +0000451 select_list = []
452 reverse_dict = {}
453 for bg_job in bg_jobs:
454 select_list.append(bg_job.sp.stdout)
455 select_list.append(bg_job.sp.stderr)
456 reverse_dict[bg_job.sp.stdout] = (bg_job,True)
457 reverse_dict[bg_job.sp.stderr] = (bg_job,False)
458
jadmanski0afbb632008-06-06 21:10:57 +0000459 if timeout:
460 stop_time = start_time + timeout
461 time_left = stop_time - time.time()
462 else:
463 time_left = None # so that select never times out
464 while not timeout or time_left > 0:
465 # select will return when stdout is ready (including when it is
466 # EOF, that is the process has terminated).
mblighf0b4a0a2008-09-03 20:46:16 +0000467 ready, _, _ = select.select(select_list, [], [], SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000468
jadmanski0afbb632008-06-06 21:10:57 +0000469 # os.read() has to be used instead of
470 # subproc.stdout.read() which will otherwise block
mbligh849a0f62008-08-28 20:12:19 +0000471 for fileno in ready:
472 bg_job,stdout = reverse_dict[fileno]
473 bg_job.process_output(stdout)
mbligh63073c92008-03-31 16:49:32 +0000474
mbligh849a0f62008-08-28 20:12:19 +0000475 remaining_jobs = [x for x in bg_jobs if x.result.exit_status is None]
476 if len(remaining_jobs) == 0:
477 return False
478 for bg_job in remaining_jobs:
479 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000480
jadmanski0afbb632008-06-06 21:10:57 +0000481 if timeout:
482 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000483
mbligh849a0f62008-08-28 20:12:19 +0000484 # Kill all processes which did not complete prior to timeout
485 for bg_job in [x for x in bg_jobs if x.result.exit_status is None]:
mbligh7afc3a62008-11-27 00:35:44 +0000486 print '* Warning: run process timeout (%s) fired' % timeout
mbligh849a0f62008-08-28 20:12:19 +0000487 nuke_subprocess(bg_job.sp)
mbligh095dc642008-10-01 03:41:35 +0000488 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000489
mbligh849a0f62008-08-28 20:12:19 +0000490 return True
mbligh63073c92008-03-31 16:49:32 +0000491
492
showard549afad2009-08-20 23:33:36 +0000493def pid_is_alive(pid):
494 """
495 True if process pid exists and is not yet stuck in Zombie state.
496 Zombies are impossible to move between cgroups, etc.
497 pid can be integer, or text of integer.
498 """
499 path = '/proc/%s/stat' % pid
500
501 try:
502 stat = read_one_line(path)
503 except IOError:
504 if not os.path.exists(path):
505 # file went away
506 return False
507 raise
508
509 return stat.split()[2] != 'Z'
510
511
512def signal_pid(pid, sig):
513 """
514 Sends a signal to a process id. Returns True if the process terminated
515 successfully, False otherwise.
516 """
517 try:
518 os.kill(pid, sig)
519 except OSError:
520 # The process may have died before we could kill it.
521 pass
522
523 for i in range(5):
524 if not pid_is_alive(pid):
525 return True
526 time.sleep(1)
527
528 # The process is still alive
529 return False
530
531
mbligh63073c92008-03-31 16:49:32 +0000532def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000533 # check if the subprocess is still alive, first
534 if subproc.poll() is not None:
535 return subproc.poll()
536
jadmanski0afbb632008-06-06 21:10:57 +0000537 # the process has not terminated within timeout,
538 # kill it via an escalating series of signals.
539 signal_queue = [signal.SIGTERM, signal.SIGKILL]
540 for sig in signal_queue:
showard549afad2009-08-20 23:33:36 +0000541 signal_pid(subproc.pid, sig)
542 if subproc.poll() is not None:
543 return subproc.poll()
mbligh63073c92008-03-31 16:49:32 +0000544
545
showard786da9a2009-10-12 20:31:20 +0000546def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
jadmanski0afbb632008-06-06 21:10:57 +0000547 # the process has not terminated within timeout,
548 # kill it via an escalating series of signals.
jadmanski0afbb632008-06-06 21:10:57 +0000549 for sig in signal_queue:
showard549afad2009-08-20 23:33:36 +0000550 if signal_pid(pid, sig):
551 return
mbligh63073c92008-03-31 16:49:32 +0000552
showard549afad2009-08-20 23:33:36 +0000553 # no signal successfully terminated the process
554 raise error.AutoservRunError('Could not kill %d' % pid, None)
mbligh298ed592009-06-15 21:52:57 +0000555
556
mbligh63073c92008-03-31 16:49:32 +0000557def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000558 """This function returns the exit status of command."""
mblighf8dffb12008-10-29 16:45:26 +0000559 return run(command, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000560 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
mbligh63073c92008-03-31 16:49:32 +0000561
562
mbligha5630a52008-09-03 22:09:50 +0000563def system_parallel(commands, timeout=None, ignore_status=False):
564 """This function returns a list of exit statuses for the respective
565 list of commands."""
566 return [bg_jobs.exit_status for bg_jobs in
mblighf8dffb12008-10-29 16:45:26 +0000567 run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000568 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
mbligh849a0f62008-08-28 20:12:19 +0000569
570
mbligh8ea61e22008-05-09 18:09:37 +0000571def system_output(command, timeout=None, ignore_status=False,
mblighc823e8d2009-10-02 00:01:35 +0000572 retain_output=False, args=()):
573 """
574 Run a command and return the stdout output.
575
576 @param command: command string to execute.
577 @param timeout: time limit in seconds before attempting to kill the
578 running process. The function will take a few seconds longer
579 than 'timeout' to complete if it has to kill the process.
580 @param ignore_status: do not raise an exception, no matter what the exit
581 code of the command is.
582 @param retain_output: set to True to make stdout/stderr of the command
583 output to be also sent to the logging system
584 @param args: sequence of strings of arguments to be given to the command
585 inside " quotes after they have been escaped for that; each
586 element in the sequence will be given as a separate command
587 argument
588
589 @return a string with the stdout output of the command.
590 """
jadmanski0afbb632008-06-06 21:10:57 +0000591 if retain_output:
mblighf8dffb12008-10-29 16:45:26 +0000592 out = run(command, timeout=timeout, ignore_status=ignore_status,
mblighc823e8d2009-10-02 00:01:35 +0000593 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
594 args=args).stdout
jadmanski0afbb632008-06-06 21:10:57 +0000595 else:
mblighc823e8d2009-10-02 00:01:35 +0000596 out = run(command, timeout=timeout, ignore_status=ignore_status,
597 args=args).stdout
598 if out[-1:] == '\n':
599 out = out[:-1]
jadmanski0afbb632008-06-06 21:10:57 +0000600 return out
mbligh63073c92008-03-31 16:49:32 +0000601
mbligh849a0f62008-08-28 20:12:19 +0000602
mbligha5630a52008-09-03 22:09:50 +0000603def system_output_parallel(commands, timeout=None, ignore_status=False,
604 retain_output=False):
605 if retain_output:
showard108d73e2009-06-22 18:14:41 +0000606 out = [bg_job.stdout for bg_job
607 in run_parallel(commands, timeout=timeout,
608 ignore_status=ignore_status,
609 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
mbligha5630a52008-09-03 22:09:50 +0000610 else:
mblighf8dffb12008-10-29 16:45:26 +0000611 out = [bg_job.stdout for bg_job in run_parallel(commands,
612 timeout=timeout, ignore_status=ignore_status)]
mbligha5630a52008-09-03 22:09:50 +0000613 for x in out:
614 if out[-1:] == '\n': out = out[:-1]
615 return out
616
617
mbligh98467952008-11-19 00:25:45 +0000618def strip_unicode(input):
619 if type(input) == list:
620 return [strip_unicode(i) for i in input]
621 elif type(input) == dict:
622 output = {}
623 for key in input.keys():
624 output[str(key)] = strip_unicode(input[key])
625 return output
626 elif type(input) == unicode:
627 return str(input)
628 else:
629 return input
630
631
mbligha5630a52008-09-03 22:09:50 +0000632def get_cpu_percentage(function, *args, **dargs):
633 """Returns a tuple containing the CPU% and return value from function call.
634
635 This function calculates the usage time by taking the difference of
636 the user and system times both before and after the function call.
637 """
638 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
639 self_pre = resource.getrusage(resource.RUSAGE_SELF)
640 start = time.time()
641 to_return = function(*args, **dargs)
642 elapsed = time.time() - start
643 self_post = resource.getrusage(resource.RUSAGE_SELF)
644 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
645
646 # Calculate CPU Percentage
647 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
648 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
649 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
650
651 return cpu_percent, to_return
652
653
mblighc1cbc992008-05-27 20:01:45 +0000654"""
655This function is used when there is a need to run more than one
656job simultaneously starting exactly at the same time. It basically returns
657a modified control file (containing the synchronization code prepended)
658whenever it is ready to run the control file. The synchronization
659is done using barriers to make sure that the jobs start at the same time.
660
661Here is how the synchronization is done to make sure that the tests
662start at exactly the same time on the client.
663sc_bar is a server barrier and s_bar, c_bar are the normal barriers
664
665 Job1 Job2 ...... JobN
666 Server: | sc_bar
667 Server: | s_bar ...... s_bar
668 Server: | at.run() at.run() ...... at.run()
669 ----------|------------------------------------------------------
670 Client | sc_bar
671 Client | c_bar c_bar ...... c_bar
672 Client | <run test> <run test> ...... <run test>
673
674
675PARAMS:
676 control_file : The control file which to which the above synchronization
677 code would be prepended to
678 host_name : The host name on which the job is going to run
679 host_num (non negative) : A number to identify the machine so that we have
680 different sets of s_bar_ports for each of the machines.
681 instance : The number of the job
682 num_jobs : Total number of jobs that are going to run in parallel with
683 this job starting at the same time
684 port_base : Port number that is used to derive the actual barrier ports.
685
686RETURN VALUE:
687 The modified control file.
688
689"""
690def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000691 instance, num_jobs, port_base=63100):
692 sc_bar_port = port_base
693 c_bar_port = port_base
694 if host_num < 0:
695 print "Please provide a non negative number for the host"
696 return None
697 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
698 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000699
jadmanski0afbb632008-06-06 21:10:57 +0000700 sc_bar_timeout = 180
701 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000702
jadmanski0afbb632008-06-06 21:10:57 +0000703 # The barrier code snippet is prepended into the conrol file
704 # dynamically before at.run() is called finally.
705 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000706
jadmanski0afbb632008-06-06 21:10:57 +0000707 # jobid is the unique name used to identify the processes
708 # trying to reach the barriers
709 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000710
jadmanski0afbb632008-06-06 21:10:57 +0000711 rendv = []
712 # rendvstr is a temp holder for the rendezvous list of the processes
713 for n in range(num_jobs):
714 rendv.append("'%s#%d'" % (host_name, n))
715 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000716
jadmanski0afbb632008-06-06 21:10:57 +0000717 if instance == 0:
718 # Do the setup and wait at the server barrier
719 # Clean up the tmp and the control dirs for the first instance
720 control_new.append('if os.path.exists(job.tmpdir):')
721 control_new.append("\t system('umount -f %s > /dev/null"
722 "2> /dev/null' % job.tmpdir,"
723 "ignore_status=True)")
724 control_new.append("\t system('rm -rf ' + job.tmpdir)")
725 control_new.append(
726 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
727 % (jobid, sc_bar_timeout, sc_bar_port))
728 control_new.append(
mbligh9c12f772009-06-22 19:03:55 +0000729 'b0.rendezvous_servers("PARALLEL_MASTER", "%s")'
jadmanski0afbb632008-06-06 21:10:57 +0000730 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000731
jadmanski0afbb632008-06-06 21:10:57 +0000732 elif instance == 1:
733 # Wait at the server barrier to wait for instance=0
734 # process to complete setup
735 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
736 port=sc_bar_port)
mbligh9c12f772009-06-22 19:03:55 +0000737 b0.rendezvous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000738
jadmanski0afbb632008-06-06 21:10:57 +0000739 if(num_jobs > 2):
740 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
741 port=s_bar_port)
mbligh9c12f772009-06-22 19:03:55 +0000742 b1.rendezvous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000743
jadmanski0afbb632008-06-06 21:10:57 +0000744 else:
745 # For the rest of the clients
746 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
mbligh9c12f772009-06-22 19:03:55 +0000747 b2.rendezvous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000748
jadmanski0afbb632008-06-06 21:10:57 +0000749 # Client side barrier for all the tests to start at the same time
750 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
751 % (jobid, c_bar_timeout, c_bar_port))
mbligh9c12f772009-06-22 19:03:55 +0000752 control_new.append("b1.rendezvous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000753
jadmanski0afbb632008-06-06 21:10:57 +0000754 # Stick in the rest of the control file
755 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000756
jadmanski0afbb632008-06-06 21:10:57 +0000757 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000758
mbligh63073c92008-03-31 16:49:32 +0000759
mblighc5ddfd12008-08-04 17:15:00 +0000760def get_arch(run_function=run):
761 """
762 Get the hardware architecture of the machine.
763 run_function is used to execute the commands. It defaults to
764 utils.run() but a custom method (if provided) should be of the
765 same schema as utils.run. It should return a CmdResult object and
766 throw a CmdError exception.
767 """
768 arch = run_function('/bin/uname -m').stdout.rstrip()
769 if re.match(r'i\d86$', arch):
770 arch = 'i386'
771 return arch
772
773
showard4745ecd2009-05-26 19:34:56 +0000774def get_num_logical_cpus_per_socket(run_function=run):
mbligh9fd9afe2009-04-28 18:27:25 +0000775 """
776 Get the number of cores (including hyperthreading) per cpu.
777 run_function is used to execute the commands. It defaults to
778 utils.run() but a custom method (if provided) should be of the
779 same schema as utils.run. It should return a CmdResult object and
780 throw a CmdError exception.
781 """
showard4745ecd2009-05-26 19:34:56 +0000782 siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
783 num_siblings = map(int,
784 re.findall(r'^siblings\s*:\s*(\d+)\s*$',
785 siblings, re.M))
786 if len(num_siblings) == 0:
787 raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
788 if min(num_siblings) != max(num_siblings):
789 raise error.TestError('Number of siblings differ %r' %
790 num_siblings)
791 return num_siblings[0]
mbligh9fd9afe2009-04-28 18:27:25 +0000792
793
jadmanski4f909252008-12-01 20:47:10 +0000794def merge_trees(src, dest):
795 """
796 Merges a source directory tree at 'src' into a destination tree at
797 'dest'. If a path is a file in both trees than the file in the source
798 tree is APPENDED to the one in the destination tree. If a path is
799 a directory in both trees then the directories are recursively merged
800 with this function. In any other case, the function will skip the
801 paths that cannot be merged (instead of failing).
802 """
803 if not os.path.exists(src):
804 return # exists only in dest
805 elif not os.path.exists(dest):
806 if os.path.isfile(src):
807 shutil.copy2(src, dest) # file only in src
808 else:
809 shutil.copytree(src, dest, symlinks=True) # dir only in src
810 return
811 elif os.path.isfile(src) and os.path.isfile(dest):
812 # src & dest are files in both trees, append src to dest
813 destfile = open(dest, "a")
814 try:
815 srcfile = open(src)
816 try:
817 destfile.write(srcfile.read())
818 finally:
819 srcfile.close()
820 finally:
821 destfile.close()
822 elif os.path.isdir(src) and os.path.isdir(dest):
823 # src & dest are directories in both trees, so recursively merge
824 for name in os.listdir(src):
825 merge_trees(os.path.join(src, name), os.path.join(dest, name))
826 else:
827 # src & dest both exist, but are incompatible
828 return
829
830
mbligh63073c92008-03-31 16:49:32 +0000831class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000832 """
833 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000834
jadmanski0afbb632008-06-06 21:10:57 +0000835 command: String containing the command line itself
836 exit_status: Integer exit code of the process
837 stdout: String containing stdout of the process
838 stderr: String containing stderr of the process
839 duration: Elapsed wall clock time running the process
840 """
mbligh63073c92008-03-31 16:49:32 +0000841
842
mblighcd63a212009-05-01 23:04:38 +0000843 def __init__(self, command="", stdout="", stderr="",
jadmanski0afbb632008-06-06 21:10:57 +0000844 exit_status=None, duration=0):
845 self.command = command
846 self.exit_status = exit_status
847 self.stdout = stdout
848 self.stderr = stderr
849 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000850
851
jadmanski0afbb632008-06-06 21:10:57 +0000852 def __repr__(self):
853 wrapper = textwrap.TextWrapper(width = 78,
854 initial_indent="\n ",
855 subsequent_indent=" ")
856
857 stdout = self.stdout.rstrip()
858 if stdout:
859 stdout = "\nstdout:\n%s" % stdout
860
861 stderr = self.stderr.rstrip()
862 if stderr:
863 stderr = "\nstderr:\n%s" % stderr
864
865 return ("* Command: %s\n"
866 "Exit status: %s\n"
867 "Duration: %s\n"
868 "%s"
869 "%s"
870 % (wrapper.fill(self.command), self.exit_status,
871 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000872
873
mbligh462c0152008-03-13 15:37:10 +0000874class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000875 def __init__(self, run_sequentially=False):
876 # Run sequentially is for debugging control files
877 self.test_list = []
878 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000879
880
jadmanski0afbb632008-06-06 21:10:57 +0000881 def add(self, *args, **dargs):
882 test = (args, dargs)
883 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000884
885
jadmanski0afbb632008-06-06 21:10:57 +0000886 def run(self, fn):
887 while self.test_list:
888 test_index = random.randint(0, len(self.test_list)-1)
889 if self.run_sequentially:
890 test_index = 0
891 (args, dargs) = self.test_list.pop(test_index)
892 fn(*args, **dargs)
mbligha7007722009-01-13 00:37:11 +0000893
894
showard5deef7f2009-09-09 18:16:58 +0000895def import_site_module(path, module, dummy=None, modulefile=None):
896 """
897 Try to import the site specific module if it exists.
898
899 @param path full filename of the source file calling this (ie __file__)
900 @param module full module name
901 @param dummy dummy value to return in case there is no symbol to import
902 @param modulefile module filename
903
904 @return site specific module or dummy
905
906 @raises ImportError if the site file exists but imports fails
907 """
908 short_module = module[module.rfind(".") + 1:]
909
910 if not modulefile:
911 modulefile = short_module + ".py"
912
913 if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
914 return __import__(module, {}, {}, [short_module])
915 return dummy
916
917
mblighdd669372009-02-03 21:57:18 +0000918def import_site_symbol(path, module, name, dummy=None, modulefile=None):
919 """
920 Try to import site specific symbol from site specific file if it exists
921
922 @param path full filename of the source file calling this (ie __file__)
923 @param module full module name
924 @param name symbol name to be imported from the site file
925 @param dummy dummy value to return in case there is no symbol to import
926 @param modulefile module filename
927
928 @return site specific symbol or dummy
929
showard5deef7f2009-09-09 18:16:58 +0000930 @raises ImportError if the site file exists but imports fails
mblighdd669372009-02-03 21:57:18 +0000931 """
showard5deef7f2009-09-09 18:16:58 +0000932 module = import_site_module(path, module, modulefile=modulefile)
933 if not module:
934 return dummy
mbligha7007722009-01-13 00:37:11 +0000935
showard5deef7f2009-09-09 18:16:58 +0000936 # special unique value to tell us if the symbol can't be imported
937 cant_import = object()
mbligha7007722009-01-13 00:37:11 +0000938
showard5deef7f2009-09-09 18:16:58 +0000939 obj = getattr(module, name, cant_import)
940 if obj is cant_import:
941 logging.error("unable to import site symbol '%s', using non-site "
942 "implementation", name)
943 return dummy
mbligh062ed152009-01-13 00:57:14 +0000944
945 return obj
946
947
948def import_site_class(path, module, classname, baseclass, modulefile=None):
949 """
950 Try to import site specific class from site specific file if it exists
951
952 Args:
953 path: full filename of the source file calling this (ie __file__)
954 module: full module name
955 classname: class name to be loaded from site file
mbligh0a8c3322009-04-28 18:32:19 +0000956 baseclass: base class object to return when no site file present or
957 to mixin when site class exists but is not inherited from baseclass
mbligh062ed152009-01-13 00:57:14 +0000958 modulefile: module filename
959
mbligh0a8c3322009-04-28 18:32:19 +0000960 Returns: baseclass if site specific class does not exist, the site specific
961 class if it exists and is inherited from baseclass or a mixin of the
962 site specific class and baseclass when the site specific class exists
963 and is not inherited from baseclass
mbligh062ed152009-01-13 00:57:14 +0000964
965 Raises: ImportError if the site file exists but imports fails
966 """
967
mblighdd669372009-02-03 21:57:18 +0000968 res = import_site_symbol(path, module, classname, None, modulefile)
mbligh0a8c3322009-04-28 18:32:19 +0000969 if res:
970 if not issubclass(res, baseclass):
971 # if not a subclass of baseclass then mix in baseclass with the
972 # site specific class object and return the result
973 res = type(classname, (res, baseclass), {})
974 else:
975 res = baseclass
mbligha7007722009-01-13 00:37:11 +0000976
mbligh062ed152009-01-13 00:57:14 +0000977 return res
978
979
980def import_site_function(path, module, funcname, dummy, modulefile=None):
981 """
982 Try to import site specific function from site specific file if it exists
983
984 Args:
985 path: full filename of the source file calling this (ie __file__)
986 module: full module name
987 funcname: function name to be imported from site file
988 dummy: dummy function to return in case there is no function to import
989 modulefile: module filename
990
991 Returns: site specific function object or dummy
992
993 Raises: ImportError if the site file exists but imports fails
994 """
995
mblighdd669372009-02-03 21:57:18 +0000996 return import_site_symbol(path, module, funcname, dummy, modulefile)
mblighfb676032009-04-01 18:25:38 +0000997
998
showard549afad2009-08-20 23:33:36 +0000999def _get_pid_path(program_name):
1000 my_path = os.path.dirname(__file__)
1001 return os.path.abspath(os.path.join(my_path, "..", "..",
1002 "%s.pid" % program_name))
1003
1004
mblighfb676032009-04-01 18:25:38 +00001005def write_pid(program_name):
1006 """
1007 Try to drop <program_name>.pid in the main autotest directory.
1008
1009 Args:
1010 program_name: prefix for file name
1011 """
showard549afad2009-08-20 23:33:36 +00001012 pidfile = open(_get_pid_path(program_name), "w")
1013 try:
1014 pidfile.write("%s\n" % os.getpid())
1015 finally:
1016 pidfile.close()
mblighfb676032009-04-01 18:25:38 +00001017
showard549afad2009-08-20 23:33:36 +00001018
1019def delete_pid_file_if_exists(program_name):
1020 """
1021 Tries to remove <program_name>.pid from the main autotest directory.
1022 """
1023 pidfile_path = _get_pid_path(program_name)
1024
1025 try:
1026 os.remove(pidfile_path)
1027 except OSError:
1028 if not os.path.exists(pidfile_path):
1029 return
1030 raise
1031
1032
1033def get_pid_from_file(program_name):
1034 """
1035 Reads the pid from <program_name>.pid in the autotest directory.
1036
1037 @param program_name the name of the program
1038 @return the pid if the file exists, None otherwise.
1039 """
1040 pidfile_path = _get_pid_path(program_name)
1041 if not os.path.exists(pidfile_path):
1042 return None
1043
1044 pidfile = open(_get_pid_path(program_name), 'r')
1045
1046 try:
1047 try:
1048 pid = int(pidfile.readline())
1049 except IOError:
1050 if not os.path.exists(pidfile_path):
1051 return None
1052 raise
1053 finally:
1054 pidfile.close()
1055
1056 return pid
1057
1058
showard8de37132009-08-31 18:33:08 +00001059def program_is_alive(program_name):
showard549afad2009-08-20 23:33:36 +00001060 """
1061 Checks if the process is alive and not in Zombie state.
1062
1063 @param program_name the name of the program
1064 @return True if still alive, False otherwise
1065 """
1066 pid = get_pid_from_file(program_name)
1067 if pid is None:
1068 return False
1069 return pid_is_alive(pid)
1070
1071
showard8de37132009-08-31 18:33:08 +00001072def signal_program(program_name, sig=signal.SIGTERM):
showard549afad2009-08-20 23:33:36 +00001073 """
1074 Sends a signal to the process listed in <program_name>.pid
1075
1076 @param program_name the name of the program
1077 @param sig signal to send
1078 """
1079 pid = get_pid_from_file(program_name)
1080 if pid:
1081 signal_pid(pid, sig)
mbligh45561782009-05-11 21:14:34 +00001082
1083
1084def get_relative_path(path, reference):
1085 """Given 2 absolute paths "path" and "reference", compute the path of
1086 "path" as relative to the directory "reference".
1087
1088 @param path the absolute path to convert to a relative path
1089 @param reference an absolute directory path to which the relative
1090 path will be computed
1091 """
1092 # normalize the paths (remove double slashes, etc)
1093 assert(os.path.isabs(path))
1094 assert(os.path.isabs(reference))
1095
1096 path = os.path.normpath(path)
1097 reference = os.path.normpath(reference)
1098
1099 # we could use os.path.split() but it splits from the end
1100 path_list = path.split(os.path.sep)[1:]
1101 ref_list = reference.split(os.path.sep)[1:]
1102
1103 # find the longest leading common path
1104 for i in xrange(min(len(path_list), len(ref_list))):
1105 if path_list[i] != ref_list[i]:
1106 # decrement i so when exiting this loop either by no match or by
1107 # end of range we are one step behind
1108 i -= 1
1109 break
1110 i += 1
1111 # drop the common part of the paths, not interested in that anymore
1112 del path_list[:i]
1113
1114 # for each uncommon component in the reference prepend a ".."
1115 path_list[:0] = ['..'] * (len(ref_list) - i)
1116
1117 return os.path.join(*path_list)
mbligh277a0e42009-07-11 00:11:45 +00001118
1119
1120def sh_escape(command):
1121 """
1122 Escape special characters from a command so that it can be passed
1123 as a double quoted (" ") string in a (ba)sh command.
1124
1125 Args:
1126 command: the command string to escape.
1127
1128 Returns:
1129 The escaped command string. The required englobing double
1130 quotes are NOT added and so should be added at some point by
1131 the caller.
1132
1133 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
1134 """
1135 command = command.replace("\\", "\\\\")
1136 command = command.replace("$", r'\$')
1137 command = command.replace('"', r'\"')
1138 command = command.replace('`', r'\`')
1139 return command