blob: 6ca33facaf80a1eda5b772c74979e454265097a0 [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
lmrfed221f2010-02-04 03:08:30 +00007try:
8 import hashlib
9except ImportError:
10 import md5, sha
mbligh999fb132010-04-23 17:22:03 +000011from autotest_lib.client.common_lib import error, logging_manager
mbligh81edd792008-08-26 16:54:02 +000012
mbligh849a0f62008-08-28 20:12:19 +000013def deprecated(func):
14 """This is a decorator which can be used to mark functions as deprecated.
15 It will result in a warning being emmitted when the function is used."""
16 def new_func(*args, **dargs):
17 warnings.warn("Call to deprecated function %s." % func.__name__,
18 category=DeprecationWarning)
19 return func(*args, **dargs)
20 new_func.__name__ = func.__name__
21 new_func.__doc__ = func.__doc__
22 new_func.__dict__.update(func.__dict__)
23 return new_func
24
25
showard108d73e2009-06-22 18:14:41 +000026class _NullStream(object):
27 def write(self, data):
28 pass
29
30
31 def flush(self):
32 pass
33
34
35TEE_TO_LOGS = object()
36_the_null_stream = _NullStream()
37
showardb45a4662009-07-15 14:27:56 +000038DEFAULT_STDOUT_LEVEL = logging.DEBUG
39DEFAULT_STDERR_LEVEL = logging.ERROR
40
showard39986a62009-12-10 21:41:53 +000041# prefixes for logging stdout/stderr of commands
42STDOUT_PREFIX = '[stdout] '
43STDERR_PREFIX = '[stderr] '
44
45
46def get_stream_tee_file(stream, level, prefix=''):
showard108d73e2009-06-22 18:14:41 +000047 if stream is None:
48 return _the_null_stream
49 if stream is TEE_TO_LOGS:
showard39986a62009-12-10 21:41:53 +000050 return logging_manager.LoggingFile(level=level, prefix=prefix)
showard108d73e2009-06-22 18:14:41 +000051 return stream
52
53
mbligh849a0f62008-08-28 20:12:19 +000054class BgJob(object):
showard170873e2009-01-07 00:22:26 +000055 def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
showardb45a4662009-07-15 14:27:56 +000056 stdin=None, stderr_level=DEFAULT_STDERR_LEVEL):
mbligh849a0f62008-08-28 20:12:19 +000057 self.command = command
showard39986a62009-12-10 21:41:53 +000058 self.stdout_tee = get_stream_tee_file(stdout_tee, DEFAULT_STDOUT_LEVEL,
59 prefix=STDOUT_PREFIX)
60 self.stderr_tee = get_stream_tee_file(stderr_tee, stderr_level,
61 prefix=STDERR_PREFIX)
mbligh849a0f62008-08-28 20:12:19 +000062 self.result = CmdResult(command)
jadmanski093a0682009-10-13 14:55:43 +000063
64 # allow for easy stdin input by string, we'll let subprocess create
65 # a pipe for stdin input and we'll write to it in the wait loop
66 if isinstance(stdin, basestring):
67 self.string_stdin = stdin
68 stdin = subprocess.PIPE
69 else:
70 self.string_stdin = None
71
mblighbd96b452008-09-03 23:14:27 +000072 if verbose:
showardb18134f2009-03-20 20:52:18 +000073 logging.debug("Running '%s'" % command)
mbligh849a0f62008-08-28 20:12:19 +000074 self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
75 stderr=subprocess.PIPE,
76 preexec_fn=self._reset_sigpipe, shell=True,
showard170873e2009-01-07 00:22:26 +000077 executable="/bin/bash",
78 stdin=stdin)
mbligh849a0f62008-08-28 20:12:19 +000079
80
81 def output_prepare(self, stdout_file=None, stderr_file=None):
82 self.stdout_file = stdout_file
83 self.stderr_file = stderr_file
84
mbligh45ffc432008-12-09 23:35:17 +000085
mbligh849a0f62008-08-28 20:12:19 +000086 def process_output(self, stdout=True, final_read=False):
87 """output_prepare must be called prior to calling this"""
88 if stdout:
89 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
90 else:
91 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
92
93 if final_read:
94 # read in all the data we can from pipe and then stop
95 data = []
96 while select.select([pipe], [], [], 0)[0]:
97 data.append(os.read(pipe.fileno(), 1024))
98 if len(data[-1]) == 0:
99 break
100 data = "".join(data)
101 else:
102 # perform a single read
103 data = os.read(pipe.fileno(), 1024)
104 buf.write(data)
showard108d73e2009-06-22 18:14:41 +0000105 tee.write(data)
mbligh849a0f62008-08-28 20:12:19 +0000106
107
108 def cleanup(self):
showard108d73e2009-06-22 18:14:41 +0000109 self.stdout_tee.flush()
110 self.stderr_tee.flush()
mbligh849a0f62008-08-28 20:12:19 +0000111 self.sp.stdout.close()
112 self.sp.stderr.close()
113 self.result.stdout = self.stdout_file.getvalue()
114 self.result.stderr = self.stderr_file.getvalue()
115
116
117 def _reset_sigpipe(self):
118 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
119
mbligh81edd792008-08-26 16:54:02 +0000120
121def ip_to_long(ip):
122 # !L is a long in network byte order
123 return struct.unpack('!L', socket.inet_aton(ip))[0]
124
125
126def long_to_ip(number):
127 # See above comment.
128 return socket.inet_ntoa(struct.pack('!L', number))
129
130
131def create_subnet_mask(bits):
mbligh81edd792008-08-26 16:54:02 +0000132 return (1 << 32) - (1 << 32-bits)
133
134
135def format_ip_with_mask(ip, mask_bits):
136 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
137 return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
mbligh6231cd62008-02-02 19:18:33 +0000138
mblighde0d47e2008-03-28 14:37:18 +0000139
jadmanskie80d4712008-10-03 16:15:59 +0000140def normalize_hostname(alias):
141 ip = socket.gethostbyname(alias)
142 return socket.gethostbyaddr(ip)[0]
143
144
mblighd6d043c2008-09-27 21:00:45 +0000145def get_ip_local_port_range():
146 match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
147 read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
148 return (int(match.group(1)), int(match.group(2)))
149
150
151def set_ip_local_port_range(lower, upper):
152 write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
153 '%d %d\n' % (lower, upper))
154
mbligh315b9412008-10-01 03:34:11 +0000155
mbligh45ffc432008-12-09 23:35:17 +0000156
157def send_email(mail_from, mail_to, subject, body):
158 """
159 Sends an email via smtp
160
161 mail_from: string with email address of sender
162 mail_to: string or list with email address(es) of recipients
163 subject: string with subject of email
164 body: (multi-line) string with body of email
165 """
166 if isinstance(mail_to, str):
167 mail_to = [mail_to]
168 msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to),
169 subject, body)
170 try:
171 mailer = smtplib.SMTP('localhost')
172 try:
173 mailer.sendmail(mail_from, mail_to, msg)
174 finally:
175 mailer.quit()
176 except Exception, e:
177 # Emails are non-critical, not errors, but don't raise them
178 print "Sending email failed. Reason: %s" % repr(e)
179
180
jadmanski5182e162008-05-13 21:48:16 +0000181def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +0000182 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +0000183
184
jamesrenc3940222010-02-19 21:57:37 +0000185def read_file(filename):
186 f = open(filename)
187 try:
188 return f.read()
189 finally:
190 f.close()
191
192
mblighb9d05512008-10-18 13:53:27 +0000193def write_one_line(filename, line):
194 open_write_close(filename, line.rstrip('\n') + '\n')
195
196
197def open_write_close(filename, data):
mbligh618ac9e2008-10-06 17:14:32 +0000198 f = open(filename, 'w')
mblighb9d05512008-10-18 13:53:27 +0000199 try:
200 f.write(data)
201 finally:
202 f.close()
jadmanski5182e162008-05-13 21:48:16 +0000203
204
mblighde0d47e2008-03-28 14:37:18 +0000205def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000206 """
207 Read a key-value pair format file into a dictionary, and return it.
208 Takes either a filename or directory name as input. If it's a
209 directory name, we assume you want the file to be called keyval.
210 """
211 if os.path.isdir(path):
212 path = os.path.join(path, 'keyval')
213 keyval = {}
jadmanski58962982009-04-21 19:54:34 +0000214 if os.path.exists(path):
215 for line in open(path):
216 line = re.sub('#.*', '', line).rstrip()
217 if not re.search(r'^[-\.\w]+=', line):
218 raise ValueError('Invalid format line: %s' % line)
219 key, value = line.split('=', 1)
220 if re.search('^\d+$', value):
221 value = int(value)
222 elif re.search('^(\d+\.)?\d+$', value):
223 value = float(value)
224 keyval[key] = value
jadmanski0afbb632008-06-06 21:10:57 +0000225 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000226
227
jadmanskicc549172008-05-21 18:11:51 +0000228def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000229 """
230 Write a key-value pair format file out to a file. This uses append
231 mode to open the file, so existing text will not be overwritten or
232 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000233
jadmanski0afbb632008-06-06 21:10:57 +0000234 If type_tag is None, then the key must be composed of alphanumeric
235 characters (or dashes+underscores). However, if type-tag is not
236 null then the keys must also have "{type_tag}" as a suffix. At
237 the moment the only valid values of type_tag are "attr" and "perf".
238 """
239 if os.path.isdir(path):
240 path = os.path.join(path, 'keyval')
241 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000242
jadmanski0afbb632008-06-06 21:10:57 +0000243 if type_tag is None:
mbligh97227ea2009-03-11 17:09:50 +0000244 key_regex = re.compile(r'^[-\.\w]+$')
jadmanski0afbb632008-06-06 21:10:57 +0000245 else:
246 if type_tag not in ('attr', 'perf'):
247 raise ValueError('Invalid type tag: %s' % type_tag)
248 escaped_tag = re.escape(type_tag)
mbligh97227ea2009-03-11 17:09:50 +0000249 key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
jadmanski0afbb632008-06-06 21:10:57 +0000250 try:
mbligh6955e232009-07-11 00:58:47 +0000251 for key in sorted(dictionary.keys()):
jadmanski0afbb632008-06-06 21:10:57 +0000252 if not key_regex.search(key):
253 raise ValueError('Invalid key: %s' % key)
mbligh6955e232009-07-11 00:58:47 +0000254 keyval.write('%s=%s\n' % (key, dictionary[key]))
jadmanski0afbb632008-06-06 21:10:57 +0000255 finally:
256 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000257
258
259def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000260 """Return true if path looks like a URL"""
261 # for now, just handle http and ftp
262 url_parts = urlparse.urlparse(path)
263 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000264
265
mblighb2896192009-07-11 00:12:37 +0000266def urlopen(url, data=None, timeout=5):
267 """Wrapper to urllib2.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000268
jadmanski0afbb632008-06-06 21:10:57 +0000269 # Save old timeout
270 old_timeout = socket.getdefaulttimeout()
271 socket.setdefaulttimeout(timeout)
272 try:
mblighb2896192009-07-11 00:12:37 +0000273 return urllib2.urlopen(url, data=data)
jadmanski0afbb632008-06-06 21:10:57 +0000274 finally:
275 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000276
277
mblighb2896192009-07-11 00:12:37 +0000278def urlretrieve(url, filename, data=None, timeout=300):
279 """Retrieve a file from given url."""
280 logging.debug('Fetching %s -> %s', url, filename)
281
282 src_file = urlopen(url, data=data, timeout=timeout)
jadmanski0afbb632008-06-06 21:10:57 +0000283 try:
mblighb2896192009-07-11 00:12:37 +0000284 dest_file = open(filename, 'wb')
285 try:
286 shutil.copyfileobj(src_file, dest_file)
287 finally:
288 dest_file.close()
jadmanski0afbb632008-06-06 21:10:57 +0000289 finally:
mblighb2896192009-07-11 00:12:37 +0000290 src_file.close()
jadmanski0afbb632008-06-06 21:10:57 +0000291
mbligh02ff2d52008-06-03 15:00:21 +0000292
lmrfed221f2010-02-04 03:08:30 +0000293def hash(type, input=None):
294 """
295 Returns an hash object of type md5 or sha1. This function is implemented in
296 order to encapsulate hash objects in a way that is compatible with python
297 2.4 and python 2.6 without warnings.
298
299 Note that even though python 2.6 hashlib supports hash types other than
300 md5 and sha1, we are artificially limiting the input values in order to
301 make the function to behave exactly the same among both python
302 implementations.
303
304 @param input: Optional input string that will be used to update the hash.
305 """
306 if type not in ['md5', 'sha1']:
307 raise ValueError("Unsupported hash type: %s" % type)
308
309 try:
310 hash = hashlib.new(type)
311 except NameError:
312 if type == 'md5':
313 hash = md5.new()
314 elif type == 'sha1':
315 hash = sha.new()
316
317 if input:
318 hash.update(input)
319
320 return hash
321
322
mbligh6231cd62008-02-02 19:18:33 +0000323def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000324 """Get a file from src, which can be local or a remote URL"""
mbligh25284cd2009-06-08 16:17:24 +0000325 if src == dest:
jadmanski0afbb632008-06-06 21:10:57 +0000326 return
mbligh25284cd2009-06-08 16:17:24 +0000327
328 if is_url(src):
mblighb2896192009-07-11 00:12:37 +0000329 urlretrieve(src, dest)
jadmanski0afbb632008-06-06 21:10:57 +0000330 else:
331 shutil.copyfile(src, dest)
mbligh25284cd2009-06-08 16:17:24 +0000332
jadmanski0afbb632008-06-06 21:10:57 +0000333 if permissions:
334 os.chmod(dest, permissions)
335 return dest
mbligh6231cd62008-02-02 19:18:33 +0000336
337
338def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000339 """
340 Receives either a path to a local file or a URL.
341 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000342
jadmanski0afbb632008-06-06 21:10:57 +0000343 unmap_url('/usr/src', 'foo.tar', '/tmp')
344 = '/usr/src/foo.tar'
345 unmap_url('/usr/src', 'http://site/file', '/tmp')
346 = '/tmp/file'
347 (after retrieving it)
348 """
349 if is_url(src):
350 url_parts = urlparse.urlparse(src)
351 filename = os.path.basename(url_parts[2])
352 dest = os.path.join(destdir, filename)
353 return get_file(src, dest)
354 else:
355 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000356
357
358def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000359 *args, **dargs):
360 """
361 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000362
jadmanski0afbb632008-06-06 21:10:57 +0000363 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000364
jadmanski0afbb632008-06-06 21:10:57 +0000365 In the preserve_srcdir case, we just check it's up to date,
366 and if not, we rerun install, without removing srcdir
367 """
368 versionfile = os.path.join(srcdir, '.version')
369 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000370
jadmanski0afbb632008-06-06 21:10:57 +0000371 if os.path.exists(versionfile):
372 old_version = pickle.load(open(versionfile))
373 if old_version == new_version:
374 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000375
jadmanski0afbb632008-06-06 21:10:57 +0000376 if install_needed:
377 if not preserve_srcdir and os.path.exists(srcdir):
378 shutil.rmtree(srcdir)
379 install(*args, **dargs)
380 if os.path.exists(srcdir):
381 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000382
383
showardb45a4662009-07-15 14:27:56 +0000384def get_stderr_level(stderr_is_expected):
385 if stderr_is_expected:
386 return DEFAULT_STDOUT_LEVEL
387 return DEFAULT_STDERR_LEVEL
388
389
mbligh63073c92008-03-31 16:49:32 +0000390def run(command, timeout=None, ignore_status=False,
showardb45a4662009-07-15 14:27:56 +0000391 stdout_tee=None, stderr_tee=None, verbose=True, stdin=None,
mblighc823e8d2009-10-02 00:01:35 +0000392 stderr_is_expected=None, args=()):
jadmanski0afbb632008-06-06 21:10:57 +0000393 """
394 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000395
mblighc823e8d2009-10-02 00:01:35 +0000396 @param command: the command line string.
397 @param timeout: time limit in seconds before attempting to kill the
398 running process. The run() function will take a few seconds
399 longer than 'timeout' to complete if it has to kill the process.
400 @param ignore_status: do not raise an exception, no matter what the exit
401 code of the command is.
402 @param stdout_tee: optional file-like object to which stdout data
403 will be written as it is generated (data will still be stored
404 in result.stdout).
405 @param stderr_tee: likewise for stderr.
406 @param verbose: if True, log the command being run.
jadmanski093a0682009-10-13 14:55:43 +0000407 @param stdin: stdin to pass to the executed process (can be a file
408 descriptor, a file object of a real file or a string).
mblighc823e8d2009-10-02 00:01:35 +0000409 @param args: sequence of strings of arguments to be given to the command
410 inside " quotes after they have been escaped for that; each
411 element in the sequence will be given as a separate command
412 argument
mbligh63073c92008-03-31 16:49:32 +0000413
mblighc823e8d2009-10-02 00:01:35 +0000414 @return a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000415
mblighc823e8d2009-10-02 00:01:35 +0000416 @raise CmdError: the exit code of the command execution was not 0
jadmanski0afbb632008-06-06 21:10:57 +0000417 """
mbligh2c7f7d62009-12-19 05:24:04 +0000418 if isinstance(args, basestring):
419 raise TypeError('Got a string for the "args" keyword argument, '
420 'need a sequence.')
421
mblighc823e8d2009-10-02 00:01:35 +0000422 for arg in args:
423 command += ' "%s"' % sh_escape(arg)
showardb45a4662009-07-15 14:27:56 +0000424 if stderr_is_expected is None:
425 stderr_is_expected = ignore_status
mblighc823e8d2009-10-02 00:01:35 +0000426
showard170873e2009-01-07 00:22:26 +0000427 bg_job = join_bg_jobs(
showardb45a4662009-07-15 14:27:56 +0000428 (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
429 stderr_level=get_stderr_level(stderr_is_expected)),),
showard170873e2009-01-07 00:22:26 +0000430 timeout)[0]
mbligh849a0f62008-08-28 20:12:19 +0000431 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000432 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000433 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000434
mbligh849a0f62008-08-28 20:12:19 +0000435 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000436
mbligh45ffc432008-12-09 23:35:17 +0000437
mbligha5630a52008-09-03 22:09:50 +0000438def run_parallel(commands, timeout=None, ignore_status=False,
439 stdout_tee=None, stderr_tee=None):
jadmanski093a0682009-10-13 14:55:43 +0000440 """
441 Behaves the same as run() with the following exceptions:
mbligha5630a52008-09-03 22:09:50 +0000442
443 - commands is a list of commands to run in parallel.
444 - ignore_status toggles whether or not an exception should be raised
445 on any error.
446
jadmanski093a0682009-10-13 14:55:43 +0000447 @return: a list of CmdResult objects
mbligha5630a52008-09-03 22:09:50 +0000448 """
449 bg_jobs = []
450 for command in commands:
showardb45a4662009-07-15 14:27:56 +0000451 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
452 stderr_level=get_stderr_level(ignore_status)))
mbligha5630a52008-09-03 22:09:50 +0000453
454 # Updates objects in bg_jobs list with their process information
455 join_bg_jobs(bg_jobs, timeout)
456
457 for bg_job in bg_jobs:
458 if not ignore_status and bg_job.result.exit_status:
459 raise error.CmdError(command, bg_job.result,
460 "Command returned non-zero exit status")
461
462 return [bg_job.result for bg_job in bg_jobs]
463
464
mbligh849a0f62008-08-28 20:12:19 +0000465@deprecated
mbligh63073c92008-03-31 16:49:32 +0000466def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000467 """Function deprecated. Please use BgJob class instead."""
468 bg_job = BgJob(command)
469 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000470
471
mbligh849a0f62008-08-28 20:12:19 +0000472def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000473 """Joins the bg_jobs with the current thread.
474
475 Returns the same list of bg_jobs objects that was passed in.
476 """
mblighae69f262009-04-17 20:14:56 +0000477 ret, timeout_error = 0, False
mbligh849a0f62008-08-28 20:12:19 +0000478 for bg_job in bg_jobs:
479 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000480
jadmanski0afbb632008-06-06 21:10:57 +0000481 try:
482 # We are holding ends to stdin, stdout pipes
483 # hence we need to be sure to close those fds no mater what
484 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000485 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
486
487 for bg_job in bg_jobs:
488 # Process stdout and stderr
489 bg_job.process_output(stdout=True,final_read=True)
490 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000491 finally:
492 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000493 for bg_job in bg_jobs:
494 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000495
mbligh849a0f62008-08-28 20:12:19 +0000496 if timeout_error:
497 # TODO: This needs to be fixed to better represent what happens when
498 # running in parallel. However this is backwards compatable, so it will
499 # do for the time being.
500 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
501 "Command(s) did not complete within %d seconds"
502 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000503
mbligh63073c92008-03-31 16:49:32 +0000504
mbligh849a0f62008-08-28 20:12:19 +0000505 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000506
mbligh849a0f62008-08-28 20:12:19 +0000507
508def _wait_for_commands(bg_jobs, start_time, timeout):
509 # This returns True if it must return due to a timeout, otherwise False.
510
mblighf0b4a0a2008-09-03 20:46:16 +0000511 # To check for processes which terminate without producing any output
512 # a 1 second timeout is used in select.
513 SELECT_TIMEOUT = 1
514
jadmanski093a0682009-10-13 14:55:43 +0000515 read_list = []
516 write_list = []
mbligh849a0f62008-08-28 20:12:19 +0000517 reverse_dict = {}
jadmanski093a0682009-10-13 14:55:43 +0000518
mbligh849a0f62008-08-28 20:12:19 +0000519 for bg_job in bg_jobs:
jadmanski093a0682009-10-13 14:55:43 +0000520 read_list.append(bg_job.sp.stdout)
521 read_list.append(bg_job.sp.stderr)
522 reverse_dict[bg_job.sp.stdout] = (bg_job, True)
523 reverse_dict[bg_job.sp.stderr] = (bg_job, False)
jamesrenf0f9dae2010-02-26 02:21:14 +0000524 if bg_job.string_stdin is not None:
jadmanski093a0682009-10-13 14:55:43 +0000525 write_list.append(bg_job.sp.stdin)
526 reverse_dict[bg_job.sp.stdin] = bg_job
mbligh849a0f62008-08-28 20:12:19 +0000527
jadmanski0afbb632008-06-06 21:10:57 +0000528 if timeout:
529 stop_time = start_time + timeout
530 time_left = stop_time - time.time()
531 else:
532 time_left = None # so that select never times out
jadmanski093a0682009-10-13 14:55:43 +0000533
jadmanski0afbb632008-06-06 21:10:57 +0000534 while not timeout or time_left > 0:
jadmanski093a0682009-10-13 14:55:43 +0000535 # select will return when we may write to stdin or when there is
536 # stdout/stderr output we can read (including when it is
jadmanski0afbb632008-06-06 21:10:57 +0000537 # EOF, that is the process has terminated).
jadmanski093a0682009-10-13 14:55:43 +0000538 read_ready, write_ready, _ = select.select(read_list, write_list, [],
539 SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000540
jadmanski0afbb632008-06-06 21:10:57 +0000541 # os.read() has to be used instead of
542 # subproc.stdout.read() which will otherwise block
jadmanski093a0682009-10-13 14:55:43 +0000543 for file_obj in read_ready:
544 bg_job, is_stdout = reverse_dict[file_obj]
545 bg_job.process_output(is_stdout)
mbligh63073c92008-03-31 16:49:32 +0000546
jadmanski093a0682009-10-13 14:55:43 +0000547 for file_obj in write_ready:
548 # we can write PIPE_BUF bytes without blocking
549 # POSIX requires PIPE_BUF is >= 512
550 bg_job = reverse_dict[file_obj]
551 file_obj.write(bg_job.string_stdin[:512])
552 bg_job.string_stdin = bg_job.string_stdin[512:]
553 # no more input data, close stdin, remove it from the select set
554 if not bg_job.string_stdin:
555 file_obj.close()
556 write_list.remove(file_obj)
557 del reverse_dict[file_obj]
558
559 all_jobs_finished = True
560 for bg_job in bg_jobs:
561 if bg_job.result.exit_status is not None:
562 continue
563
mbligh849a0f62008-08-28 20:12:19 +0000564 bg_job.result.exit_status = bg_job.sp.poll()
jadmanski093a0682009-10-13 14:55:43 +0000565 if bg_job.result.exit_status is not None:
566 # process exited, remove its stdout/stdin from the select set
jadmanskidf432e62010-06-11 14:32:28 +0000567 bg_job.result.duration = time.time() - start_time
jadmanski093a0682009-10-13 14:55:43 +0000568 read_list.remove(bg_job.sp.stdout)
569 read_list.remove(bg_job.sp.stderr)
570 del reverse_dict[bg_job.sp.stdout]
571 del reverse_dict[bg_job.sp.stderr]
572 else:
573 all_jobs_finished = False
574
575 if all_jobs_finished:
576 return False
mbligh8ea61e22008-05-09 18:09:37 +0000577
jadmanski0afbb632008-06-06 21:10:57 +0000578 if timeout:
579 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000580
mbligh849a0f62008-08-28 20:12:19 +0000581 # Kill all processes which did not complete prior to timeout
jadmanski093a0682009-10-13 14:55:43 +0000582 for bg_job in bg_jobs:
583 if bg_job.result.exit_status is not None:
584 continue
585
586 logging.warn('run process timeout (%s) fired on: %s', timeout,
587 bg_job.command)
mbligh849a0f62008-08-28 20:12:19 +0000588 nuke_subprocess(bg_job.sp)
mbligh095dc642008-10-01 03:41:35 +0000589 bg_job.result.exit_status = bg_job.sp.poll()
jadmanskidf432e62010-06-11 14:32:28 +0000590 bg_job.result.duration = time.time() - start_time
mbligh8ea61e22008-05-09 18:09:37 +0000591
mbligh849a0f62008-08-28 20:12:19 +0000592 return True
mbligh63073c92008-03-31 16:49:32 +0000593
594
showard549afad2009-08-20 23:33:36 +0000595def pid_is_alive(pid):
596 """
597 True if process pid exists and is not yet stuck in Zombie state.
598 Zombies are impossible to move between cgroups, etc.
599 pid can be integer, or text of integer.
600 """
601 path = '/proc/%s/stat' % pid
602
603 try:
604 stat = read_one_line(path)
605 except IOError:
606 if not os.path.exists(path):
607 # file went away
608 return False
609 raise
610
611 return stat.split()[2] != 'Z'
612
613
614def signal_pid(pid, sig):
615 """
616 Sends a signal to a process id. Returns True if the process terminated
617 successfully, False otherwise.
618 """
619 try:
620 os.kill(pid, sig)
621 except OSError:
622 # The process may have died before we could kill it.
623 pass
624
625 for i in range(5):
626 if not pid_is_alive(pid):
627 return True
628 time.sleep(1)
629
630 # The process is still alive
631 return False
632
633
mbligh63073c92008-03-31 16:49:32 +0000634def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000635 # check if the subprocess is still alive, first
636 if subproc.poll() is not None:
637 return subproc.poll()
638
jadmanski0afbb632008-06-06 21:10:57 +0000639 # the process has not terminated within timeout,
640 # kill it via an escalating series of signals.
641 signal_queue = [signal.SIGTERM, signal.SIGKILL]
642 for sig in signal_queue:
showard549afad2009-08-20 23:33:36 +0000643 signal_pid(subproc.pid, sig)
644 if subproc.poll() is not None:
645 return subproc.poll()
mbligh63073c92008-03-31 16:49:32 +0000646
647
showard786da9a2009-10-12 20:31:20 +0000648def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
jadmanski0afbb632008-06-06 21:10:57 +0000649 # the process has not terminated within timeout,
650 # kill it via an escalating series of signals.
jadmanski0afbb632008-06-06 21:10:57 +0000651 for sig in signal_queue:
showard549afad2009-08-20 23:33:36 +0000652 if signal_pid(pid, sig):
653 return
mbligh63073c92008-03-31 16:49:32 +0000654
showard549afad2009-08-20 23:33:36 +0000655 # no signal successfully terminated the process
656 raise error.AutoservRunError('Could not kill %d' % pid, None)
mbligh298ed592009-06-15 21:52:57 +0000657
658
mbligh63073c92008-03-31 16:49:32 +0000659def system(command, timeout=None, ignore_status=False):
mbligh104a5382010-02-02 18:15:39 +0000660 """
661 Run a command
662
663 @param timeout: timeout in seconds
664 @param ignore_status: if ignore_status=False, throw an exception if the
665 command's exit code is non-zero
666 if ignore_stauts=True, return the exit code.
lmrfed221f2010-02-04 03:08:30 +0000667
mbligh104a5382010-02-02 18:15:39 +0000668 @return exit status of command
669 (note, this will always be zero unless ignore_status=True)
670 """
mblighf8dffb12008-10-29 16:45:26 +0000671 return run(command, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000672 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
mbligh63073c92008-03-31 16:49:32 +0000673
674
mbligha5630a52008-09-03 22:09:50 +0000675def system_parallel(commands, timeout=None, ignore_status=False):
676 """This function returns a list of exit statuses for the respective
677 list of commands."""
678 return [bg_jobs.exit_status for bg_jobs in
mblighf8dffb12008-10-29 16:45:26 +0000679 run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000680 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
mbligh849a0f62008-08-28 20:12:19 +0000681
682
mbligh8ea61e22008-05-09 18:09:37 +0000683def system_output(command, timeout=None, ignore_status=False,
mblighc823e8d2009-10-02 00:01:35 +0000684 retain_output=False, args=()):
685 """
686 Run a command and return the stdout output.
687
688 @param command: command string to execute.
689 @param timeout: time limit in seconds before attempting to kill the
690 running process. The function will take a few seconds longer
691 than 'timeout' to complete if it has to kill the process.
692 @param ignore_status: do not raise an exception, no matter what the exit
693 code of the command is.
694 @param retain_output: set to True to make stdout/stderr of the command
695 output to be also sent to the logging system
696 @param args: sequence of strings of arguments to be given to the command
697 inside " quotes after they have been escaped for that; each
698 element in the sequence will be given as a separate command
699 argument
700
701 @return a string with the stdout output of the command.
702 """
jadmanski0afbb632008-06-06 21:10:57 +0000703 if retain_output:
mblighf8dffb12008-10-29 16:45:26 +0000704 out = run(command, timeout=timeout, ignore_status=ignore_status,
mblighc823e8d2009-10-02 00:01:35 +0000705 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
706 args=args).stdout
jadmanski0afbb632008-06-06 21:10:57 +0000707 else:
mblighc823e8d2009-10-02 00:01:35 +0000708 out = run(command, timeout=timeout, ignore_status=ignore_status,
709 args=args).stdout
710 if out[-1:] == '\n':
711 out = out[:-1]
jadmanski0afbb632008-06-06 21:10:57 +0000712 return out
mbligh63073c92008-03-31 16:49:32 +0000713
mbligh849a0f62008-08-28 20:12:19 +0000714
mbligha5630a52008-09-03 22:09:50 +0000715def system_output_parallel(commands, timeout=None, ignore_status=False,
716 retain_output=False):
717 if retain_output:
showard108d73e2009-06-22 18:14:41 +0000718 out = [bg_job.stdout for bg_job
719 in run_parallel(commands, timeout=timeout,
720 ignore_status=ignore_status,
721 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
mbligha5630a52008-09-03 22:09:50 +0000722 else:
mblighf8dffb12008-10-29 16:45:26 +0000723 out = [bg_job.stdout for bg_job in run_parallel(commands,
724 timeout=timeout, ignore_status=ignore_status)]
mbligha5630a52008-09-03 22:09:50 +0000725 for x in out:
726 if out[-1:] == '\n': out = out[:-1]
727 return out
728
729
mbligh98467952008-11-19 00:25:45 +0000730def strip_unicode(input):
731 if type(input) == list:
732 return [strip_unicode(i) for i in input]
733 elif type(input) == dict:
734 output = {}
735 for key in input.keys():
736 output[str(key)] = strip_unicode(input[key])
737 return output
738 elif type(input) == unicode:
739 return str(input)
740 else:
741 return input
742
743
mbligha5630a52008-09-03 22:09:50 +0000744def get_cpu_percentage(function, *args, **dargs):
745 """Returns a tuple containing the CPU% and return value from function call.
746
747 This function calculates the usage time by taking the difference of
748 the user and system times both before and after the function call.
749 """
750 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
751 self_pre = resource.getrusage(resource.RUSAGE_SELF)
752 start = time.time()
753 to_return = function(*args, **dargs)
754 elapsed = time.time() - start
755 self_post = resource.getrusage(resource.RUSAGE_SELF)
756 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
757
758 # Calculate CPU Percentage
759 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
760 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
761 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
762
763 return cpu_percent, to_return
764
765
mblighc5ddfd12008-08-04 17:15:00 +0000766def get_arch(run_function=run):
767 """
768 Get the hardware architecture of the machine.
769 run_function is used to execute the commands. It defaults to
770 utils.run() but a custom method (if provided) should be of the
771 same schema as utils.run. It should return a CmdResult object and
772 throw a CmdError exception.
773 """
774 arch = run_function('/bin/uname -m').stdout.rstrip()
775 if re.match(r'i\d86$', arch):
776 arch = 'i386'
777 return arch
778
779
showard4745ecd2009-05-26 19:34:56 +0000780def get_num_logical_cpus_per_socket(run_function=run):
mbligh9fd9afe2009-04-28 18:27:25 +0000781 """
782 Get the number of cores (including hyperthreading) per cpu.
783 run_function is used to execute the commands. It defaults to
784 utils.run() but a custom method (if provided) should be of the
785 same schema as utils.run. It should return a CmdResult object and
786 throw a CmdError exception.
787 """
showard4745ecd2009-05-26 19:34:56 +0000788 siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
789 num_siblings = map(int,
790 re.findall(r'^siblings\s*:\s*(\d+)\s*$',
791 siblings, re.M))
792 if len(num_siblings) == 0:
793 raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
794 if min(num_siblings) != max(num_siblings):
795 raise error.TestError('Number of siblings differ %r' %
796 num_siblings)
797 return num_siblings[0]
mbligh9fd9afe2009-04-28 18:27:25 +0000798
799
jadmanski4f909252008-12-01 20:47:10 +0000800def merge_trees(src, dest):
801 """
802 Merges a source directory tree at 'src' into a destination tree at
803 'dest'. If a path is a file in both trees than the file in the source
804 tree is APPENDED to the one in the destination tree. If a path is
805 a directory in both trees then the directories are recursively merged
806 with this function. In any other case, the function will skip the
807 paths that cannot be merged (instead of failing).
808 """
809 if not os.path.exists(src):
810 return # exists only in dest
811 elif not os.path.exists(dest):
812 if os.path.isfile(src):
813 shutil.copy2(src, dest) # file only in src
814 else:
815 shutil.copytree(src, dest, symlinks=True) # dir only in src
816 return
817 elif os.path.isfile(src) and os.path.isfile(dest):
818 # src & dest are files in both trees, append src to dest
819 destfile = open(dest, "a")
820 try:
821 srcfile = open(src)
822 try:
823 destfile.write(srcfile.read())
824 finally:
825 srcfile.close()
826 finally:
827 destfile.close()
828 elif os.path.isdir(src) and os.path.isdir(dest):
829 # src & dest are directories in both trees, so recursively merge
830 for name in os.listdir(src):
831 merge_trees(os.path.join(src, name), os.path.join(dest, name))
832 else:
833 # src & dest both exist, but are incompatible
834 return
835
836
mbligh63073c92008-03-31 16:49:32 +0000837class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000838 """
839 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000840
jadmanski0afbb632008-06-06 21:10:57 +0000841 command: String containing the command line itself
842 exit_status: Integer exit code of the process
843 stdout: String containing stdout of the process
844 stderr: String containing stderr of the process
845 duration: Elapsed wall clock time running the process
846 """
mbligh63073c92008-03-31 16:49:32 +0000847
848
mblighcd63a212009-05-01 23:04:38 +0000849 def __init__(self, command="", stdout="", stderr="",
jadmanski0afbb632008-06-06 21:10:57 +0000850 exit_status=None, duration=0):
851 self.command = command
852 self.exit_status = exit_status
853 self.stdout = stdout
854 self.stderr = stderr
855 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000856
857
jadmanski0afbb632008-06-06 21:10:57 +0000858 def __repr__(self):
859 wrapper = textwrap.TextWrapper(width = 78,
860 initial_indent="\n ",
861 subsequent_indent=" ")
862
863 stdout = self.stdout.rstrip()
864 if stdout:
865 stdout = "\nstdout:\n%s" % stdout
866
867 stderr = self.stderr.rstrip()
868 if stderr:
869 stderr = "\nstderr:\n%s" % stderr
870
871 return ("* Command: %s\n"
872 "Exit status: %s\n"
873 "Duration: %s\n"
874 "%s"
875 "%s"
876 % (wrapper.fill(self.command), self.exit_status,
877 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000878
879
mbligh462c0152008-03-13 15:37:10 +0000880class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000881 def __init__(self, run_sequentially=False):
882 # Run sequentially is for debugging control files
883 self.test_list = []
884 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000885
886
jadmanski0afbb632008-06-06 21:10:57 +0000887 def add(self, *args, **dargs):
888 test = (args, dargs)
889 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000890
891
jadmanski0afbb632008-06-06 21:10:57 +0000892 def run(self, fn):
893 while self.test_list:
894 test_index = random.randint(0, len(self.test_list)-1)
895 if self.run_sequentially:
896 test_index = 0
897 (args, dargs) = self.test_list.pop(test_index)
898 fn(*args, **dargs)
mbligha7007722009-01-13 00:37:11 +0000899
900
showard5deef7f2009-09-09 18:16:58 +0000901def import_site_module(path, module, dummy=None, modulefile=None):
902 """
903 Try to import the site specific module if it exists.
904
905 @param path full filename of the source file calling this (ie __file__)
906 @param module full module name
907 @param dummy dummy value to return in case there is no symbol to import
908 @param modulefile module filename
909
910 @return site specific module or dummy
911
912 @raises ImportError if the site file exists but imports fails
913 """
914 short_module = module[module.rfind(".") + 1:]
915
916 if not modulefile:
917 modulefile = short_module + ".py"
918
919 if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
920 return __import__(module, {}, {}, [short_module])
921 return dummy
922
923
mblighdd669372009-02-03 21:57:18 +0000924def import_site_symbol(path, module, name, dummy=None, modulefile=None):
925 """
926 Try to import site specific symbol from site specific file if it exists
927
928 @param path full filename of the source file calling this (ie __file__)
929 @param module full module name
930 @param name symbol name to be imported from the site file
931 @param dummy dummy value to return in case there is no symbol to import
932 @param modulefile module filename
933
934 @return site specific symbol or dummy
935
showard5deef7f2009-09-09 18:16:58 +0000936 @raises ImportError if the site file exists but imports fails
mblighdd669372009-02-03 21:57:18 +0000937 """
showard5deef7f2009-09-09 18:16:58 +0000938 module = import_site_module(path, module, modulefile=modulefile)
939 if not module:
940 return dummy
mbligha7007722009-01-13 00:37:11 +0000941
showard5deef7f2009-09-09 18:16:58 +0000942 # special unique value to tell us if the symbol can't be imported
943 cant_import = object()
mbligha7007722009-01-13 00:37:11 +0000944
showard5deef7f2009-09-09 18:16:58 +0000945 obj = getattr(module, name, cant_import)
946 if obj is cant_import:
mblighf989e582010-04-01 17:10:35 +0000947 logging.debug("unable to import site symbol '%s', using non-site "
showard5deef7f2009-09-09 18:16:58 +0000948 "implementation", name)
949 return dummy
mbligh062ed152009-01-13 00:57:14 +0000950
951 return obj
952
953
954def import_site_class(path, module, classname, baseclass, modulefile=None):
955 """
956 Try to import site specific class from site specific file if it exists
957
958 Args:
959 path: full filename of the source file calling this (ie __file__)
960 module: full module name
961 classname: class name to be loaded from site file
mbligh0a8c3322009-04-28 18:32:19 +0000962 baseclass: base class object to return when no site file present or
963 to mixin when site class exists but is not inherited from baseclass
mbligh062ed152009-01-13 00:57:14 +0000964 modulefile: module filename
965
mbligh0a8c3322009-04-28 18:32:19 +0000966 Returns: baseclass if site specific class does not exist, the site specific
967 class if it exists and is inherited from baseclass or a mixin of the
968 site specific class and baseclass when the site specific class exists
969 and is not inherited from baseclass
mbligh062ed152009-01-13 00:57:14 +0000970
971 Raises: ImportError if the site file exists but imports fails
972 """
973
mblighdd669372009-02-03 21:57:18 +0000974 res = import_site_symbol(path, module, classname, None, modulefile)
mbligh0a8c3322009-04-28 18:32:19 +0000975 if res:
976 if not issubclass(res, baseclass):
977 # if not a subclass of baseclass then mix in baseclass with the
978 # site specific class object and return the result
979 res = type(classname, (res, baseclass), {})
980 else:
981 res = baseclass
mbligha7007722009-01-13 00:37:11 +0000982
mbligh062ed152009-01-13 00:57:14 +0000983 return res
984
985
986def import_site_function(path, module, funcname, dummy, modulefile=None):
987 """
988 Try to import site specific function from site specific file if it exists
989
990 Args:
991 path: full filename of the source file calling this (ie __file__)
992 module: full module name
993 funcname: function name to be imported from site file
994 dummy: dummy function to return in case there is no function to import
995 modulefile: module filename
996
997 Returns: site specific function object or dummy
998
999 Raises: ImportError if the site file exists but imports fails
1000 """
1001
mblighdd669372009-02-03 21:57:18 +00001002 return import_site_symbol(path, module, funcname, dummy, modulefile)
mblighfb676032009-04-01 18:25:38 +00001003
1004
showard549afad2009-08-20 23:33:36 +00001005def _get_pid_path(program_name):
1006 my_path = os.path.dirname(__file__)
1007 return os.path.abspath(os.path.join(my_path, "..", "..",
1008 "%s.pid" % program_name))
1009
1010
mblighfb676032009-04-01 18:25:38 +00001011def write_pid(program_name):
1012 """
1013 Try to drop <program_name>.pid in the main autotest directory.
1014
1015 Args:
1016 program_name: prefix for file name
1017 """
showard549afad2009-08-20 23:33:36 +00001018 pidfile = open(_get_pid_path(program_name), "w")
1019 try:
1020 pidfile.write("%s\n" % os.getpid())
1021 finally:
1022 pidfile.close()
mblighfb676032009-04-01 18:25:38 +00001023
showard549afad2009-08-20 23:33:36 +00001024
1025def delete_pid_file_if_exists(program_name):
1026 """
1027 Tries to remove <program_name>.pid from the main autotest directory.
1028 """
1029 pidfile_path = _get_pid_path(program_name)
1030
1031 try:
1032 os.remove(pidfile_path)
1033 except OSError:
1034 if not os.path.exists(pidfile_path):
1035 return
1036 raise
1037
1038
1039def get_pid_from_file(program_name):
1040 """
1041 Reads the pid from <program_name>.pid in the autotest directory.
1042
1043 @param program_name the name of the program
1044 @return the pid if the file exists, None otherwise.
1045 """
1046 pidfile_path = _get_pid_path(program_name)
1047 if not os.path.exists(pidfile_path):
1048 return None
1049
1050 pidfile = open(_get_pid_path(program_name), 'r')
1051
1052 try:
1053 try:
1054 pid = int(pidfile.readline())
1055 except IOError:
1056 if not os.path.exists(pidfile_path):
1057 return None
1058 raise
1059 finally:
1060 pidfile.close()
1061
1062 return pid
1063
1064
showard8de37132009-08-31 18:33:08 +00001065def program_is_alive(program_name):
showard549afad2009-08-20 23:33:36 +00001066 """
1067 Checks if the process is alive and not in Zombie state.
1068
1069 @param program_name the name of the program
1070 @return True if still alive, False otherwise
1071 """
1072 pid = get_pid_from_file(program_name)
1073 if pid is None:
1074 return False
1075 return pid_is_alive(pid)
1076
1077
showard8de37132009-08-31 18:33:08 +00001078def signal_program(program_name, sig=signal.SIGTERM):
showard549afad2009-08-20 23:33:36 +00001079 """
1080 Sends a signal to the process listed in <program_name>.pid
1081
1082 @param program_name the name of the program
1083 @param sig signal to send
1084 """
1085 pid = get_pid_from_file(program_name)
1086 if pid:
1087 signal_pid(pid, sig)
mbligh45561782009-05-11 21:14:34 +00001088
1089
1090def get_relative_path(path, reference):
1091 """Given 2 absolute paths "path" and "reference", compute the path of
1092 "path" as relative to the directory "reference".
1093
1094 @param path the absolute path to convert to a relative path
1095 @param reference an absolute directory path to which the relative
1096 path will be computed
1097 """
1098 # normalize the paths (remove double slashes, etc)
1099 assert(os.path.isabs(path))
1100 assert(os.path.isabs(reference))
1101
1102 path = os.path.normpath(path)
1103 reference = os.path.normpath(reference)
1104
1105 # we could use os.path.split() but it splits from the end
1106 path_list = path.split(os.path.sep)[1:]
1107 ref_list = reference.split(os.path.sep)[1:]
1108
1109 # find the longest leading common path
1110 for i in xrange(min(len(path_list), len(ref_list))):
1111 if path_list[i] != ref_list[i]:
1112 # decrement i so when exiting this loop either by no match or by
1113 # end of range we are one step behind
1114 i -= 1
1115 break
1116 i += 1
1117 # drop the common part of the paths, not interested in that anymore
1118 del path_list[:i]
1119
1120 # for each uncommon component in the reference prepend a ".."
1121 path_list[:0] = ['..'] * (len(ref_list) - i)
1122
1123 return os.path.join(*path_list)
mbligh277a0e42009-07-11 00:11:45 +00001124
1125
1126def sh_escape(command):
1127 """
1128 Escape special characters from a command so that it can be passed
1129 as a double quoted (" ") string in a (ba)sh command.
1130
1131 Args:
1132 command: the command string to escape.
1133
1134 Returns:
1135 The escaped command string. The required englobing double
1136 quotes are NOT added and so should be added at some point by
1137 the caller.
1138
1139 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
1140 """
1141 command = command.replace("\\", "\\\\")
1142 command = command.replace("$", r'\$')
1143 command = command.replace('"', r'\"')
1144 command = command.replace('`', r'\`')
1145 return command
mblighab88fbb2010-04-08 18:31:13 +00001146
1147
1148def configure(extra=None, configure='./configure'):
1149 """
1150 Run configure passing in the correct host, build, and target options.
1151
1152 @param extra: extra command line arguments to pass to configure
1153 @param configure: which configure script to use
1154 """
1155 args = []
1156 if 'CHOST' in os.environ:
1157 args.append('--host=' + os.environ['CHOST'])
1158 if 'CBUILD' in os.environ:
1159 args.append('--build=' + os.environ['CBUILD'])
1160 if 'CTARGET' in os.environ:
1161 args.append('--target=' + os.environ['CTARGET'])
1162 if extra:
1163 args.append(extra)
1164
1165 system('%s %s' % (configure, ' '.join(args)))
1166
mbligh777db852010-04-22 21:59:57 +00001167
1168def compare_versions(ver1, ver2):
mbligh58609322010-05-03 17:58:20 +00001169 """Version number comparison between ver1 and ver2 strings.
mbligh777db852010-04-22 21:59:57 +00001170
mbligh58609322010-05-03 17:58:20 +00001171 >>> compare_tuple("1", "2")
1172 -1
1173 >>> compare_tuple("foo-1.1", "foo-1.2")
1174 -1
1175 >>> compare_tuple("1.2", "1.2a")
1176 -1
1177 >>> compare_tuple("1.2b", "1.2a")
1178 1
1179 >>> compare_tuple("1.3.5.3a", "1.3.5.3b")
1180 -1
mbligh777db852010-04-22 21:59:57 +00001181
1182 Args:
mbligh58609322010-05-03 17:58:20 +00001183 ver1: version string
1184 ver2: version string
mbligh777db852010-04-22 21:59:57 +00001185
1186 Returns:
1187 int: 1 if ver1 > ver2
1188 0 if ver1 == ver2
1189 -1 if ver1 < ver2
1190 """
mbligh58609322010-05-03 17:58:20 +00001191 ax = re.split('[.-]', ver1)
1192 ay = re.split('[.-]', ver2)
1193 while len(ax) > 0 and len(ay) > 0:
1194 cx = ax.pop(0)
1195 cy = ay.pop(0)
1196 maxlen = max(len(cx), len(cy))
1197 c = cmp(cx.zfill(maxlen), cy.zfill(maxlen))
1198 if c != 0:
1199 return c
1200 return cmp(len(ax), len(ay))
jadmanski4ceb7c22010-06-11 14:32:04 +00001201
1202
1203def args_to_dict(args):
1204 """Convert autoserv extra arguments in the form of key=val or key:val to a
1205 dictionary. Each argument key is converted to lowercase dictionary key.
1206
1207 Args:
1208 args - list of autoserv extra arguments.
1209
1210 Returns:
1211 dictionary
1212 """
1213 arg_re = re.compile(r'(\w+)[:=](.*)$')
1214 dict = {}
1215 for arg in args:
1216 match = arg_re.match(arg)
1217 if match:
1218 dict[match.group(1).lower()] = match.group(2)
1219 else:
1220 logging.warning("args_to_dict: argument '%s' doesn't match "
1221 "'%s' pattern. Ignored." % (arg, arg_re.pattern))
1222 return dict