base_utils.py revision cd69dc0d26c44dd7d8b61c972cc962e2cb786ef2
1#
2# Copyright 2008 Google Inc. Released under the GPL v2
3
4# pylint: disable-msg=C0111
5
6import os, pickle, random, re, resource, select, shutil, signal, StringIO
7import socket, struct, subprocess, time, textwrap, urlparse
8import warnings, smtplib, logging, urllib2
9import itertools
10from threading import Thread, Event
11try:
12    import hashlib
13except ImportError:
14    import md5, sha
15from autotest_lib.client.common_lib import error, logging_manager
16
17def deprecated(func):
18    """This is a decorator which can be used to mark functions as deprecated.
19    It will result in a warning being emmitted when the function is used."""
20    def new_func(*args, **dargs):
21        warnings.warn("Call to deprecated function %s." % func.__name__,
22                      category=DeprecationWarning)
23        return func(*args, **dargs)
24    new_func.__name__ = func.__name__
25    new_func.__doc__ = func.__doc__
26    new_func.__dict__.update(func.__dict__)
27    return new_func
28
29
30class _NullStream(object):
31    def write(self, data):
32        pass
33
34
35    def flush(self):
36        pass
37
38
39TEE_TO_LOGS = object()
40_the_null_stream = _NullStream()
41
42DEFAULT_STDOUT_LEVEL = logging.DEBUG
43DEFAULT_STDERR_LEVEL = logging.ERROR
44
45# prefixes for logging stdout/stderr of commands
46STDOUT_PREFIX = '[stdout] '
47STDERR_PREFIX = '[stderr] '
48
49def custom_warning_handler(message, category, filename, lineno, file=None,
50                           line=None):
51    """Custom handler to log at the WARNING error level. Ignores |file|."""
52    logging.warning(warnings.formatwarning(message, category, filename, lineno,
53                                           line))
54
55warnings.showwarning = custom_warning_handler
56
57def get_stream_tee_file(stream, level, prefix=''):
58    if stream is None:
59        return _the_null_stream
60    if stream is TEE_TO_LOGS:
61        return logging_manager.LoggingFile(level=level, prefix=prefix)
62    return stream
63
64
65def _join_with_nickname(base_string, nickname):
66    if nickname:
67        return '%s BgJob "%s" ' % (base_string, nickname)
68    return base_string
69
70
71# TODO: Cleanup and possibly eliminate no_pipes, which is only used
72# in our master-ssh connection process, while fixing underlying
73# semantics problem in BgJob. See crbug.com/279312
74class BgJob(object):
75    def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
76                 stdin=None, stderr_level=DEFAULT_STDERR_LEVEL, nickname=None,
77                 no_pipes=False):
78        """Create and start a new BgJob.
79
80        This constructor creates a new BgJob, and uses Popen to start a new
81        subprocess with given command. It returns without blocking on execution
82        of the subprocess.
83
84        After starting a new BgJob, use output_prepare to connect the process's
85        stdout and stderr pipes to the stream of your choice.
86
87        When the job is running, the jobs's output streams are only read from
88        when process_output is called.
89
90        @param command: command to be executed in new subprocess. May be either
91                        a list, or a string (in which case Popen will be called
92                        with shell=True)
93        @param stdout_tee: Optional additional stream that the process's stdout
94                           stream output will be written to. Or, specify
95                           base_utils.TEE_TO_LOGS and the output will handled by
96                           the standard logging_manager.
97        @param stderr_tee: Same as stdout_tee, but for stderr.
98        @param verbose: Boolean, make BgJob logging more verbose.
99        @param stdin: Stream object, will be passed to Popen as the new
100                      process's stdin.
101        @param stderr_level: A logging level value. If stderr_tee was set to
102                             base_utils.TEE_TO_LOGS, sets the level that tee'd
103                             stderr output will be logged at. Ignored
104                             otherwise.
105        @param nickname: Optional string, to be included in logging messages
106        @param no_pipes: Boolean, default False. If True, this subprocess
107                         created by this BgJob does NOT use subprocess.PIPE
108                         for its stdin or stderr streams. Instead, these
109                         streams are connected to the logging manager
110                         (regardless of the values of stdout_tee and
111                         stderr_tee).
112                         If no_pipes is True, then calls to output_prepare,
113                         process_output, and cleanup will result in an
114                         InvalidBgJobCall exception. no_pipes should be
115                         True for BgJobs that do not interact via stdout/stderr
116                         with other BgJobs, or long runing background jobs that
117                         will never be joined with join_bg_jobs, such as the
118                         master-ssh connection BgJob.
119        """
120        self.command = command
121        self._no_pipes = no_pipes
122        if no_pipes:
123            stdout_tee = TEE_TO_LOGS
124            stderr_tee = TEE_TO_LOGS
125        self.stdout_tee = get_stream_tee_file(stdout_tee, DEFAULT_STDOUT_LEVEL,
126                prefix=_join_with_nickname(STDOUT_PREFIX, nickname))
127        self.stderr_tee = get_stream_tee_file(stderr_tee, stderr_level,
128                prefix=_join_with_nickname(STDERR_PREFIX, nickname))
129        self.result = CmdResult(command)
130
131        # allow for easy stdin input by string, we'll let subprocess create
132        # a pipe for stdin input and we'll write to it in the wait loop
133        if isinstance(stdin, basestring):
134            self.string_stdin = stdin
135            stdin = subprocess.PIPE
136        else:
137            self.string_stdin = None
138
139
140        if no_pipes:
141            stdout_param = self.stdout_tee
142            stderr_param = self.stderr_tee
143        else:
144            stdout_param = subprocess.PIPE
145            stderr_param = subprocess.PIPE
146
147        if verbose:
148            logging.debug("Running '%s'", command)
149        if type(command) == list:
150            self.sp = subprocess.Popen(command,
151                                       stdout=stdout_param,
152                                       stderr=stderr_param,
153                                       preexec_fn=self._reset_sigpipe,
154                                       stdin=stdin)
155        else:
156            self.sp = subprocess.Popen(command, stdout=stdout_param,
157                                       stderr=stderr_param,
158                                       preexec_fn=self._reset_sigpipe, shell=True,
159                                       executable="/bin/bash",
160                                       stdin=stdin)
161
162        self._output_prepare_called = False
163        self._process_output_warned = False
164        self._cleanup_called = False
165        self.stdout_file = _the_null_stream
166        self.stderr_file = _the_null_stream
167
168    def output_prepare(self, stdout_file=_the_null_stream,
169                       stderr_file=_the_null_stream):
170        """Connect the subprocess's stdout and stderr to streams.
171
172        Subsequent calls to output_prepare are permitted, and will reassign
173        the streams. However, this will have the side effect that the ultimate
174        call to cleanup() will only remember the stdout and stderr data up to
175        the last output_prepare call when saving this data to BgJob.result.
176
177        @param stdout_file: Stream that output from the process's stdout pipe
178                            will be written to. Default: a null stream.
179        @param stderr_file: Stream that output from the process's stdout pipe
180                            will be written to. Default: a null stream.
181        """
182        if self._no_pipes:
183            raise error.InvalidBgJobCall('Cannot call output_prepare on a '
184                                         'job with no_pipes=True.')
185        if self._output_prepare_called:
186            logging.warn('BgJob [%s] received a duplicate call to '
187                         'output prepare. Allowing, but this may result '
188                         'in data missing from BgJob.result.')
189        self.stdout_file = stdout_file
190        self.stderr_file = stderr_file
191        self._output_prepare_called = True
192
193
194    def process_output(self, stdout=True, final_read=False):
195        """Read from process's output stream, and write data to destinations.
196
197        This function reads up to 1024 bytes from the background job's
198        stdout or stderr stream, and writes the resulting data to the BgJob's
199        output tee and to the stream set up in output_prepare.
200
201        Warning: Calls to process_output will block on reads from the
202        subprocess stream, and will block on writes to the configured
203        destination stream.
204
205        @param stdout: True = read and process data from job's stdout.
206                       False = from stderr.
207                       Default: True
208        @param final_read: Do not read only 1024 bytes from stream. Instead,
209                           read and process all data until end of the stream.
210
211        """
212        if self._no_pipes:
213            raise error.InvalidBgJobCall('Cannot call process_output on '
214                                         'a job with no_pipes=True')
215        if not self._output_prepare_called and not self._process_output_warned:
216            logging.warn('BgJob with command [%s] handled a process_output '
217                         'call before output_prepare was called. Some output '
218                         'data discarded. Future warnings suppressed.',
219                         self.command)
220            self._process_output_warned = True
221        if stdout:
222            pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
223        else:
224            pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
225
226        if final_read:
227            # read in all the data we can from pipe and then stop
228            data = []
229            while select.select([pipe], [], [], 0)[0]:
230                data.append(os.read(pipe.fileno(), 1024))
231                if len(data[-1]) == 0:
232                    break
233            data = "".join(data)
234        else:
235            # perform a single read
236            data = os.read(pipe.fileno(), 1024)
237        buf.write(data)
238        tee.write(data)
239
240
241    def cleanup(self):
242        """Clean up after BgJob.
243
244        Flush the stdout_tee and stderr_tee buffers, close the
245        subprocess stdout and stderr buffers, and saves data from
246        the configured stdout and stderr destination streams to
247        self.result. Duplicate calls ignored with a warning.
248        """
249        if self._no_pipes:
250            raise error.InvalidBgJobCall('Cannot call cleanup on '
251                                         'a job with no_pipes=True')
252        if self._cleanup_called:
253            logging.warn('BgJob [%s] received a duplicate call to '
254                         'cleanup. Ignoring.', self.command)
255            return
256        try:
257            self.stdout_tee.flush()
258            self.stderr_tee.flush()
259            self.sp.stdout.close()
260            self.sp.stderr.close()
261            self.result.stdout = self.stdout_file.getvalue()
262            self.result.stderr = self.stderr_file.getvalue()
263        finally:
264            self._cleanup_called = True
265
266
267    def _reset_sigpipe(self):
268        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
269
270
271def ip_to_long(ip):
272    # !L is a long in network byte order
273    return struct.unpack('!L', socket.inet_aton(ip))[0]
274
275
276def long_to_ip(number):
277    # See above comment.
278    return socket.inet_ntoa(struct.pack('!L', number))
279
280
281def create_subnet_mask(bits):
282    return (1 << 32) - (1 << 32-bits)
283
284
285def format_ip_with_mask(ip, mask_bits):
286    masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
287    return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
288
289
290def normalize_hostname(alias):
291    ip = socket.gethostbyname(alias)
292    return socket.gethostbyaddr(ip)[0]
293
294
295def get_ip_local_port_range():
296    match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
297                     read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
298    return (int(match.group(1)), int(match.group(2)))
299
300
301def set_ip_local_port_range(lower, upper):
302    write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
303                   '%d %d\n' % (lower, upper))
304
305
306def send_email(mail_from, mail_to, subject, body):
307    """
308    Sends an email via smtp
309
310    mail_from: string with email address of sender
311    mail_to: string or list with email address(es) of recipients
312    subject: string with subject of email
313    body: (multi-line) string with body of email
314    """
315    if isinstance(mail_to, str):
316        mail_to = [mail_to]
317    msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to),
318                                                   subject, body)
319    try:
320        mailer = smtplib.SMTP('localhost')
321        try:
322            mailer.sendmail(mail_from, mail_to, msg)
323        finally:
324            mailer.quit()
325    except Exception, e:
326        # Emails are non-critical, not errors, but don't raise them
327        print "Sending email failed. Reason: %s" % repr(e)
328
329
330def read_one_line(filename):
331    return open(filename, 'r').readline().rstrip('\n')
332
333
334def read_file(filename):
335    f = open(filename)
336    try:
337        return f.read()
338    finally:
339        f.close()
340
341
342def get_field(data, param, linestart="", sep=" "):
343    """
344    Parse data from string.
345    @param data: Data to parse.
346        example:
347          data:
348             cpu   324 345 34  5 345
349             cpu0  34  11  34 34  33
350             ^^^^
351             start of line
352             params 0   1   2  3   4
353    @param param: Position of parameter after linestart marker.
354    @param linestart: String to which start line with parameters.
355    @param sep: Separator between parameters regular expression.
356    """
357    search = re.compile(r"(?<=^%s)\s*(.*)" % linestart, re.MULTILINE)
358    find = search.search(data)
359    if find != None:
360        return re.split("%s" % sep, find.group(1))[param]
361    else:
362        print "There is no line which starts with %s in data." % linestart
363        return None
364
365
366def print_dirinfo(basedir):
367    """
368    Print the contents of basedir if it exists, or it's first ancestor.
369
370    Print the contents of the first directory that exists in the parent
371    hierarchy of basedir, and all it's children. Also include the number
372    of files they contain, so we know which ones are empty. Stop if the
373    ancestor walk reaches '/'. Note the path passed in must be absolute,
374    since otherwise we could hang on a bogus path.
375
376    @param basedir: The base directory to start from.
377    """
378    if not os.path.isabs(basedir):
379        logging.info('%s is not absolute, not attempting to walk.', basedir)
380        return
381
382    def path_visitor(_, dir, files):
383        logging.info('%s: %s files', dir, len(files))
384
385    while not os.path.exists(basedir):
386        logging.info('%s doesnt exist, moving to parent dir.', basedir)
387        basedir = os.path.abspath(os.path.join(basedir, os.pardir))
388        if basedir == '/':
389            logging.info('Could not find any parent dir.')
390            return
391
392    logging.info('printing contents of %s.', basedir)
393    os.path.walk(basedir, path_visitor, None)
394
395
396def write_one_line(filename, line):
397    open_write_close(filename, str(line).rstrip('\n') + '\n')
398
399
400def open_write_close(filename, data):
401    f = open(filename, 'w')
402    try:
403        f.write(data)
404    finally:
405        f.close()
406
407
408def locate_file(path, base_dir=None):
409  """Locates a file.
410
411  @param path: The path of the file being located. Could be absolute or relative
412      path. For relative path, it tries to locate the file from base_dir.
413  @param base_dir (optional): Base directory of the relative path.
414
415  @returns Absolute path of the file if found. None if path is None.
416  @raises error.TestFail if the file is not found.
417  """
418  if path is None:
419    return None
420
421  if not os.path.isabs(path) and base_dir is not None:
422    # Assume the relative path is based in autotest directory.
423    path = os.path.join(base_dir, path)
424  if not os.path.isfile(path):
425    raise error.TestFail('ERROR: Unable to find %s' % path)
426  return path
427
428
429def matrix_to_string(matrix, header=None):
430    """
431    Return a pretty, aligned string representation of a nxm matrix.
432
433    This representation can be used to print any tabular data, such as
434    database results. It works by scanning the lengths of each element
435    in each column, and determining the format string dynamically.
436
437    @param matrix: Matrix representation (list with n rows of m elements).
438    @param header: Optional tuple or list with header elements to be displayed.
439    """
440    if type(header) is list:
441        header = tuple(header)
442    lengths = []
443    if header:
444        for column in header:
445            lengths.append(len(column))
446    for row in matrix:
447        for i, column in enumerate(row):
448            column = unicode(column).encode("utf-8")
449            cl = len(column)
450            try:
451                ml = lengths[i]
452                if cl > ml:
453                    lengths[i] = cl
454            except IndexError:
455                lengths.append(cl)
456
457    lengths = tuple(lengths)
458    format_string = ""
459    for length in lengths:
460        format_string += "%-" + str(length) + "s "
461    format_string += "\n"
462
463    matrix_str = ""
464    if header:
465        matrix_str += format_string % header
466    for row in matrix:
467        matrix_str += format_string % tuple(row)
468
469    return matrix_str
470
471
472def read_keyval(path):
473    """
474    Read a key-value pair format file into a dictionary, and return it.
475    Takes either a filename or directory name as input. If it's a
476    directory name, we assume you want the file to be called keyval.
477    """
478    if os.path.isdir(path):
479        path = os.path.join(path, 'keyval')
480    keyval = {}
481    if os.path.exists(path):
482        for line in open(path):
483            line = re.sub('#.*', '', line).rstrip()
484            if not re.search(r'^[-\.\w]+=', line):
485                raise ValueError('Invalid format line: %s' % line)
486            key, value = line.split('=', 1)
487            if re.search('^\d+$', value):
488                value = int(value)
489            elif re.search('^(\d+\.)?\d+$', value):
490                value = float(value)
491            keyval[key] = value
492    return keyval
493
494
495def write_keyval(path, dictionary, type_tag=None, tap_report=None):
496    """
497    Write a key-value pair format file out to a file. This uses append
498    mode to open the file, so existing text will not be overwritten or
499    reparsed.
500
501    If type_tag is None, then the key must be composed of alphanumeric
502    characters (or dashes+underscores). However, if type-tag is not
503    null then the keys must also have "{type_tag}" as a suffix. At
504    the moment the only valid values of type_tag are "attr" and "perf".
505
506    @param path: full path of the file to be written
507    @param dictionary: the items to write
508    @param type_tag: see text above
509    """
510    if os.path.isdir(path):
511        path = os.path.join(path, 'keyval')
512    keyval = open(path, 'a')
513
514    if type_tag is None:
515        key_regex = re.compile(r'^[-\.\w]+$')
516    else:
517        if type_tag not in ('attr', 'perf'):
518            raise ValueError('Invalid type tag: %s' % type_tag)
519        escaped_tag = re.escape(type_tag)
520        key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
521    try:
522        for key in sorted(dictionary.keys()):
523            if not key_regex.search(key):
524                raise ValueError('Invalid key: %s' % key)
525            keyval.write('%s=%s\n' % (key, dictionary[key]))
526    finally:
527        keyval.close()
528
529    # same for tap
530    if tap_report is not None and tap_report.do_tap_report:
531        tap_report.record_keyval(path, dictionary, type_tag=type_tag)
532
533class FileFieldMonitor(object):
534    """
535    Monitors the information from the file and reports it's values.
536
537    It gather the information at start and stop of the measurement or
538    continuously during the measurement.
539    """
540    class Monitor(Thread):
541        """
542        Internal monitor class to ensure continuous monitor of monitored file.
543        """
544        def __init__(self, master):
545            """
546            @param master: Master class which control Monitor
547            """
548            Thread.__init__(self)
549            self.master = master
550
551        def run(self):
552            """
553            Start monitor in thread mode
554            """
555            while not self.master.end_event.isSet():
556                self.master._get_value(self.master.logging)
557                time.sleep(self.master.time_step)
558
559
560    def __init__(self, status_file, data_to_read, mode_diff, continuously=False,
561                 contlogging=False, separator=" +", time_step=0.1):
562        """
563        Initialize variables.
564        @param status_file: File contain status.
565        @param mode_diff: If True make a difference of value, else average.
566        @param data_to_read: List of tuples with data position.
567            format: [(start_of_line,position in params)]
568            example:
569              data:
570                 cpu   324 345 34  5 345
571                 cpu0  34  11  34 34  33
572                 ^^^^
573                 start of line
574                 params 0   1   2  3   4
575        @param mode_diff: True to subtract old value from new value,
576            False make average of the values.
577        @parma continuously: Start the monitoring thread using the time_step
578            as the measurement period.
579        @param contlogging: Log data in continuous run.
580        @param separator: Regular expression of separator.
581        @param time_step: Time period of the monitoring value.
582        """
583        self.end_event = Event()
584        self.start_time = 0
585        self.end_time = 0
586        self.test_time = 0
587
588        self.status_file = status_file
589        self.separator = separator
590        self.data_to_read = data_to_read
591        self.num_of_params = len(self.data_to_read)
592        self.mode_diff = mode_diff
593        self.continuously = continuously
594        self.time_step = time_step
595
596        self.value = [0 for i in range(self.num_of_params)]
597        self.old_value = [0 for i in range(self.num_of_params)]
598        self.log = []
599        self.logging = contlogging
600
601        self.started = False
602        self.num_of_get_value = 0
603        self.monitor = None
604
605
606    def _get_value(self, logging=True):
607        """
608        Return current values.
609        @param logging: If true log value in memory. There can be problem
610          with long run.
611        """
612        data = read_file(self.status_file)
613        value = []
614        for i in range(self.num_of_params):
615            value.append(int(get_field(data,
616                             self.data_to_read[i][1],
617                             self.data_to_read[i][0],
618                             self.separator)))
619
620        if logging:
621            self.log.append(value)
622        if not self.mode_diff:
623            value = map(lambda x, y: x + y, value, self.old_value)
624
625        self.old_value = value
626        self.num_of_get_value += 1
627        return value
628
629
630    def start(self):
631        """
632        Start value monitor.
633        """
634        if self.started:
635            self.stop()
636        self.old_value = [0 for i in range(self.num_of_params)]
637        self.num_of_get_value = 0
638        self.log = []
639        self.end_event.clear()
640        self.start_time = time.time()
641        self._get_value()
642        self.started = True
643        if (self.continuously):
644            self.monitor = FileFieldMonitor.Monitor(self)
645            self.monitor.start()
646
647
648    def stop(self):
649        """
650        Stop value monitor.
651        """
652        if self.started:
653            self.started = False
654            self.end_time = time.time()
655            self.test_time = self.end_time - self.start_time
656            self.value = self._get_value()
657            if (self.continuously):
658                self.end_event.set()
659                self.monitor.join()
660            if (self.mode_diff):
661                self.value = map(lambda x, y: x - y, self.log[-1], self.log[0])
662            else:
663                self.value = map(lambda x: x / self.num_of_get_value,
664                                 self.value)
665
666
667    def get_status(self):
668        """
669        @return: Status of monitored process average value,
670            time of test and array of monitored values and time step of
671            continuous run.
672        """
673        if self.started:
674            self.stop()
675        if self.mode_diff:
676            for i in range(len(self.log) - 1):
677                self.log[i] = (map(lambda x, y: x - y,
678                                   self.log[i + 1], self.log[i]))
679            self.log.pop()
680        return (self.value, self.test_time, self.log, self.time_step)
681
682
683def is_url(path):
684    """Return true if path looks like a URL"""
685    # for now, just handle http and ftp
686    url_parts = urlparse.urlparse(path)
687    return (url_parts[0] in ('http', 'ftp'))
688
689
690def urlopen(url, data=None, timeout=5):
691    """Wrapper to urllib2.urlopen with timeout addition."""
692
693    # Save old timeout
694    old_timeout = socket.getdefaulttimeout()
695    socket.setdefaulttimeout(timeout)
696    try:
697        return urllib2.urlopen(url, data=data)
698    finally:
699        socket.setdefaulttimeout(old_timeout)
700
701
702def urlretrieve(url, filename, data=None, timeout=300):
703    """Retrieve a file from given url."""
704    logging.debug('Fetching %s -> %s', url, filename)
705
706    src_file = urlopen(url, data=data, timeout=timeout)
707    try:
708        dest_file = open(filename, 'wb')
709        try:
710            shutil.copyfileobj(src_file, dest_file)
711        finally:
712            dest_file.close()
713    finally:
714        src_file.close()
715
716
717def hash(type, input=None):
718    """
719    Returns an hash object of type md5 or sha1. This function is implemented in
720    order to encapsulate hash objects in a way that is compatible with python
721    2.4 and python 2.6 without warnings.
722
723    Note that even though python 2.6 hashlib supports hash types other than
724    md5 and sha1, we are artificially limiting the input values in order to
725    make the function to behave exactly the same among both python
726    implementations.
727
728    @param input: Optional input string that will be used to update the hash.
729    """
730    if type not in ['md5', 'sha1']:
731        raise ValueError("Unsupported hash type: %s" % type)
732
733    try:
734        hash = hashlib.new(type)
735    except NameError:
736        if type == 'md5':
737            hash = md5.new()
738        elif type == 'sha1':
739            hash = sha.new()
740
741    if input:
742        hash.update(input)
743
744    return hash
745
746
747def get_file(src, dest, permissions=None):
748    """Get a file from src, which can be local or a remote URL"""
749    if src == dest:
750        return
751
752    if is_url(src):
753        urlretrieve(src, dest)
754    else:
755        shutil.copyfile(src, dest)
756
757    if permissions:
758        os.chmod(dest, permissions)
759    return dest
760
761
762def unmap_url(srcdir, src, destdir='.'):
763    """
764    Receives either a path to a local file or a URL.
765    returns either the path to the local file, or the fetched URL
766
767    unmap_url('/usr/src', 'foo.tar', '/tmp')
768                            = '/usr/src/foo.tar'
769    unmap_url('/usr/src', 'http://site/file', '/tmp')
770                            = '/tmp/file'
771                            (after retrieving it)
772    """
773    if is_url(src):
774        url_parts = urlparse.urlparse(src)
775        filename = os.path.basename(url_parts[2])
776        dest = os.path.join(destdir, filename)
777        return get_file(src, dest)
778    else:
779        return os.path.join(srcdir, src)
780
781
782def update_version(srcdir, preserve_srcdir, new_version, install,
783                   *args, **dargs):
784    """
785    Make sure srcdir is version new_version
786
787    If not, delete it and install() the new version.
788
789    In the preserve_srcdir case, we just check it's up to date,
790    and if not, we rerun install, without removing srcdir
791    """
792    versionfile = os.path.join(srcdir, '.version')
793    install_needed = True
794
795    if os.path.exists(versionfile):
796        old_version = pickle.load(open(versionfile))
797        if old_version == new_version:
798            install_needed = False
799
800    if install_needed:
801        if not preserve_srcdir and os.path.exists(srcdir):
802            shutil.rmtree(srcdir)
803        install(*args, **dargs)
804        if os.path.exists(srcdir):
805            pickle.dump(new_version, open(versionfile, 'w'))
806
807
808def get_stderr_level(stderr_is_expected):
809    if stderr_is_expected:
810        return DEFAULT_STDOUT_LEVEL
811    return DEFAULT_STDERR_LEVEL
812
813
814def run(command, timeout=None, ignore_status=False,
815        stdout_tee=None, stderr_tee=None, verbose=True, stdin=None,
816        stderr_is_expected=None, args=(), nickname=None):
817    """
818    Run a command on the host.
819
820    @param command: the command line string.
821    @param timeout: time limit in seconds before attempting to kill the
822            running process. The run() function will take a few seconds
823            longer than 'timeout' to complete if it has to kill the process.
824    @param ignore_status: do not raise an exception, no matter what the exit
825            code of the command is.
826    @param stdout_tee: optional file-like object to which stdout data
827            will be written as it is generated (data will still be stored
828            in result.stdout).
829    @param stderr_tee: likewise for stderr.
830    @param verbose: if True, log the command being run.
831    @param stdin: stdin to pass to the executed process (can be a file
832            descriptor, a file object of a real file or a string).
833    @param args: sequence of strings of arguments to be given to the command
834            inside " quotes after they have been escaped for that; each
835            element in the sequence will be given as a separate command
836            argument
837    @param nickname: Short string that will appear in logging messages
838                     associated with this command.
839
840    @return a CmdResult object
841
842    @raise CmdError: the exit code of the command execution was not 0
843    """
844    if isinstance(args, basestring):
845        raise TypeError('Got a string for the "args" keyword argument, '
846                        'need a sequence.')
847
848    for arg in args:
849        command += ' "%s"' % sh_escape(arg)
850    if stderr_is_expected is None:
851        stderr_is_expected = ignore_status
852
853    bg_job = join_bg_jobs(
854        (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
855               stderr_level=get_stderr_level(stderr_is_expected),
856               nickname=nickname),), timeout)[0]
857    if not ignore_status and bg_job.result.exit_status:
858        raise error.CmdError(command, bg_job.result,
859                             "Command returned non-zero exit status")
860
861    return bg_job.result
862
863
864def run_parallel(commands, timeout=None, ignore_status=False,
865                 stdout_tee=None, stderr_tee=None,
866                 nicknames=[]):
867    """
868    Behaves the same as run() with the following exceptions:
869
870    - commands is a list of commands to run in parallel.
871    - ignore_status toggles whether or not an exception should be raised
872      on any error.
873
874    @return: a list of CmdResult objects
875    """
876    bg_jobs = []
877    for (command, nickname) in itertools.izip_longest(commands, nicknames):
878        bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
879                             stderr_level=get_stderr_level(ignore_status),
880                             nickname=nickname))
881
882    # Updates objects in bg_jobs list with their process information
883    join_bg_jobs(bg_jobs, timeout)
884
885    for bg_job in bg_jobs:
886        if not ignore_status and bg_job.result.exit_status:
887            raise error.CmdError(command, bg_job.result,
888                                 "Command returned non-zero exit status")
889
890    return [bg_job.result for bg_job in bg_jobs]
891
892
893@deprecated
894def run_bg(command):
895    """Function deprecated. Please use BgJob class instead."""
896    bg_job = BgJob(command)
897    return bg_job.sp, bg_job.result
898
899
900def join_bg_jobs(bg_jobs, timeout=None):
901    """Joins the bg_jobs with the current thread.
902
903    Returns the same list of bg_jobs objects that was passed in.
904    """
905    ret, timeout_error = 0, False
906    for bg_job in bg_jobs:
907        bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
908
909    try:
910        # We are holding ends to stdin, stdout pipes
911        # hence we need to be sure to close those fds no mater what
912        start_time = time.time()
913        timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
914
915        for bg_job in bg_jobs:
916            # Process stdout and stderr
917            bg_job.process_output(stdout=True,final_read=True)
918            bg_job.process_output(stdout=False,final_read=True)
919    finally:
920        # close our ends of the pipes to the sp no matter what
921        for bg_job in bg_jobs:
922            bg_job.cleanup()
923
924    if timeout_error:
925        # TODO: This needs to be fixed to better represent what happens when
926        # running in parallel. However this is backwards compatable, so it will
927        # do for the time being.
928        raise error.CmdTimeoutError(
929                bg_jobs[0].command, bg_jobs[0].result,
930                "Command(s) did not complete within %d seconds" % timeout)
931
932
933    return bg_jobs
934
935
936def _wait_for_commands(bg_jobs, start_time, timeout):
937    # This returns True if it must return due to a timeout, otherwise False.
938
939    # To check for processes which terminate without producing any output
940    # a 1 second timeout is used in select.
941    SELECT_TIMEOUT = 1
942
943    read_list = []
944    write_list = []
945    reverse_dict = {}
946
947    for bg_job in bg_jobs:
948        read_list.append(bg_job.sp.stdout)
949        read_list.append(bg_job.sp.stderr)
950        reverse_dict[bg_job.sp.stdout] = (bg_job, True)
951        reverse_dict[bg_job.sp.stderr] = (bg_job, False)
952        if bg_job.string_stdin is not None:
953            write_list.append(bg_job.sp.stdin)
954            reverse_dict[bg_job.sp.stdin] = bg_job
955
956    if timeout:
957        stop_time = start_time + timeout
958        time_left = stop_time - time.time()
959    else:
960        time_left = None # so that select never times out
961
962    while not timeout or time_left > 0:
963        # select will return when we may write to stdin or when there is
964        # stdout/stderr output we can read (including when it is
965        # EOF, that is the process has terminated).
966        read_ready, write_ready, _ = select.select(read_list, write_list, [],
967                                                   SELECT_TIMEOUT)
968
969        # os.read() has to be used instead of
970        # subproc.stdout.read() which will otherwise block
971        for file_obj in read_ready:
972            bg_job, is_stdout = reverse_dict[file_obj]
973            bg_job.process_output(is_stdout)
974
975        for file_obj in write_ready:
976            # we can write PIPE_BUF bytes without blocking
977            # POSIX requires PIPE_BUF is >= 512
978            bg_job = reverse_dict[file_obj]
979            file_obj.write(bg_job.string_stdin[:512])
980            bg_job.string_stdin = bg_job.string_stdin[512:]
981            # no more input data, close stdin, remove it from the select set
982            if not bg_job.string_stdin:
983                file_obj.close()
984                write_list.remove(file_obj)
985                del reverse_dict[file_obj]
986
987        all_jobs_finished = True
988        for bg_job in bg_jobs:
989            if bg_job.result.exit_status is not None:
990                continue
991
992            bg_job.result.exit_status = bg_job.sp.poll()
993            if bg_job.result.exit_status is not None:
994                # process exited, remove its stdout/stdin from the select set
995                bg_job.result.duration = time.time() - start_time
996                read_list.remove(bg_job.sp.stdout)
997                read_list.remove(bg_job.sp.stderr)
998                del reverse_dict[bg_job.sp.stdout]
999                del reverse_dict[bg_job.sp.stderr]
1000            else:
1001                all_jobs_finished = False
1002
1003        if all_jobs_finished:
1004            return False
1005
1006        if timeout:
1007            time_left = stop_time - time.time()
1008
1009    # Kill all processes which did not complete prior to timeout
1010    for bg_job in bg_jobs:
1011        if bg_job.result.exit_status is not None:
1012            continue
1013
1014        logging.warn('run process timeout (%s) fired on: %s', timeout,
1015                     bg_job.command)
1016        if nuke_subprocess(bg_job.sp) is None:
1017            # If process could not be SIGKILL'd, log kernel stack.
1018            logging.warn(utils.read_file('/proc/' + bg_job.sp.pid + '/stack'))
1019        bg_job.result.exit_status = bg_job.sp.poll()
1020        bg_job.result.duration = time.time() - start_time
1021
1022    return True
1023
1024
1025def pid_is_alive(pid):
1026    """
1027    True if process pid exists and is not yet stuck in Zombie state.
1028    Zombies are impossible to move between cgroups, etc.
1029    pid can be integer, or text of integer.
1030    """
1031    path = '/proc/%s/stat' % pid
1032
1033    try:
1034        stat = read_one_line(path)
1035    except IOError:
1036        if not os.path.exists(path):
1037            # file went away
1038            return False
1039        raise
1040
1041    return stat.split()[2] != 'Z'
1042
1043
1044def signal_pid(pid, sig):
1045    """
1046    Sends a signal to a process id. Returns True if the process terminated
1047    successfully, False otherwise.
1048    """
1049    try:
1050        os.kill(pid, sig)
1051    except OSError:
1052        # The process may have died before we could kill it.
1053        pass
1054
1055    for i in range(5):
1056        if not pid_is_alive(pid):
1057            return True
1058        time.sleep(1)
1059
1060    # The process is still alive
1061    return False
1062
1063
1064def nuke_subprocess(subproc):
1065    # check if the subprocess is still alive, first
1066    if subproc.poll() is not None:
1067        return subproc.poll()
1068
1069    # the process has not terminated within timeout,
1070    # kill it via an escalating series of signals.
1071    signal_queue = [signal.SIGTERM, signal.SIGKILL]
1072    for sig in signal_queue:
1073        signal_pid(subproc.pid, sig)
1074        if subproc.poll() is not None:
1075            return subproc.poll()
1076
1077
1078def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
1079    # the process has not terminated within timeout,
1080    # kill it via an escalating series of signals.
1081    pid_path = '/proc/%d/'
1082    if not os.path.exists(pid_path % pid):
1083        # Assume that if the pid does not exist in proc it is already dead.
1084        logging.error('No listing in /proc for pid:%d.', pid)
1085        raise error.AutoservPidAlreadyDeadError('Could not kill nonexistant '
1086                                                'pid: %s.', pid)
1087    for sig in signal_queue:
1088        if signal_pid(pid, sig):
1089            return
1090
1091    # no signal successfully terminated the process
1092    raise error.AutoservRunError('Could not kill %d for process name: %s' % (
1093            pid, get_process_name(pid)), None)
1094
1095
1096def system(command, timeout=None, ignore_status=False):
1097    """
1098    Run a command
1099
1100    @param timeout: timeout in seconds
1101    @param ignore_status: if ignore_status=False, throw an exception if the
1102            command's exit code is non-zero
1103            if ignore_stauts=True, return the exit code.
1104
1105    @return exit status of command
1106            (note, this will always be zero unless ignore_status=True)
1107    """
1108    return run(command, timeout=timeout, ignore_status=ignore_status,
1109               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
1110
1111
1112def system_parallel(commands, timeout=None, ignore_status=False):
1113    """This function returns a list of exit statuses for the respective
1114    list of commands."""
1115    return [bg_jobs.exit_status for bg_jobs in
1116            run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
1117                         stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
1118
1119
1120def system_output(command, timeout=None, ignore_status=False,
1121                  retain_output=False, args=()):
1122    """
1123    Run a command and return the stdout output.
1124
1125    @param command: command string to execute.
1126    @param timeout: time limit in seconds before attempting to kill the
1127            running process. The function will take a few seconds longer
1128            than 'timeout' to complete if it has to kill the process.
1129    @param ignore_status: do not raise an exception, no matter what the exit
1130            code of the command is.
1131    @param retain_output: set to True to make stdout/stderr of the command
1132            output to be also sent to the logging system
1133    @param args: sequence of strings of arguments to be given to the command
1134            inside " quotes after they have been escaped for that; each
1135            element in the sequence will be given as a separate command
1136            argument
1137
1138    @return a string with the stdout output of the command.
1139    """
1140    if retain_output:
1141        out = run(command, timeout=timeout, ignore_status=ignore_status,
1142                  stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
1143                  args=args).stdout
1144    else:
1145        out = run(command, timeout=timeout, ignore_status=ignore_status,
1146                  args=args).stdout
1147    if out[-1:] == '\n':
1148        out = out[:-1]
1149    return out
1150
1151
1152def system_output_parallel(commands, timeout=None, ignore_status=False,
1153                           retain_output=False):
1154    if retain_output:
1155        out = [bg_job.stdout for bg_job
1156               in run_parallel(commands, timeout=timeout,
1157                               ignore_status=ignore_status,
1158                               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
1159    else:
1160        out = [bg_job.stdout for bg_job in run_parallel(commands,
1161                                  timeout=timeout, ignore_status=ignore_status)]
1162    for x in out:
1163        if out[-1:] == '\n': out = out[:-1]
1164    return out
1165
1166
1167def strip_unicode(input):
1168    if type(input) == list:
1169        return [strip_unicode(i) for i in input]
1170    elif type(input) == dict:
1171        output = {}
1172        for key in input.keys():
1173            output[str(key)] = strip_unicode(input[key])
1174        return output
1175    elif type(input) == unicode:
1176        return str(input)
1177    else:
1178        return input
1179
1180
1181def get_cpu_percentage(function, *args, **dargs):
1182    """Returns a tuple containing the CPU% and return value from function call.
1183
1184    This function calculates the usage time by taking the difference of
1185    the user and system times both before and after the function call.
1186    """
1187    child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
1188    self_pre = resource.getrusage(resource.RUSAGE_SELF)
1189    start = time.time()
1190    to_return = function(*args, **dargs)
1191    elapsed = time.time() - start
1192    self_post = resource.getrusage(resource.RUSAGE_SELF)
1193    child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
1194
1195    # Calculate CPU Percentage
1196    s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
1197    c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
1198    cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
1199
1200    return cpu_percent, to_return
1201
1202
1203class SystemLoad(object):
1204    """
1205    Get system and/or process values and return average value of load.
1206    """
1207    def __init__(self, pids, advanced=False, time_step=0.1, cpu_cont=False,
1208                 use_log=False):
1209        """
1210        @param pids: List of pids to be monitored. If pid = 0 whole system will
1211          be monitored. pid == 0 means whole system.
1212        @param advanced: monitor add value for system irq count and softirq
1213          for process minor and maior page fault
1214        @param time_step: Time step for continuous monitoring.
1215        @param cpu_cont: If True monitor CPU load continuously.
1216        @param use_log: If true every monitoring is logged for dump.
1217        """
1218        self.pids = []
1219        self.stats = {}
1220        for pid in pids:
1221            if pid == 0:
1222                cpu = FileFieldMonitor("/proc/stat",
1223                                       [("cpu", 0), # User Time
1224                                        ("cpu", 2), # System Time
1225                                        ("intr", 0), # IRQ Count
1226                                        ("softirq", 0)], # Soft IRQ Count
1227                                       True,
1228                                       cpu_cont,
1229                                       use_log,
1230                                       " +",
1231                                       time_step)
1232                mem = FileFieldMonitor("/proc/meminfo",
1233                                       [("MemTotal:", 0), # Mem Total
1234                                        ("MemFree:", 0), # Mem Free
1235                                        ("Buffers:", 0), # Buffers
1236                                        ("Cached:", 0)], # Cached
1237                                       False,
1238                                       True,
1239                                       use_log,
1240                                       " +",
1241                                       time_step)
1242                self.stats[pid] = ["TOTAL", cpu, mem]
1243                self.pids.append(pid)
1244            else:
1245                name = ""
1246                if (type(pid) is int):
1247                    self.pids.append(pid)
1248                    name = get_process_name(pid)
1249                else:
1250                    self.pids.append(pid[0])
1251                    name = pid[1]
1252
1253                cpu = FileFieldMonitor("/proc/%d/stat" %
1254                                       self.pids[-1],
1255                                       [("", 13), # User Time
1256                                        ("", 14), # System Time
1257                                        ("", 9), # Minority Page Fault
1258                                        ("", 11)], # Majority Page Fault
1259                                       True,
1260                                       cpu_cont,
1261                                       use_log,
1262                                       " +",
1263                                       time_step)
1264                mem = FileFieldMonitor("/proc/%d/status" %
1265                                       self.pids[-1],
1266                                       [("VmSize:", 0), # Virtual Memory Size
1267                                        ("VmRSS:", 0), # Resident Set Size
1268                                        ("VmPeak:", 0), # Peak VM Size
1269                                        ("VmSwap:", 0)], # VM in Swap
1270                                       False,
1271                                       True,
1272                                       use_log,
1273                                       " +",
1274                                       time_step)
1275                self.stats[self.pids[-1]] = [name, cpu, mem]
1276
1277        self.advanced = advanced
1278
1279
1280    def __str__(self):
1281        """
1282        Define format how to print
1283        """
1284        out = ""
1285        for pid in self.pids:
1286            for stat in self.stats[pid][1:]:
1287                out += str(stat.get_status()) + "\n"
1288        return out
1289
1290
1291    def start(self, pids=[]):
1292        """
1293        Start monitoring of the process system usage.
1294        @param pids: List of PIDs you intend to control. Use pids=[] to control
1295            all defined PIDs.
1296        """
1297        if pids == []:
1298            pids = self.pids
1299
1300        for pid in pids:
1301            for stat in self.stats[pid][1:]:
1302                stat.start()
1303
1304
1305    def stop(self, pids=[]):
1306        """
1307        Stop monitoring of the process system usage.
1308        @param pids: List of PIDs you intend to control. Use pids=[] to control
1309            all defined PIDs.
1310        """
1311        if pids == []:
1312            pids = self.pids
1313
1314        for pid in pids:
1315            for stat in self.stats[pid][1:]:
1316                stat.stop()
1317
1318
1319    def dump(self, pids=[]):
1320        """
1321        Get the status of monitoring.
1322        @param pids: List of PIDs you intend to control. Use pids=[] to control
1323            all defined PIDs.
1324         @return:
1325            tuple([cpu load], [memory load]):
1326                ([(PID1, (PID1_cpu_meas)), (PID2, (PID2_cpu_meas)), ...],
1327                 [(PID1, (PID1_mem_meas)), (PID2, (PID2_mem_meas)), ...])
1328
1329            PID1_cpu_meas:
1330                average_values[], test_time, cont_meas_values[[]], time_step
1331            PID1_mem_meas:
1332                average_values[], test_time, cont_meas_values[[]], time_step
1333            where average_values[] are the measured values (mem_free,swap,...)
1334            which are described in SystemLoad.__init__()-FileFieldMonitor.
1335            cont_meas_values[[]] is a list of average_values in the sampling
1336            times.
1337        """
1338        if pids == []:
1339            pids = self.pids
1340
1341        cpus = []
1342        memory = []
1343        for pid in pids:
1344            stat = (pid, self.stats[pid][1].get_status())
1345            cpus.append(stat)
1346        for pid in pids:
1347            stat = (pid, self.stats[pid][2].get_status())
1348            memory.append(stat)
1349
1350        return (cpus, memory)
1351
1352
1353    def get_cpu_status_string(self, pids=[]):
1354        """
1355        Convert status to string array.
1356        @param pids: List of PIDs you intend to control. Use pids=[] to control
1357            all defined PIDs.
1358        @return: String format to table.
1359        """
1360        if pids == []:
1361            pids = self.pids
1362
1363        headers = ["NAME",
1364                   ("%7s") % "PID",
1365                   ("%5s") % "USER",
1366                   ("%5s") % "SYS",
1367                   ("%5s") % "SUM"]
1368        if self.advanced:
1369            headers.extend(["MINFLT/IRQC",
1370                            "MAJFLT/SOFTIRQ"])
1371        headers.append(("%11s") % "TIME")
1372        textstatus = []
1373        for pid in pids:
1374            stat = self.stats[pid][1].get_status()
1375            time = stat[1]
1376            stat = stat[0]
1377            textstatus.append(["%s" % self.stats[pid][0],
1378                               "%7s" % pid,
1379                               "%4.0f%%" % (stat[0] / time),
1380                               "%4.0f%%" % (stat[1] / time),
1381                               "%4.0f%%" % ((stat[0] + stat[1]) / time),
1382                               "%10.3fs" % time])
1383            if self.advanced:
1384                textstatus[-1].insert(-1, "%11d" % stat[2])
1385                textstatus[-1].insert(-1, "%14d" % stat[3])
1386
1387        return matrix_to_string(textstatus, tuple(headers))
1388
1389
1390    def get_mem_status_string(self, pids=[]):
1391        """
1392        Convert status to string array.
1393        @param pids: List of PIDs you intend to control. Use pids=[] to control
1394            all defined PIDs.
1395        @return: String format to table.
1396        """
1397        if pids == []:
1398            pids = self.pids
1399
1400        headers = ["NAME",
1401                   ("%7s") % "PID",
1402                   ("%8s") % "TOTAL/VMSIZE",
1403                   ("%8s") % "FREE/VMRSS",
1404                   ("%8s") % "BUFFERS/VMPEAK",
1405                   ("%8s") % "CACHED/VMSWAP",
1406                   ("%11s") % "TIME"]
1407        textstatus = []
1408        for pid in pids:
1409            stat = self.stats[pid][2].get_status()
1410            time = stat[1]
1411            stat = stat[0]
1412            textstatus.append(["%s" % self.stats[pid][0],
1413                               "%7s" % pid,
1414                               "%10dMB" % (stat[0] / 1024),
1415                               "%8dMB" % (stat[1] / 1024),
1416                               "%12dMB" % (stat[2] / 1024),
1417                               "%11dMB" % (stat[3] / 1024),
1418                               "%10.3fs" % time])
1419
1420        return matrix_to_string(textstatus, tuple(headers))
1421
1422
1423def get_arch(run_function=run):
1424    """
1425    Get the hardware architecture of the machine.
1426    """
1427    return re.sub(r'i\d86$', 'i386', os.uname()[4])
1428
1429def get_arch_userspace(run_function=run):
1430    """
1431    Get the architecture by userspace (possibly different from kernel).
1432    """
1433    archs = {
1434        'arm': 'ELF 32-bit.*, ARM,',
1435        'i386': 'ELF 32-bit.*, Intel 80386,',
1436        'x86_64': 'ELF 64-bit.*, x86-64,',
1437    }
1438    filestr = run_function('file -b /bin/true').stdout.rstrip()
1439    for a, regex in archs.iteritems():
1440        if re.match(regex, filestr):
1441            return a
1442
1443    return get_arch()
1444
1445def get_board():
1446    """
1447    Get the board name from /etc/lsb-release.
1448    """
1449    return re.search('BOARD=(.*)', read_file('/etc/lsb-release')).group(1)
1450
1451
1452def get_num_logical_cpus_per_socket(run_function=run):
1453    """
1454    Get the number of cores (including hyperthreading) per cpu.
1455    run_function is used to execute the commands. It defaults to
1456    utils.run() but a custom method (if provided) should be of the
1457    same schema as utils.run. It should return a CmdResult object and
1458    throw a CmdError exception.
1459    """
1460    siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
1461    num_siblings = map(int,
1462                       re.findall(r'^siblings\s*:\s*(\d+)\s*$',
1463                                  siblings, re.M))
1464    if len(num_siblings) == 0:
1465        raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
1466    if min(num_siblings) != max(num_siblings):
1467        raise error.TestError('Number of siblings differ %r' %
1468                              num_siblings)
1469    return num_siblings[0]
1470
1471
1472def merge_trees(src, dest):
1473    """
1474    Merges a source directory tree at 'src' into a destination tree at
1475    'dest'. If a path is a file in both trees than the file in the source
1476    tree is APPENDED to the one in the destination tree. If a path is
1477    a directory in both trees then the directories are recursively merged
1478    with this function. In any other case, the function will skip the
1479    paths that cannot be merged (instead of failing).
1480    """
1481    if not os.path.exists(src):
1482        return # exists only in dest
1483    elif not os.path.exists(dest):
1484        if os.path.isfile(src):
1485            shutil.copy2(src, dest) # file only in src
1486        else:
1487            shutil.copytree(src, dest, symlinks=True) # dir only in src
1488        return
1489    elif os.path.isfile(src) and os.path.isfile(dest):
1490        # src & dest are files in both trees, append src to dest
1491        destfile = open(dest, "a")
1492        try:
1493            srcfile = open(src)
1494            try:
1495                destfile.write(srcfile.read())
1496            finally:
1497                srcfile.close()
1498        finally:
1499            destfile.close()
1500    elif os.path.isdir(src) and os.path.isdir(dest):
1501        # src & dest are directories in both trees, so recursively merge
1502        for name in os.listdir(src):
1503            merge_trees(os.path.join(src, name), os.path.join(dest, name))
1504    else:
1505        # src & dest both exist, but are incompatible
1506        return
1507
1508
1509class CmdResult(object):
1510    """
1511    Command execution result.
1512
1513    command:     String containing the command line itself
1514    exit_status: Integer exit code of the process
1515    stdout:      String containing stdout of the process
1516    stderr:      String containing stderr of the process
1517    duration:    Elapsed wall clock time running the process
1518    """
1519
1520
1521    def __init__(self, command="", stdout="", stderr="",
1522                 exit_status=None, duration=0):
1523        self.command = command
1524        self.exit_status = exit_status
1525        self.stdout = stdout
1526        self.stderr = stderr
1527        self.duration = duration
1528
1529
1530    def __repr__(self):
1531        wrapper = textwrap.TextWrapper(width = 78,
1532                                       initial_indent="\n    ",
1533                                       subsequent_indent="    ")
1534
1535        stdout = self.stdout.rstrip()
1536        if stdout:
1537            stdout = "\nstdout:\n%s" % stdout
1538
1539        stderr = self.stderr.rstrip()
1540        if stderr:
1541            stderr = "\nstderr:\n%s" % stderr
1542
1543        return ("* Command: %s\n"
1544                "Exit status: %s\n"
1545                "Duration: %s\n"
1546                "%s"
1547                "%s"
1548                % (wrapper.fill(str(self.command)), self.exit_status,
1549                self.duration, stdout, stderr))
1550
1551
1552class run_randomly:
1553    def __init__(self, run_sequentially=False):
1554        # Run sequentially is for debugging control files
1555        self.test_list = []
1556        self.run_sequentially = run_sequentially
1557
1558
1559    def add(self, *args, **dargs):
1560        test = (args, dargs)
1561        self.test_list.append(test)
1562
1563
1564    def run(self, fn):
1565        while self.test_list:
1566            test_index = random.randint(0, len(self.test_list)-1)
1567            if self.run_sequentially:
1568                test_index = 0
1569            (args, dargs) = self.test_list.pop(test_index)
1570            fn(*args, **dargs)
1571
1572
1573def import_site_module(path, module, dummy=None, modulefile=None):
1574    """
1575    Try to import the site specific module if it exists.
1576
1577    @param path full filename of the source file calling this (ie __file__)
1578    @param module full module name
1579    @param dummy dummy value to return in case there is no symbol to import
1580    @param modulefile module filename
1581
1582    @return site specific module or dummy
1583
1584    @raises ImportError if the site file exists but imports fails
1585    """
1586    short_module = module[module.rfind(".") + 1:]
1587
1588    if not modulefile:
1589        modulefile = short_module + ".py"
1590
1591    if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
1592        return __import__(module, {}, {}, [short_module])
1593    return dummy
1594
1595
1596def import_site_symbol(path, module, name, dummy=None, modulefile=None):
1597    """
1598    Try to import site specific symbol from site specific file if it exists
1599
1600    @param path full filename of the source file calling this (ie __file__)
1601    @param module full module name
1602    @param name symbol name to be imported from the site file
1603    @param dummy dummy value to return in case there is no symbol to import
1604    @param modulefile module filename
1605
1606    @return site specific symbol or dummy
1607
1608    @raises ImportError if the site file exists but imports fails
1609    """
1610    module = import_site_module(path, module, modulefile=modulefile)
1611    if not module:
1612        return dummy
1613
1614    # special unique value to tell us if the symbol can't be imported
1615    cant_import = object()
1616
1617    obj = getattr(module, name, cant_import)
1618    if obj is cant_import:
1619        logging.debug("unable to import site symbol '%s', using non-site "
1620                      "implementation", name)
1621        return dummy
1622
1623    return obj
1624
1625
1626def import_site_class(path, module, classname, baseclass, modulefile=None):
1627    """
1628    Try to import site specific class from site specific file if it exists
1629
1630    Args:
1631        path: full filename of the source file calling this (ie __file__)
1632        module: full module name
1633        classname: class name to be loaded from site file
1634        baseclass: base class object to return when no site file present or
1635            to mixin when site class exists but is not inherited from baseclass
1636        modulefile: module filename
1637
1638    Returns: baseclass if site specific class does not exist, the site specific
1639        class if it exists and is inherited from baseclass or a mixin of the
1640        site specific class and baseclass when the site specific class exists
1641        and is not inherited from baseclass
1642
1643    Raises: ImportError if the site file exists but imports fails
1644    """
1645
1646    res = import_site_symbol(path, module, classname, None, modulefile)
1647    if res:
1648        if not issubclass(res, baseclass):
1649            # if not a subclass of baseclass then mix in baseclass with the
1650            # site specific class object and return the result
1651            res = type(classname, (res, baseclass), {})
1652    else:
1653        res = baseclass
1654
1655    return res
1656
1657
1658def import_site_function(path, module, funcname, dummy, modulefile=None):
1659    """
1660    Try to import site specific function from site specific file if it exists
1661
1662    Args:
1663        path: full filename of the source file calling this (ie __file__)
1664        module: full module name
1665        funcname: function name to be imported from site file
1666        dummy: dummy function to return in case there is no function to import
1667        modulefile: module filename
1668
1669    Returns: site specific function object or dummy
1670
1671    Raises: ImportError if the site file exists but imports fails
1672    """
1673
1674    return import_site_symbol(path, module, funcname, dummy, modulefile)
1675
1676
1677def _get_pid_path(program_name):
1678    my_path = os.path.dirname(__file__)
1679    return os.path.abspath(os.path.join(my_path, "..", "..",
1680                                        "%s.pid" % program_name))
1681
1682
1683def write_pid(program_name):
1684    """
1685    Try to drop <program_name>.pid in the main autotest directory.
1686
1687    Args:
1688      program_name: prefix for file name
1689    """
1690    pidfile = open(_get_pid_path(program_name), "w")
1691    try:
1692        pidfile.write("%s\n" % os.getpid())
1693    finally:
1694        pidfile.close()
1695
1696
1697def delete_pid_file_if_exists(program_name):
1698    """
1699    Tries to remove <program_name>.pid from the main autotest directory.
1700    """
1701    pidfile_path = _get_pid_path(program_name)
1702
1703    try:
1704        os.remove(pidfile_path)
1705    except OSError:
1706        if not os.path.exists(pidfile_path):
1707            return
1708        raise
1709
1710
1711def get_pid_from_file(program_name):
1712    """
1713    Reads the pid from <program_name>.pid in the autotest directory.
1714
1715    @param program_name the name of the program
1716    @return the pid if the file exists, None otherwise.
1717    """
1718    pidfile_path = _get_pid_path(program_name)
1719    if not os.path.exists(pidfile_path):
1720        return None
1721
1722    pidfile = open(_get_pid_path(program_name), 'r')
1723
1724    try:
1725        try:
1726            pid = int(pidfile.readline())
1727        except IOError:
1728            if not os.path.exists(pidfile_path):
1729                return None
1730            raise
1731    finally:
1732        pidfile.close()
1733
1734    return pid
1735
1736
1737def get_process_name(pid):
1738    """
1739    Get process name from PID.
1740    @param pid: PID of process.
1741    @return: Process name if PID stat file exists or 'Dead PID' if it does not.
1742    """
1743    pid_stat_path = "/proc/%d/stat"
1744    if not os.path.exists(pid_stat_path % pid):
1745        return "Dead Pid"
1746    return get_field(read_file(pid_stat_path % pid), 1)[1:-1]
1747
1748
1749def program_is_alive(program_name):
1750    """
1751    Checks if the process is alive and not in Zombie state.
1752
1753    @param program_name the name of the program
1754    @return True if still alive, False otherwise
1755    """
1756    pid = get_pid_from_file(program_name)
1757    if pid is None:
1758        return False
1759    return pid_is_alive(pid)
1760
1761
1762def signal_program(program_name, sig=signal.SIGTERM):
1763    """
1764    Sends a signal to the process listed in <program_name>.pid
1765
1766    @param program_name the name of the program
1767    @param sig signal to send
1768    """
1769    pid = get_pid_from_file(program_name)
1770    if pid:
1771        signal_pid(pid, sig)
1772
1773
1774def get_relative_path(path, reference):
1775    """Given 2 absolute paths "path" and "reference", compute the path of
1776    "path" as relative to the directory "reference".
1777
1778    @param path the absolute path to convert to a relative path
1779    @param reference an absolute directory path to which the relative
1780        path will be computed
1781    """
1782    # normalize the paths (remove double slashes, etc)
1783    assert(os.path.isabs(path))
1784    assert(os.path.isabs(reference))
1785
1786    path = os.path.normpath(path)
1787    reference = os.path.normpath(reference)
1788
1789    # we could use os.path.split() but it splits from the end
1790    path_list = path.split(os.path.sep)[1:]
1791    ref_list = reference.split(os.path.sep)[1:]
1792
1793    # find the longest leading common path
1794    for i in xrange(min(len(path_list), len(ref_list))):
1795        if path_list[i] != ref_list[i]:
1796            # decrement i so when exiting this loop either by no match or by
1797            # end of range we are one step behind
1798            i -= 1
1799            break
1800    i += 1
1801    # drop the common part of the paths, not interested in that anymore
1802    del path_list[:i]
1803
1804    # for each uncommon component in the reference prepend a ".."
1805    path_list[:0] = ['..'] * (len(ref_list) - i)
1806
1807    return os.path.join(*path_list)
1808
1809
1810def sh_escape(command):
1811    """
1812    Escape special characters from a command so that it can be passed
1813    as a double quoted (" ") string in a (ba)sh command.
1814
1815    Args:
1816            command: the command string to escape.
1817
1818    Returns:
1819            The escaped command string. The required englobing double
1820            quotes are NOT added and so should be added at some point by
1821            the caller.
1822
1823    See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
1824    """
1825    command = command.replace("\\", "\\\\")
1826    command = command.replace("$", r'\$')
1827    command = command.replace('"', r'\"')
1828    command = command.replace('`', r'\`')
1829    return command
1830
1831
1832def configure(extra=None, configure='./configure'):
1833    """
1834    Run configure passing in the correct host, build, and target options.
1835
1836    @param extra: extra command line arguments to pass to configure
1837    @param configure: which configure script to use
1838    """
1839    args = []
1840    if 'CHOST' in os.environ:
1841        args.append('--host=' + os.environ['CHOST'])
1842    if 'CBUILD' in os.environ:
1843        args.append('--build=' + os.environ['CBUILD'])
1844    if 'CTARGET' in os.environ:
1845        args.append('--target=' + os.environ['CTARGET'])
1846    if extra:
1847        args.append(extra)
1848
1849    system('%s %s' % (configure, ' '.join(args)))
1850
1851
1852def make(extra='', make='make', timeout=None, ignore_status=False):
1853    """
1854    Run make, adding MAKEOPTS to the list of options.
1855
1856    @param extra: extra command line arguments to pass to make.
1857    """
1858    cmd = '%s %s %s' % (make, os.environ.get('MAKEOPTS', ''), extra)
1859    return system(cmd, timeout=timeout, ignore_status=ignore_status)
1860
1861
1862def compare_versions(ver1, ver2):
1863    """Version number comparison between ver1 and ver2 strings.
1864
1865    >>> compare_tuple("1", "2")
1866    -1
1867    >>> compare_tuple("foo-1.1", "foo-1.2")
1868    -1
1869    >>> compare_tuple("1.2", "1.2a")
1870    -1
1871    >>> compare_tuple("1.2b", "1.2a")
1872    1
1873    >>> compare_tuple("1.3.5.3a", "1.3.5.3b")
1874    -1
1875
1876    Args:
1877        ver1: version string
1878        ver2: version string
1879
1880    Returns:
1881        int:  1 if ver1 >  ver2
1882              0 if ver1 == ver2
1883             -1 if ver1 <  ver2
1884    """
1885    ax = re.split('[.-]', ver1)
1886    ay = re.split('[.-]', ver2)
1887    while len(ax) > 0 and len(ay) > 0:
1888        cx = ax.pop(0)
1889        cy = ay.pop(0)
1890        maxlen = max(len(cx), len(cy))
1891        c = cmp(cx.zfill(maxlen), cy.zfill(maxlen))
1892        if c != 0:
1893            return c
1894    return cmp(len(ax), len(ay))
1895
1896
1897def args_to_dict(args):
1898    """Convert autoserv extra arguments in the form of key=val or key:val to a
1899    dictionary.  Each argument key is converted to lowercase dictionary key.
1900
1901    Args:
1902        args - list of autoserv extra arguments.
1903
1904    Returns:
1905        dictionary
1906    """
1907    arg_re = re.compile(r'(\w+)[:=](.*)$')
1908    dict = {}
1909    for arg in args:
1910        match = arg_re.match(arg)
1911        if match:
1912            dict[match.group(1).lower()] = match.group(2)
1913        else:
1914            logging.warning("args_to_dict: argument '%s' doesn't match "
1915                            "'%s' pattern. Ignored.", arg, arg_re.pattern)
1916    return dict
1917
1918
1919def get_unused_port():
1920    """
1921    Finds a semi-random available port. A race condition is still
1922    possible after the port number is returned, if another process
1923    happens to bind it.
1924
1925    Returns:
1926        A port number that is unused on both TCP and UDP.
1927    """
1928
1929    def try_bind(port, socket_type, socket_proto):
1930        s = socket.socket(socket.AF_INET, socket_type, socket_proto)
1931        try:
1932            try:
1933                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1934                s.bind(('', port))
1935                return s.getsockname()[1]
1936            except socket.error:
1937                return None
1938        finally:
1939            s.close()
1940
1941    # On the 2.6 kernel, calling try_bind() on UDP socket returns the
1942    # same port over and over. So always try TCP first.
1943    while True:
1944        # Ask the OS for an unused port.
1945        port = try_bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP)
1946        # Check if this port is unused on the other protocol.
1947        if port and try_bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP):
1948            return port
1949
1950
1951def ask(question, auto=False):
1952    """
1953    Raw input with a prompt that emulates logging.
1954
1955    @param question: Question to be asked
1956    @param auto: Whether to return "y" instead of asking the question
1957    """
1958    if auto:
1959        logging.info("%s (y/n) y", question)
1960        return "y"
1961    return raw_input("%s INFO | %s (y/n) " %
1962                     (time.strftime("%H:%M:%S", time.localtime()), question))
1963
1964
1965def rdmsr(address, cpu=0):
1966    """
1967    Reads an x86 MSR from the specified CPU, returns as long integer.
1968    """
1969    with open('/dev/cpu/%s/msr' % cpu, 'r', 0) as fd:
1970        fd.seek(address)
1971        return struct.unpack('=Q', fd.read(8))[0]
1972
1973
1974def wait_for_value(func,
1975                   expected_value=None,
1976                   min_threshold=None,
1977                   max_threshold=None,
1978                   timeout_sec=10):
1979    """
1980    Returns the value of func().  If |expected_value|, |min_threshold|, and
1981    |max_threshold| are not set, returns immediately.
1982
1983    If |expected_value| is set, polls the return value until |expected_value| is
1984    reached, and returns that value.
1985
1986    If either |max_threshold| or |min_threshold| is set, this function will
1987    will repeatedly call func() until the return value reaches or exceeds one of
1988    these thresholds.
1989
1990    Polling will stop after |timeout_sec| regardless of these thresholds.
1991
1992    @param func: function whose return value is to be waited on.
1993    @param expected_value: wait for func to return this value.
1994    @param min_threshold: wait for func value to reach or fall below this value.
1995    @param max_threshold: wait for func value to reach or rise above this value.
1996    @param timeout_sec: Number of seconds to wait before giving up and
1997                        returning whatever value func() last returned.
1998
1999    Return value:
2000        The most recent return value of func().
2001    """
2002    value = None
2003    start_time_sec = time.time()
2004    while True:
2005        value = func()
2006        if (expected_value is None and \
2007            min_threshold is None and \
2008            max_threshold is None) or \
2009           (expected_value is not None and value == expected_value) or \
2010           (min_threshold is not None and value <= min_threshold) or \
2011           (max_threshold is not None and value >= max_threshold):
2012            break
2013
2014        if time.time() - start_time_sec >= timeout_sec:
2015            break
2016        time.sleep(0.1)
2017
2018    return value
2019
2020
2021def call_xrandr(args_string=''):
2022    """
2023    Calls xrandr with the args given by args_string.
2024    |args_string| is a single string containing all arguments.
2025    e.g. call_xrandr('--output LVDS1 --off') will invoke:
2026        'xrandr --output LVDS1 --off'
2027
2028    Return value: Output of xrandr
2029    """
2030
2031    cmd = 'xrandr'
2032    xauth = '/home/chronos/.Xauthority'
2033    environment = 'DISPLAY=:0.0 XAUTHORITY=%s' % xauth
2034    return system_output('%s %s %s' % (environment, cmd, args_string))
2035
2036
2037def get_xrandr_output_state():
2038    """
2039    Retrieves the status of display outputs using Xrandr.
2040
2041    Return value: dictionary of display states.
2042                  key = output name
2043                  value = False if off, True if on
2044    """
2045
2046    output = call_xrandr().split('\n')
2047    xrandr_outputs = {}
2048    current_output_name = ''
2049
2050    # Parse output of xrandr, line by line.
2051    for line in output:
2052        if line[0:5] == 'Screen':
2053            continue
2054        # If the line contains "connected", it is a connected display, as
2055        # opposed to a disconnected output.
2056        if line.find(' connected') != -1:
2057            current_output_name = line.split()[0]
2058            xrandr_outputs[current_output_name] = False
2059            continue
2060
2061        # If "connected" was not found, this is a line that shows a display
2062        # mode, e.g:    1920x1080      50.0     60.0     24.0
2063        # Check if this has an asterisk indicating it's on.
2064        if line.find('*') != -1 and current_output_name != '' :
2065            xrandr_outputs[current_output_name] = True
2066            # Reset the output name since this should not be set more than once.
2067            current_output_name = ''
2068
2069    return xrandr_outputs
2070
2071
2072def set_xrandr_output(output_name, enable):
2073    """
2074    Sets the output given by |output_name| on or off.
2075
2076    Parameters:
2077        output_name       name of output, e.g. 'HDMI1', 'LVDS1', 'DP1'
2078        enable            True or False, indicating whether to turn on or off
2079    """
2080
2081    call_xrandr('--output %s --%s' % (output_name, 'auto' if enable else 'off'))
2082
2083
2084def restart_job(name):
2085    """
2086    Restarts an upstart job if it's running.
2087    If it's not running, start it.
2088    """
2089
2090    if system_output('status %s' % name).find('start/running') != -1:
2091        system_output('restart %s' % name)
2092    else:
2093        system_output('start %s' % name)
2094
2095