Merge remote branch 'cros/upstream' into autotest-rebase

Merged to upstream trunk@5066, from trunk@4749.

There is no way I could enlist each individual CL from the upstream here since it will blow up the changelist description field.

BUG=
TEST=
Had patched this CL into a fresh cut client to avoid any side effect.
run_remote_test bvt from both emerged location and third_party/autotest/file.

Both test passed!

We should also keep any eye on this to see how it gets propagated into cautotest server.
TBR=dalecurtis

Change-Id: I72f2bc7a9de530178484aea1bfb5ace68bcad029
diff --git a/.gitignore b/.gitignore
index 355a9e3..bba2340 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@
 client/tests/kvm/env
 client/tmp
 client/tests/kvm/*.cfg
+server/tmp
+tko/parsers/test/site_scenarios
+ExternalSource
diff --git a/cli/atest b/cli/atest
index 3174d02..8386920 100755
--- a/cli/atest
+++ b/cli/atest
@@ -1,7 +1,6 @@
 #!/usr/bin/python -u
 
-import base64, sys
-
+import sys
 import common
 from autotest_lib.cli import atest
 
diff --git a/cli/job.py b/cli/job.py
index a5122be..5eb00a5 100644
--- a/cli/job.py
+++ b/cli/job.py
@@ -602,6 +602,12 @@
         for field in ('name', 'created_on', 'id', 'owner'):
             del clone_info['job'][field]
 
+        # Also remove parameterized_job field, as the feature still is
+        # incomplete, this tool does not attempt to support it for now,
+        # it uses a different API function and it breaks create_job()
+        if clone_info['job'].has_key('parameterized_job'):
+            del clone_info['job']['parameterized_job']
+
         # Keyword args cannot be unicode strings
         self.data.update((str(key), val)
                          for key, val in clone_info['job'].iteritems())
diff --git a/client/bin/kernel.py b/client/bin/kernel.py
index f3f5a38..cb0ef99 100644
--- a/client/bin/kernel.py
+++ b/client/bin/kernel.py
@@ -332,15 +332,17 @@
             utils.extract_tarball_to_dir(tarball, self.build_dir)
 
 
-    def extraversion(self, tag, append=1):
+    def extraversion(self, tag, append=True):
         os.chdir(self.build_dir)
-        extraversion_sub = r's/^EXTRAVERSION =\s*\(.*\)/EXTRAVERSION = '
+        extraversion_sub = r's/^CONFIG_LOCALVERSION=\s*"\(.*\)"/CONFIG_LOCALVERSION='
+        cfg = self.build_dir + '/.config'
         if append:
-            p = extraversion_sub + '\\1-%s/' % tag
+            p = extraversion_sub + '"\\1-%s"/' % tag
         else:
-            p = extraversion_sub + '-%s/' % tag
-        utils.system('mv Makefile Makefile.old')
-        utils.system('sed "%s" < Makefile.old > Makefile' % p)
+            p = extraversion_sub + '"-%s"/' % tag
+        utils.system('mv %s %s.old' % (cfg, cfg))
+        utils.system("sed '%s' < %s.old > %s" % (p, cfg, cfg))
+        self.config(make='oldconfig')
 
 
     @log.record
diff --git a/client/bin/kernel_unittest.py b/client/bin/kernel_unittest.py
index 6761c05..4ed0d37 100755
--- a/client/bin/kernel_unittest.py
+++ b/client/bin/kernel_unittest.py
@@ -425,13 +425,17 @@
     def test_extraversion(self):
         self.construct_kernel()
         tag = "tag"
+        # setup
+        self.god.stub_function(self.kernel, "config")
 
         # record
         os.chdir.expect_call(self.build_dir)
-        extraversion_sub = r's/^EXTRAVERSION =\s*\(.*\)/EXTRAVERSION = '
-        p = extraversion_sub + '\\1-%s/' % tag
-        utils.system.expect_call('mv Makefile Makefile.old')
-        utils.system.expect_call('sed "%s" < Makefile.old > Makefile' % p)
+        extraversion_sub = r's/^CONFIG_LOCALVERSION=\s*"\(.*\)"/CONFIG_LOCALVERSION='
+        cfg = self.build_dir + '/.config'
+        p = extraversion_sub + '"\\1-%s"/' % tag
+        utils.system.expect_call('mv %s %s.old' % (cfg, cfg))
+        utils.system.expect_call("sed '%s' < %s.old > %s" % (p, cfg, cfg))
+        self.kernel.config.expect_call(make='oldconfig')
 
         # run and check
         self.kernel.extraversion(tag)
diff --git a/client/bin/partition.py b/client/bin/partition.py
index 355d1b3..7381f75 100644
--- a/client/bin/partition.py
+++ b/client/bin/partition.py
@@ -185,7 +185,7 @@
     mount_info = set()
     for p in partition_list:
         try:
-            uuid = utils.system_output('blkid -s UUID -o value %s' % p.device)
+            uuid = utils.system_output('blkid -p -s UUID -o value %s' % p.device)
         except error.CmdError:
             # fall back to using the partition
             uuid = p.device
diff --git a/client/common_lib/base_barrier.py b/client/common_lib/base_barrier.py
index e4de635..e1063a9 100644
--- a/client/common_lib/base_barrier.py
+++ b/client/common_lib/base_barrier.py
@@ -5,6 +5,16 @@
 # default barrier port
 _DEFAULT_PORT = 11922
 
+def get_host_from_id(hostid):
+    # Remove any trailing local identifier following a #.
+    # This allows multiple members per host which is particularly
+    # helpful in testing.
+    if not hostid.startswith('#'):
+        return hostid.split('#')[0]
+    else:
+        raise error.BarrierError(
+            "Invalid Host id: Host Address should be specified")
+
 
 class BarrierAbortError(error.BarrierError):
     """Special BarrierError raised when an explicit abort is requested."""
@@ -159,17 +169,6 @@
         self._waiting = {}  # Maps from hostname -> (client, addr) tuples.
 
 
-    def _get_host_from_id(self, hostid):
-        # Remove any trailing local identifier following a #.
-        # This allows multiple members per host which is particularly
-        # helpful in testing.
-        if not hostid.startswith('#'):
-            return hostid.split('#')[0]
-        else:
-            raise error.BarrierError(
-                    "Invalid Host id: Host Address should be specified")
-
-
     def _update_timeout(self, timeout):
         if timeout is not None and self._start_time is not None:
             self._timeout_secs = (time() - self._start_time) + timeout
@@ -397,14 +396,14 @@
                 remote.settimeout(30)
                 if is_master:
                     # Connect to all slaves.
-                    host = self._get_host_from_id(self._members[self._seen])
+                    host = get_host_from_id(self._members[self._seen])
                     logging.info("calling slave: %s", host)
                     connection = (remote, (host, self._port))
                     remote.connect(connection[1])
                     self._master_welcome(connection)
                 else:
                     # Just connect to the master.
-                    host = self._get_host_from_id(self._masterid)
+                    host = get_host_from_id(self._masterid)
                     logging.info("calling master")
                     connection = (remote, (host, self._port))
                     remote.connect(connection[1])
diff --git a/client/common_lib/base_barrier_unittest.py b/client/common_lib/base_barrier_unittest.py
index 52d8e17..71ea538 100755
--- a/client/common_lib/base_barrier_unittest.py
+++ b/client/common_lib/base_barrier_unittest.py
@@ -5,7 +5,7 @@
 import os, sys, socket, errno, unittest, threading
 from time import time, sleep
 import common
-from autotest_lib.client.common_lib import error, barrier
+from autotest_lib.client.common_lib import error, barrier, base_barrier
 from autotest_lib.client.common_lib.test_utils import mock
 
 
@@ -46,15 +46,14 @@
 
 
     def test_get_host_from_id(self):
-        b = barrier.barrier('127.0.0.1#', 'testgethost', 100)
-
-        hostname = b._get_host_from_id('my_host')
+        hostname = base_barrier.get_host_from_id('my_host')
         self.assertEqual(hostname, 'my_host')
 
-        hostname = b._get_host_from_id('my_host#')
+        hostname = base_barrier.get_host_from_id('my_host#')
         self.assertEqual(hostname, 'my_host')
 
-        self.assertRaises(error.BarrierError, b._get_host_from_id, '#my_host')
+        self.assertRaises(error.BarrierError,
+                          base_barrier.get_host_from_id, '#my_host')
 
 
     def test_update_timeout(self):
diff --git a/client/common_lib/base_job.py b/client/common_lib/base_job.py
index 3c77d38..4a2271c 100644
--- a/client/common_lib/base_job.py
+++ b/client/common_lib/base_job.py
@@ -422,6 +422,9 @@
     TIMESTAMP_FIELD = 'timestamp'
     LOCALTIME_FIELD = 'localtime'
 
+    # non-space whitespace is forbidden in any fields
+    BAD_CHAR_REGEX = re.compile(r'[\t\n\r\v\f]')
+
     def __init__(self, status_code, subdir, operation, message, fields,
                  timestamp=None):
         """Construct a status.log entry.
@@ -439,18 +442,16 @@
 
         @raise ValueError: if any of the parameters are invalid
         """
-        # non-space whitespace is forbidden in any fields
-        bad_char_regex = r'[\t\n\r\v\f]'
 
         if not log.is_valid_status(status_code):
             raise ValueError('status code %r is not valid' % status_code)
         self.status_code = status_code
 
-        if subdir and re.search(bad_char_regex, subdir):
+        if subdir and self.BAD_CHAR_REGEX.search(subdir):
             raise ValueError('Invalid character in subdir string')
         self.subdir = subdir
 
-        if operation and re.search(bad_char_regex, operation):
+        if operation and self.BAD_CHAR_REGEX.search(operation):
             raise ValueError('Invalid character in operation string')
         self.operation = operation
 
@@ -460,7 +461,7 @@
         message_lines = message.split('\n')
         self.message = message_lines[0].replace('\t', ' ' * 8)
         self.extra_message_lines = message_lines[1:]
-        if re.search(bad_char_regex, self.message):
+        if self.BAD_CHAR_REGEX.search(self.message):
             raise ValueError('Invalid character in message %r' % self.message)
 
         if not fields:
@@ -468,7 +469,7 @@
         else:
             self.fields = fields.copy()
         for key, value in self.fields.iteritems():
-            if re.search(bad_char_regex, key + value):
+            if self.BAD_CHAR_REGEX.search(key + value):
                 raise ValueError('Invalid character in %r=%r field'
                                  % (key, value))
 
diff --git a/client/common_lib/base_utils.py b/client/common_lib/base_utils.py
new file mode 100644
index 0000000..101599b
--- /dev/null
+++ b/client/common_lib/base_utils.py
@@ -0,0 +1,1715 @@
+#
+# Copyright 2008 Google Inc. Released under the GPL v2
+
+import os, pickle, random, re, resource, select, shutil, signal, StringIO
+import socket, struct, subprocess, sys, time, textwrap, urlparse
+import warnings, smtplib, logging, urllib2
+from threading import Thread, Event
+try:
+    import hashlib
+except ImportError:
+    import md5, sha
+from autotest_lib.client.common_lib import error, logging_manager
+
+def deprecated(func):
+    """This is a decorator which can be used to mark functions as deprecated.
+    It will result in a warning being emmitted when the function is used."""
+    def new_func(*args, **dargs):
+        warnings.warn("Call to deprecated function %s." % func.__name__,
+                      category=DeprecationWarning)
+        return func(*args, **dargs)
+    new_func.__name__ = func.__name__
+    new_func.__doc__ = func.__doc__
+    new_func.__dict__.update(func.__dict__)
+    return new_func
+
+
+class _NullStream(object):
+    def write(self, data):
+        pass
+
+
+    def flush(self):
+        pass
+
+
+TEE_TO_LOGS = object()
+_the_null_stream = _NullStream()
+
+DEFAULT_STDOUT_LEVEL = logging.DEBUG
+DEFAULT_STDERR_LEVEL = logging.ERROR
+
+# prefixes for logging stdout/stderr of commands
+STDOUT_PREFIX = '[stdout] '
+STDERR_PREFIX = '[stderr] '
+
+
+def get_stream_tee_file(stream, level, prefix=''):
+    if stream is None:
+        return _the_null_stream
+    if stream is TEE_TO_LOGS:
+        return logging_manager.LoggingFile(level=level, prefix=prefix)
+    return stream
+
+
+class BgJob(object):
+    def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
+                 stdin=None, stderr_level=DEFAULT_STDERR_LEVEL):
+        self.command = command
+        self.stdout_tee = get_stream_tee_file(stdout_tee, DEFAULT_STDOUT_LEVEL,
+                                              prefix=STDOUT_PREFIX)
+        self.stderr_tee = get_stream_tee_file(stderr_tee, stderr_level,
+                                              prefix=STDERR_PREFIX)
+        self.result = CmdResult(command)
+
+        # allow for easy stdin input by string, we'll let subprocess create
+        # a pipe for stdin input and we'll write to it in the wait loop
+        if isinstance(stdin, basestring):
+            self.string_stdin = stdin
+            stdin = subprocess.PIPE
+        else:
+            self.string_stdin = None
+
+        if verbose:
+            logging.debug("Running '%s'" % command)
+        self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   preexec_fn=self._reset_sigpipe, shell=True,
+
+                                   # Default shell in ChromeOS test image is
+                                   # already bash. We're seeing shell-init
+                                   # errors if this value is set.
+
+                                   #executable="/bin/bash",
+                                   stdin=stdin)
+
+
+    def output_prepare(self, stdout_file=None, stderr_file=None):
+        self.stdout_file = stdout_file
+        self.stderr_file = stderr_file
+
+
+    def process_output(self, stdout=True, final_read=False):
+        """output_prepare must be called prior to calling this"""
+        if stdout:
+            pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
+        else:
+            pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
+
+        if final_read:
+            # read in all the data we can from pipe and then stop
+            data = []
+            while select.select([pipe], [], [], 0)[0]:
+                data.append(os.read(pipe.fileno(), 1024))
+                if len(data[-1]) == 0:
+                    break
+            data = "".join(data)
+        else:
+            # perform a single read
+            data = os.read(pipe.fileno(), 1024)
+        buf.write(data)
+        tee.write(data)
+
+
+    def cleanup(self):
+        self.stdout_tee.flush()
+        self.stderr_tee.flush()
+        self.sp.stdout.close()
+        self.sp.stderr.close()
+        self.result.stdout = self.stdout_file.getvalue()
+        self.result.stderr = self.stderr_file.getvalue()
+
+
+    def _reset_sigpipe(self):
+        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+
+def ip_to_long(ip):
+    # !L is a long in network byte order
+    return struct.unpack('!L', socket.inet_aton(ip))[0]
+
+
+def long_to_ip(number):
+    # See above comment.
+    return socket.inet_ntoa(struct.pack('!L', number))
+
+
+def create_subnet_mask(bits):
+    return (1 << 32) - (1 << 32-bits)
+
+
+def format_ip_with_mask(ip, mask_bits):
+    masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
+    return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
+
+
+def normalize_hostname(alias):
+    ip = socket.gethostbyname(alias)
+    return socket.gethostbyaddr(ip)[0]
+
+
+def get_ip_local_port_range():
+    match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
+                     read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
+    return (int(match.group(1)), int(match.group(2)))
+
+
+def set_ip_local_port_range(lower, upper):
+    write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
+                   '%d %d\n' % (lower, upper))
+
+
+
+def send_email(mail_from, mail_to, subject, body):
+    """
+    Sends an email via smtp
+
+    mail_from: string with email address of sender
+    mail_to: string or list with email address(es) of recipients
+    subject: string with subject of email
+    body: (multi-line) string with body of email
+    """
+    if isinstance(mail_to, str):
+        mail_to = [mail_to]
+    msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to),
+                                                   subject, body)
+    try:
+        mailer = smtplib.SMTP('localhost')
+        try:
+            mailer.sendmail(mail_from, mail_to, msg)
+        finally:
+            mailer.quit()
+    except Exception, e:
+        # Emails are non-critical, not errors, but don't raise them
+        print "Sending email failed. Reason: %s" % repr(e)
+
+
+def read_one_line(filename):
+    return open(filename, 'r').readline().rstrip('\n')
+
+
+def read_file(filename):
+    f = open(filename)
+    try:
+        return f.read()
+    finally:
+        f.close()
+
+
+def get_field(data, param, linestart="", sep=" "):
+    """
+    Parse data from string.
+    @param data: Data to parse.
+        example:
+          data:
+             cpu   324 345 34  5 345
+             cpu0  34  11  34 34  33
+             ^^^^
+             start of line
+             params 0   1   2  3   4
+    @param param: Position of parameter after linestart marker.
+    @param linestart: String to which start line with parameters.
+    @param sep: Separator between parameters regular expression.
+    """
+    search = re.compile(r"(?<=^%s)\s*(.*)" % linestart, re.MULTILINE)
+    find = search.search(data)
+    if find != None:
+        return re.split("%s" % sep, find.group(1))[param]
+    else:
+        print "There is no line which starts with %s in data." % linestart
+        return None
+
+
+def write_one_line(filename, line):
+    open_write_close(filename, line.rstrip('\n') + '\n')
+
+
+def open_write_close(filename, data):
+    f = open(filename, 'w')
+    try:
+        f.write(data)
+    finally:
+        f.close()
+
+
+def matrix_to_string(matrix, header=None):
+    """
+    Return a pretty, aligned string representation of a nxm matrix.
+
+    This representation can be used to print any tabular data, such as
+    database results. It works by scanning the lengths of each element
+    in each column, and determining the format string dynamically.
+
+    @param matrix: Matrix representation (list with n rows of m elements).
+    @param header: Optional tuple or list with header elements to be displayed.
+    """
+    if type(header) is list:
+        header = tuple(header)
+    lengths = []
+    if header:
+        for column in header:
+            lengths.append(len(column))
+    for row in matrix:
+        for column in row:
+            i = row.index(column)
+            cl = len(column)
+            try:
+                ml = lengths[i]
+                if cl > ml:
+                    lengths[i] = cl
+            except IndexError:
+                lengths.append(cl)
+
+    lengths = tuple(lengths)
+    format_string = ""
+    for length in lengths:
+        format_string += "%-" + str(length) + "s "
+    format_string += "\n"
+
+    matrix_str = ""
+    if header:
+        matrix_str += format_string % header
+    for row in matrix:
+        matrix_str += format_string % tuple(row)
+
+    return matrix_str
+
+
+def read_keyval(path):
+    """
+    Read a key-value pair format file into a dictionary, and return it.
+    Takes either a filename or directory name as input. If it's a
+    directory name, we assume you want the file to be called keyval.
+    """
+    if os.path.isdir(path):
+        path = os.path.join(path, 'keyval')
+    keyval = {}
+    if os.path.exists(path):
+        for line in open(path):
+            line = re.sub('#.*', '', line).rstrip()
+            if not re.search(r'^[-\.\w]+=', line):
+                raise ValueError('Invalid format line: %s' % line)
+            key, value = line.split('=', 1)
+            if re.search('^\d+$', value):
+                value = int(value)
+            elif re.search('^(\d+\.)?\d+$', value):
+                value = float(value)
+            keyval[key] = value
+    return keyval
+
+
+def write_keyval(path, dictionary, type_tag=None):
+    """
+    Write a key-value pair format file out to a file. This uses append
+    mode to open the file, so existing text will not be overwritten or
+    reparsed.
+
+    If type_tag is None, then the key must be composed of alphanumeric
+    characters (or dashes+underscores). However, if type-tag is not
+    null then the keys must also have "{type_tag}" as a suffix. At
+    the moment the only valid values of type_tag are "attr" and "perf".
+    """
+    if os.path.isdir(path):
+        path = os.path.join(path, 'keyval')
+    keyval = open(path, 'a')
+
+    if type_tag is None:
+        key_regex = re.compile(r'^[-\.\w]+$')
+    else:
+        if type_tag not in ('attr', 'perf'):
+            raise ValueError('Invalid type tag: %s' % type_tag)
+        escaped_tag = re.escape(type_tag)
+        key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
+    try:
+        for key in sorted(dictionary.keys()):
+            if not key_regex.search(key):
+                raise ValueError('Invalid key: %s' % key)
+            keyval.write('%s=%s\n' % (key, dictionary[key]))
+    finally:
+        keyval.close()
+
+
+class FileFieldMonitor(object):
+    """
+    Monitors the information from the file and reports it's values.
+
+    It gather the information at start and stop of the measurement or
+    continuously during the measurement.
+    """
+    class Monitor(Thread):
+        """
+        Internal monitor class to ensure continuous monitor of monitored file.
+        """
+        def __init__(self, master):
+            """
+            @param master: Master class which control Monitor
+            """
+            Thread.__init__(self)
+            self.master = master
+
+        def run(self):
+            """
+            Start monitor in thread mode
+            """
+            while not self.master.end_event.isSet():
+                self.master._get_value(self.master.logging)
+                time.sleep(self.master.time_step)
+
+
+    def __init__(self, status_file, data_to_read, mode_diff, continuously=False,
+                 contlogging=False, separator=" +", time_step=0.1):
+        """
+        Initialize variables.
+        @param status_file: File contain status.
+        @param mode_diff: If True make a difference of value, else average.
+        @param data_to_read: List of tuples with data position.
+            format: [(start_of_line,position in params)]
+            example:
+              data:
+                 cpu   324 345 34  5 345
+                 cpu0  34  11  34 34  33
+                 ^^^^
+                 start of line
+                 params 0   1   2  3   4
+        @param mode_diff: True to subtract old value from new value,
+            False make average of the values.
+        @parma continuously: Start the monitoring thread using the time_step
+            as the measurement period.
+        @param contlogging: Log data in continuous run.
+        @param separator: Regular expression of separator.
+        @param time_step: Time period of the monitoring value.
+        """
+        self.end_event = Event()
+        self.start_time = 0
+        self.end_time = 0
+        self.test_time = 0
+
+        self.status_file = status_file
+        self.separator = separator
+        self.data_to_read = data_to_read
+        self.num_of_params = len(self.data_to_read)
+        self.mode_diff = mode_diff
+        self.continuously = continuously
+        self.time_step = time_step
+
+        self.value = [0 for i in range(self.num_of_params)]
+        self.old_value = [0 for i in range(self.num_of_params)]
+        self.log = []
+        self.logging = contlogging
+
+        self.started = False
+        self.num_of_get_value = 0
+        self.monitor = None
+
+
+    def _get_value(self, logging=True):
+        """
+        Return current values.
+        @param logging: If true log value in memory. There can be problem
+          with long run.
+        """
+        data = read_file(self.status_file)
+        value = []
+        for i in range(self.num_of_params):
+            value.append(int(get_field(data,
+                             self.data_to_read[i][1],
+                             self.data_to_read[i][0],
+                             self.separator)))
+
+        if logging:
+            self.log.append(value)
+        if not self.mode_diff:
+            value = map(lambda x, y: x + y, value, self.old_value)
+
+        self.old_value = value
+        self.num_of_get_value += 1
+        return value
+
+
+    def start(self):
+        """
+        Start value monitor.
+        """
+        if self.started:
+            self.stop()
+        self.old_value = [0 for i in range(self.num_of_params)]
+        self.num_of_get_value = 0
+        self.log = []
+        self.end_event.clear()
+        self.start_time = time.time()
+        self._get_value()
+        self.started = True
+        if (self.continuously):
+            self.monitor = FileFieldMonitor.Monitor(self)
+            self.monitor.start()
+
+
+    def stop(self):
+        """
+        Stop value monitor.
+        """
+        if self.started:
+            self.started = False
+            self.end_time = time.time()
+            self.test_time = self.end_time - self.start_time
+            self.value = self._get_value()
+            if (self.continuously):
+                self.end_event.set()
+                self.monitor.join()
+            if (self.mode_diff):
+                self.value = map(lambda x, y: x - y, self.log[-1], self.log[0])
+            else:
+                self.value = map(lambda x: x / self.num_of_get_value,
+                                 self.value)
+
+
+    def get_status(self):
+        """
+        @return: Status of monitored process average value,
+            time of test and array of monitored values and time step of
+            continuous run.
+        """
+        if self.started:
+            self.stop()
+        if self.mode_diff:
+            for i in range(len(self.log) - 1):
+                self.log[i] = (map(lambda x, y: x - y,
+                                   self.log[i + 1], self.log[i]))
+            self.log.pop()
+        return (self.value, self.test_time, self.log, self.time_step)
+
+
+def is_url(path):
+    """Return true if path looks like a URL"""
+    # for now, just handle http and ftp
+    url_parts = urlparse.urlparse(path)
+    return (url_parts[0] in ('http', 'ftp'))
+
+
+def urlopen(url, data=None, timeout=5):
+    """Wrapper to urllib2.urlopen with timeout addition."""
+
+    # Save old timeout
+    old_timeout = socket.getdefaulttimeout()
+    socket.setdefaulttimeout(timeout)
+    try:
+        return urllib2.urlopen(url, data=data)
+    finally:
+        socket.setdefaulttimeout(old_timeout)
+
+
+def urlretrieve(url, filename, data=None, timeout=300):
+    """Retrieve a file from given url."""
+    logging.debug('Fetching %s -> %s', url, filename)
+
+    src_file = urlopen(url, data=data, timeout=timeout)
+    try:
+        dest_file = open(filename, 'wb')
+        try:
+            shutil.copyfileobj(src_file, dest_file)
+        finally:
+            dest_file.close()
+    finally:
+        src_file.close()
+
+
+def hash(type, input=None):
+    """
+    Returns an hash object of type md5 or sha1. This function is implemented in
+    order to encapsulate hash objects in a way that is compatible with python
+    2.4 and python 2.6 without warnings.
+
+    Note that even though python 2.6 hashlib supports hash types other than
+    md5 and sha1, we are artificially limiting the input values in order to
+    make the function to behave exactly the same among both python
+    implementations.
+
+    @param input: Optional input string that will be used to update the hash.
+    """
+    if type not in ['md5', 'sha1']:
+        raise ValueError("Unsupported hash type: %s" % type)
+
+    try:
+        hash = hashlib.new(type)
+    except NameError:
+        if type == 'md5':
+            hash = md5.new()
+        elif type == 'sha1':
+            hash = sha.new()
+
+    if input:
+        hash.update(input)
+
+    return hash
+
+
+def get_file(src, dest, permissions=None):
+    """Get a file from src, which can be local or a remote URL"""
+    if src == dest:
+        return
+
+    if is_url(src):
+        urlretrieve(src, dest)
+    else:
+        shutil.copyfile(src, dest)
+
+    if permissions:
+        os.chmod(dest, permissions)
+    return dest
+
+
+def unmap_url(srcdir, src, destdir='.'):
+    """
+    Receives either a path to a local file or a URL.
+    returns either the path to the local file, or the fetched URL
+
+    unmap_url('/usr/src', 'foo.tar', '/tmp')
+                            = '/usr/src/foo.tar'
+    unmap_url('/usr/src', 'http://site/file', '/tmp')
+                            = '/tmp/file'
+                            (after retrieving it)
+    """
+    if is_url(src):
+        url_parts = urlparse.urlparse(src)
+        filename = os.path.basename(url_parts[2])
+        dest = os.path.join(destdir, filename)
+        return get_file(src, dest)
+    else:
+        return os.path.join(srcdir, src)
+
+
+def update_version(srcdir, preserve_srcdir, new_version, install,
+                   *args, **dargs):
+    """
+    Make sure srcdir is version new_version
+
+    If not, delete it and install() the new version.
+
+    In the preserve_srcdir case, we just check it's up to date,
+    and if not, we rerun install, without removing srcdir
+    """
+    versionfile = os.path.join(srcdir, '.version')
+    install_needed = True
+
+    if os.path.exists(versionfile):
+        old_version = pickle.load(open(versionfile))
+        if old_version == new_version:
+            install_needed = False
+
+    if install_needed:
+        if not preserve_srcdir and os.path.exists(srcdir):
+            shutil.rmtree(srcdir)
+        install(*args, **dargs)
+        if os.path.exists(srcdir):
+            pickle.dump(new_version, open(versionfile, 'w'))
+
+
+def get_stderr_level(stderr_is_expected):
+    if stderr_is_expected:
+        return DEFAULT_STDOUT_LEVEL
+    return DEFAULT_STDERR_LEVEL
+
+
+def run(command, timeout=None, ignore_status=False,
+        stdout_tee=None, stderr_tee=None, verbose=True, stdin=None,
+        stderr_is_expected=None, args=()):
+    """
+    Run a command on the host.
+
+    @param command: the command line string.
+    @param timeout: time limit in seconds before attempting to kill the
+            running process. The run() function will take a few seconds
+            longer than 'timeout' to complete if it has to kill the process.
+    @param ignore_status: do not raise an exception, no matter what the exit
+            code of the command is.
+    @param stdout_tee: optional file-like object to which stdout data
+            will be written as it is generated (data will still be stored
+            in result.stdout).
+    @param stderr_tee: likewise for stderr.
+    @param verbose: if True, log the command being run.
+    @param stdin: stdin to pass to the executed process (can be a file
+            descriptor, a file object of a real file or a string).
+    @param args: sequence of strings of arguments to be given to the command
+            inside " quotes after they have been escaped for that; each
+            element in the sequence will be given as a separate command
+            argument
+
+    @return a CmdResult object
+
+    @raise CmdError: the exit code of the command execution was not 0
+    """
+    if isinstance(args, basestring):
+        raise TypeError('Got a string for the "args" keyword argument, '
+                        'need a sequence.')
+
+    for arg in args:
+        command += ' "%s"' % sh_escape(arg)
+    if stderr_is_expected is None:
+        stderr_is_expected = ignore_status
+
+    bg_job = join_bg_jobs(
+        (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
+               stderr_level=get_stderr_level(stderr_is_expected)),),
+        timeout)[0]
+    if not ignore_status and bg_job.result.exit_status:
+        raise error.CmdError(command, bg_job.result,
+                             "Command returned non-zero exit status")
+
+    return bg_job.result
+
+
+def run_parallel(commands, timeout=None, ignore_status=False,
+                 stdout_tee=None, stderr_tee=None):
+    """
+    Behaves the same as run() with the following exceptions:
+
+    - commands is a list of commands to run in parallel.
+    - ignore_status toggles whether or not an exception should be raised
+      on any error.
+
+    @return: a list of CmdResult objects
+    """
+    bg_jobs = []
+    for command in commands:
+        bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
+                             stderr_level=get_stderr_level(ignore_status)))
+
+    # Updates objects in bg_jobs list with their process information
+    join_bg_jobs(bg_jobs, timeout)
+
+    for bg_job in bg_jobs:
+        if not ignore_status and bg_job.result.exit_status:
+            raise error.CmdError(command, bg_job.result,
+                                 "Command returned non-zero exit status")
+
+    return [bg_job.result for bg_job in bg_jobs]
+
+
+@deprecated
+def run_bg(command):
+    """Function deprecated. Please use BgJob class instead."""
+    bg_job = BgJob(command)
+    return bg_job.sp, bg_job.result
+
+
+def join_bg_jobs(bg_jobs, timeout=None):
+    """Joins the bg_jobs with the current thread.
+
+    Returns the same list of bg_jobs objects that was passed in.
+    """
+    ret, timeout_error = 0, False
+    for bg_job in bg_jobs:
+        bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
+
+    try:
+        # We are holding ends to stdin, stdout pipes
+        # hence we need to be sure to close those fds no mater what
+        start_time = time.time()
+        timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
+
+        for bg_job in bg_jobs:
+            # Process stdout and stderr
+            bg_job.process_output(stdout=True,final_read=True)
+            bg_job.process_output(stdout=False,final_read=True)
+    finally:
+        # close our ends of the pipes to the sp no matter what
+        for bg_job in bg_jobs:
+            bg_job.cleanup()
+
+    if timeout_error:
+        # TODO: This needs to be fixed to better represent what happens when
+        # running in parallel. However this is backwards compatable, so it will
+        # do for the time being.
+        raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
+                             "Command(s) did not complete within %d seconds"
+                             % timeout)
+
+
+    return bg_jobs
+
+
+def _wait_for_commands(bg_jobs, start_time, timeout):
+    # This returns True if it must return due to a timeout, otherwise False.
+
+    # To check for processes which terminate without producing any output
+    # a 1 second timeout is used in select.
+    SELECT_TIMEOUT = 1
+
+    read_list = []
+    write_list = []
+    reverse_dict = {}
+
+    for bg_job in bg_jobs:
+        read_list.append(bg_job.sp.stdout)
+        read_list.append(bg_job.sp.stderr)
+        reverse_dict[bg_job.sp.stdout] = (bg_job, True)
+        reverse_dict[bg_job.sp.stderr] = (bg_job, False)
+        if bg_job.string_stdin is not None:
+            write_list.append(bg_job.sp.stdin)
+            reverse_dict[bg_job.sp.stdin] = bg_job
+
+    if timeout:
+        stop_time = start_time + timeout
+        time_left = stop_time - time.time()
+    else:
+        time_left = None # so that select never times out
+
+    while not timeout or time_left > 0:
+        # select will return when we may write to stdin or when there is
+        # stdout/stderr output we can read (including when it is
+        # EOF, that is the process has terminated).
+        read_ready, write_ready, _ = select.select(read_list, write_list, [],
+                                                   SELECT_TIMEOUT)
+
+        # os.read() has to be used instead of
+        # subproc.stdout.read() which will otherwise block
+        for file_obj in read_ready:
+            bg_job, is_stdout = reverse_dict[file_obj]
+            bg_job.process_output(is_stdout)
+
+        for file_obj in write_ready:
+            # we can write PIPE_BUF bytes without blocking
+            # POSIX requires PIPE_BUF is >= 512
+            bg_job = reverse_dict[file_obj]
+            file_obj.write(bg_job.string_stdin[:512])
+            bg_job.string_stdin = bg_job.string_stdin[512:]
+            # no more input data, close stdin, remove it from the select set
+            if not bg_job.string_stdin:
+                file_obj.close()
+                write_list.remove(file_obj)
+                del reverse_dict[file_obj]
+
+        all_jobs_finished = True
+        for bg_job in bg_jobs:
+            if bg_job.result.exit_status is not None:
+                continue
+
+            bg_job.result.exit_status = bg_job.sp.poll()
+            if bg_job.result.exit_status is not None:
+                # process exited, remove its stdout/stdin from the select set
+                bg_job.result.duration = time.time() - start_time
+                read_list.remove(bg_job.sp.stdout)
+                read_list.remove(bg_job.sp.stderr)
+                del reverse_dict[bg_job.sp.stdout]
+                del reverse_dict[bg_job.sp.stderr]
+            else:
+                all_jobs_finished = False
+
+        if all_jobs_finished:
+            return False
+
+        if timeout:
+            time_left = stop_time - time.time()
+
+    # Kill all processes which did not complete prior to timeout
+    for bg_job in bg_jobs:
+        if bg_job.result.exit_status is not None:
+            continue
+
+        logging.warn('run process timeout (%s) fired on: %s', timeout,
+                     bg_job.command)
+        nuke_subprocess(bg_job.sp)
+        bg_job.result.exit_status = bg_job.sp.poll()
+        bg_job.result.duration = time.time() - start_time
+
+    return True
+
+
+def pid_is_alive(pid):
+    """
+    True if process pid exists and is not yet stuck in Zombie state.
+    Zombies are impossible to move between cgroups, etc.
+    pid can be integer, or text of integer.
+    """
+    path = '/proc/%s/stat' % pid
+
+    try:
+        stat = read_one_line(path)
+    except IOError:
+        if not os.path.exists(path):
+            # file went away
+            return False
+        raise
+
+    return stat.split()[2] != 'Z'
+
+
+def signal_pid(pid, sig):
+    """
+    Sends a signal to a process id. Returns True if the process terminated
+    successfully, False otherwise.
+    """
+    try:
+        os.kill(pid, sig)
+    except OSError:
+        # The process may have died before we could kill it.
+        pass
+
+    for i in range(5):
+        if not pid_is_alive(pid):
+            return True
+        time.sleep(1)
+
+    # The process is still alive
+    return False
+
+
+def nuke_subprocess(subproc):
+    # check if the subprocess is still alive, first
+    if subproc.poll() is not None:
+        return subproc.poll()
+
+    # the process has not terminated within timeout,
+    # kill it via an escalating series of signals.
+    signal_queue = [signal.SIGTERM, signal.SIGKILL]
+    for sig in signal_queue:
+        signal_pid(subproc.pid, sig)
+        if subproc.poll() is not None:
+            return subproc.poll()
+
+
+def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
+    # the process has not terminated within timeout,
+    # kill it via an escalating series of signals.
+    for sig in signal_queue:
+        if signal_pid(pid, sig):
+            return
+
+    # no signal successfully terminated the process
+    raise error.AutoservRunError('Could not kill %d' % pid, None)
+
+
+def system(command, timeout=None, ignore_status=False):
+    """
+    Run a command
+
+    @param timeout: timeout in seconds
+    @param ignore_status: if ignore_status=False, throw an exception if the
+            command's exit code is non-zero
+            if ignore_stauts=True, return the exit code.
+
+    @return exit status of command
+            (note, this will always be zero unless ignore_status=True)
+    """
+    return run(command, timeout=timeout, ignore_status=ignore_status,
+               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
+
+
+def system_parallel(commands, timeout=None, ignore_status=False):
+    """This function returns a list of exit statuses for the respective
+    list of commands."""
+    return [bg_jobs.exit_status for bg_jobs in
+            run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
+                         stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
+
+
+def system_output(command, timeout=None, ignore_status=False,
+                  retain_output=False, args=()):
+    """
+    Run a command and return the stdout output.
+
+    @param command: command string to execute.
+    @param timeout: time limit in seconds before attempting to kill the
+            running process. The function will take a few seconds longer
+            than 'timeout' to complete if it has to kill the process.
+    @param ignore_status: do not raise an exception, no matter what the exit
+            code of the command is.
+    @param retain_output: set to True to make stdout/stderr of the command
+            output to be also sent to the logging system
+    @param args: sequence of strings of arguments to be given to the command
+            inside " quotes after they have been escaped for that; each
+            element in the sequence will be given as a separate command
+            argument
+
+    @return a string with the stdout output of the command.
+    """
+    if retain_output:
+        out = run(command, timeout=timeout, ignore_status=ignore_status,
+                  stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
+                  args=args).stdout
+    else:
+        out = run(command, timeout=timeout, ignore_status=ignore_status,
+                  args=args).stdout
+    if out[-1:] == '\n':
+        out = out[:-1]
+    return out
+
+
+def system_output_parallel(commands, timeout=None, ignore_status=False,
+                           retain_output=False):
+    if retain_output:
+        out = [bg_job.stdout for bg_job
+               in run_parallel(commands, timeout=timeout,
+                               ignore_status=ignore_status,
+                               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
+    else:
+        out = [bg_job.stdout for bg_job in run_parallel(commands,
+                                  timeout=timeout, ignore_status=ignore_status)]
+    for x in out:
+        if out[-1:] == '\n': out = out[:-1]
+    return out
+
+
+def strip_unicode(input):
+    if type(input) == list:
+        return [strip_unicode(i) for i in input]
+    elif type(input) == dict:
+        output = {}
+        for key in input.keys():
+            output[str(key)] = strip_unicode(input[key])
+        return output
+    elif type(input) == unicode:
+        return str(input)
+    else:
+        return input
+
+
+def get_cpu_percentage(function, *args, **dargs):
+    """Returns a tuple containing the CPU% and return value from function call.
+
+    This function calculates the usage time by taking the difference of
+    the user and system times both before and after the function call.
+    """
+    child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
+    self_pre = resource.getrusage(resource.RUSAGE_SELF)
+    start = time.time()
+    to_return = function(*args, **dargs)
+    elapsed = time.time() - start
+    self_post = resource.getrusage(resource.RUSAGE_SELF)
+    child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
+
+    # Calculate CPU Percentage
+    s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
+    c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
+    cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
+
+    return cpu_percent, to_return
+
+
+class SystemLoad(object):
+    """
+    Get system and/or process values and return average value of load.
+    """
+    def __init__(self, pids, advanced=False, time_step=0.1, cpu_cont=False,
+                 use_log=False):
+        """
+        @param pids: List of pids to be monitored. If pid = 0 whole system will
+          be monitored. pid == 0 means whole system.
+        @param advanced: monitor add value for system irq count and softirq
+          for process minor and maior page fault
+        @param time_step: Time step for continuous monitoring.
+        @param cpu_cont: If True monitor CPU load continuously.
+        @param use_log: If true every monitoring is logged for dump.
+        """
+        self.pids = []
+        self.stats = {}
+        for pid in pids:
+            if pid == 0:
+                cpu = FileFieldMonitor("/proc/stat",
+                                       [("cpu", 0), # User Time
+                                        ("cpu", 2), # System Time
+                                        ("intr", 0), # IRQ Count
+                                        ("softirq", 0)], # Soft IRQ Count
+                                       True,
+                                       cpu_cont,
+                                       use_log,
+                                       " +",
+                                       time_step)
+                mem = FileFieldMonitor("/proc/meminfo",
+                                       [("MemTotal:", 0), # Mem Total
+                                        ("MemFree:", 0), # Mem Free
+                                        ("Buffers:", 0), # Buffers
+                                        ("Cached:", 0)], # Cached
+                                       False,
+                                       True,
+                                       use_log,
+                                       " +",
+                                       time_step)
+                self.stats[pid] = ["TOTAL", cpu, mem]
+                self.pids.append(pid)
+            else:
+                name = ""
+                if (type(pid) is int):
+                    self.pids.append(pid)
+                    name = get_process_name(pid)
+                else:
+                    self.pids.append(pid[0])
+                    name = pid[1]
+
+                cpu = FileFieldMonitor("/proc/%d/stat" %
+                                       self.pids[-1],
+                                       [("", 13), # User Time
+                                        ("", 14), # System Time
+                                        ("", 9), # Minority Page Fault
+                                        ("", 11)], # Majority Page Fault
+                                       True,
+                                       cpu_cont,
+                                       use_log,
+                                       " +",
+                                       time_step)
+                mem = FileFieldMonitor("/proc/%d/status" %
+                                       self.pids[-1],
+                                       [("VmSize:", 0), # Virtual Memory Size
+                                        ("VmRSS:", 0), # Resident Set Size
+                                        ("VmPeak:", 0), # Peak VM Size
+                                        ("VmSwap:", 0)], # VM in Swap
+                                       False,
+                                       True,
+                                       use_log,
+                                       " +",
+                                       time_step)
+                self.stats[self.pids[-1]] = [name, cpu, mem]
+
+        self.advanced = advanced
+
+
+    def __str__(self):
+        """
+        Define format how to print
+        """
+        out = ""
+        for pid in self.pids:
+            for stat in self.stats[pid][1:]:
+                out += str(stat.get_status()) + "\n"
+        return out
+
+
+    def start(self, pids=[]):
+        """
+        Start monitoring of the process system usage.
+        @param pids: List of PIDs you intend to control. Use pids=[] to control
+            all defined PIDs.
+        """
+        if pids == []:
+            pids = self.pids
+
+        for pid in pids:
+            for stat in self.stats[pid][1:]:
+                stat.start()
+
+
+    def stop(self, pids=[]):
+        """
+        Stop monitoring of the process system usage.
+        @param pids: List of PIDs you intend to control. Use pids=[] to control
+            all defined PIDs.
+        """
+        if pids == []:
+            pids = self.pids
+
+        for pid in pids:
+            for stat in self.stats[pid][1:]:
+                stat.stop()
+
+
+    def dump(self, pids=[]):
+        """
+        Get the status of monitoring.
+        @param pids: List of PIDs you intend to control. Use pids=[] to control
+            all defined PIDs.
+         @return:
+            tuple([cpu load], [memory load]):
+                ([(PID1, (PID1_cpu_meas)), (PID2, (PID2_cpu_meas)), ...],
+                 [(PID1, (PID1_mem_meas)), (PID2, (PID2_mem_meas)), ...])
+
+            PID1_cpu_meas:
+                average_values[], test_time, cont_meas_values[[]], time_step
+            PID1_mem_meas:
+                average_values[], test_time, cont_meas_values[[]], time_step
+            where average_values[] are the measured values (mem_free,swap,...)
+            which are described in SystemLoad.__init__()-FileFieldMonitor.
+            cont_meas_values[[]] is a list of average_values in the sampling
+            times.
+        """
+        if pids == []:
+            pids = self.pids
+
+        cpus = []
+        memory = []
+        for pid in pids:
+            stat = (pid, self.stats[pid][1].get_status())
+            cpus.append(stat)
+        for pid in pids:
+            stat = (pid, self.stats[pid][2].get_status())
+            memory.append(stat)
+
+        return (cpus, memory)
+
+
+    def get_cpu_status_string(self, pids=[]):
+        """
+        Convert status to string array.
+        @param pids: List of PIDs you intend to control. Use pids=[] to control
+            all defined PIDs.
+        @return: String format to table.
+        """
+        if pids == []:
+            pids = self.pids
+
+        headers = ["NAME",
+                   ("%7s") % "PID",
+                   ("%5s") % "USER",
+                   ("%5s") % "SYS",
+                   ("%5s") % "SUM"]
+        if self.advanced:
+            headers.extend(["MINFLT/IRQC",
+                            "MAJFLT/SOFTIRQ"])
+        headers.append(("%11s") % "TIME")
+        textstatus = []
+        for pid in pids:
+            stat = self.stats[pid][1].get_status()
+            time = stat[1]
+            stat = stat[0]
+            textstatus.append(["%s" % self.stats[pid][0],
+                               "%7s" % pid,
+                               "%4.0f%%" % (stat[0] / time),
+                               "%4.0f%%" % (stat[1] / time),
+                               "%4.0f%%" % ((stat[0] + stat[1]) / time),
+                               "%10.3fs" % time])
+            if self.advanced:
+                textstatus[-1].insert(-1, "%11d" % stat[2])
+                textstatus[-1].insert(-1, "%14d" % stat[3])
+
+        return matrix_to_string(textstatus, tuple(headers))
+
+
+    def get_mem_status_string(self, pids=[]):
+        """
+        Convert status to string array.
+        @param pids: List of PIDs you intend to control. Use pids=[] to control
+            all defined PIDs.
+        @return: String format to table.
+        """
+        if pids == []:
+            pids = self.pids
+
+        headers = ["NAME",
+                   ("%7s") % "PID",
+                   ("%8s") % "TOTAL/VMSIZE",
+                   ("%8s") % "FREE/VMRSS",
+                   ("%8s") % "BUFFERS/VMPEAK",
+                   ("%8s") % "CACHED/VMSWAP",
+                   ("%11s") % "TIME"]
+        textstatus = []
+        for pid in pids:
+            stat = self.stats[pid][2].get_status()
+            time = stat[1]
+            stat = stat[0]
+            textstatus.append(["%s" % self.stats[pid][0],
+                               "%7s" % pid,
+                               "%10dMB" % (stat[0] / 1024),
+                               "%8dMB" % (stat[1] / 1024),
+                               "%12dMB" % (stat[2] / 1024),
+                               "%11dMB" % (stat[3] / 1024),
+                               "%10.3fs" % time])
+
+        return matrix_to_string(textstatus, tuple(headers))
+
+
+def get_arch(run_function=run):
+    """
+    Get the hardware architecture of the machine.
+    run_function is used to execute the commands. It defaults to
+    utils.run() but a custom method (if provided) should be of the
+    same schema as utils.run. It should return a CmdResult object and
+    throw a CmdError exception.
+    """
+    arch = run_function('/bin/uname -m').stdout.rstrip()
+    if re.match(r'i\d86$', arch):
+        arch = 'i386'
+    return arch
+
+
+def get_num_logical_cpus_per_socket(run_function=run):
+    """
+    Get the number of cores (including hyperthreading) per cpu.
+    run_function is used to execute the commands. It defaults to
+    utils.run() but a custom method (if provided) should be of the
+    same schema as utils.run. It should return a CmdResult object and
+    throw a CmdError exception.
+    """
+    siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
+    num_siblings = map(int,
+                       re.findall(r'^siblings\s*:\s*(\d+)\s*$',
+                                  siblings, re.M))
+    if len(num_siblings) == 0:
+        raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
+    if min(num_siblings) != max(num_siblings):
+        raise error.TestError('Number of siblings differ %r' %
+                              num_siblings)
+    return num_siblings[0]
+
+
+def merge_trees(src, dest):
+    """
+    Merges a source directory tree at 'src' into a destination tree at
+    'dest'. If a path is a file in both trees than the file in the source
+    tree is APPENDED to the one in the destination tree. If a path is
+    a directory in both trees then the directories are recursively merged
+    with this function. In any other case, the function will skip the
+    paths that cannot be merged (instead of failing).
+    """
+    if not os.path.exists(src):
+        return # exists only in dest
+    elif not os.path.exists(dest):
+        if os.path.isfile(src):
+            shutil.copy2(src, dest) # file only in src
+        else:
+            shutil.copytree(src, dest, symlinks=True) # dir only in src
+        return
+    elif os.path.isfile(src) and os.path.isfile(dest):
+        # src & dest are files in both trees, append src to dest
+        destfile = open(dest, "a")
+        try:
+            srcfile = open(src)
+            try:
+                destfile.write(srcfile.read())
+            finally:
+                srcfile.close()
+        finally:
+            destfile.close()
+    elif os.path.isdir(src) and os.path.isdir(dest):
+        # src & dest are directories in both trees, so recursively merge
+        for name in os.listdir(src):
+            merge_trees(os.path.join(src, name), os.path.join(dest, name))
+    else:
+        # src & dest both exist, but are incompatible
+        return
+
+
+class CmdResult(object):
+    """
+    Command execution result.
+
+    command:     String containing the command line itself
+    exit_status: Integer exit code of the process
+    stdout:      String containing stdout of the process
+    stderr:      String containing stderr of the process
+    duration:    Elapsed wall clock time running the process
+    """
+
+
+    def __init__(self, command="", stdout="", stderr="",
+                 exit_status=None, duration=0):
+        self.command = command
+        self.exit_status = exit_status
+        self.stdout = stdout
+        self.stderr = stderr
+        self.duration = duration
+
+
+    def __repr__(self):
+        wrapper = textwrap.TextWrapper(width = 78,
+                                       initial_indent="\n    ",
+                                       subsequent_indent="    ")
+
+        stdout = self.stdout.rstrip()
+        if stdout:
+            stdout = "\nstdout:\n%s" % stdout
+
+        stderr = self.stderr.rstrip()
+        if stderr:
+            stderr = "\nstderr:\n%s" % stderr
+
+        return ("* Command: %s\n"
+                "Exit status: %s\n"
+                "Duration: %s\n"
+                "%s"
+                "%s"
+                % (wrapper.fill(self.command), self.exit_status,
+                self.duration, stdout, stderr))
+
+
+class run_randomly:
+    def __init__(self, run_sequentially=False):
+        # Run sequentially is for debugging control files
+        self.test_list = []
+        self.run_sequentially = run_sequentially
+
+
+    def add(self, *args, **dargs):
+        test = (args, dargs)
+        self.test_list.append(test)
+
+
+    def run(self, fn):
+        while self.test_list:
+            test_index = random.randint(0, len(self.test_list)-1)
+            if self.run_sequentially:
+                test_index = 0
+            (args, dargs) = self.test_list.pop(test_index)
+            fn(*args, **dargs)
+
+
+def import_site_module(path, module, dummy=None, modulefile=None):
+    """
+    Try to import the site specific module if it exists.
+
+    @param path full filename of the source file calling this (ie __file__)
+    @param module full module name
+    @param dummy dummy value to return in case there is no symbol to import
+    @param modulefile module filename
+
+    @return site specific module or dummy
+
+    @raises ImportError if the site file exists but imports fails
+    """
+    short_module = module[module.rfind(".") + 1:]
+
+    if not modulefile:
+        modulefile = short_module + ".py"
+
+    if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
+        return __import__(module, {}, {}, [short_module])
+    return dummy
+
+
+def import_site_symbol(path, module, name, dummy=None, modulefile=None):
+    """
+    Try to import site specific symbol from site specific file if it exists
+
+    @param path full filename of the source file calling this (ie __file__)
+    @param module full module name
+    @param name symbol name to be imported from the site file
+    @param dummy dummy value to return in case there is no symbol to import
+    @param modulefile module filename
+
+    @return site specific symbol or dummy
+
+    @raises ImportError if the site file exists but imports fails
+    """
+    module = import_site_module(path, module, modulefile=modulefile)
+    if not module:
+        return dummy
+
+    # special unique value to tell us if the symbol can't be imported
+    cant_import = object()
+
+    obj = getattr(module, name, cant_import)
+    if obj is cant_import:
+        logging.debug("unable to import site symbol '%s', using non-site "
+                      "implementation", name)
+        return dummy
+
+    return obj
+
+
+def import_site_class(path, module, classname, baseclass, modulefile=None):
+    """
+    Try to import site specific class from site specific file if it exists
+
+    Args:
+        path: full filename of the source file calling this (ie __file__)
+        module: full module name
+        classname: class name to be loaded from site file
+        baseclass: base class object to return when no site file present or
+            to mixin when site class exists but is not inherited from baseclass
+        modulefile: module filename
+
+    Returns: baseclass if site specific class does not exist, the site specific
+        class if it exists and is inherited from baseclass or a mixin of the
+        site specific class and baseclass when the site specific class exists
+        and is not inherited from baseclass
+
+    Raises: ImportError if the site file exists but imports fails
+    """
+
+    res = import_site_symbol(path, module, classname, None, modulefile)
+    if res:
+        if not issubclass(res, baseclass):
+            # if not a subclass of baseclass then mix in baseclass with the
+            # site specific class object and return the result
+            res = type(classname, (res, baseclass), {})
+    else:
+        res = baseclass
+
+    return res
+
+
+def import_site_function(path, module, funcname, dummy, modulefile=None):
+    """
+    Try to import site specific function from site specific file if it exists
+
+    Args:
+        path: full filename of the source file calling this (ie __file__)
+        module: full module name
+        funcname: function name to be imported from site file
+        dummy: dummy function to return in case there is no function to import
+        modulefile: module filename
+
+    Returns: site specific function object or dummy
+
+    Raises: ImportError if the site file exists but imports fails
+    """
+
+    return import_site_symbol(path, module, funcname, dummy, modulefile)
+
+
+def _get_pid_path(program_name):
+    my_path = os.path.dirname(__file__)
+    return os.path.abspath(os.path.join(my_path, "..", "..",
+                                        "%s.pid" % program_name))
+
+
+def write_pid(program_name):
+    """
+    Try to drop <program_name>.pid in the main autotest directory.
+
+    Args:
+      program_name: prefix for file name
+    """
+    pidfile = open(_get_pid_path(program_name), "w")
+    try:
+        pidfile.write("%s\n" % os.getpid())
+    finally:
+        pidfile.close()
+
+
+def delete_pid_file_if_exists(program_name):
+    """
+    Tries to remove <program_name>.pid from the main autotest directory.
+    """
+    pidfile_path = _get_pid_path(program_name)
+
+    try:
+        os.remove(pidfile_path)
+    except OSError:
+        if not os.path.exists(pidfile_path):
+            return
+        raise
+
+
+def get_pid_from_file(program_name):
+    """
+    Reads the pid from <program_name>.pid in the autotest directory.
+
+    @param program_name the name of the program
+    @return the pid if the file exists, None otherwise.
+    """
+    pidfile_path = _get_pid_path(program_name)
+    if not os.path.exists(pidfile_path):
+        return None
+
+    pidfile = open(_get_pid_path(program_name), 'r')
+
+    try:
+        try:
+            pid = int(pidfile.readline())
+        except IOError:
+            if not os.path.exists(pidfile_path):
+                return None
+            raise
+    finally:
+        pidfile.close()
+
+    return pid
+
+
+def get_process_name(pid):
+    """
+    Get process name from PID.
+    @param pid: PID of process.
+    """
+    return get_field(read_file("/proc/%d/stat" % pid), 1)[1:-1]
+
+
+def program_is_alive(program_name):
+    """
+    Checks if the process is alive and not in Zombie state.
+
+    @param program_name the name of the program
+    @return True if still alive, False otherwise
+    """
+    pid = get_pid_from_file(program_name)
+    if pid is None:
+        return False
+    return pid_is_alive(pid)
+
+
+def signal_program(program_name, sig=signal.SIGTERM):
+    """
+    Sends a signal to the process listed in <program_name>.pid
+
+    @param program_name the name of the program
+    @param sig signal to send
+    """
+    pid = get_pid_from_file(program_name)
+    if pid:
+        signal_pid(pid, sig)
+
+
+def get_relative_path(path, reference):
+    """Given 2 absolute paths "path" and "reference", compute the path of
+    "path" as relative to the directory "reference".
+
+    @param path the absolute path to convert to a relative path
+    @param reference an absolute directory path to which the relative
+        path will be computed
+    """
+    # normalize the paths (remove double slashes, etc)
+    assert(os.path.isabs(path))
+    assert(os.path.isabs(reference))
+
+    path = os.path.normpath(path)
+    reference = os.path.normpath(reference)
+
+    # we could use os.path.split() but it splits from the end
+    path_list = path.split(os.path.sep)[1:]
+    ref_list = reference.split(os.path.sep)[1:]
+
+    # find the longest leading common path
+    for i in xrange(min(len(path_list), len(ref_list))):
+        if path_list[i] != ref_list[i]:
+            # decrement i so when exiting this loop either by no match or by
+            # end of range we are one step behind
+            i -= 1
+            break
+    i += 1
+    # drop the common part of the paths, not interested in that anymore
+    del path_list[:i]
+
+    # for each uncommon component in the reference prepend a ".."
+    path_list[:0] = ['..'] * (len(ref_list) - i)
+
+    return os.path.join(*path_list)
+
+
+def sh_escape(command):
+    """
+    Escape special characters from a command so that it can be passed
+    as a double quoted (" ") string in a (ba)sh command.
+
+    Args:
+            command: the command string to escape.
+
+    Returns:
+            The escaped command string. The required englobing double
+            quotes are NOT added and so should be added at some point by
+            the caller.
+
+    See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
+    """
+    command = command.replace("\\", "\\\\")
+    command = command.replace("$", r'\$')
+    command = command.replace('"', r'\"')
+    command = command.replace('`', r'\`')
+    return command
+
+
+def configure(extra=None, configure='./configure'):
+    """
+    Run configure passing in the correct host, build, and target options.
+
+    @param extra: extra command line arguments to pass to configure
+    @param configure: which configure script to use
+    """
+    args = []
+    if 'CHOST' in os.environ:
+        args.append('--host=' + os.environ['CHOST'])
+    if 'CBUILD' in os.environ:
+        args.append('--build=' + os.environ['CBUILD'])
+    if 'CTARGET' in os.environ:
+        args.append('--target=' + os.environ['CTARGET'])
+    if extra:
+        args.append(extra)
+
+    system('%s %s' % (configure, ' '.join(args)))
+
+
+def make(extra='', make='make', timeout=None, ignore_status=False):
+    """
+    Run make, adding MAKEOPTS to the list of options.
+
+    @param extra: extra command line arguments to pass to make.
+    """
+    cmd = '%s %s %s' % (make, os.environ.get('MAKEOPTS', ''), extra)
+    return system(cmd, timeout=timeout, ignore_status=ignore_status)
+
+
+def compare_versions(ver1, ver2):
+    """Version number comparison between ver1 and ver2 strings.
+
+    >>> compare_tuple("1", "2")
+    -1
+    >>> compare_tuple("foo-1.1", "foo-1.2")
+    -1
+    >>> compare_tuple("1.2", "1.2a")
+    -1
+    >>> compare_tuple("1.2b", "1.2a")
+    1
+    >>> compare_tuple("1.3.5.3a", "1.3.5.3b")
+    -1
+
+    Args:
+        ver1: version string
+        ver2: version string
+
+    Returns:
+        int:  1 if ver1 >  ver2
+              0 if ver1 == ver2
+             -1 if ver1 <  ver2
+    """
+    ax = re.split('[.-]', ver1)
+    ay = re.split('[.-]', ver2)
+    while len(ax) > 0 and len(ay) > 0:
+        cx = ax.pop(0)
+        cy = ay.pop(0)
+        maxlen = max(len(cx), len(cy))
+        c = cmp(cx.zfill(maxlen), cy.zfill(maxlen))
+        if c != 0:
+            return c
+    return cmp(len(ax), len(ay))
+
+
+def args_to_dict(args):
+    """Convert autoserv extra arguments in the form of key=val or key:val to a
+    dictionary.  Each argument key is converted to lowercase dictionary key.
+
+    Args:
+        args - list of autoserv extra arguments.
+
+    Returns:
+        dictionary
+    """
+    arg_re = re.compile(r'(\w+)[:=](.*)$')
+    dict = {}
+    for arg in args:
+        match = arg_re.match(arg)
+        if match:
+            dict[match.group(1).lower()] = match.group(2)
+        else:
+            logging.warning("args_to_dict: argument '%s' doesn't match "
+                            "'%s' pattern. Ignored." % (arg, arg_re.pattern))
+    return dict
+
+
+def get_unused_port():
+    """
+    Finds a semi-random available port. A race condition is still
+    possible after the port number is returned, if another process
+    happens to bind it.
+
+    Returns:
+        A port number that is unused on both TCP and UDP.
+    """
+
+    def try_bind(port, socket_type, socket_proto):
+        s = socket.socket(socket.AF_INET, socket_type, socket_proto)
+        try:
+            try:
+                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+                s.bind(('', port))
+                return s.getsockname()[1]
+            except socket.error:
+                return None
+        finally:
+            s.close()
+
+    # On the 2.6 kernel, calling try_bind() on UDP socket returns the
+    # same port over and over. So always try TCP first.
+    while True:
+        # Ask the OS for an unused port.
+        port = try_bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP)
+        # Check if this port is unused on the other protocol.
+        if port and try_bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP):
+            return port
diff --git a/client/common_lib/base_utils_unittest.py b/client/common_lib/base_utils_unittest.py
new file mode 100755
index 0000000..de5983c
--- /dev/null
+++ b/client/common_lib/base_utils_unittest.py
@@ -0,0 +1,799 @@
+#!/usr/bin/python
+
+import os, unittest, StringIO, socket, urllib2, shutil, subprocess, logging
+
+import common
+from autotest_lib.client.common_lib import base_utils, autotemp
+from autotest_lib.client.common_lib.test_utils import mock
+
+
+class test_read_one_line(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+        self.god.stub_function(base_utils, "open")
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def test_ip_to_long(self):
+        self.assertEqual(base_utils.ip_to_long('0.0.0.0'), 0)
+        self.assertEqual(base_utils.ip_to_long('255.255.255.255'), 4294967295)
+        self.assertEqual(base_utils.ip_to_long('192.168.0.1'), 3232235521)
+        self.assertEqual(base_utils.ip_to_long('1.2.4.8'), 16909320)
+
+
+    def test_long_to_ip(self):
+        self.assertEqual(base_utils.long_to_ip(0), '0.0.0.0')
+        self.assertEqual(base_utils.long_to_ip(4294967295), '255.255.255.255')
+        self.assertEqual(base_utils.long_to_ip(3232235521), '192.168.0.1')
+        self.assertEqual(base_utils.long_to_ip(16909320), '1.2.4.8')
+
+
+    def test_create_subnet_mask(self):
+        self.assertEqual(base_utils.create_subnet_mask(0), 0)
+        self.assertEqual(base_utils.create_subnet_mask(32), 4294967295)
+        self.assertEqual(base_utils.create_subnet_mask(25), 4294967168)
+
+
+    def test_format_ip_with_mask(self):
+        self.assertEqual(base_utils.format_ip_with_mask('192.168.0.1', 0),
+                         '0.0.0.0/0')
+        self.assertEqual(base_utils.format_ip_with_mask('192.168.0.1', 32),
+                         '192.168.0.1/32')
+        self.assertEqual(base_utils.format_ip_with_mask('192.168.0.1', 26),
+                         '192.168.0.0/26')
+        self.assertEqual(base_utils.format_ip_with_mask('192.168.0.255', 26),
+                         '192.168.0.192/26')
+
+
+    def create_test_file(self, contents):
+        test_file = StringIO.StringIO(contents)
+        base_utils.open.expect_call("filename", "r").and_return(test_file)
+
+
+    def test_reads_one_line_file(self):
+        self.create_test_file("abc\n")
+        self.assertEqual("abc", base_utils.read_one_line("filename"))
+        self.god.check_playback()
+
+
+    def test_strips_read_lines(self):
+        self.create_test_file("abc   \n")
+        self.assertEqual("abc   ", base_utils.read_one_line("filename"))
+        self.god.check_playback()
+
+
+    def test_drops_extra_lines(self):
+        self.create_test_file("line 1\nline 2\nline 3\n")
+        self.assertEqual("line 1", base_utils.read_one_line("filename"))
+        self.god.check_playback()
+
+
+    def test_works_on_empty_file(self):
+        self.create_test_file("")
+        self.assertEqual("", base_utils.read_one_line("filename"))
+        self.god.check_playback()
+
+
+    def test_works_on_file_with_no_newlines(self):
+        self.create_test_file("line but no newline")
+        self.assertEqual("line but no newline",
+                         base_utils.read_one_line("filename"))
+        self.god.check_playback()
+
+
+    def test_preserves_leading_whitespace(self):
+        self.create_test_file("   has leading whitespace")
+        self.assertEqual("   has leading whitespace",
+                         base_utils.read_one_line("filename"))
+
+
+class test_write_one_line(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+        self.god.stub_function(base_utils, "open")
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def get_write_one_line_output(self, content):
+        test_file = mock.SaveDataAfterCloseStringIO()
+        base_utils.open.expect_call("filename", "w").and_return(test_file)
+        base_utils.write_one_line("filename", content)
+        self.god.check_playback()
+        return test_file.final_data
+
+
+    def test_writes_one_line_file(self):
+        self.assertEqual("abc\n", self.get_write_one_line_output("abc"))
+
+
+    def test_preserves_existing_newline(self):
+        self.assertEqual("abc\n", self.get_write_one_line_output("abc\n"))
+
+
+    def test_preserves_leading_whitespace(self):
+        self.assertEqual("   abc\n", self.get_write_one_line_output("   abc"))
+
+
+    def test_preserves_trailing_whitespace(self):
+        self.assertEqual("abc   \n", self.get_write_one_line_output("abc   "))
+
+
+    def test_handles_empty_input(self):
+        self.assertEqual("\n", self.get_write_one_line_output(""))
+
+
+class test_open_write_close(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+        self.god.stub_function(base_utils, "open")
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def test_simple_functionality(self):
+        data = "\n\nwhee\n"
+        test_file = mock.SaveDataAfterCloseStringIO()
+        base_utils.open.expect_call("filename", "w").and_return(test_file)
+        base_utils.open_write_close("filename", data)
+        self.god.check_playback()
+        self.assertEqual(data, test_file.final_data)
+
+
+class test_read_keyval(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+        self.god.stub_function(base_utils, "open")
+        self.god.stub_function(os.path, "isdir")
+        self.god.stub_function(os.path, "exists")
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def create_test_file(self, filename, contents):
+        test_file = StringIO.StringIO(contents)
+        os.path.exists.expect_call(filename).and_return(True)
+        base_utils.open.expect_call(filename).and_return(test_file)
+
+
+    def read_keyval(self, contents):
+        os.path.isdir.expect_call("file").and_return(False)
+        self.create_test_file("file", contents)
+        keyval = base_utils.read_keyval("file")
+        self.god.check_playback()
+        return keyval
+
+
+    def test_returns_empty_when_file_doesnt_exist(self):
+        os.path.isdir.expect_call("file").and_return(False)
+        os.path.exists.expect_call("file").and_return(False)
+        self.assertEqual({}, base_utils.read_keyval("file"))
+        self.god.check_playback()
+
+
+    def test_accesses_files_directly(self):
+        os.path.isdir.expect_call("file").and_return(False)
+        self.create_test_file("file", "")
+        base_utils.read_keyval("file")
+        self.god.check_playback()
+
+
+    def test_accesses_directories_through_keyval_file(self):
+        os.path.isdir.expect_call("dir").and_return(True)
+        self.create_test_file("dir/keyval", "")
+        base_utils.read_keyval("dir")
+        self.god.check_playback()
+
+
+    def test_values_are_rstripped(self):
+        keyval = self.read_keyval("a=b   \n")
+        self.assertEquals(keyval, {"a": "b"})
+
+
+    def test_comments_are_ignored(self):
+        keyval = self.read_keyval("a=b # a comment\n")
+        self.assertEquals(keyval, {"a": "b"})
+
+
+    def test_integers_become_ints(self):
+        keyval = self.read_keyval("a=1\n")
+        self.assertEquals(keyval, {"a": 1})
+        self.assertEquals(int, type(keyval["a"]))
+
+
+    def test_float_values_become_floats(self):
+        keyval = self.read_keyval("a=1.5\n")
+        self.assertEquals(keyval, {"a": 1.5})
+        self.assertEquals(float, type(keyval["a"]))
+
+
+    def test_multiple_lines(self):
+        keyval = self.read_keyval("a=one\nb=two\n")
+        self.assertEquals(keyval, {"a": "one", "b": "two"})
+
+
+    def test_the_last_duplicate_line_is_used(self):
+        keyval = self.read_keyval("a=one\nb=two\na=three\n")
+        self.assertEquals(keyval, {"a": "three", "b": "two"})
+
+
+    def test_extra_equals_are_included_in_values(self):
+        keyval = self.read_keyval("a=b=c\n")
+        self.assertEquals(keyval, {"a": "b=c"})
+
+
+    def test_non_alphanumeric_keynames_are_rejected(self):
+        self.assertRaises(ValueError, self.read_keyval, "a$=one\n")
+
+
+    def test_underscores_are_allowed_in_key_names(self):
+        keyval = self.read_keyval("a_b=value\n")
+        self.assertEquals(keyval, {"a_b": "value"})
+
+
+    def test_dashes_are_allowed_in_key_names(self):
+        keyval = self.read_keyval("a-b=value\n")
+        self.assertEquals(keyval, {"a-b": "value"})
+
+
+class test_write_keyval(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+        self.god.stub_function(base_utils, "open")
+        self.god.stub_function(os.path, "isdir")
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def assertHasLines(self, value, lines):
+        vlines = value.splitlines()
+        vlines.sort()
+        self.assertEquals(vlines, sorted(lines))
+
+
+    def write_keyval(self, filename, dictionary, expected_filename=None,
+                     type_tag=None):
+        if expected_filename is None:
+            expected_filename = filename
+        test_file = StringIO.StringIO()
+        self.god.stub_function(test_file, "close")
+        base_utils.open.expect_call(expected_filename,
+                                    "a").and_return(test_file)
+        test_file.close.expect_call()
+        if type_tag is None:
+            base_utils.write_keyval(filename, dictionary)
+        else:
+            base_utils.write_keyval(filename, dictionary, type_tag)
+        return test_file.getvalue()
+
+
+    def write_keyval_file(self, dictionary, type_tag=None):
+        os.path.isdir.expect_call("file").and_return(False)
+        return self.write_keyval("file", dictionary, type_tag=type_tag)
+
+
+    def test_accesses_files_directly(self):
+        os.path.isdir.expect_call("file").and_return(False)
+        result = self.write_keyval("file", {"a": "1"})
+        self.assertEquals(result, "a=1\n")
+
+
+    def test_accesses_directories_through_keyval_file(self):
+        os.path.isdir.expect_call("dir").and_return(True)
+        result = self.write_keyval("dir", {"b": "2"}, "dir/keyval")
+        self.assertEquals(result, "b=2\n")
+
+
+    def test_numbers_are_stringified(self):
+        result = self.write_keyval_file({"c": 3})
+        self.assertEquals(result, "c=3\n")
+
+
+    def test_type_tags_are_excluded_by_default(self):
+        result = self.write_keyval_file({"d": "a string"})
+        self.assertEquals(result, "d=a string\n")
+        self.assertRaises(ValueError, self.write_keyval_file,
+                          {"d{perf}": "a string"})
+
+
+    def test_perf_tags_are_allowed(self):
+        result = self.write_keyval_file({"a{perf}": 1, "b{perf}": 2},
+                                        type_tag="perf")
+        self.assertHasLines(result, ["a{perf}=1", "b{perf}=2"])
+        self.assertRaises(ValueError, self.write_keyval_file,
+                          {"a": 1, "b": 2}, type_tag="perf")
+
+
+    def test_non_alphanumeric_keynames_are_rejected(self):
+        self.assertRaises(ValueError, self.write_keyval_file, {"x$": 0})
+
+
+    def test_underscores_are_allowed_in_key_names(self):
+        result = self.write_keyval_file({"a_b": "value"})
+        self.assertEquals(result, "a_b=value\n")
+
+
+    def test_dashes_are_allowed_in_key_names(self):
+        result = self.write_keyval_file({"a-b": "value"})
+        self.assertEquals(result, "a-b=value\n")
+
+
+class test_is_url(unittest.TestCase):
+    def test_accepts_http(self):
+        self.assertTrue(base_utils.is_url("http://example.com"))
+
+
+    def test_accepts_ftp(self):
+        self.assertTrue(base_utils.is_url("ftp://ftp.example.com"))
+
+
+    def test_rejects_local_path(self):
+        self.assertFalse(base_utils.is_url("/home/username/file"))
+
+
+    def test_rejects_local_filename(self):
+        self.assertFalse(base_utils.is_url("filename"))
+
+
+    def test_rejects_relative_local_path(self):
+        self.assertFalse(base_utils.is_url("somedir/somesubdir/file"))
+
+
+    def test_rejects_local_path_containing_url(self):
+        self.assertFalse(base_utils.is_url("somedir/http://path/file"))
+
+
+class test_urlopen(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def stub_urlopen_with_timeout_comparison(self, test_func, expected_return,
+                                             *expected_args):
+        expected_args += (None,) * (2 - len(expected_args))
+        def urlopen(url, data=None):
+            self.assertEquals(expected_args, (url,data))
+            test_func(socket.getdefaulttimeout())
+            return expected_return
+        self.god.stub_with(urllib2, "urlopen", urlopen)
+
+
+    def stub_urlopen_with_timeout_check(self, expected_timeout,
+                                        expected_return, *expected_args):
+        def test_func(timeout):
+            self.assertEquals(timeout, expected_timeout)
+        self.stub_urlopen_with_timeout_comparison(test_func, expected_return,
+                                                  *expected_args)
+
+
+    def test_timeout_set_during_call(self):
+        self.stub_urlopen_with_timeout_check(30, "retval", "url")
+        retval = base_utils.urlopen("url", timeout=30)
+        self.assertEquals(retval, "retval")
+
+
+    def test_timeout_reset_after_call(self):
+        old_timeout = socket.getdefaulttimeout()
+        self.stub_urlopen_with_timeout_check(30, None, "url")
+        try:
+            socket.setdefaulttimeout(1234)
+            base_utils.urlopen("url", timeout=30)
+            self.assertEquals(1234, socket.getdefaulttimeout())
+        finally:
+            socket.setdefaulttimeout(old_timeout)
+
+
+    def test_timeout_set_by_default(self):
+        def test_func(timeout):
+            self.assertTrue(timeout is not None)
+        self.stub_urlopen_with_timeout_comparison(test_func, None, "url")
+        base_utils.urlopen("url")
+
+
+    def test_args_are_untouched(self):
+        self.stub_urlopen_with_timeout_check(30, None, "http://url",
+                                             "POST data")
+        base_utils.urlopen("http://url", timeout=30, data="POST data")
+
+
+class test_urlretrieve(unittest.TestCase):
+    def setUp(self):
+        self.god = mock.mock_god()
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def test_urlopen_passed_arguments(self):
+        self.god.stub_function(base_utils, "urlopen")
+        self.god.stub_function(base_utils.shutil, "copyfileobj")
+        self.god.stub_function(base_utils, "open")
+
+        url = "url"
+        dest = "somefile"
+        data = object()
+        timeout = 10
+
+        src_file = self.god.create_mock_class(file, "file")
+        dest_file = self.god.create_mock_class(file, "file")
+
+        (base_utils.urlopen.expect_call(url, data=data, timeout=timeout)
+                .and_return(src_file))
+        base_utils.open.expect_call(dest, "wb").and_return(dest_file)
+        base_utils.shutil.copyfileobj.expect_call(src_file, dest_file)
+        dest_file.close.expect_call()
+        src_file.close.expect_call()
+
+        base_utils.urlretrieve(url, dest, data=data, timeout=timeout)
+        self.god.check_playback()
+
+
+class test_merge_trees(unittest.TestCase):
+    # a some path-handling helper functions
+    def src(self, *path_segments):
+        return os.path.join(self.src_tree.name, *path_segments)
+
+
+    def dest(self, *path_segments):
+        return os.path.join(self.dest_tree.name, *path_segments)
+
+
+    def paths(self, *path_segments):
+        return self.src(*path_segments), self.dest(*path_segments)
+
+
+    def assertFileEqual(self, *path_segments):
+        src, dest = self.paths(*path_segments)
+        self.assertEqual(True, os.path.isfile(src))
+        self.assertEqual(True, os.path.isfile(dest))
+        self.assertEqual(os.path.getsize(src), os.path.getsize(dest))
+        self.assertEqual(open(src).read(), open(dest).read())
+
+
+    def assertFileContents(self, contents, *path_segments):
+        dest = self.dest(*path_segments)
+        self.assertEqual(True, os.path.isfile(dest))
+        self.assertEqual(os.path.getsize(dest), len(contents))
+        self.assertEqual(contents, open(dest).read())
+
+
+    def setUp(self):
+        self.src_tree = autotemp.tempdir(unique_id='utilsrc')
+        self.dest_tree = autotemp.tempdir(unique_id='utilsdest')
+
+        # empty subdirs
+        os.mkdir(self.src("empty"))
+        os.mkdir(self.dest("empty"))
+
+
+    def tearDown(self):
+        self.src_tree.clean()
+        self.dest_tree.clean()
+
+
+    def test_both_dont_exist(self):
+        base_utils.merge_trees(*self.paths("empty"))
+
+
+    def test_file_only_at_src(self):
+        print >> open(self.src("src_only"), "w"), "line 1"
+        base_utils.merge_trees(*self.paths("src_only"))
+        self.assertFileEqual("src_only")
+
+
+    def test_file_only_at_dest(self):
+        print >> open(self.dest("dest_only"), "w"), "line 1"
+        base_utils.merge_trees(*self.paths("dest_only"))
+        self.assertEqual(False, os.path.exists(self.src("dest_only")))
+        self.assertFileContents("line 1\n", "dest_only")
+
+
+    def test_file_at_both(self):
+        print >> open(self.dest("in_both"), "w"), "line 1"
+        print >> open(self.src("in_both"), "w"), "line 2"
+        base_utils.merge_trees(*self.paths("in_both"))
+        self.assertFileContents("line 1\nline 2\n", "in_both")
+
+
+    def test_directory_with_files_in_both(self):
+        print >> open(self.dest("in_both"), "w"), "line 1"
+        print >> open(self.src("in_both"), "w"), "line 3"
+        base_utils.merge_trees(*self.paths())
+        self.assertFileContents("line 1\nline 3\n", "in_both")
+
+
+    def test_directory_with_mix_of_files(self):
+        print >> open(self.dest("in_dest"), "w"), "dest line"
+        print >> open(self.src("in_src"), "w"), "src line"
+        base_utils.merge_trees(*self.paths())
+        self.assertFileContents("dest line\n", "in_dest")
+        self.assertFileContents("src line\n", "in_src")
+
+
+    def test_directory_with_subdirectories(self):
+        os.mkdir(self.src("src_subdir"))
+        print >> open(self.src("src_subdir", "subfile"), "w"), "subdir line"
+        os.mkdir(self.src("both_subdir"))
+        os.mkdir(self.dest("both_subdir"))
+        print >> open(self.src("both_subdir", "subfile"), "w"), "src line"
+        print >> open(self.dest("both_subdir", "subfile"), "w"), "dest line"
+        base_utils.merge_trees(*self.paths())
+        self.assertFileContents("subdir line\n", "src_subdir", "subfile")
+        self.assertFileContents("dest line\nsrc line\n", "both_subdir",
+                                "subfile")
+
+
+class test_get_relative_path(unittest.TestCase):
+    def test_not_absolute(self):
+        self.assertRaises(AssertionError,
+                          base_utils.get_relative_path, "a", "b")
+
+    def test_same_dir(self):
+        self.assertEqual(base_utils.get_relative_path("/a/b/c", "/a/b"), "c")
+
+    def test_forward_dir(self):
+        self.assertEqual(base_utils.get_relative_path("/a/b/c/d", "/a/b"),
+                         "c/d")
+
+    def test_previous_dir(self):
+        self.assertEqual(base_utils.get_relative_path("/a/b", "/a/b/c/d"),
+                         "../..")
+
+    def test_parallel_dir(self):
+        self.assertEqual(base_utils.get_relative_path("/a/c/d", "/a/b/c/d"),
+                         "../../../c/d")
+
+
+class test_sh_escape(unittest.TestCase):
+    def _test_in_shell(self, text):
+        escaped_text = base_utils.sh_escape(text)
+        proc = subprocess.Popen('echo "%s"' % escaped_text, shell=True,
+                                stdin=open(os.devnull, 'r'),
+                                stdout=subprocess.PIPE,
+                                stderr=open(os.devnull, 'w'))
+        stdout, _ = proc.communicate()
+        self.assertEqual(proc.returncode, 0)
+        self.assertEqual(stdout[:-1], text)
+
+
+    def test_normal_string(self):
+        self._test_in_shell('abcd')
+
+
+    def test_spaced_string(self):
+        self._test_in_shell('abcd efgh')
+
+
+    def test_dollar(self):
+        self._test_in_shell('$')
+
+
+    def test_single_quote(self):
+        self._test_in_shell('\'')
+
+
+    def test_single_quoted_string(self):
+        self._test_in_shell('\'efgh\'')
+
+
+    def test_double_quote(self):
+        self._test_in_shell('"')
+
+
+    def test_double_quoted_string(self):
+        self._test_in_shell('"abcd"')
+
+
+    def test_backtick(self):
+        self._test_in_shell('`')
+
+
+    def test_backticked_string(self):
+        self._test_in_shell('`jklm`')
+
+
+    def test_backslash(self):
+        self._test_in_shell('\\')
+
+
+    def test_backslashed_special_characters(self):
+        self._test_in_shell('\\$')
+        self._test_in_shell('\\"')
+        self._test_in_shell('\\\'')
+        self._test_in_shell('\\`')
+
+
+    def test_backslash_codes(self):
+        self._test_in_shell('\\n')
+        self._test_in_shell('\\r')
+        self._test_in_shell('\\t')
+        self._test_in_shell('\\v')
+        self._test_in_shell('\\b')
+        self._test_in_shell('\\a')
+        self._test_in_shell('\\000')
+
+
+class test_run(unittest.TestCase):
+    """
+    Test the base_utils.run() function.
+
+    Note: This test runs simple external commands to test the base_utils.run()
+    API without assuming implementation details.
+    """
+    def setUp(self):
+        self.god = mock.mock_god()
+        self.god.stub_function(base_utils.logging, 'warn')
+        self.god.stub_function(base_utils.logging, 'debug')
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+
+
+    def __check_result(self, result, command, exit_status=0, stdout='',
+                       stderr=''):
+        self.assertEquals(result.command, command)
+        self.assertEquals(result.exit_status, exit_status)
+        self.assertEquals(result.stdout, stdout)
+        self.assertEquals(result.stderr, stderr)
+
+
+    def test_default_simple(self):
+        cmd = 'echo "hello world"'
+        # expect some king of logging.debug() call but don't care about args
+        base_utils.logging.debug.expect_any_call()
+        self.__check_result(base_utils.run(cmd), cmd, stdout='hello world\n')
+
+
+    def test_default_failure(self):
+        cmd = 'exit 11'
+        try:
+            base_utils.run(cmd, verbose=False)
+        except base_utils.error.CmdError, err:
+            self.__check_result(err.result_obj, cmd, exit_status=11)
+
+
+    def test_ignore_status(self):
+        cmd = 'echo error >&2 && exit 11'
+        self.__check_result(base_utils.run(cmd, ignore_status=True,
+                                           verbose=False),
+                            cmd, exit_status=11, stderr='error\n')
+
+
+    def test_timeout(self):
+        # we expect a logging.warn() message, don't care about the contents
+        base_utils.logging.warn.expect_any_call()
+        try:
+            base_utils.run('echo -n output && sleep 10',
+                           timeout=1, verbose=False)
+        except base_utils.error.CmdError, err:
+            self.assertEquals(err.result_obj.stdout, 'output')
+
+
+    def test_stdout_stderr_tee(self):
+        cmd = 'echo output && echo error >&2'
+        stdout_tee = StringIO.StringIO()
+        stderr_tee = StringIO.StringIO()
+
+        self.__check_result(base_utils.run(
+                cmd, stdout_tee=stdout_tee, stderr_tee=stderr_tee,
+                verbose=False), cmd, stdout='output\n', stderr='error\n')
+        self.assertEqual(stdout_tee.getvalue(), 'output\n')
+        self.assertEqual(stderr_tee.getvalue(), 'error\n')
+
+
+    def test_stdin_string(self):
+        cmd = 'cat'
+        self.__check_result(base_utils.run(cmd, verbose=False, stdin='hi!\n'),
+                            cmd, stdout='hi!\n')
+
+
+    def test_safe_args(self):
+        cmd = 'echo "hello \\"world" "again"'
+        self.__check_result(base_utils.run(
+                'echo', verbose=False, args=('hello "world', 'again')), cmd,
+                stdout='hello "world again\n')
+
+
+    def test_safe_args_given_string(self):
+        cmd = 'echo "hello \\"world" "again"'
+        self.assertRaises(TypeError, base_utils.run, 'echo', args='hello')
+
+
+class test_compare_versions(unittest.TestCase):
+    def test_zerofill(self):
+        self.assertEqual(base_utils.compare_versions('1.7', '1.10'), -1)
+        self.assertEqual(base_utils.compare_versions('1.222', '1.3'), 1)
+        self.assertEqual(base_utils.compare_versions('1.03', '1.3'), 0)
+
+
+    def test_unequal_len(self):
+        self.assertEqual(base_utils.compare_versions('1.3', '1.3.4'), -1)
+        self.assertEqual(base_utils.compare_versions('1.3.1', '1.3'), 1)
+
+
+    def test_dash_delimited(self):
+        self.assertEqual(base_utils.compare_versions('1-2-3', '1-5-1'), -1)
+        self.assertEqual(base_utils.compare_versions('1-2-1', '1-1-1'), 1)
+        self.assertEqual(base_utils.compare_versions('1-2-4', '1-2-4'), 0)
+
+
+    def test_alphabets(self):
+        self.assertEqual(base_utils.compare_versions('m.l.b', 'n.b.a'), -1)
+        self.assertEqual(base_utils.compare_versions('n.b.a', 'm.l.b'), 1)
+        self.assertEqual(base_utils.compare_versions('abc.e', 'abc.e'), 0)
+
+
+    def test_mix_symbols(self):
+        self.assertEqual(base_utils.compare_versions('k-320.1', 'k-320.3'), -1)
+        self.assertEqual(base_utils.compare_versions('k-231.5', 'k-231.1'), 1)
+        self.assertEqual(base_utils.compare_versions('k-231.1', 'k-231.1'), 0)
+
+        self.assertEqual(base_utils.compare_versions('k.320-1', 'k.320-3'), -1)
+        self.assertEqual(base_utils.compare_versions('k.231-5', 'k.231-1'), 1)
+        self.assertEqual(base_utils.compare_versions('k.231-1', 'k.231-1'), 0)
+
+
+class test_args_to_dict(unittest.TestCase):
+    def test_no_args(self):
+        result = base_utils.args_to_dict([])
+        self.assertEqual({}, result)
+
+
+    def test_matches(self):
+        result = base_utils.args_to_dict(['aBc:DeF', 'SyS=DEf', 'XY_Z:',
+                                     'F__o0O=', 'B8r:=:=', '_bAZ_=:=:'])
+        self.assertEqual(result, {'abc':'DeF', 'sys':'DEf', 'xy_z':'',
+                                  'f__o0o':'', 'b8r':'=:=', '_baz_':':=:'})
+
+
+    def test_unmatches(self):
+        # Temporarily shut warning messages from args_to_dict() when an argument
+        # doesn't match its pattern.
+        logger = logging.getLogger()
+        saved_level = logger.level
+        logger.setLevel(logging.ERROR)
+
+        try:
+            result = base_utils.args_to_dict(['ab-c:DeF', '--SyS=DEf', 'a*=b',
+                                              'a*b', ':VAL', '=VVV', 'WORD'])
+            self.assertEqual({}, result)
+        finally:
+            # Restore level.
+            logger.setLevel(saved_level)
+
+
+class test_get_random_port(unittest.TestCase):
+    def do_bind(self, port, socket_type, socket_proto):
+        s = socket.socket(socket.AF_INET, socket_type, socket_proto)
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        s.bind(('', port))
+        return s
+
+
+    def test_get_port(self):
+        for _ in xrange(100):
+            p = base_utils.get_unused_port()
+            s = self.do_bind(p, socket.SOCK_STREAM, socket.IPPROTO_TCP)
+            self.assert_(s.getsockname())
+            s = self.do_bind(p, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+            self.assert_(s.getsockname())
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/client/common_lib/control_data.py b/client/common_lib/control_data.py
index 5b8caf2..dcc49cd 100644
--- a/client/common_lib/control_data.py
+++ b/client/common_lib/control_data.py
@@ -19,6 +19,7 @@
         self.experimental = False
         self.run_verify = True
         self.sync_count = 1
+        self.test_parameters = set()
 
         diff = REQUIRED_VARS - set(vars)
         if len(diff) > 0:
@@ -134,6 +135,7 @@
     def set_test_type(self, val):
         self._set_option('test_type', val, ['client', 'server'])
 
+
     def set_test_parameters(self, val):
         self._set_set('test_parameters', val)
 
diff --git a/client/common_lib/logging_manager.py b/client/common_lib/logging_manager.py
index e34e9c9..96f718a 100644
--- a/client/common_lib/logging_manager.py
+++ b/client/common_lib/logging_manager.py
@@ -79,7 +79,7 @@
     return rv
 
 
-if sys.version_info[:2] > (2, 6):
+if sys.version_info[:2] > (2, 7):
     warnings.warn('This module has not been reviewed for Python %s' %
                   sys.version)
 
diff --git a/client/common_lib/magic.py b/client/common_lib/magic.py
old mode 100644
new mode 100755
diff --git a/client/common_lib/software_manager.py b/client/common_lib/software_manager.py
new file mode 100755
index 0000000..f67f667
--- /dev/null
+++ b/client/common_lib/software_manager.py
@@ -0,0 +1,788 @@
+#!/usr/bin/python
+"""
+Software package management library.
+
+This is an abstraction layer on top of the existing distributions high level
+package managers. It supports package operations useful for testing purposes,
+and multiple high level package managers (here called backends). If you want
+to make this lib to support your particular package manager/distro, please
+implement the given backend class.
+
+@author: Higor Vieira Alves (halves@br.ibm.com)
+@author: Lucas Meneghel Rodrigues (lmr@redhat.com)
+@author: Ramon de Carvalho Valle (rcvalle@br.ibm.com)
+
+@copyright: IBM 2008-2009
+@copyright: Red Hat 2009-2010
+"""
+import os, re, logging, ConfigParser, optparse, random, string
+try:
+    import yum
+except:
+    pass
+import common
+from autotest_lib.client.bin import os_dep, utils
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import logging_config, logging_manager
+
+
+def generate_random_string(length):
+    """
+    Return a random string using alphanumeric characters.
+
+    @length: Length of the string that will be generated.
+    """
+    r = random.SystemRandom()
+    str = ""
+    chars = string.letters + string.digits
+    while length > 0:
+        str += r.choice(chars)
+        length -= 1
+    return str
+
+
+class SoftwareManagerLoggingConfig(logging_config.LoggingConfig):
+    """
+    Used with the sole purpose of providing convenient logging setup
+    for the KVM test auxiliary programs.
+    """
+    def configure_logging(self, results_dir=None, verbose=False):
+        super(SoftwareManagerLoggingConfig, self).configure_logging(
+                                                            use_console=True,
+                                                            verbose=verbose)
+
+
+class SystemInspector(object):
+    """
+    System inspector class.
+
+    This may grow up to include more complete reports of operating system and
+    machine properties.
+    """
+    def __init__(self):
+        """
+        Probe system, and save information for future reference.
+        """
+        self.distro = utils.get_os_vendor()
+        self.high_level_pms = ['apt-get', 'yum', 'zypper']
+
+
+    def get_package_management(self):
+        """
+        Determine the supported package management systems present on the
+        system. If more than one package management system installed, try
+        to find the best supported system.
+        """
+        list_supported = []
+        for high_level_pm in self.high_level_pms:
+            try:
+                os_dep.command(high_level_pm)
+                list_supported.append(high_level_pm)
+            except:
+                pass
+
+        pm_supported = None
+        if len(list_supported) == 0:
+            pm_supported = None
+        if len(list_supported) == 1:
+            pm_supported = list_supported[0]
+        elif len(list_supported) > 1:
+            if 'apt-get' in list_supported and self.distro in ['Debian', 'Ubuntu']:
+                pm_supported = 'apt-get'
+            elif 'yum' in list_supported and self.distro == 'Fedora':
+                pm_supported = 'yum'
+            else:
+                pm_supported = list_supported[0]
+
+        logging.debug('Package Manager backend: %s' % pm_supported)
+        return pm_supported
+
+
+class SoftwareManager(object):
+    """
+    Package management abstraction layer.
+
+    It supports a set of common package operations for testing purposes, and it
+    uses the concept of a backend, a helper class that implements the set of
+    operations of a given package management tool.
+    """
+    def __init__(self):
+        """
+        Class constructor.
+
+        Determines the best supported package management system for the given
+        operating system running and initializes the appropriate backend.
+        """
+        inspector = SystemInspector()
+        backend_type = inspector.get_package_management()
+        if backend_type == 'yum':
+            self.backend = YumBackend()
+        elif backend_type == 'zypper':
+            self.backend = ZypperBackend()
+        elif backend_type == 'apt-get':
+            self.backend = AptBackend()
+        else:
+            raise NotImplementedError('Unimplemented package management '
+                                      'system: %s.' % backend_type)
+
+
+    def check_installed(self, name, version=None, arch=None):
+        """
+        Check whether a package is installed on this system.
+
+        @param name: Package name.
+        @param version: Package version.
+        @param arch: Package architecture.
+        """
+        return self.backend.check_installed(name, version, arch)
+
+
+    def list_all(self):
+        """
+        List all installed packages.
+        """
+        return self.backend.list_all()
+
+
+    def list_files(self, name):
+        """
+        Get a list of all files installed by package [name].
+
+        @param name: Package name.
+        """
+        return self.backend.list_files(name)
+
+
+    def install(self, name):
+        """
+        Install package [name].
+
+        @param name: Package name.
+        """
+        return self.backend.install(name)
+
+
+    def remove(self, name):
+        """
+        Remove package [name].
+
+        @param name: Package name.
+        """
+        return self.backend.remove(name)
+
+
+    def add_repo(self, url):
+        """
+        Add package repo described by [url].
+
+        @param name: URL of the package repo.
+        """
+        return self.backend.add_repo(url)
+
+
+    def remove_repo(self, url):
+        """
+        Remove package repo described by [url].
+
+        @param url: URL of the package repo.
+        """
+        return self.backend.remove_repo(url)
+
+
+    def upgrade(self):
+        """
+        Upgrade all packages available.
+        """
+        return self.backend.upgrade()
+
+
+    def provides(self, file):
+        """
+        Returns a list of packages that provides a given capability to the
+        system (be it a binary, a library).
+
+        @param file: Path to the file.
+        """
+        return self.backend.provides(file)
+
+
+    def install_what_provides(self, file):
+        """
+        Installs package that provides [file].
+
+        @param file: Path to file.
+        """
+        provides = self.provides(file)
+        if provides is not None:
+            self.install(provides)
+        else:
+            logging.warning('No package seems to provide %s', file)
+
+
+class RpmBackend(object):
+    """
+    This class implements operations executed with the rpm package manager.
+
+    rpm is a lower level package manager, used by higher level managers such
+    as yum and zypper.
+    """
+    def __init__(self):
+        self.lowlevel_base_cmd = os_dep.command('rpm')
+
+
+    def _check_installed_version(self, name, version):
+        """
+        Helper for the check_installed public method.
+
+        @param name: Package name.
+        @param version: Package version.
+        """
+        cmd = (self.lowlevel_base_cmd + ' -q --qf %{VERSION} ' + name +
+               ' 2> /dev/null')
+        inst_version = utils.system_output(cmd)
+
+        if inst_version >= version:
+            return True
+        else:
+            return False
+
+
+    def check_installed(self, name, version=None, arch=None):
+        """
+        Check if package [name] is installed.
+
+        @param name: Package name.
+        @param version: Package version.
+        @param arch: Package architecture.
+        """
+        if arch:
+            cmd = (self.lowlevel_base_cmd + ' -q --qf %{ARCH} ' + name +
+                   ' 2> /dev/null')
+            inst_archs = utils.system_output(cmd)
+            inst_archs = inst_archs.split('\n')
+
+            for inst_arch in inst_archs:
+                if inst_arch == arch:
+                    return self._check_installed_version(name, version)
+            return False
+
+        elif version:
+            return self._check_installed_version(name, version)
+        else:
+            cmd = 'rpm -q ' + name + ' 2> /dev/null'
+            return (os.system(cmd) == 0)
+
+
+    def list_all(self):
+        """
+        List all installed packages.
+        """
+        installed_packages = utils.system_output('rpm -qa').splitlines()
+        return installed_packages
+
+
+    def list_files(self, name):
+        """
+        List files installed on the system by package [name].
+
+        @param name: Package name.
+        """
+        path = os.path.abspath(name)
+        if os.path.isfile(path):
+            option = '-qlp'
+            name = path
+        else:
+            option = '-ql'
+
+        l_cmd = 'rpm' + ' ' + option + ' ' + name + ' 2> /dev/null'
+
+        try:
+            result = utils.system_output(l_cmd)
+            list_files = result.split('\n')
+            return list_files
+        except error.CmdError:
+            return []
+
+
+class DpkgBackend(object):
+    """
+    This class implements operations executed with the dpkg package manager.
+
+    dpkg is a lower level package manager, used by higher level managers such
+    as apt and aptitude.
+    """
+    def __init__(self):
+        self.lowlevel_base_cmd = os_dep.command('dpkg')
+
+
+    def check_installed(self, name):
+        if os.path.isfile(name):
+            n_cmd = (self.lowlevel_base_cmd + ' -f ' + name +
+                     ' Package 2>/dev/null')
+            name = utils.system_output(n_cmd)
+        i_cmd = self.lowlevel_base_cmd + ' -s ' + name + ' 2>/dev/null'
+        # Checking if package is installed
+        package_status = utils.system_output(i_cmd, ignore_status=True)
+        not_inst_pattern = re.compile('not-installed', re.IGNORECASE)
+        dpkg_not_installed = re.search(not_inst_pattern, package_status)
+        if dpkg_not_installed:
+            return False
+        return True
+
+
+    def list_all(self):
+        """
+        List all packages available in the system.
+        """
+        installed_packages = []
+        raw_list = utils.system_output('dpkg -l').splitlines()[5:]
+        for line in raw_list:
+            parts = line.split()
+            if parts[0] == "ii":  # only grab "installed" packages
+                installed_packages.append("%s-%s" % (parts[1], parts[2]))
+
+
+    def list_files(self, package):
+        """
+        List files installed by package [package].
+
+        @param package: Package name.
+        @return: List of paths installed by package.
+        """
+        if os.path.isfile(package):
+            l_cmd = self.lowlevel_base_cmd + ' -c ' + package
+        else:
+            l_cmd = self.lowlevel_base_cmd + ' -l ' + package
+        return utils.system_output(l_cmd).split('\n')
+
+
+class YumBackend(RpmBackend):
+    """
+    Implements the yum backend for software manager.
+
+    Set of operations for the yum package manager, commonly found on Yellow Dog
+    Linux and Red Hat based distributions, such as Fedora and Red Hat
+    Enterprise Linux.
+    """
+    def __init__(self):
+        """
+        Initializes the base command and the yum package repository.
+        """
+        super(YumBackend, self).__init__()
+        executable = os_dep.command('yum')
+        base_arguments = '-y'
+        self.base_command = executable + ' ' + base_arguments
+        self.repo_file_path = '/etc/yum.repos.d/autotest.repo'
+        self.cfgparser = ConfigParser.ConfigParser()
+        self.cfgparser.read(self.repo_file_path)
+        y_cmd = executable + ' --version | head -1'
+        self.yum_version = utils.system_output(y_cmd, ignore_status=True)
+        logging.debug('Yum backend initialized')
+        logging.debug('Yum version: %s' % self.yum_version)
+        self.yum_base = yum.YumBase()
+
+
+    def _cleanup(self):
+        """
+        Clean up the yum cache so new package information can be downloaded.
+        """
+        utils.system("yum clean all")
+
+
+    def install(self, name):
+        """
+        Installs package [name]. Handles local installs.
+        """
+        if os.path.isfile(name):
+            name = os.path.abspath(name)
+            command = 'localinstall'
+        else:
+            command = 'install'
+
+        i_cmd = self.base_command + ' ' + command + ' ' + name
+
+        try:
+            utils.system(i_cmd)
+            return True
+        except:
+            return False
+
+
+    def remove(self, name):
+        """
+        Removes package [name].
+
+        @param name: Package name (eg. 'ipython').
+        """
+        r_cmd = self.base_command + ' ' + 'erase' + ' ' + name
+        try:
+            utils.system(r_cmd)
+            return True
+        except:
+            return False
+
+
+    def add_repo(self, url):
+        """
+        Adds package repository located on [url].
+
+        @param url: Universal Resource Locator of the repository.
+        """
+        # Check if we URL is already set
+        for section in self.cfgparser.sections():
+            for option, value in self.cfgparser.items(section):
+                if option == 'url' and value == url:
+                    return True
+
+        # Didn't find it, let's set it up
+        while True:
+            section_name = 'software_manager' + '_' + generate_random_string(4)
+            if not self.cfgparser.has_section(section_name):
+                break
+        self.cfgparser.add_section(section_name)
+        self.cfgparser.set(section_name, 'name',
+                           'Repository added by the autotest software manager.')
+        self.cfgparser.set(section_name, 'url', url)
+        self.cfgparser.set(section_name, 'enabled', 1)
+        self.cfgparser.set(section_name, 'gpgcheck', 0)
+        self.cfgparser.write(self.repo_file_path)
+
+
+    def remove_repo(self, url):
+        """
+        Removes package repository located on [url].
+
+        @param url: Universal Resource Locator of the repository.
+        """
+        for section in self.cfgparser.sections():
+            for option, value in self.cfgparser.items(section):
+                if option == 'url' and value == url:
+                    self.cfgparser.remove_section(section)
+                    self.cfgparser.write(self.repo_file_path)
+
+
+    def upgrade(self):
+        """
+        Upgrade all available packages.
+        """
+        r_cmd = self.base_command + ' ' + 'update'
+        try:
+            utils.system(r_cmd)
+            return True
+        except:
+            return False
+
+
+    def provides(self, name):
+        """
+        Returns a list of packages that provides a given capability.
+
+        @param name: Capability name (eg, 'foo').
+        """
+        d_provides = self.yum_base.searchPackageProvides(args=[name])
+        provides_list = [key for key in d_provides]
+        if provides_list:
+            logging.info("Package %s provides %s", provides_list[0], name)
+            return str(provides_list[0])
+        else:
+            return None
+
+
+class ZypperBackend(RpmBackend):
+    """
+    Implements the zypper backend for software manager.
+
+    Set of operations for the zypper package manager, found on SUSE Linux.
+    """
+    def __init__(self):
+        """
+        Initializes the base command and the yum package repository.
+        """
+        super(ZypperBackend, self).__init__()
+        self.base_command = os_dep.command('zypper') + ' -n'
+        z_cmd = self.base_command + ' --version'
+        self.zypper_version = utils.system_output(z_cmd, ignore_status=True)
+        logging.debug('Zypper backend initialized')
+        logging.debug('Zypper version: %s' % self.zypper_version)
+
+
+    def install(self, name):
+        """
+        Installs package [name]. Handles local installs.
+
+        @param name: Package Name.
+        """
+        path = os.path.abspath(name)
+        i_cmd = self.base_command + ' install -l ' + name
+        try:
+            utils.system(i_cmd)
+            return True
+        except:
+            return False
+
+
+    def add_repo(self, url):
+        """
+        Adds repository [url].
+
+        @param url: URL for the package repository.
+        """
+        ar_cmd = self.base_command + ' addrepo ' + url
+        try:
+            utils.system(ar_cmd)
+            return True
+        except:
+            return False
+
+
+    def remove_repo(self, url):
+        """
+        Removes repository [url].
+
+        @param url: URL for the package repository.
+        """
+        rr_cmd = self.base_command + ' removerepo ' + url
+        try:
+            utils.system(rr_cmd)
+            return True
+        except:
+            return False
+
+
+    def remove(self, name):
+        """
+        Removes package [name].
+        """
+        r_cmd = self.base_command + ' ' + 'erase' + ' ' + name
+
+        try:
+            utils.system(r_cmd)
+            return True
+        except:
+            return False
+
+
+    def upgrade(self):
+        """
+        Upgrades all packages of the system.
+        """
+        u_cmd = self.base_command + ' update -l'
+
+        try:
+            utils.system(u_cmd)
+            return True
+        except:
+            return False
+
+
+    def provides(self, name):
+        """
+        Searches for what provides a given file.
+
+        @param name: File path.
+        """
+        p_cmd = self.base_command + ' what-provides ' + name
+        list_provides = []
+        try:
+            p_output = utils.system_output(p_cmd).split('\n')[4:]
+            for line in p_output:
+                line = [a.strip() for a in line.split('|')]
+                try:
+                    state, pname, type, version, arch, repository = line
+                    if pname not in list_provides:
+                        list_provides.append(pname)
+                except IndexError:
+                    pass
+            if len(list_provides) > 1:
+                logging.warning('More than one package found, '
+                                'opting by the first queue result')
+            if list_provides:
+                logging.info("Package %s provides %s", list_provides[0], name)
+                return list_provides[0]
+            return None
+        except:
+            return None
+
+
+class AptBackend(DpkgBackend):
+    """
+    Implements the apt backend for software manager.
+
+    Set of operations for the apt package manager, commonly found on Debian and
+    Debian based distributions, such as Ubuntu Linux.
+    """
+    def __init__(self):
+        """
+        Initializes the base command and the debian package repository.
+        """
+        super(AptBackend, self).__init__()
+        executable = os_dep.command('apt-get')
+        self.base_command = executable + ' -y'
+        self.repo_file_path = '/etc/apt/sources.list.d/autotest'
+        self.apt_version = utils.system_output('apt-get -v | head -1',
+                                               ignore_status=True)
+        logging.debug('Apt backend initialized')
+        logging.debug('apt version: %s' % self.apt_version)
+
+
+    def install(self, name):
+        """
+        Installs package [name].
+
+        @param name: Package name.
+        """
+        command = 'install'
+        i_cmd = self.base_command + ' ' + command + ' ' + name
+
+        try:
+            utils.system(i_cmd)
+            return True
+        except:
+            return False
+
+
+    def remove(self, name):
+        """
+        Remove package [name].
+
+        @param name: Package name.
+        """
+        command = 'remove'
+        flag = '--purge'
+        r_cmd = self.base_command + ' ' + command + ' ' + flag + ' ' + name
+
+        try:
+            utils.system(r_cmd)
+            return True
+        except:
+            return False
+
+
+    def add_repo(self, repo):
+        """
+        Add an apt repository.
+
+        @param repo: Repository string. Example:
+                'deb http://archive.ubuntu.com/ubuntu/ maverick universe'
+        """
+        repo_file = open(self.repo_file_path, 'a')
+        repo_file_contents = repo_file.read()
+        if repo not in repo_file_contents:
+            repo_file.write(repo)
+
+
+    def remove_repo(self, repo):
+        """
+        Remove an apt repository.
+
+        @param repo: Repository string. Example:
+                'deb http://archive.ubuntu.com/ubuntu/ maverick universe'
+        """
+        repo_file = open(self.repo_file_path, 'r')
+        new_file_contents = []
+        for line in repo_file.readlines:
+            if not line == repo:
+                new_file_contents.append(line)
+        repo_file.close()
+        new_file_contents = "\n".join(new_file_contents)
+        repo_file.open(self.repo_file_path, 'w')
+        repo_file.write(new_file_contents)
+        repo_file.close()
+
+
+    def upgrade(self):
+        """
+        Upgrade all packages of the system with eventual new versions.
+        """
+        ud_command = 'update'
+        ud_cmd = self.base_command + ' ' + ud_command
+        try:
+            utils.system(ud_cmd)
+        except:
+            logging.error("Apt package update failed")
+        up_command = 'upgrade'
+        up_cmd = self.base_command + ' ' + up_command
+        try:
+            utils.system(up_cmd)
+            return True
+        except:
+            return False
+
+
+    def provides(self, file):
+        """
+        Return a list of packages that provide [file].
+
+        @param file: File path.
+        """
+        if not self.check_installed('apt-file'):
+            self.install('apt-file')
+        command = os_dep.command('apt-file')
+        cache_update_cmd = command + ' update'
+        try:
+            utils.system(cache_update_cmd, ignore_status=True)
+        except:
+            logging.error("Apt file cache update failed")
+        fu_cmd = command + ' search ' + file
+        try:
+            provides = utils.system_output(fu_cmd).split('\n')
+            list_provides = []
+            for line in provides:
+                if line:
+                    try:
+                        line = line.split(':')
+                        package = line[0].strip()
+                        path = line[1].strip()
+                        if path == file and package not in list_provides:
+                            list_provides.append(package)
+                    except IndexError:
+                        pass
+            if len(list_provides) > 1:
+                logging.warning('More than one package found, '
+                                'opting by the first queue result')
+            if list_provides:
+                logging.info("Package %s provides %s", list_provides[0], file)
+                return list_provides[0]
+            return None
+        except:
+            return None
+
+
+if __name__ == '__main__':
+    parser = optparse.OptionParser(
+    "usage: %prog [install|remove|list-all|list-files|add-repo|remove-repo|"
+    "upgrade|what-provides|install-what-provides] arguments")
+    parser.add_option('--verbose', dest="debug", action='store_true',
+                      help='include debug messages in console output')
+
+    options, args = parser.parse_args()
+    debug = options.debug
+    logging_manager.configure_logging(SoftwareManagerLoggingConfig(),
+                                      verbose=debug)
+    software_manager = SoftwareManager()
+    if args:
+        action = args[0]
+        args = " ".join(args[1:])
+    else:
+        action = 'show-help'
+
+    if action == 'install':
+        software_manager.install(args)
+    elif action == 'remove':
+        software_manager.remove(args)
+    if action == 'list-all':
+        software_manager.list_all()
+    elif action == 'list-files':
+        software_manager.list_files(args)
+    elif action == 'add-repo':
+        software_manager.add_repo(args)
+    elif action == 'remove-repo':
+        software_manager.remove_repo(args)
+    elif action == 'upgrade':
+        software_manager.upgrade()
+    elif action == 'what-provides':
+        software_manager.provides(args)
+    elif action == 'install-what-provides':
+        software_manager.install_what_provides(args)
+    elif action == 'show-help':
+        parser.print_help()
diff --git a/client/common_lib/utils.py b/client/common_lib/utils.py
index 101599b..382f79d 100644
--- a/client/common_lib/utils.py
+++ b/client/common_lib/utils.py
@@ -1,1715 +1,13 @@
-#
-# Copyright 2008 Google Inc. Released under the GPL v2
+"""
+Convenience functions for use by tests or whomever.
 
-import os, pickle, random, re, resource, select, shutil, signal, StringIO
-import socket, struct, subprocess, sys, time, textwrap, urlparse
-import warnings, smtplib, logging, urllib2
-from threading import Thread, Event
-try:
-    import hashlib
-except ImportError:
-    import md5, sha
-from autotest_lib.client.common_lib import error, logging_manager
+NOTE: this is a mixin library that pulls in functions from several places
+Note carefully what the precendece order is
 
-def deprecated(func):
-    """This is a decorator which can be used to mark functions as deprecated.
-    It will result in a warning being emmitted when the function is used."""
-    def new_func(*args, **dargs):
-        warnings.warn("Call to deprecated function %s." % func.__name__,
-                      category=DeprecationWarning)
-        return func(*args, **dargs)
-    new_func.__name__ = func.__name__
-    new_func.__doc__ = func.__doc__
-    new_func.__dict__.update(func.__dict__)
-    return new_func
+There's no really good way to do this, as this isn't a class we can do
+inheritance with, just a collection of static methods.
+"""
 
-
-class _NullStream(object):
-    def write(self, data):
-        pass
-
-
-    def flush(self):
-        pass
-
-
-TEE_TO_LOGS = object()
-_the_null_stream = _NullStream()
-
-DEFAULT_STDOUT_LEVEL = logging.DEBUG
-DEFAULT_STDERR_LEVEL = logging.ERROR
-
-# prefixes for logging stdout/stderr of commands
-STDOUT_PREFIX = '[stdout] '
-STDERR_PREFIX = '[stderr] '
-
-
-def get_stream_tee_file(stream, level, prefix=''):
-    if stream is None:
-        return _the_null_stream
-    if stream is TEE_TO_LOGS:
-        return logging_manager.LoggingFile(level=level, prefix=prefix)
-    return stream
-
-
-class BgJob(object):
-    def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
-                 stdin=None, stderr_level=DEFAULT_STDERR_LEVEL):
-        self.command = command
-        self.stdout_tee = get_stream_tee_file(stdout_tee, DEFAULT_STDOUT_LEVEL,
-                                              prefix=STDOUT_PREFIX)
-        self.stderr_tee = get_stream_tee_file(stderr_tee, stderr_level,
-                                              prefix=STDERR_PREFIX)
-        self.result = CmdResult(command)
-
-        # allow for easy stdin input by string, we'll let subprocess create
-        # a pipe for stdin input and we'll write to it in the wait loop
-        if isinstance(stdin, basestring):
-            self.string_stdin = stdin
-            stdin = subprocess.PIPE
-        else:
-            self.string_stdin = None
-
-        if verbose:
-            logging.debug("Running '%s'" % command)
-        self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
-                                   stderr=subprocess.PIPE,
-                                   preexec_fn=self._reset_sigpipe, shell=True,
-
-                                   # Default shell in ChromeOS test image is
-                                   # already bash. We're seeing shell-init
-                                   # errors if this value is set.
-
-                                   #executable="/bin/bash",
-                                   stdin=stdin)
-
-
-    def output_prepare(self, stdout_file=None, stderr_file=None):
-        self.stdout_file = stdout_file
-        self.stderr_file = stderr_file
-
-
-    def process_output(self, stdout=True, final_read=False):
-        """output_prepare must be called prior to calling this"""
-        if stdout:
-            pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
-        else:
-            pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
-
-        if final_read:
-            # read in all the data we can from pipe and then stop
-            data = []
-            while select.select([pipe], [], [], 0)[0]:
-                data.append(os.read(pipe.fileno(), 1024))
-                if len(data[-1]) == 0:
-                    break
-            data = "".join(data)
-        else:
-            # perform a single read
-            data = os.read(pipe.fileno(), 1024)
-        buf.write(data)
-        tee.write(data)
-
-
-    def cleanup(self):
-        self.stdout_tee.flush()
-        self.stderr_tee.flush()
-        self.sp.stdout.close()
-        self.sp.stderr.close()
-        self.result.stdout = self.stdout_file.getvalue()
-        self.result.stderr = self.stderr_file.getvalue()
-
-
-    def _reset_sigpipe(self):
-        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-
-
-def ip_to_long(ip):
-    # !L is a long in network byte order
-    return struct.unpack('!L', socket.inet_aton(ip))[0]
-
-
-def long_to_ip(number):
-    # See above comment.
-    return socket.inet_ntoa(struct.pack('!L', number))
-
-
-def create_subnet_mask(bits):
-    return (1 << 32) - (1 << 32-bits)
-
-
-def format_ip_with_mask(ip, mask_bits):
-    masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
-    return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
-
-
-def normalize_hostname(alias):
-    ip = socket.gethostbyname(alias)
-    return socket.gethostbyaddr(ip)[0]
-
-
-def get_ip_local_port_range():
-    match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
-                     read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
-    return (int(match.group(1)), int(match.group(2)))
-
-
-def set_ip_local_port_range(lower, upper):
-    write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
-                   '%d %d\n' % (lower, upper))
-
-
-
-def send_email(mail_from, mail_to, subject, body):
-    """
-    Sends an email via smtp
-
-    mail_from: string with email address of sender
-    mail_to: string or list with email address(es) of recipients
-    subject: string with subject of email
-    body: (multi-line) string with body of email
-    """
-    if isinstance(mail_to, str):
-        mail_to = [mail_to]
-    msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to),
-                                                   subject, body)
-    try:
-        mailer = smtplib.SMTP('localhost')
-        try:
-            mailer.sendmail(mail_from, mail_to, msg)
-        finally:
-            mailer.quit()
-    except Exception, e:
-        # Emails are non-critical, not errors, but don't raise them
-        print "Sending email failed. Reason: %s" % repr(e)
-
-
-def read_one_line(filename):
-    return open(filename, 'r').readline().rstrip('\n')
-
-
-def read_file(filename):
-    f = open(filename)
-    try:
-        return f.read()
-    finally:
-        f.close()
-
-
-def get_field(data, param, linestart="", sep=" "):
-    """
-    Parse data from string.
-    @param data: Data to parse.
-        example:
-          data:
-             cpu   324 345 34  5 345
-             cpu0  34  11  34 34  33
-             ^^^^
-             start of line
-             params 0   1   2  3   4
-    @param param: Position of parameter after linestart marker.
-    @param linestart: String to which start line with parameters.
-    @param sep: Separator between parameters regular expression.
-    """
-    search = re.compile(r"(?<=^%s)\s*(.*)" % linestart, re.MULTILINE)
-    find = search.search(data)
-    if find != None:
-        return re.split("%s" % sep, find.group(1))[param]
-    else:
-        print "There is no line which starts with %s in data." % linestart
-        return None
-
-
-def write_one_line(filename, line):
-    open_write_close(filename, line.rstrip('\n') + '\n')
-
-
-def open_write_close(filename, data):
-    f = open(filename, 'w')
-    try:
-        f.write(data)
-    finally:
-        f.close()
-
-
-def matrix_to_string(matrix, header=None):
-    """
-    Return a pretty, aligned string representation of a nxm matrix.
-
-    This representation can be used to print any tabular data, such as
-    database results. It works by scanning the lengths of each element
-    in each column, and determining the format string dynamically.
-
-    @param matrix: Matrix representation (list with n rows of m elements).
-    @param header: Optional tuple or list with header elements to be displayed.
-    """
-    if type(header) is list:
-        header = tuple(header)
-    lengths = []
-    if header:
-        for column in header:
-            lengths.append(len(column))
-    for row in matrix:
-        for column in row:
-            i = row.index(column)
-            cl = len(column)
-            try:
-                ml = lengths[i]
-                if cl > ml:
-                    lengths[i] = cl
-            except IndexError:
-                lengths.append(cl)
-
-    lengths = tuple(lengths)
-    format_string = ""
-    for length in lengths:
-        format_string += "%-" + str(length) + "s "
-    format_string += "\n"
-
-    matrix_str = ""
-    if header:
-        matrix_str += format_string % header
-    for row in matrix:
-        matrix_str += format_string % tuple(row)
-
-    return matrix_str
-
-
-def read_keyval(path):
-    """
-    Read a key-value pair format file into a dictionary, and return it.
-    Takes either a filename or directory name as input. If it's a
-    directory name, we assume you want the file to be called keyval.
-    """
-    if os.path.isdir(path):
-        path = os.path.join(path, 'keyval')
-    keyval = {}
-    if os.path.exists(path):
-        for line in open(path):
-            line = re.sub('#.*', '', line).rstrip()
-            if not re.search(r'^[-\.\w]+=', line):
-                raise ValueError('Invalid format line: %s' % line)
-            key, value = line.split('=', 1)
-            if re.search('^\d+$', value):
-                value = int(value)
-            elif re.search('^(\d+\.)?\d+$', value):
-                value = float(value)
-            keyval[key] = value
-    return keyval
-
-
-def write_keyval(path, dictionary, type_tag=None):
-    """
-    Write a key-value pair format file out to a file. This uses append
-    mode to open the file, so existing text will not be overwritten or
-    reparsed.
-
-    If type_tag is None, then the key must be composed of alphanumeric
-    characters (or dashes+underscores). However, if type-tag is not
-    null then the keys must also have "{type_tag}" as a suffix. At
-    the moment the only valid values of type_tag are "attr" and "perf".
-    """
-    if os.path.isdir(path):
-        path = os.path.join(path, 'keyval')
-    keyval = open(path, 'a')
-
-    if type_tag is None:
-        key_regex = re.compile(r'^[-\.\w]+$')
-    else:
-        if type_tag not in ('attr', 'perf'):
-            raise ValueError('Invalid type tag: %s' % type_tag)
-        escaped_tag = re.escape(type_tag)
-        key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
-    try:
-        for key in sorted(dictionary.keys()):
-            if not key_regex.search(key):
-                raise ValueError('Invalid key: %s' % key)
-            keyval.write('%s=%s\n' % (key, dictionary[key]))
-    finally:
-        keyval.close()
-
-
-class FileFieldMonitor(object):
-    """
-    Monitors the information from the file and reports it's values.
-
-    It gather the information at start and stop of the measurement or
-    continuously during the measurement.
-    """
-    class Monitor(Thread):
-        """
-        Internal monitor class to ensure continuous monitor of monitored file.
-        """
-        def __init__(self, master):
-            """
-            @param master: Master class which control Monitor
-            """
-            Thread.__init__(self)
-            self.master = master
-
-        def run(self):
-            """
-            Start monitor in thread mode
-            """
-            while not self.master.end_event.isSet():
-                self.master._get_value(self.master.logging)
-                time.sleep(self.master.time_step)
-
-
-    def __init__(self, status_file, data_to_read, mode_diff, continuously=False,
-                 contlogging=False, separator=" +", time_step=0.1):
-        """
-        Initialize variables.
-        @param status_file: File contain status.
-        @param mode_diff: If True make a difference of value, else average.
-        @param data_to_read: List of tuples with data position.
-            format: [(start_of_line,position in params)]
-            example:
-              data:
-                 cpu   324 345 34  5 345
-                 cpu0  34  11  34 34  33
-                 ^^^^
-                 start of line
-                 params 0   1   2  3   4
-        @param mode_diff: True to subtract old value from new value,
-            False make average of the values.
-        @parma continuously: Start the monitoring thread using the time_step
-            as the measurement period.
-        @param contlogging: Log data in continuous run.
-        @param separator: Regular expression of separator.
-        @param time_step: Time period of the monitoring value.
-        """
-        self.end_event = Event()
-        self.start_time = 0
-        self.end_time = 0
-        self.test_time = 0
-
-        self.status_file = status_file
-        self.separator = separator
-        self.data_to_read = data_to_read
-        self.num_of_params = len(self.data_to_read)
-        self.mode_diff = mode_diff
-        self.continuously = continuously
-        self.time_step = time_step
-
-        self.value = [0 for i in range(self.num_of_params)]
-        self.old_value = [0 for i in range(self.num_of_params)]
-        self.log = []
-        self.logging = contlogging
-
-        self.started = False
-        self.num_of_get_value = 0
-        self.monitor = None
-
-
-    def _get_value(self, logging=True):
-        """
-        Return current values.
-        @param logging: If true log value in memory. There can be problem
-          with long run.
-        """
-        data = read_file(self.status_file)
-        value = []
-        for i in range(self.num_of_params):
-            value.append(int(get_field(data,
-                             self.data_to_read[i][1],
-                             self.data_to_read[i][0],
-                             self.separator)))
-
-        if logging:
-            self.log.append(value)
-        if not self.mode_diff:
-            value = map(lambda x, y: x + y, value, self.old_value)
-
-        self.old_value = value
-        self.num_of_get_value += 1
-        return value
-
-
-    def start(self):
-        """
-        Start value monitor.
-        """
-        if self.started:
-            self.stop()
-        self.old_value = [0 for i in range(self.num_of_params)]
-        self.num_of_get_value = 0
-        self.log = []
-        self.end_event.clear()
-        self.start_time = time.time()
-        self._get_value()
-        self.started = True
-        if (self.continuously):
-            self.monitor = FileFieldMonitor.Monitor(self)
-            self.monitor.start()
-
-
-    def stop(self):
-        """
-        Stop value monitor.
-        """
-        if self.started:
-            self.started = False
-            self.end_time = time.time()
-            self.test_time = self.end_time - self.start_time
-            self.value = self._get_value()
-            if (self.continuously):
-                self.end_event.set()
-                self.monitor.join()
-            if (self.mode_diff):
-                self.value = map(lambda x, y: x - y, self.log[-1], self.log[0])
-            else:
-                self.value = map(lambda x: x / self.num_of_get_value,
-                                 self.value)
-
-
-    def get_status(self):
-        """
-        @return: Status of monitored process average value,
-            time of test and array of monitored values and time step of
-            continuous run.
-        """
-        if self.started:
-            self.stop()
-        if self.mode_diff:
-            for i in range(len(self.log) - 1):
-                self.log[i] = (map(lambda x, y: x - y,
-                                   self.log[i + 1], self.log[i]))
-            self.log.pop()
-        return (self.value, self.test_time, self.log, self.time_step)
-
-
-def is_url(path):
-    """Return true if path looks like a URL"""
-    # for now, just handle http and ftp
-    url_parts = urlparse.urlparse(path)
-    return (url_parts[0] in ('http', 'ftp'))
-
-
-def urlopen(url, data=None, timeout=5):
-    """Wrapper to urllib2.urlopen with timeout addition."""
-
-    # Save old timeout
-    old_timeout = socket.getdefaulttimeout()
-    socket.setdefaulttimeout(timeout)
-    try:
-        return urllib2.urlopen(url, data=data)
-    finally:
-        socket.setdefaulttimeout(old_timeout)
-
-
-def urlretrieve(url, filename, data=None, timeout=300):
-    """Retrieve a file from given url."""
-    logging.debug('Fetching %s -> %s', url, filename)
-
-    src_file = urlopen(url, data=data, timeout=timeout)
-    try:
-        dest_file = open(filename, 'wb')
-        try:
-            shutil.copyfileobj(src_file, dest_file)
-        finally:
-            dest_file.close()
-    finally:
-        src_file.close()
-
-
-def hash(type, input=None):
-    """
-    Returns an hash object of type md5 or sha1. This function is implemented in
-    order to encapsulate hash objects in a way that is compatible with python
-    2.4 and python 2.6 without warnings.
-
-    Note that even though python 2.6 hashlib supports hash types other than
-    md5 and sha1, we are artificially limiting the input values in order to
-    make the function to behave exactly the same among both python
-    implementations.
-
-    @param input: Optional input string that will be used to update the hash.
-    """
-    if type not in ['md5', 'sha1']:
-        raise ValueError("Unsupported hash type: %s" % type)
-
-    try:
-        hash = hashlib.new(type)
-    except NameError:
-        if type == 'md5':
-            hash = md5.new()
-        elif type == 'sha1':
-            hash = sha.new()
-
-    if input:
-        hash.update(input)
-
-    return hash
-
-
-def get_file(src, dest, permissions=None):
-    """Get a file from src, which can be local or a remote URL"""
-    if src == dest:
-        return
-
-    if is_url(src):
-        urlretrieve(src, dest)
-    else:
-        shutil.copyfile(src, dest)
-
-    if permissions:
-        os.chmod(dest, permissions)
-    return dest
-
-
-def unmap_url(srcdir, src, destdir='.'):
-    """
-    Receives either a path to a local file or a URL.
-    returns either the path to the local file, or the fetched URL
-
-    unmap_url('/usr/src', 'foo.tar', '/tmp')
-                            = '/usr/src/foo.tar'
-    unmap_url('/usr/src', 'http://site/file', '/tmp')
-                            = '/tmp/file'
-                            (after retrieving it)
-    """
-    if is_url(src):
-        url_parts = urlparse.urlparse(src)
-        filename = os.path.basename(url_parts[2])
-        dest = os.path.join(destdir, filename)
-        return get_file(src, dest)
-    else:
-        return os.path.join(srcdir, src)
-
-
-def update_version(srcdir, preserve_srcdir, new_version, install,
-                   *args, **dargs):
-    """
-    Make sure srcdir is version new_version
-
-    If not, delete it and install() the new version.
-
-    In the preserve_srcdir case, we just check it's up to date,
-    and if not, we rerun install, without removing srcdir
-    """
-    versionfile = os.path.join(srcdir, '.version')
-    install_needed = True
-
-    if os.path.exists(versionfile):
-        old_version = pickle.load(open(versionfile))
-        if old_version == new_version:
-            install_needed = False
-
-    if install_needed:
-        if not preserve_srcdir and os.path.exists(srcdir):
-            shutil.rmtree(srcdir)
-        install(*args, **dargs)
-        if os.path.exists(srcdir):
-            pickle.dump(new_version, open(versionfile, 'w'))
-
-
-def get_stderr_level(stderr_is_expected):
-    if stderr_is_expected:
-        return DEFAULT_STDOUT_LEVEL
-    return DEFAULT_STDERR_LEVEL
-
-
-def run(command, timeout=None, ignore_status=False,
-        stdout_tee=None, stderr_tee=None, verbose=True, stdin=None,
-        stderr_is_expected=None, args=()):
-    """
-    Run a command on the host.
-
-    @param command: the command line string.
-    @param timeout: time limit in seconds before attempting to kill the
-            running process. The run() function will take a few seconds
-            longer than 'timeout' to complete if it has to kill the process.
-    @param ignore_status: do not raise an exception, no matter what the exit
-            code of the command is.
-    @param stdout_tee: optional file-like object to which stdout data
-            will be written as it is generated (data will still be stored
-            in result.stdout).
-    @param stderr_tee: likewise for stderr.
-    @param verbose: if True, log the command being run.
-    @param stdin: stdin to pass to the executed process (can be a file
-            descriptor, a file object of a real file or a string).
-    @param args: sequence of strings of arguments to be given to the command
-            inside " quotes after they have been escaped for that; each
-            element in the sequence will be given as a separate command
-            argument
-
-    @return a CmdResult object
-
-    @raise CmdError: the exit code of the command execution was not 0
-    """
-    if isinstance(args, basestring):
-        raise TypeError('Got a string for the "args" keyword argument, '
-                        'need a sequence.')
-
-    for arg in args:
-        command += ' "%s"' % sh_escape(arg)
-    if stderr_is_expected is None:
-        stderr_is_expected = ignore_status
-
-    bg_job = join_bg_jobs(
-        (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
-               stderr_level=get_stderr_level(stderr_is_expected)),),
-        timeout)[0]
-    if not ignore_status and bg_job.result.exit_status:
-        raise error.CmdError(command, bg_job.result,
-                             "Command returned non-zero exit status")
-
-    return bg_job.result
-
-
-def run_parallel(commands, timeout=None, ignore_status=False,
-                 stdout_tee=None, stderr_tee=None):
-    """
-    Behaves the same as run() with the following exceptions:
-
-    - commands is a list of commands to run in parallel.
-    - ignore_status toggles whether or not an exception should be raised
-      on any error.
-
-    @return: a list of CmdResult objects
-    """
-    bg_jobs = []
-    for command in commands:
-        bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
-                             stderr_level=get_stderr_level(ignore_status)))
-
-    # Updates objects in bg_jobs list with their process information
-    join_bg_jobs(bg_jobs, timeout)
-
-    for bg_job in bg_jobs:
-        if not ignore_status and bg_job.result.exit_status:
-            raise error.CmdError(command, bg_job.result,
-                                 "Command returned non-zero exit status")
-
-    return [bg_job.result for bg_job in bg_jobs]
-
-
-@deprecated
-def run_bg(command):
-    """Function deprecated. Please use BgJob class instead."""
-    bg_job = BgJob(command)
-    return bg_job.sp, bg_job.result
-
-
-def join_bg_jobs(bg_jobs, timeout=None):
-    """Joins the bg_jobs with the current thread.
-
-    Returns the same list of bg_jobs objects that was passed in.
-    """
-    ret, timeout_error = 0, False
-    for bg_job in bg_jobs:
-        bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
-
-    try:
-        # We are holding ends to stdin, stdout pipes
-        # hence we need to be sure to close those fds no mater what
-        start_time = time.time()
-        timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
-
-        for bg_job in bg_jobs:
-            # Process stdout and stderr
-            bg_job.process_output(stdout=True,final_read=True)
-            bg_job.process_output(stdout=False,final_read=True)
-    finally:
-        # close our ends of the pipes to the sp no matter what
-        for bg_job in bg_jobs:
-            bg_job.cleanup()
-
-    if timeout_error:
-        # TODO: This needs to be fixed to better represent what happens when
-        # running in parallel. However this is backwards compatable, so it will
-        # do for the time being.
-        raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
-                             "Command(s) did not complete within %d seconds"
-                             % timeout)
-
-
-    return bg_jobs
-
-
-def _wait_for_commands(bg_jobs, start_time, timeout):
-    # This returns True if it must return due to a timeout, otherwise False.
-
-    # To check for processes which terminate without producing any output
-    # a 1 second timeout is used in select.
-    SELECT_TIMEOUT = 1
-
-    read_list = []
-    write_list = []
-    reverse_dict = {}
-
-    for bg_job in bg_jobs:
-        read_list.append(bg_job.sp.stdout)
-        read_list.append(bg_job.sp.stderr)
-        reverse_dict[bg_job.sp.stdout] = (bg_job, True)
-        reverse_dict[bg_job.sp.stderr] = (bg_job, False)
-        if bg_job.string_stdin is not None:
-            write_list.append(bg_job.sp.stdin)
-            reverse_dict[bg_job.sp.stdin] = bg_job
-
-    if timeout:
-        stop_time = start_time + timeout
-        time_left = stop_time - time.time()
-    else:
-        time_left = None # so that select never times out
-
-    while not timeout or time_left > 0:
-        # select will return when we may write to stdin or when there is
-        # stdout/stderr output we can read (including when it is
-        # EOF, that is the process has terminated).
-        read_ready, write_ready, _ = select.select(read_list, write_list, [],
-                                                   SELECT_TIMEOUT)
-
-        # os.read() has to be used instead of
-        # subproc.stdout.read() which will otherwise block
-        for file_obj in read_ready:
-            bg_job, is_stdout = reverse_dict[file_obj]
-            bg_job.process_output(is_stdout)
-
-        for file_obj in write_ready:
-            # we can write PIPE_BUF bytes without blocking
-            # POSIX requires PIPE_BUF is >= 512
-            bg_job = reverse_dict[file_obj]
-            file_obj.write(bg_job.string_stdin[:512])
-            bg_job.string_stdin = bg_job.string_stdin[512:]
-            # no more input data, close stdin, remove it from the select set
-            if not bg_job.string_stdin:
-                file_obj.close()
-                write_list.remove(file_obj)
-                del reverse_dict[file_obj]
-
-        all_jobs_finished = True
-        for bg_job in bg_jobs:
-            if bg_job.result.exit_status is not None:
-                continue
-
-            bg_job.result.exit_status = bg_job.sp.poll()
-            if bg_job.result.exit_status is not None:
-                # process exited, remove its stdout/stdin from the select set
-                bg_job.result.duration = time.time() - start_time
-                read_list.remove(bg_job.sp.stdout)
-                read_list.remove(bg_job.sp.stderr)
-                del reverse_dict[bg_job.sp.stdout]
-                del reverse_dict[bg_job.sp.stderr]
-            else:
-                all_jobs_finished = False
-
-        if all_jobs_finished:
-            return False
-
-        if timeout:
-            time_left = stop_time - time.time()
-
-    # Kill all processes which did not complete prior to timeout
-    for bg_job in bg_jobs:
-        if bg_job.result.exit_status is not None:
-            continue
-
-        logging.warn('run process timeout (%s) fired on: %s', timeout,
-                     bg_job.command)
-        nuke_subprocess(bg_job.sp)
-        bg_job.result.exit_status = bg_job.sp.poll()
-        bg_job.result.duration = time.time() - start_time
-
-    return True
-
-
-def pid_is_alive(pid):
-    """
-    True if process pid exists and is not yet stuck in Zombie state.
-    Zombies are impossible to move between cgroups, etc.
-    pid can be integer, or text of integer.
-    """
-    path = '/proc/%s/stat' % pid
-
-    try:
-        stat = read_one_line(path)
-    except IOError:
-        if not os.path.exists(path):
-            # file went away
-            return False
-        raise
-
-    return stat.split()[2] != 'Z'
-
-
-def signal_pid(pid, sig):
-    """
-    Sends a signal to a process id. Returns True if the process terminated
-    successfully, False otherwise.
-    """
-    try:
-        os.kill(pid, sig)
-    except OSError:
-        # The process may have died before we could kill it.
-        pass
-
-    for i in range(5):
-        if not pid_is_alive(pid):
-            return True
-        time.sleep(1)
-
-    # The process is still alive
-    return False
-
-
-def nuke_subprocess(subproc):
-    # check if the subprocess is still alive, first
-    if subproc.poll() is not None:
-        return subproc.poll()
-
-    # the process has not terminated within timeout,
-    # kill it via an escalating series of signals.
-    signal_queue = [signal.SIGTERM, signal.SIGKILL]
-    for sig in signal_queue:
-        signal_pid(subproc.pid, sig)
-        if subproc.poll() is not None:
-            return subproc.poll()
-
-
-def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
-    # the process has not terminated within timeout,
-    # kill it via an escalating series of signals.
-    for sig in signal_queue:
-        if signal_pid(pid, sig):
-            return
-
-    # no signal successfully terminated the process
-    raise error.AutoservRunError('Could not kill %d' % pid, None)
-
-
-def system(command, timeout=None, ignore_status=False):
-    """
-    Run a command
-
-    @param timeout: timeout in seconds
-    @param ignore_status: if ignore_status=False, throw an exception if the
-            command's exit code is non-zero
-            if ignore_stauts=True, return the exit code.
-
-    @return exit status of command
-            (note, this will always be zero unless ignore_status=True)
-    """
-    return run(command, timeout=timeout, ignore_status=ignore_status,
-               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
-
-
-def system_parallel(commands, timeout=None, ignore_status=False):
-    """This function returns a list of exit statuses for the respective
-    list of commands."""
-    return [bg_jobs.exit_status for bg_jobs in
-            run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
-                         stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
-
-
-def system_output(command, timeout=None, ignore_status=False,
-                  retain_output=False, args=()):
-    """
-    Run a command and return the stdout output.
-
-    @param command: command string to execute.
-    @param timeout: time limit in seconds before attempting to kill the
-            running process. The function will take a few seconds longer
-            than 'timeout' to complete if it has to kill the process.
-    @param ignore_status: do not raise an exception, no matter what the exit
-            code of the command is.
-    @param retain_output: set to True to make stdout/stderr of the command
-            output to be also sent to the logging system
-    @param args: sequence of strings of arguments to be given to the command
-            inside " quotes after they have been escaped for that; each
-            element in the sequence will be given as a separate command
-            argument
-
-    @return a string with the stdout output of the command.
-    """
-    if retain_output:
-        out = run(command, timeout=timeout, ignore_status=ignore_status,
-                  stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
-                  args=args).stdout
-    else:
-        out = run(command, timeout=timeout, ignore_status=ignore_status,
-                  args=args).stdout
-    if out[-1:] == '\n':
-        out = out[:-1]
-    return out
-
-
-def system_output_parallel(commands, timeout=None, ignore_status=False,
-                           retain_output=False):
-    if retain_output:
-        out = [bg_job.stdout for bg_job
-               in run_parallel(commands, timeout=timeout,
-                               ignore_status=ignore_status,
-                               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
-    else:
-        out = [bg_job.stdout for bg_job in run_parallel(commands,
-                                  timeout=timeout, ignore_status=ignore_status)]
-    for x in out:
-        if out[-1:] == '\n': out = out[:-1]
-    return out
-
-
-def strip_unicode(input):
-    if type(input) == list:
-        return [strip_unicode(i) for i in input]
-    elif type(input) == dict:
-        output = {}
-        for key in input.keys():
-            output[str(key)] = strip_unicode(input[key])
-        return output
-    elif type(input) == unicode:
-        return str(input)
-    else:
-        return input
-
-
-def get_cpu_percentage(function, *args, **dargs):
-    """Returns a tuple containing the CPU% and return value from function call.
-
-    This function calculates the usage time by taking the difference of
-    the user and system times both before and after the function call.
-    """
-    child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
-    self_pre = resource.getrusage(resource.RUSAGE_SELF)
-    start = time.time()
-    to_return = function(*args, **dargs)
-    elapsed = time.time() - start
-    self_post = resource.getrusage(resource.RUSAGE_SELF)
-    child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
-
-    # Calculate CPU Percentage
-    s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
-    c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
-    cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
-
-    return cpu_percent, to_return
-
-
-class SystemLoad(object):
-    """
-    Get system and/or process values and return average value of load.
-    """
-    def __init__(self, pids, advanced=False, time_step=0.1, cpu_cont=False,
-                 use_log=False):
-        """
-        @param pids: List of pids to be monitored. If pid = 0 whole system will
-          be monitored. pid == 0 means whole system.
-        @param advanced: monitor add value for system irq count and softirq
-          for process minor and maior page fault
-        @param time_step: Time step for continuous monitoring.
-        @param cpu_cont: If True monitor CPU load continuously.
-        @param use_log: If true every monitoring is logged for dump.
-        """
-        self.pids = []
-        self.stats = {}
-        for pid in pids:
-            if pid == 0:
-                cpu = FileFieldMonitor("/proc/stat",
-                                       [("cpu", 0), # User Time
-                                        ("cpu", 2), # System Time
-                                        ("intr", 0), # IRQ Count
-                                        ("softirq", 0)], # Soft IRQ Count
-                                       True,
-                                       cpu_cont,
-                                       use_log,
-                                       " +",
-                                       time_step)
-                mem = FileFieldMonitor("/proc/meminfo",
-                                       [("MemTotal:", 0), # Mem Total
-                                        ("MemFree:", 0), # Mem Free
-                                        ("Buffers:", 0), # Buffers
-                                        ("Cached:", 0)], # Cached
-                                       False,
-                                       True,
-                                       use_log,
-                                       " +",
-                                       time_step)
-                self.stats[pid] = ["TOTAL", cpu, mem]
-                self.pids.append(pid)
-            else:
-                name = ""
-                if (type(pid) is int):
-                    self.pids.append(pid)
-                    name = get_process_name(pid)
-                else:
-                    self.pids.append(pid[0])
-                    name = pid[1]
-
-                cpu = FileFieldMonitor("/proc/%d/stat" %
-                                       self.pids[-1],
-                                       [("", 13), # User Time
-                                        ("", 14), # System Time
-                                        ("", 9), # Minority Page Fault
-                                        ("", 11)], # Majority Page Fault
-                                       True,
-                                       cpu_cont,
-                                       use_log,
-                                       " +",
-                                       time_step)
-                mem = FileFieldMonitor("/proc/%d/status" %
-                                       self.pids[-1],
-                                       [("VmSize:", 0), # Virtual Memory Size
-                                        ("VmRSS:", 0), # Resident Set Size
-                                        ("VmPeak:", 0), # Peak VM Size
-                                        ("VmSwap:", 0)], # VM in Swap
-                                       False,
-                                       True,
-                                       use_log,
-                                       " +",
-                                       time_step)
-                self.stats[self.pids[-1]] = [name, cpu, mem]
-
-        self.advanced = advanced
-
-
-    def __str__(self):
-        """
-        Define format how to print
-        """
-        out = ""
-        for pid in self.pids:
-            for stat in self.stats[pid][1:]:
-                out += str(stat.get_status()) + "\n"
-        return out
-
-
-    def start(self, pids=[]):
-        """
-        Start monitoring of the process system usage.
-        @param pids: List of PIDs you intend to control. Use pids=[] to control
-            all defined PIDs.
-        """
-        if pids == []:
-            pids = self.pids
-
-        for pid in pids:
-            for stat in self.stats[pid][1:]:
-                stat.start()
-
-
-    def stop(self, pids=[]):
-        """
-        Stop monitoring of the process system usage.
-        @param pids: List of PIDs you intend to control. Use pids=[] to control
-            all defined PIDs.
-        """
-        if pids == []:
-            pids = self.pids
-
-        for pid in pids:
-            for stat in self.stats[pid][1:]:
-                stat.stop()
-
-
-    def dump(self, pids=[]):
-        """
-        Get the status of monitoring.
-        @param pids: List of PIDs you intend to control. Use pids=[] to control
-            all defined PIDs.
-         @return:
-            tuple([cpu load], [memory load]):
-                ([(PID1, (PID1_cpu_meas)), (PID2, (PID2_cpu_meas)), ...],
-                 [(PID1, (PID1_mem_meas)), (PID2, (PID2_mem_meas)), ...])
-
-            PID1_cpu_meas:
-                average_values[], test_time, cont_meas_values[[]], time_step
-            PID1_mem_meas:
-                average_values[], test_time, cont_meas_values[[]], time_step
-            where average_values[] are the measured values (mem_free,swap,...)
-            which are described in SystemLoad.__init__()-FileFieldMonitor.
-            cont_meas_values[[]] is a list of average_values in the sampling
-            times.
-        """
-        if pids == []:
-            pids = self.pids
-
-        cpus = []
-        memory = []
-        for pid in pids:
-            stat = (pid, self.stats[pid][1].get_status())
-            cpus.append(stat)
-        for pid in pids:
-            stat = (pid, self.stats[pid][2].get_status())
-            memory.append(stat)
-
-        return (cpus, memory)
-
-
-    def get_cpu_status_string(self, pids=[]):
-        """
-        Convert status to string array.
-        @param pids: List of PIDs you intend to control. Use pids=[] to control
-            all defined PIDs.
-        @return: String format to table.
-        """
-        if pids == []:
-            pids = self.pids
-
-        headers = ["NAME",
-                   ("%7s") % "PID",
-                   ("%5s") % "USER",
-                   ("%5s") % "SYS",
-                   ("%5s") % "SUM"]
-        if self.advanced:
-            headers.extend(["MINFLT/IRQC",
-                            "MAJFLT/SOFTIRQ"])
-        headers.append(("%11s") % "TIME")
-        textstatus = []
-        for pid in pids:
-            stat = self.stats[pid][1].get_status()
-            time = stat[1]
-            stat = stat[0]
-            textstatus.append(["%s" % self.stats[pid][0],
-                               "%7s" % pid,
-                               "%4.0f%%" % (stat[0] / time),
-                               "%4.0f%%" % (stat[1] / time),
-                               "%4.0f%%" % ((stat[0] + stat[1]) / time),
-                               "%10.3fs" % time])
-            if self.advanced:
-                textstatus[-1].insert(-1, "%11d" % stat[2])
-                textstatus[-1].insert(-1, "%14d" % stat[3])
-
-        return matrix_to_string(textstatus, tuple(headers))
-
-
-    def get_mem_status_string(self, pids=[]):
-        """
-        Convert status to string array.
-        @param pids: List of PIDs you intend to control. Use pids=[] to control
-            all defined PIDs.
-        @return: String format to table.
-        """
-        if pids == []:
-            pids = self.pids
-
-        headers = ["NAME",
-                   ("%7s") % "PID",
-                   ("%8s") % "TOTAL/VMSIZE",
-                   ("%8s") % "FREE/VMRSS",
-                   ("%8s") % "BUFFERS/VMPEAK",
-                   ("%8s") % "CACHED/VMSWAP",
-                   ("%11s") % "TIME"]
-        textstatus = []
-        for pid in pids:
-            stat = self.stats[pid][2].get_status()
-            time = stat[1]
-            stat = stat[0]
-            textstatus.append(["%s" % self.stats[pid][0],
-                               "%7s" % pid,
-                               "%10dMB" % (stat[0] / 1024),
-                               "%8dMB" % (stat[1] / 1024),
-                               "%12dMB" % (stat[2] / 1024),
-                               "%11dMB" % (stat[3] / 1024),
-                               "%10.3fs" % time])
-
-        return matrix_to_string(textstatus, tuple(headers))
-
-
-def get_arch(run_function=run):
-    """
-    Get the hardware architecture of the machine.
-    run_function is used to execute the commands. It defaults to
-    utils.run() but a custom method (if provided) should be of the
-    same schema as utils.run. It should return a CmdResult object and
-    throw a CmdError exception.
-    """
-    arch = run_function('/bin/uname -m').stdout.rstrip()
-    if re.match(r'i\d86$', arch):
-        arch = 'i386'
-    return arch
-
-
-def get_num_logical_cpus_per_socket(run_function=run):
-    """
-    Get the number of cores (including hyperthreading) per cpu.
-    run_function is used to execute the commands. It defaults to
-    utils.run() but a custom method (if provided) should be of the
-    same schema as utils.run. It should return a CmdResult object and
-    throw a CmdError exception.
-    """
-    siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
-    num_siblings = map(int,
-                       re.findall(r'^siblings\s*:\s*(\d+)\s*$',
-                                  siblings, re.M))
-    if len(num_siblings) == 0:
-        raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
-    if min(num_siblings) != max(num_siblings):
-        raise error.TestError('Number of siblings differ %r' %
-                              num_siblings)
-    return num_siblings[0]
-
-
-def merge_trees(src, dest):
-    """
-    Merges a source directory tree at 'src' into a destination tree at
-    'dest'. If a path is a file in both trees than the file in the source
-    tree is APPENDED to the one in the destination tree. If a path is
-    a directory in both trees then the directories are recursively merged
-    with this function. In any other case, the function will skip the
-    paths that cannot be merged (instead of failing).
-    """
-    if not os.path.exists(src):
-        return # exists only in dest
-    elif not os.path.exists(dest):
-        if os.path.isfile(src):
-            shutil.copy2(src, dest) # file only in src
-        else:
-            shutil.copytree(src, dest, symlinks=True) # dir only in src
-        return
-    elif os.path.isfile(src) and os.path.isfile(dest):
-        # src & dest are files in both trees, append src to dest
-        destfile = open(dest, "a")
-        try:
-            srcfile = open(src)
-            try:
-                destfile.write(srcfile.read())
-            finally:
-                srcfile.close()
-        finally:
-            destfile.close()
-    elif os.path.isdir(src) and os.path.isdir(dest):
-        # src & dest are directories in both trees, so recursively merge
-        for name in os.listdir(src):
-            merge_trees(os.path.join(src, name), os.path.join(dest, name))
-    else:
-        # src & dest both exist, but are incompatible
-        return
-
-
-class CmdResult(object):
-    """
-    Command execution result.
-
-    command:     String containing the command line itself
-    exit_status: Integer exit code of the process
-    stdout:      String containing stdout of the process
-    stderr:      String containing stderr of the process
-    duration:    Elapsed wall clock time running the process
-    """
-
-
-    def __init__(self, command="", stdout="", stderr="",
-                 exit_status=None, duration=0):
-        self.command = command
-        self.exit_status = exit_status
-        self.stdout = stdout
-        self.stderr = stderr
-        self.duration = duration
-
-
-    def __repr__(self):
-        wrapper = textwrap.TextWrapper(width = 78,
-                                       initial_indent="\n    ",
-                                       subsequent_indent="    ")
-
-        stdout = self.stdout.rstrip()
-        if stdout:
-            stdout = "\nstdout:\n%s" % stdout
-
-        stderr = self.stderr.rstrip()
-        if stderr:
-            stderr = "\nstderr:\n%s" % stderr
-
-        return ("* Command: %s\n"
-                "Exit status: %s\n"
-                "Duration: %s\n"
-                "%s"
-                "%s"
-                % (wrapper.fill(self.command), self.exit_status,
-                self.duration, stdout, stderr))
-
-
-class run_randomly:
-    def __init__(self, run_sequentially=False):
-        # Run sequentially is for debugging control files
-        self.test_list = []
-        self.run_sequentially = run_sequentially
-
-
-    def add(self, *args, **dargs):
-        test = (args, dargs)
-        self.test_list.append(test)
-
-
-    def run(self, fn):
-        while self.test_list:
-            test_index = random.randint(0, len(self.test_list)-1)
-            if self.run_sequentially:
-                test_index = 0
-            (args, dargs) = self.test_list.pop(test_index)
-            fn(*args, **dargs)
-
-
-def import_site_module(path, module, dummy=None, modulefile=None):
-    """
-    Try to import the site specific module if it exists.
-
-    @param path full filename of the source file calling this (ie __file__)
-    @param module full module name
-    @param dummy dummy value to return in case there is no symbol to import
-    @param modulefile module filename
-
-    @return site specific module or dummy
-
-    @raises ImportError if the site file exists but imports fails
-    """
-    short_module = module[module.rfind(".") + 1:]
-
-    if not modulefile:
-        modulefile = short_module + ".py"
-
-    if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
-        return __import__(module, {}, {}, [short_module])
-    return dummy
-
-
-def import_site_symbol(path, module, name, dummy=None, modulefile=None):
-    """
-    Try to import site specific symbol from site specific file if it exists
-
-    @param path full filename of the source file calling this (ie __file__)
-    @param module full module name
-    @param name symbol name to be imported from the site file
-    @param dummy dummy value to return in case there is no symbol to import
-    @param modulefile module filename
-
-    @return site specific symbol or dummy
-
-    @raises ImportError if the site file exists but imports fails
-    """
-    module = import_site_module(path, module, modulefile=modulefile)
-    if not module:
-        return dummy
-
-    # special unique value to tell us if the symbol can't be imported
-    cant_import = object()
-
-    obj = getattr(module, name, cant_import)
-    if obj is cant_import:
-        logging.debug("unable to import site symbol '%s', using non-site "
-                      "implementation", name)
-        return dummy
-
-    return obj
-
-
-def import_site_class(path, module, classname, baseclass, modulefile=None):
-    """
-    Try to import site specific class from site specific file if it exists
-
-    Args:
-        path: full filename of the source file calling this (ie __file__)
-        module: full module name
-        classname: class name to be loaded from site file
-        baseclass: base class object to return when no site file present or
-            to mixin when site class exists but is not inherited from baseclass
-        modulefile: module filename
-
-    Returns: baseclass if site specific class does not exist, the site specific
-        class if it exists and is inherited from baseclass or a mixin of the
-        site specific class and baseclass when the site specific class exists
-        and is not inherited from baseclass
-
-    Raises: ImportError if the site file exists but imports fails
-    """
-
-    res = import_site_symbol(path, module, classname, None, modulefile)
-    if res:
-        if not issubclass(res, baseclass):
-            # if not a subclass of baseclass then mix in baseclass with the
-            # site specific class object and return the result
-            res = type(classname, (res, baseclass), {})
-    else:
-        res = baseclass
-
-    return res
-
-
-def import_site_function(path, module, funcname, dummy, modulefile=None):
-    """
-    Try to import site specific function from site specific file if it exists
-
-    Args:
-        path: full filename of the source file calling this (ie __file__)
-        module: full module name
-        funcname: function name to be imported from site file
-        dummy: dummy function to return in case there is no function to import
-        modulefile: module filename
-
-    Returns: site specific function object or dummy
-
-    Raises: ImportError if the site file exists but imports fails
-    """
-
-    return import_site_symbol(path, module, funcname, dummy, modulefile)
-
-
-def _get_pid_path(program_name):
-    my_path = os.path.dirname(__file__)
-    return os.path.abspath(os.path.join(my_path, "..", "..",
-                                        "%s.pid" % program_name))
-
-
-def write_pid(program_name):
-    """
-    Try to drop <program_name>.pid in the main autotest directory.
-
-    Args:
-      program_name: prefix for file name
-    """
-    pidfile = open(_get_pid_path(program_name), "w")
-    try:
-        pidfile.write("%s\n" % os.getpid())
-    finally:
-        pidfile.close()
-
-
-def delete_pid_file_if_exists(program_name):
-    """
-    Tries to remove <program_name>.pid from the main autotest directory.
-    """
-    pidfile_path = _get_pid_path(program_name)
-
-    try:
-        os.remove(pidfile_path)
-    except OSError:
-        if not os.path.exists(pidfile_path):
-            return
-        raise
-
-
-def get_pid_from_file(program_name):
-    """
-    Reads the pid from <program_name>.pid in the autotest directory.
-
-    @param program_name the name of the program
-    @return the pid if the file exists, None otherwise.
-    """
-    pidfile_path = _get_pid_path(program_name)
-    if not os.path.exists(pidfile_path):
-        return None
-
-    pidfile = open(_get_pid_path(program_name), 'r')
-
-    try:
-        try:
-            pid = int(pidfile.readline())
-        except IOError:
-            if not os.path.exists(pidfile_path):
-                return None
-            raise
-    finally:
-        pidfile.close()
-
-    return pid
-
-
-def get_process_name(pid):
-    """
-    Get process name from PID.
-    @param pid: PID of process.
-    """
-    return get_field(read_file("/proc/%d/stat" % pid), 1)[1:-1]
-
-
-def program_is_alive(program_name):
-    """
-    Checks if the process is alive and not in Zombie state.
-
-    @param program_name the name of the program
-    @return True if still alive, False otherwise
-    """
-    pid = get_pid_from_file(program_name)
-    if pid is None:
-        return False
-    return pid_is_alive(pid)
-
-
-def signal_program(program_name, sig=signal.SIGTERM):
-    """
-    Sends a signal to the process listed in <program_name>.pid
-
-    @param program_name the name of the program
-    @param sig signal to send
-    """
-    pid = get_pid_from_file(program_name)
-    if pid:
-        signal_pid(pid, sig)
-
-
-def get_relative_path(path, reference):
-    """Given 2 absolute paths "path" and "reference", compute the path of
-    "path" as relative to the directory "reference".
-
-    @param path the absolute path to convert to a relative path
-    @param reference an absolute directory path to which the relative
-        path will be computed
-    """
-    # normalize the paths (remove double slashes, etc)
-    assert(os.path.isabs(path))
-    assert(os.path.isabs(reference))
-
-    path = os.path.normpath(path)
-    reference = os.path.normpath(reference)
-
-    # we could use os.path.split() but it splits from the end
-    path_list = path.split(os.path.sep)[1:]
-    ref_list = reference.split(os.path.sep)[1:]
-
-    # find the longest leading common path
-    for i in xrange(min(len(path_list), len(ref_list))):
-        if path_list[i] != ref_list[i]:
-            # decrement i so when exiting this loop either by no match or by
-            # end of range we are one step behind
-            i -= 1
-            break
-    i += 1
-    # drop the common part of the paths, not interested in that anymore
-    del path_list[:i]
-
-    # for each uncommon component in the reference prepend a ".."
-    path_list[:0] = ['..'] * (len(ref_list) - i)
-
-    return os.path.join(*path_list)
-
-
-def sh_escape(command):
-    """
-    Escape special characters from a command so that it can be passed
-    as a double quoted (" ") string in a (ba)sh command.
-
-    Args:
-            command: the command string to escape.
-
-    Returns:
-            The escaped command string. The required englobing double
-            quotes are NOT added and so should be added at some point by
-            the caller.
-
-    See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
-    """
-    command = command.replace("\\", "\\\\")
-    command = command.replace("$", r'\$')
-    command = command.replace('"', r'\"')
-    command = command.replace('`', r'\`')
-    return command
-
-
-def configure(extra=None, configure='./configure'):
-    """
-    Run configure passing in the correct host, build, and target options.
-
-    @param extra: extra command line arguments to pass to configure
-    @param configure: which configure script to use
-    """
-    args = []
-    if 'CHOST' in os.environ:
-        args.append('--host=' + os.environ['CHOST'])
-    if 'CBUILD' in os.environ:
-        args.append('--build=' + os.environ['CBUILD'])
-    if 'CTARGET' in os.environ:
-        args.append('--target=' + os.environ['CTARGET'])
-    if extra:
-        args.append(extra)
-
-    system('%s %s' % (configure, ' '.join(args)))
-
-
-def make(extra='', make='make', timeout=None, ignore_status=False):
-    """
-    Run make, adding MAKEOPTS to the list of options.
-
-    @param extra: extra command line arguments to pass to make.
-    """
-    cmd = '%s %s %s' % (make, os.environ.get('MAKEOPTS', ''), extra)
-    return system(cmd, timeout=timeout, ignore_status=ignore_status)
-
-
-def compare_versions(ver1, ver2):
-    """Version number comparison between ver1 and ver2 strings.
-
-    >>> compare_tuple("1", "2")
-    -1
-    >>> compare_tuple("foo-1.1", "foo-1.2")
-    -1
-    >>> compare_tuple("1.2", "1.2a")
-    -1
-    >>> compare_tuple("1.2b", "1.2a")
-    1
-    >>> compare_tuple("1.3.5.3a", "1.3.5.3b")
-    -1
-
-    Args:
-        ver1: version string
-        ver2: version string
-
-    Returns:
-        int:  1 if ver1 >  ver2
-              0 if ver1 == ver2
-             -1 if ver1 <  ver2
-    """
-    ax = re.split('[.-]', ver1)
-    ay = re.split('[.-]', ver2)
-    while len(ax) > 0 and len(ay) > 0:
-        cx = ax.pop(0)
-        cy = ay.pop(0)
-        maxlen = max(len(cx), len(cy))
-        c = cmp(cx.zfill(maxlen), cy.zfill(maxlen))
-        if c != 0:
-            return c
-    return cmp(len(ax), len(ay))
-
-
-def args_to_dict(args):
-    """Convert autoserv extra arguments in the form of key=val or key:val to a
-    dictionary.  Each argument key is converted to lowercase dictionary key.
-
-    Args:
-        args - list of autoserv extra arguments.
-
-    Returns:
-        dictionary
-    """
-    arg_re = re.compile(r'(\w+)[:=](.*)$')
-    dict = {}
-    for arg in args:
-        match = arg_re.match(arg)
-        if match:
-            dict[match.group(1).lower()] = match.group(2)
-        else:
-            logging.warning("args_to_dict: argument '%s' doesn't match "
-                            "'%s' pattern. Ignored." % (arg, arg_re.pattern))
-    return dict
-
-
-def get_unused_port():
-    """
-    Finds a semi-random available port. A race condition is still
-    possible after the port number is returned, if another process
-    happens to bind it.
-
-    Returns:
-        A port number that is unused on both TCP and UDP.
-    """
-
-    def try_bind(port, socket_type, socket_proto):
-        s = socket.socket(socket.AF_INET, socket_type, socket_proto)
-        try:
-            try:
-                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-                s.bind(('', port))
-                return s.getsockname()[1]
-            except socket.error:
-                return None
-        finally:
-            s.close()
-
-    # On the 2.6 kernel, calling try_bind() on UDP socket returns the
-    # same port over and over. So always try TCP first.
-    while True:
-        # Ask the OS for an unused port.
-        port = try_bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP)
-        # Check if this port is unused on the other protocol.
-        if port and try_bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP):
-            return port
+from autotest_lib.client.common_lib.base_utils import *
+if os.path.exists(os.path.join(os.path.dirname(__file__), 'site_utils.py')):
+    from autotest_lib.client.common_lib.site_utils import *
diff --git a/client/profilers/cpistat/cpistat.py b/client/profilers/cpistat/cpistat.py
index 01524fb..1a4328d 100644
--- a/client/profilers/cpistat/cpistat.py
+++ b/client/profilers/cpistat/cpistat.py
@@ -17,7 +17,7 @@
     def start(self, test):
         cmd = os.path.join(self.bindir, 'site_cpistat')
         if not os.path.exists(cmd):
-           cmd = os.path.join(self.bindir, 'cpistat')
+            cmd = os.path.join(self.bindir, 'cpistat')
         logfile = open(os.path.join(test.profdir, "cpistat"), 'w')
         p = subprocess.Popen(cmd, stdout=logfile,
                              stderr=subprocess.STDOUT)
diff --git a/client/samples/filesystem b/client/samples/filesystem
deleted file mode 100644
index 2fd1b9d..0000000
--- a/client/samples/filesystem
+++ /dev/null
@@ -1,25 +0,0 @@
-# Uncomment this line, and replace the device with something sensible
-# for you ...
-# fs = job.partition('/dev/hda2', job.tmpdir)
-# or ...
-
-part = job.partition('/tmp/looped', 1024, job.tmpdir)
-
-# dbench 1024, ltp, 1024-byte blocksize, a few other things.  Lots of fscking.
-# I haven't tested nobh mode yet, 
-# and I have yet to point run-bash-shared-mapping at it.
-# (different mount options for ext3)
-
-def test_fs():
-	part.mkfs(fstype)
-	part.mount()
-	try:
-		job.run_test('fsx', dir=part.mountpoint, tag=fstype)
-		job.run_test('iozone', dir=part.mountpoint, tag=fstype)
-		job.run_test('dbench', dir=part.mountpoint, tag=fstype)
-	finally:
-		part.unmount()
-		part.fsck()
-
-for fstype in ('ext2', 'ext3', 'jfs', 'xfs', 'reiserfs'):
-	job.run_group(test_fs)
diff --git a/client/tests/hackbench/hackbench.py b/client/tests/hackbench/hackbench.py
index 5861888..15e93d7 100644
--- a/client/tests/hackbench/hackbench.py
+++ b/client/tests/hackbench/hackbench.py
@@ -18,9 +18,9 @@
     def setup(self):
         os.chdir(self.srcdir)
         if 'CC' in os.environ:
-          cc = '$CC'
+            cc = '$CC'
         else:
-          cc = 'cc'
+            cc = 'cc'
         utils.system('%s -lpthread hackbench.c -o hackbench' % cc)
 
 
diff --git a/client/tests/iozone/iozone.py b/client/tests/iozone/iozone.py
old mode 100755
new mode 100644
diff --git a/client/tests/kvm/build.cfg.sample b/client/tests/kvm/build.cfg.sample
index 860192b..a689ed4 100644
--- a/client/tests/kvm/build.cfg.sample
+++ b/client/tests/kvm/build.cfg.sample
@@ -16,30 +16,42 @@
         save_results = no
         variants:
             - release:
-                mode = release
+                install_mode = release
                 ## Install from a kvm release. You can optionally specify
                 ## a release tag. If you omit it, the build test will get
                 ## the latest release tag available at that moment.
                 # release_tag = 84
                 release_dir = http://downloads.sourceforge.net/project/kvm/
                 release_listing = http://sourceforge.net/projects/kvm/files/
+                # In some cases, you might want to provide a ROM dir, so ROM
+                # files can be copied from there to your source based install
+                # path_to_rom_images = /usr/share/kvm
             - snapshot:
-                mode = snapshot
+                install_mode = snapshot
                 ## Install from a kvm snapshot location. You can optionally
                 ## specify a snapshot date. If you omit it, the test will get
                 ## yesterday's snapshot.
                 # snapshot_date = 20090712
                 snapshot_dir = http://foo.org/kvm-snapshots/
+                # In some cases, you might want to provide a ROM dir, so ROM
+                # files can be copied from there to your source based install
+                # path_to_rom_images = /usr/share/kvm
             - localtar:
-                mode = localtar
+                install_mode = localtar
                 ## Install from tarball located on the host's filesystem.
                 tarball = /tmp/kvm-84.tar.gz
+                # In some cases, you might want to provide a ROM dir, so ROM
+                # files can be copied from there to your source based install
+                # path_to_rom_images = /usr/share/kvm
             - localsrc:
-                mode = localsrc
+                install_mode = localsrc
                 ## Install from tarball located on the host's filesystem.
                 srcdir = /tmp/kvm-84
+                # In some cases, you might want to provide a ROM dir, so ROM
+                # files can be copied from there to your source based install
+                # path_to_rom_images = /usr/share/kvm
             - git:
-                mode = git
+                install_mode = git
                 ## Install KVM from git repositories.
                 ## If you provide only "git_repo" and "user_git_repo", the
                 ## build test will assume it will perform all build from the
@@ -64,8 +76,11 @@
                 # kmod_lbranch = kmod_lbranch_name
                 # kmod_commit = kmod_commit_name
                 # kmod_patches = ['http://foo.com/patch1', 'http://foo.com/patch2']
+                # In some cases, you might want to provide a ROM dir, so ROM
+                # files can be copied from there to your source based install
+                # path_to_rom_images = /usr/share/kvm
             - yum:
-                mode = yum
+                install_mode = yum
                 src_pkg = qemu
                 ## Name of the rpms we need installed
                 pkg_list = ['qemu-kvm', 'qemu-kvm-tools', 'qemu-system-x86', 'qemu-common', 'qemu-img']
@@ -74,7 +89,7 @@
                 ## List of RPMs that will be installed
                 pkg_path_list = ['http://foo.com/rpm1', 'http://foo.com/rpm2']
             - koji:
-                mode = koji
+                install_mode = koji
                 ## Install KVM from koji (Fedora build server)
                 ## It is possible to install packages right from Koji if you
                 ## provide a release tag or a build.
diff --git a/client/tests/kvm/deps/finish.exe b/client/tests/kvm/deps/finish.exe
old mode 100755
new mode 100644
Binary files differ
diff --git a/client/tests/kvm/deps/rss.exe b/client/tests/kvm/deps/rss.exe
old mode 100755
new mode 100644
Binary files differ
diff --git a/client/tests/kvm/deps/test_clock_getres/Makefile b/client/tests/kvm/deps/test_clock_getres/Makefile
new file mode 100644
index 0000000..b4f73c7
--- /dev/null
+++ b/client/tests/kvm/deps/test_clock_getres/Makefile
@@ -0,0 +1,11 @@
+CC = gcc
+PROG = test_clock_getres
+SRC = test_clock_getres.c
+LIBS = -lrt
+
+all: $(PROG)
+
+$(PROG):
+	$(CC) $(LIBS) -o $(PROG) $(SRC)
+clean:
+	rm -f $(PROG)
diff --git a/client/tests/kvm/deps/test_clock_getres/test_clock_getres.c b/client/tests/kvm/deps/test_clock_getres/test_clock_getres.c
new file mode 100644
index 0000000..81d3b9c
--- /dev/null
+++ b/client/tests/kvm/deps/test_clock_getres/test_clock_getres.c
@@ -0,0 +1,58 @@
+/*
+ *  Test clock resolution for KVM guests that have kvm-clock as clock source
+ *
+ *  Copyright (c) 2010 Red Hat, Inc
+ *  Author: Lucas Meneghel Rodrigues <lmr@redhat.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void) {
+	struct timespec res;
+	int clock_return = clock_getres(CLOCK_MONOTONIC, &res);
+	char clocksource[50];
+	char line[80];
+	FILE *fr;
+	if ((fr = fopen(
+			"/sys/devices/system/clocksource/clocksource0/current_clocksource",
+			"rt")) == NULL) {
+		perror("fopen");
+		return EXIT_FAILURE;
+	}
+	while (fgets(line, 80, fr) != NULL) {
+		sscanf(line, "%s", &clocksource);
+	}
+	fclose(fr);
+	if (!strncmp(clocksource, "kvm-clock", strlen("kvm-clock"))) {
+		if (clock_return == 0) {
+			if (res.tv_sec > 1 || res.tv_nsec > 100) {
+				printf("FAIL: clock_getres returned bad clock resolution\n");
+				return EXIT_FAILURE;
+			} else {
+				printf("PASS: check successful\n");
+				return EXIT_SUCCESS;
+			}
+		} else {
+			printf("FAIL: clock_getres failed\n");
+			return EXIT_FAILURE;
+		}
+	} else {
+		printf("FAIL: invalid clock source: %s\n", clocksource);
+		return EXIT_FAILURE;
+	}
+}
diff --git a/client/tests/kvm/deps/whql_delete_machine_15.exe b/client/tests/kvm/deps/whql_delete_machine_15.exe
old mode 100755
new mode 100644
Binary files differ
diff --git a/client/tests/kvm/deps/whql_submission_15.cs b/client/tests/kvm/deps/whql_submission_15.cs
index 8fa6856..2a29ac5 100644
--- a/client/tests/kvm/deps/whql_submission_15.cs
+++ b/client/tests/kvm/deps/whql_submission_15.cs
@@ -16,20 +16,140 @@
 {
     class AutoJob
     {
+        // Wait for a machine to show up in the data store
+        static void FindMachine(IResourcePool rootPool, string machineName)
+        {
+            Console.WriteLine("Looking for machine '{0}'", machineName);
+            IResource machine = null;
+            while (true)
+            {
+                try
+                {
+                    machine = rootPool.GetResourceByName(machineName);
+                }
+                catch (Exception e)
+                {
+                    Console.WriteLine("Warning: " + e.Message);
+                }
+                // Make sure the machine is valid
+                if (machine != null &&
+                    machine.OperatingSystem != null &&
+                    machine.OperatingSystem.Length > 0 &&
+                    machine.ProcessorArchitecture != null &&
+                    machine.ProcessorArchitecture.Length > 0 &&
+                    machine.GetDevices().Length > 0)
+                    break;
+                System.Threading.Thread.Sleep(1000);
+            }
+            Console.WriteLine("Client machine '{0}' found ({1}, {2})",
+                machineName, machine.OperatingSystem, machine.ProcessorArchitecture);
+        }
+
+        // Delete a machine pool if it exists
+        static void DeleteResourcePool(IDeviceScript script, string poolName)
+        {
+            while (true)
+            {
+                try
+                {
+                    IResourcePool pool = script.GetResourcePoolByName(poolName);
+                    if (pool != null)
+                        script.DeleteResourcePool(pool);
+                    break;
+                }
+                catch (Exception e)
+                {
+                    Console.WriteLine("Warning: " + e.Message);
+                    System.Threading.Thread.Sleep(1000);
+                }
+            }
+        }
+
+        // Set the machine's status to 'Reset' and optionally wait for it to become ready
+        static void ResetMachine(IResourcePool rootPool, string machineName, bool wait)
+        {
+            Console.WriteLine("Resetting machine '{0}'", machineName);
+            IResource machine;
+            while (true)
+            {
+                try
+                {
+                    machine = rootPool.GetResourceByName(machineName);
+                    machine.ChangeResourceStatus("Reset");
+                    break;
+                }
+                catch (Exception e)
+                {
+                    Console.WriteLine("Warning: " + e.Message);
+                    System.Threading.Thread.Sleep(5000);
+                }
+            }
+            if (wait)
+            {
+                Console.WriteLine("Waiting for machine '{0}' to be ready", machineName);
+                while (machine.Status != "Ready")
+                {
+                    try
+                    {
+                        machine = rootPool.GetResourceByName(machineName);
+                    }
+                    catch (Exception e)
+                    {
+                        Console.WriteLine("Warning: " + e.Message);
+                    }
+                    System.Threading.Thread.Sleep(1000);
+                }
+                Console.WriteLine("Machine '{0}' is ready", machineName);
+            }
+        }
+
+        // Look for a device in a machine, and if not found, keep trying for 3 minutes
+        static IDevice GetDevice(IResourcePool rootPool, string machineName, string regexStr)
+        {
+            Regex deviceRegex = new Regex(regexStr, RegexOptions.IgnoreCase);
+            int numAttempts = 1;
+            DateTime endTime = DateTime.Now.AddSeconds(180);
+            while (DateTime.Now < endTime)
+            {
+                IResource machine = rootPool.GetResourceByName(machineName);
+                Console.WriteLine("Looking for device '{0}' in machine '{1}' (machine has {2} devices)",
+                    regexStr, machineName, machine.GetDevices().Length);
+                foreach (IDevice d in machine.GetDevices())
+                {
+                    if (deviceRegex.IsMatch(d.FriendlyName))
+                    {
+                        Console.WriteLine("Found device '{0}'", d.FriendlyName);
+                        return d;
+                    }
+                }
+                Console.WriteLine("Device not found");
+                if (numAttempts % 5 == 0)
+                    ResetMachine(rootPool, machineName, true);
+                else
+                    System.Threading.Thread.Sleep(5000);
+                numAttempts++;
+            }
+            Console.WriteLine("Error: device '{0}' not found", deviceRegex);
+            return null;
+        }
+
         static int Main(string[] args)
         {
-            if (args.Length != 5)
+            if (args.Length < 5)
             {
                 Console.WriteLine("Error: incorrect number of command line arguments");
-                Console.WriteLine("Usage: {0} serverName clientName machinePoolName submissionName timeout",
+                Console.WriteLine("Usage: {0} serverName machinePoolName submissionName timeout machineName0 machineName1 ...",
                     System.Environment.GetCommandLineArgs()[0]);
                 return 1;
             }
             string serverName = args[0];
-            string clientName = args[1];
-            string machinePoolName = args[2];
-            string submissionName = args[3];
-            double timeout = Convert.ToDouble(args[4]);
+            string machinePoolName = args[1];
+            string submissionName = args[2];
+            double timeout = Convert.ToDouble(args[3]);
+
+            List<string> machines = new List<string>();
+            for (int i = 4; i < args.Length; i++)
+                machines.Add(args[i]);
 
             try
             {
@@ -37,37 +157,17 @@
                 Console.WriteLine("Initializing DeviceScript object");
                 DeviceScript script = new DeviceScript();
                 Console.WriteLine("Connecting to data store");
-
                 script.ConnectToNamedDataStore(serverName);
 
-                // Find client machine
+                // Wait for client machines to become available
                 IResourcePool rootPool = script.GetResourcePoolByName("$");
-                Console.WriteLine("Looking for client machine '{0}'", clientName);
-                IResource machine = null;
-                while (true)
-                {
-                    try
-                    {
-                        machine = rootPool.GetResourceByName(clientName);
-                    }
-                    catch (Exception e)
-                    {
-                        Console.WriteLine("Warning: " + e.Message);
-                    }
-                    // Make sure the machine is valid
-                    if (machine != null &&
-                        machine.OperatingSystem != null &&
-                        machine.OperatingSystem.Length > 0 &&
-                        machine.ProcessorArchitecture != null &&
-                        machine.ProcessorArchitecture.Length > 0 &&
-                        machine.GetDevices().Length > 0)
-                        break;
-                    System.Threading.Thread.Sleep(1000);
-                }
-                Console.WriteLine("Client machine '{0}' found ({1}, {2})",
-                    clientName, machine.OperatingSystem, machine.ProcessorArchitecture);
+                foreach (string machineName in machines)
+                    FindMachine(rootPool, machineName);
 
-                // Create machine pool and add client machine to it
+                // Delete the machine pool if it already exists
+                DeleteResourcePool(script, machinePoolName);
+
+                // Create the machine pool and add the client machines to it
                 // (this must be done because jobs cannot be scheduled for machines in the
                 // default pool)
                 try
@@ -79,76 +179,27 @@
                     Console.WriteLine("Warning: " + e.Message);
                 }
                 IResourcePool newPool = script.GetResourcePoolByName(machinePoolName);
-                Console.WriteLine("Moving the client machine to pool '{0}'", machinePoolName);
-                machine.ChangeResourcePool(newPool);
+                foreach (string machineName in machines)
+                {
+                    Console.WriteLine("Moving machine '{0}' to pool '{1}'", machineName, machinePoolName);
+                    rootPool.GetResourceByName(machineName).ChangeResourcePool(newPool);
+                }
 
                 // Reset client machine
-                if (machine.Status != "Ready")
-                {
-                    Console.WriteLine("Changing the client machine's status to 'Reset'");
-                    while (true)
-                    {
-                        try
-                        {
-                            machine = rootPool.GetResourceByName(clientName);
-                            machine.ChangeResourceStatus("Unsafe");
-                            System.Threading.Thread.Sleep(5000);
-                            machine.ChangeResourceStatus("Reset");
-                            break;
-                        }
-                        catch (Exception e)
-                        {
-                            Console.WriteLine("Warning: " + e.Message);
-                        }
-                        System.Threading.Thread.Sleep(5000);
-                    }
-                    Console.WriteLine("Waiting for client machine to be ready");
-                    while (machine.Status != "Ready")
-                    {
-                        try
-                        {
-                            machine = rootPool.GetResourceByName(clientName);
-                        }
-                        catch (Exception e)
-                        {
-                            Console.WriteLine("Warning: " + e.Message);
-                        }
-                        System.Threading.Thread.Sleep(1000);
-                    }
-                }
-                Console.WriteLine("Client machine is ready");
+                foreach (string machineName in machines)
+                    ResetMachine(rootPool, machineName, true);
 
-                // Get requested device regex and look for a matching device
-                Console.WriteLine("Device to test: ");
-                Regex deviceRegex = new Regex(Console.ReadLine(), RegexOptions.IgnoreCase);
-                Console.WriteLine("Looking for device '{0}'", deviceRegex);
-                IDevice device;
-                DateTime endTime = DateTime.Now.AddSeconds(120);
-                while (DateTime.Now < endTime)
-                {
-                    machine = rootPool.GetResourceByName(clientName);
-                    Console.WriteLine("(Client machine has {0} devices)", machine.GetDevices().Length);
-                    foreach (IDevice d in machine.GetDevices())
-                    {
-                        if (deviceRegex.IsMatch(d.FriendlyName))
-                        {
-                            device = d;
-                            goto deviceFound;
-                        }
-                    }
-                    System.Threading.Thread.Sleep(5000);
-                }
-                Console.WriteLine("Error: device '{0}' not found", deviceRegex);
-                return 1;
-
-            deviceFound:
-                Console.WriteLine("Found device '{0}'", device.FriendlyName);
+                // Get requested device regex and look for a matching device in the first machine
+                Console.WriteLine("Device to test:");
+                IDevice device = GetDevice(rootPool, machines[0], Console.ReadLine());
+                if (device == null)
+                    return 1;
 
                 // Get requested jobs regex
-                Console.WriteLine("Jobs to run: ");
+                Console.WriteLine("Jobs to run:");
                 Regex jobRegex = new Regex(Console.ReadLine(), RegexOptions.IgnoreCase);
 
-                // Create submission
+                // Create a submission
                 Object[] existingSubmissions = script.GetSubmissionByName(submissionName);
                 if (existingSubmissions.Length > 0)
                 {
@@ -156,83 +207,126 @@
                         submissionName);
                     script.DeleteSubmission(((ISubmission)existingSubmissions[0]).Id);
                 }
-                Console.WriteLine("Creating submission '{0}'", submissionName);
-                ISubmission submission = script.CreateHardwareSubmission(submissionName,
-                    newPool.ResourcePoolId, device.InstanceId);
+                string hardwareId = device.InstanceId.Remove(device.InstanceId.LastIndexOf("\\"));
+                Console.WriteLine("Creating submission '{0}' (hardware ID: {1})", submissionName, hardwareId);
+                ISubmission submission = script.CreateHardwareSubmission(submissionName, newPool.ResourcePoolId, hardwareId);
 
-                // Get DeviceData objects from the user
+                // Set submission DeviceData
                 List<Object> deviceDataList = new List<Object>();
                 while (true)
                 {
                     ISubmissionDeviceData dd = script.CreateNewSubmissionDeviceData();
-                    Console.WriteLine("DeviceData name: ");
+                    Console.WriteLine("DeviceData name:");
                     dd.Name = Console.ReadLine();
                     if (dd.Name.Length == 0)
                         break;
-                    Console.WriteLine("DeviceData data: ");
+                    Console.WriteLine("DeviceData data:");
                     dd.Data = Console.ReadLine();
                     deviceDataList.Add(dd);
                 }
-
-                // Set the submission's DeviceData
                 submission.SetDeviceData(deviceDataList.ToArray());
 
-                // Get descriptors from the user
+                // Set submission descriptors
                 List<Object> descriptorList = new List<Object>();
                 while (true)
                 {
-                    Console.WriteLine("Descriptor path: ");
+                    Console.WriteLine("Descriptor path:");
                     string descriptorPath = Console.ReadLine();
                     if (descriptorPath.Length == 0)
                         break;
                     descriptorList.Add(script.GetDescriptorByPath(descriptorPath));
                 }
-
-                // Set the submission's descriptors
                 submission.SetLogoDescriptors(descriptorList.ToArray());
 
-                // Create a schedule
-                ISchedule schedule = script.CreateNewSchedule();
+                // Set machine dimensions
+                foreach (string machineName in machines)
+                {
+                    IResource machine = rootPool.GetResourceByName(machineName);
+                    while (true)
+                    {
+                        Console.WriteLine("Dimension name ({0}):", machineName);
+                        string dimName = Console.ReadLine();
+                        if (dimName.Length == 0)
+                            break;
+                        Console.WriteLine("Dimension value ({0}):", machineName);
+                        machine.SetDimension(dimName, Console.ReadLine());
+                    }
+                    // Set the WDKSubmissionId dimension for all machines
+                    machine.SetDimension("WDKSubmissionId", submission.Id.ToString() + "_" + submission.Name);
+                }
+
+                // Get job parameters
+                List<string> paramNames = new List<string>();
+                List<string> paramValues = new List<string>();
+                foreach (string machineName in machines)
+                {
+                    while (true)
+                    {
+                        Console.WriteLine("Parameter name ({0}):", machineName);
+                        string paramName = Console.ReadLine();
+                        if (paramName.Length == 0)
+                            break;
+                        Console.WriteLine("Device regex ({0}):", machineName);
+                        IDevice d = GetDevice(rootPool, machineName, Console.ReadLine());
+                        if (d == null)
+                            return 1;
+                        string deviceName = d.GetAttribute("name")[0].ToString();
+                        Console.WriteLine("Setting parameter value to '{0}'", deviceName);
+                        paramNames.Add(paramName);
+                        paramValues.Add(deviceName);
+                    }
+                }
+
+                // Find jobs that match the requested pattern
                 Console.WriteLine("Scheduling jobs:");
-                int jobCount = 0;
+                List<IJob> jobs = new List<IJob>();
                 foreach (IJob j in submission.GetJobs())
                 {
                     if (jobRegex.IsMatch(j.Name))
-                     {
-                        Console.WriteLine("  " + j.Name);
-                        schedule.AddDeviceJob(device, j);
-                        jobCount++;
+                    {
+                        Console.WriteLine("    " + j.Name);
+                        // Set job parameters
+                        for (int i = 0; i < paramNames.Count; i++)
+                        {
+                            IParameter p = j.GetParameterByName(paramNames[i]);
+                            if (p != null)
+                                p.ScheduleValue = paramValues[i];
+                        }
+                        jobs.Add(j);
                     }
                 }
-                if (jobCount == 0)
+                if (jobs.Count == 0)
                 {
                     Console.WriteLine("Error: no submission jobs match pattern '{0}'", jobRegex);
                     return 1;
                 }
+
+                // Create a schedule, add jobs to it and run it
+                ISchedule schedule = script.CreateNewSchedule();
+                foreach (IScheduleItem item in submission.ProcessJobs(jobs.ToArray()))
+                {
+                    item.Device = device;
+                    schedule.AddScheduleItem(item);
+                }
                 schedule.AddSubmission(submission);
                 schedule.SetResourcePool(newPool);
                 script.RunSchedule(schedule);
 
                 // Wait for jobs to complete
-                Console.WriteLine("Waiting for all jobs to complete (timeout={0})", timeout);
-                endTime = DateTime.Now.AddSeconds(timeout);
-                int numCompleted = 0, numFailed = 0;
-                while (numCompleted < submission.GetResults().Length && DateTime.Now < endTime)
+                Console.WriteLine("Waiting for all jobs to complete (timeout={0}s)", timeout);
+                DateTime endTime = DateTime.Now.AddSeconds(timeout);
+                int numCompleted, numFailed;
+                do
                 {
-                    // Sleep for 30 seconds
                     System.Threading.Thread.Sleep(30000);
-                    // Count completed submission jobs
-                    numCompleted = 0;
-                    foreach (IResult r in submission.GetResults())
-                        if (r.ResultStatus != "InProgress")
-                            numCompleted++;
-                    // Report results in a Python readable format and count failed schedule jobs
-                    // (submission jobs are a subset of schedule jobs)
+                    // Report results in a Python readable format and count completed and failed schedule jobs
+                    numCompleted = numFailed = 0;
                     Console.WriteLine();
                     Console.WriteLine("---- [");
-                    numFailed = 0;
                     foreach (IResult r in schedule.GetResults())
                     {
+                        if (r.ResultStatus != "InProgress") numCompleted++;
+                        if (r.ResultStatus == "Investigate") numFailed++;
                         Console.WriteLine("  {");
                         Console.WriteLine("    'id': {0}, 'job': r'''{1}''',", r.Job.Id, r.Job.Name);
                         Console.WriteLine("    'logs': r'''{0}''',", r.LogLocation);
@@ -243,10 +337,10 @@
                         Console.WriteLine("    'pass': {0}, 'fail': {1}, 'notrun': {2}, 'notapplicable': {3}",
                             r.Pass, r.Fail, r.NotRun, r.NotApplicable);
                         Console.WriteLine("  },");
-                        numFailed += r.Fail;
                     }
                     Console.WriteLine("] ----");
-                }
+                } while (numCompleted < schedule.GetResults().Length && DateTime.Now < endTime);
+
                 Console.WriteLine();
 
                 // Cancel incomplete jobs
@@ -254,26 +348,16 @@
                     if (r.ResultStatus == "InProgress")
                         r.Cancel();
 
-                // Set the machine's status to Unsafe and then Reset
-                try
-                {
-                    machine = rootPool.GetResourceByName(clientName);
-                    machine.ChangeResourceStatus("Unsafe");
-                    System.Threading.Thread.Sleep(5000);
-                    machine.ChangeResourceStatus("Reset");
-                }
-                catch (Exception e)
-                {
-                    Console.WriteLine("Warning: " + e.Message);
-                }
+                // Reset the machines
+                foreach (string machineName in machines)
+                    ResetMachine(rootPool, machineName, false);
 
                 // Report failures
-                if (numCompleted < submission.GetResults().Length)
+                if (numCompleted < schedule.GetResults().Length)
                     Console.WriteLine("Some jobs did not complete on time.");
                 if (numFailed > 0)
                     Console.WriteLine("Some jobs failed.");
-
-                if (numFailed > 0 || numCompleted < submission.GetResults().Length)
+                if (numFailed > 0 || numCompleted < schedule.GetResults().Length)
                     return 1;
 
                 Console.WriteLine("All jobs completed.");
diff --git a/client/tests/kvm/deps/whql_submission_15.exe b/client/tests/kvm/deps/whql_submission_15.exe
old mode 100755
new mode 100644
index 4f30aa8..605e2e3
--- a/client/tests/kvm/deps/whql_submission_15.exe
+++ b/client/tests/kvm/deps/whql_submission_15.exe
Binary files differ
diff --git a/client/tests/kvm/get_started.py b/client/tests/kvm/get_started.py
index 6fa6b5f..126d8a7 100755
--- a/client/tests/kvm/get_started.py
+++ b/client/tests/kvm/get_started.py
@@ -82,11 +82,11 @@
     logging.info("3 - Verifying iso (make sure we have the OS ISO needed for "
                  "the default test set)")
 
-    iso_name = "Fedora-13-x86_64-DVD.iso"
-    fedora_dir = "pub/fedora/linux/releases/13/Fedora/x86_64/iso"
+    iso_name = "Fedora-14-x86_64-DVD.iso"
+    fedora_dir = "pub/fedora/linux/releases/14/Fedora/x86_64/iso"
     url = os.path.join("http://download.fedoraproject.org/", fedora_dir,
                        iso_name)
-    hash = "65c7f1aad3feb888ae3daadaf45d4a2a32b8773a"
+    hash = "38a4078011bac74493db7ecc53c9d9fbc96dbbd5"
     destination = os.path.join(base_dir, 'isos', 'linux')
     check_iso(url, destination, hash)
 
diff --git a/client/tests/kvm/installer.py b/client/tests/kvm/installer.py
new file mode 100644
index 0000000..30ee657
--- /dev/null
+++ b/client/tests/kvm/installer.py
@@ -0,0 +1,781 @@
+import time, os, sys, urllib, re, signal, logging, datetime, glob, ConfigParser
+import shutil
+from autotest_lib.client.bin import utils, test, os_dep
+from autotest_lib.client.common_lib import error
+import kvm_utils
+
+
+def check_configure_options(script_path):
+    """
+    Return the list of available options (flags) of a given kvm configure build
+    script.
+
+    @param script: Path to the configure script
+    """
+    abspath = os.path.abspath(script_path)
+    help_raw = utils.system_output('%s --help' % abspath, ignore_status=True)
+    help_output = help_raw.split("\n")
+    option_list = []
+    for line in help_output:
+        cleaned_line = line.lstrip()
+        if cleaned_line.startswith("--"):
+            option = cleaned_line.split()[0]
+            option = option.split("=")[0]
+            option_list.append(option)
+
+    return option_list
+
+
+def kill_qemu_processes():
+    """
+    Kills all qemu processes, also kills all processes holding /dev/kvm down.
+    """
+    logging.debug("Killing any qemu processes that might be left behind")
+    utils.system("pkill qemu", ignore_status=True)
+    # Let's double check to see if some other process is holding /dev/kvm
+    if os.path.isfile("/dev/kvm"):
+        utils.system("fuser -k /dev/kvm", ignore_status=True)
+
+
+def cpu_vendor():
+    vendor = "intel"
+    if os.system("grep vmx /proc/cpuinfo 1>/dev/null") != 0:
+        vendor = "amd"
+    logging.debug("Detected CPU vendor as '%s'" %(vendor))
+    return vendor
+
+
+def _unload_kvm_modules(mod_list):
+    logging.info("Unloading previously loaded KVM modules")
+    for module in reversed(mod_list):
+        utils.unload_module(module)
+
+
+def _load_kvm_modules(mod_list, module_dir=None, load_stock=False):
+    """
+    Just load the KVM modules, without killing Qemu or unloading previous
+    modules.
+
+    Load modules present on any sub directory of module_dir. Function will walk
+    through module_dir until it finds the modules.
+
+    @param module_dir: Directory where the KVM modules are located.
+    @param load_stock: Whether we are going to load system kernel modules.
+    @param extra_modules: List of extra modules to load.
+    """
+    if module_dir:
+        logging.info("Loading the built KVM modules...")
+        kvm_module_path = None
+        kvm_vendor_module_path = None
+        abort = False
+
+        list_modules = ['%s.ko' % (m) for m in mod_list]
+
+        list_module_paths = []
+        for folder, subdirs, files in os.walk(module_dir):
+            for module in list_modules:
+                if module in files:
+                    module_path = os.path.join(folder, module)
+                    list_module_paths.append(module_path)
+
+        # We might need to arrange the modules in the correct order
+        # to avoid module load problems
+        list_modules_load = []
+        for module in list_modules:
+            for module_path in list_module_paths:
+                if os.path.basename(module_path) == module:
+                    list_modules_load.append(module_path)
+
+        if len(list_module_paths) != len(list_modules):
+            logging.error("KVM modules not found. If you don't want to use the "
+                          "modules built by this test, make sure the option "
+                          "load_modules: 'no' is marked on the test control "
+                          "file.")
+            raise error.TestError("The modules %s were requested to be loaded, "
+                                  "but the only modules found were %s" %
+                                  (list_modules, list_module_paths))
+
+        for module_path in list_modules_load:
+            try:
+                utils.system("insmod %s" % module_path)
+            except Exception, e:
+                raise error.TestFail("Failed to load KVM modules: %s" % e)
+
+    if load_stock:
+        logging.info("Loading current system KVM modules...")
+        for module in mod_list:
+            utils.system("modprobe %s" % module)
+
+
+def create_symlinks(test_bindir, prefix=None, bin_list=None, unittest=None):
+    """
+    Create symbolic links for the appropriate qemu and qemu-img commands on
+    the kvm test bindir.
+
+    @param test_bindir: KVM test bindir
+    @param prefix: KVM prefix path
+    @param bin_list: List of qemu binaries to link
+    @param unittest: Path to configuration file unittests.cfg
+    """
+    qemu_path = os.path.join(test_bindir, "qemu")
+    qemu_img_path = os.path.join(test_bindir, "qemu-img")
+    qemu_unittest_path = os.path.join(test_bindir, "unittests")
+    if os.path.lexists(qemu_path):
+        os.unlink(qemu_path)
+    if os.path.lexists(qemu_img_path):
+        os.unlink(qemu_img_path)
+    if unittest and os.path.lexists(qemu_unittest_path):
+        os.unlink(qemu_unittest_path)
+
+    logging.debug("Linking qemu binaries")
+
+    if bin_list:
+        for bin in bin_list:
+            if os.path.basename(bin) == 'qemu-kvm':
+                os.symlink(bin, qemu_path)
+            elif os.path.basename(bin) == 'qemu-img':
+                os.symlink(bin, qemu_img_path)
+
+    elif prefix:
+        kvm_qemu = os.path.join(prefix, "bin", "qemu-system-x86_64")
+        if not os.path.isfile(kvm_qemu):
+            raise error.TestError('Invalid qemu path')
+        kvm_qemu_img = os.path.join(prefix, "bin", "qemu-img")
+        if not os.path.isfile(kvm_qemu_img):
+            raise error.TestError('Invalid qemu-img path')
+        os.symlink(kvm_qemu, qemu_path)
+        os.symlink(kvm_qemu_img, qemu_img_path)
+
+    if unittest:
+        logging.debug("Linking unittest dir")
+        os.symlink(unittest, qemu_unittest_path)
+
+
+def install_roms(rom_dir, prefix):
+    logging.debug("Path to roms specified. Copying roms to install prefix")
+    rom_dst_dir = os.path.join(prefix, 'share', 'qemu')
+    for rom_src in glob.glob('%s/*.bin' % rom_dir):
+        rom_dst = os.path.join(rom_dst_dir, os.path.basename(rom_src))
+        logging.debug("Copying rom file %s to %s", rom_src, rom_dst)
+        shutil.copy(rom_src, rom_dst)
+
+
+def save_build(build_dir, dest_dir):
+    logging.debug('Saving the result of the build on %s', dest_dir)
+    base_name = os.path.basename(build_dir)
+    tarball_name = base_name + '.tar.bz2'
+    os.chdir(os.path.dirname(build_dir))
+    utils.system('tar -cjf %s %s' % (tarball_name, base_name))
+    shutil.move(tarball_name, os.path.join(dest_dir, tarball_name))
+
+
+class KvmInstallException(Exception):
+    pass
+
+
+class FailedKvmInstall(KvmInstallException):
+    pass
+
+
+class KvmNotInstalled(KvmInstallException):
+    pass
+
+
+class BaseInstaller(object):
+    # default value for load_stock argument
+    load_stock_modules = True
+    def __init__(self, mode=None):
+        self.install_mode = mode
+        self._full_module_list = None
+
+    def set_install_params(self, test, params):
+        self.params = params
+
+        load_modules = params.get('load_modules', 'no')
+        if not load_modules or load_modules == 'yes':
+            self.should_load_modules = True
+        elif load_modules == 'no':
+            self.should_load_modules = False
+        default_extra_modules = str(None)
+        self.extra_modules = eval(params.get("extra_modules",
+                                             default_extra_modules))
+
+        self.cpu_vendor = cpu_vendor()
+
+        self.srcdir = test.srcdir
+        if not os.path.isdir(self.srcdir):
+            os.makedirs(self.srcdir)
+
+        self.test_bindir = test.bindir
+        self.results_dir = test.resultsdir
+
+        # KVM build prefix, for the modes that do need it
+        prefix = os.path.join(test.bindir, 'build')
+        self.prefix = os.path.abspath(prefix)
+
+        # Current host kernel directory
+        default_host_kernel_source = '/lib/modules/%s/build' % os.uname()[2]
+        self.host_kernel_srcdir = params.get('host_kernel_source',
+                                             default_host_kernel_source)
+
+        # Extra parameters that can be passed to the configure script
+        self.extra_configure_options = params.get('extra_configure_options',
+                                                  None)
+
+        # Do we want to save the result of the build on test.resultsdir?
+        self.save_results = True
+        save_results = params.get('save_results', 'no')
+        if save_results == 'no':
+            self.save_results = False
+
+        self._full_module_list = list(self._module_list())
+
+
+    def full_module_list(self):
+        """Return the module list used by the installer
+
+        Used by the module_probe test, to avoid using utils.unload_module().
+        """
+        if self._full_module_list is None:
+            raise KvmNotInstalled("KVM modules not installed yet (installer: %s)" % (type(self)))
+        return self._full_module_list
+
+
+    def _module_list(self):
+        """Generate the list of modules that need to be loaded
+        """
+        yield 'kvm'
+        yield 'kvm-%s' % (self.cpu_vendor)
+        if self.extra_modules:
+            for module in self.extra_modules:
+                yield module
+
+
+    def _load_modules(self, mod_list):
+        """
+        Load the KVM modules
+
+        May be overridden by subclasses.
+        """
+        _load_kvm_modules(mod_list, load_stock=self.load_stock_modules)
+
+
+    def load_modules(self, mod_list=None):
+        if mod_list is None:
+            mod_list = self.full_module_list()
+        self._load_modules(mod_list)
+
+
+    def _unload_modules(self, mod_list=None):
+        """
+        Just unload the KVM modules, without trying to kill Qemu
+        """
+        if mod_list is None:
+            mod_list = self.full_module_list()
+        _unload_kvm_modules(mod_list)
+
+
+    def unload_modules(self, mod_list=None):
+        """
+        Kill Qemu and unload the KVM modules
+        """
+        kill_qemu_processes()
+        self._unload_modules(mod_list)
+
+
+    def reload_modules(self):
+        """
+        Reload the KVM modules after killing Qemu and unloading the current modules
+        """
+        self.unload_modules()
+        self.load_modules()
+
+
+    def reload_modules_if_needed(self):
+        if self.should_load_modules:
+            self.reload_modules()
+
+
+class YumInstaller(BaseInstaller):
+    """
+    Class that uses yum to install and remove packages.
+    """
+    load_stock_modules = True
+    def set_install_params(self, test, params):
+        super(YumInstaller, self).set_install_params(test, params)
+        # Checking if all required dependencies are available
+        os_dep.command("rpm")
+        os_dep.command("yum")
+
+        default_pkg_list = str(['qemu-kvm', 'qemu-kvm-tools'])
+        default_qemu_bin_paths = str(['/usr/bin/qemu-kvm', '/usr/bin/qemu-img'])
+        default_pkg_path_list = str(None)
+        self.pkg_list = eval(params.get("pkg_list", default_pkg_list))
+        self.pkg_path_list = eval(params.get("pkg_path_list",
+                                             default_pkg_path_list))
+        self.qemu_bin_paths = eval(params.get("qemu_bin_paths",
+                                              default_qemu_bin_paths))
+
+
+    def _clean_previous_installs(self):
+        kill_qemu_processes()
+        removable_packages = ""
+        for pkg in self.pkg_list:
+            removable_packages += " %s" % pkg
+
+        utils.system("yum remove -y %s" % removable_packages)
+
+
+    def _get_packages(self):
+        for pkg in self.pkg_path_list:
+            utils.get_file(pkg, os.path.join(self.srcdir,
+                                             os.path.basename(pkg)))
+
+
+    def _install_packages(self):
+        """
+        Install all downloaded packages.
+        """
+        os.chdir(self.srcdir)
+        utils.system("yum install --nogpgcheck -y *.rpm")
+
+
+    def install(self):
+        self._clean_previous_installs()
+        self._get_packages()
+        self._install_packages()
+        create_symlinks(test_bindir=self.test_bindir,
+                        bin_list=self.qemu_bin_paths)
+        self.reload_modules_if_needed()
+        if self.save_results:
+            save_build(self.srcdir, self.results_dir)
+
+
+class KojiInstaller(YumInstaller):
+    """
+    Class that handles installing KVM from the fedora build service, koji.
+    It uses yum to install and remove packages.
+    """
+    load_stock_modules = True
+    def set_install_params(self, test, params):
+        """
+        Gets parameters and initializes the package downloader.
+
+        @param test: kvm test object
+        @param params: Dictionary with test arguments
+        """
+        super(KojiInstaller, self).set_install_params(test, params)
+        default_koji_cmd = '/usr/bin/koji'
+        default_src_pkg = 'qemu'
+        self.src_pkg = params.get("src_pkg", default_src_pkg)
+        self.tag = params.get("koji_tag", None)
+        self.build = params.get("koji_build", None)
+        self.koji_cmd = params.get("koji_cmd", default_koji_cmd)
+
+
+    def _get_packages(self):
+        """
+        Downloads the specific arch RPMs for the specific build name.
+        """
+        downloader = kvm_utils.KojiDownloader(cmd=self.koji_cmd)
+        downloader.get(src_package=self.src_pkg, tag=self.tag,
+                            build=self.build, dst_dir=self.srcdir)
+
+
+    def install(self):
+        super(KojiInstaller, self)._clean_previous_installs()
+        self._get_packages()
+        super(KojiInstaller, self)._install_packages()
+        create_symlinks(test_bindir=self.test_bindir,
+                        bin_list=self.qemu_bin_paths)
+        self.reload_modules_if_needed()
+        if self.save_results:
+            save_build(self.srcdir, self.results_dir)
+
+
+class SourceDirInstaller(BaseInstaller):
+    """
+    Class that handles building/installing KVM directly from a tarball or
+    a single source code dir.
+    """
+    def set_install_params(self, test, params):
+        """
+        Initializes class attributes, and retrieves KVM code.
+
+        @param test: kvm test object
+        @param params: Dictionary with test arguments
+        """
+        super(SourceDirInstaller, self).set_install_params(test, params)
+
+        self.mod_install_dir = os.path.join(self.prefix, 'modules')
+        self.installed_kmods = False  # it will be set to True in case we
+                                      # installed our own modules
+
+        srcdir = params.get("srcdir", None)
+        self.path_to_roms = params.get("path_to_rom_images", None)
+
+        if self.install_mode == 'localsrc':
+            if srcdir is None:
+                raise error.TestError("Install from source directory specified"
+                                      "but no source directory provided on the"
+                                      "control file.")
+            else:
+                shutil.copytree(srcdir, self.srcdir)
+
+        if self.install_mode == 'release':
+            release_tag = params.get("release_tag")
+            release_dir = params.get("release_dir")
+            release_listing = params.get("release_listing")
+            logging.info("Installing KVM from release tarball")
+            if not release_tag:
+                release_tag = kvm_utils.get_latest_kvm_release_tag(
+                                                                release_listing)
+            tarball = os.path.join(release_dir, 'kvm', release_tag,
+                                   "kvm-%s.tar.gz" % release_tag)
+            logging.info("Retrieving release kvm-%s" % release_tag)
+            tarball = utils.unmap_url("/", tarball, "/tmp")
+
+        elif self.install_mode == 'snapshot':
+            logging.info("Installing KVM from snapshot")
+            snapshot_dir = params.get("snapshot_dir")
+            if not snapshot_dir:
+                raise error.TestError("Snapshot dir not provided")
+            snapshot_date = params.get("snapshot_date")
+            if not snapshot_date:
+                # Take yesterday's snapshot
+                d = (datetime.date.today() -
+                     datetime.timedelta(1)).strftime("%Y%m%d")
+            else:
+                d = snapshot_date
+            tarball = os.path.join(snapshot_dir, "kvm-snapshot-%s.tar.gz" % d)
+            logging.info("Retrieving kvm-snapshot-%s" % d)
+            tarball = utils.unmap_url("/", tarball, "/tmp")
+
+        elif self.install_mode == 'localtar':
+            tarball = params.get("tarball")
+            if not tarball:
+                raise error.TestError("KVM Tarball install specified but no"
+                                      " tarball provided on control file.")
+            logging.info("Installing KVM from a local tarball")
+            logging.info("Using tarball %s")
+            tarball = utils.unmap_url("/", params.get("tarball"), "/tmp")
+
+        if self.install_mode in ['release', 'snapshot', 'localtar']:
+            utils.extract_tarball_to_dir(tarball, self.srcdir)
+
+        if self.install_mode in ['release', 'snapshot', 'localtar', 'srcdir']:
+            self.repo_type = kvm_utils.check_kvm_source_dir(self.srcdir)
+            configure_script = os.path.join(self.srcdir, 'configure')
+            self.configure_options = check_configure_options(configure_script)
+
+
+    def _build(self):
+        make_jobs = utils.count_cpus()
+        os.chdir(self.srcdir)
+        # For testing purposes, it's better to build qemu binaries with
+        # debugging symbols, so we can extract more meaningful stack traces.
+        cfg = "./configure --prefix=%s" % self.prefix
+        if "--disable-strip" in self.configure_options:
+            cfg += " --disable-strip"
+        steps = [cfg, "make clean", "make -j %s" % make_jobs]
+        logging.info("Building KVM")
+        for step in steps:
+            utils.system(step)
+
+
+    def _install_kmods_old_userspace(self, userspace_path):
+        """
+        Run the module install command.
+
+        This is for the "old userspace" code, that contained a 'kernel' subdirectory
+        with the kmod build code.
+
+        The code would be much simpler if we could specify the module install
+        path as parameter to the toplevel Makefile. As we can't do that and
+        the module install code doesn't use --prefix, we have to call
+        'make -C kernel install' directly, setting the module directory
+        parameters.
+
+        If the userspace tree doens't have a 'kernel' subdirectory, the
+        module install step will be skipped.
+
+        @param userspace_path: the path the kvm-userspace directory
+        """
+        kdir = os.path.join(userspace_path, 'kernel')
+        if os.path.isdir(kdir):
+            os.chdir(kdir)
+            # INSTALLDIR is the target dir for the modules
+            # ORIGMODDIR is the dir where the old modules will be removed. we
+            #            don't want to mess with the system modules, so set it
+            #            to a non-existing directory
+            utils.system('make install INSTALLDIR=%s ORIGMODDIR=/tmp/no-old-modules' % (self.mod_install_dir))
+            self.installed_kmods = True
+
+
+    def _install_kmods(self, kmod_path):
+        """Run the module install command for the kmod-kvm repository
+
+        @param kmod_path: the path to the kmod-kvm.git working copy
+        """
+        os.chdir(kmod_path)
+        utils.system('make modules_install DESTDIR=%s' % (self.mod_install_dir))
+        self.installed_kmods = True
+
+
+    def _install(self):
+        os.chdir(self.srcdir)
+        logging.info("Installing KVM userspace")
+        if self.repo_type == 1:
+            utils.system("make -C qemu install")
+            self._install_kmods_old_userspace(self.srcdir)
+        elif self.repo_type == 2:
+            utils.system("make install")
+        if self.path_to_roms:
+            install_roms(self.path_to_roms, self.prefix)
+        create_symlinks(self.test_bindir, self.prefix)
+
+
+    def _load_modules(self, mod_list):
+        # load the installed KVM modules in case we installed them
+        # ourselves. Otherwise, just load the system modules.
+        if self.installed_kmods:
+            logging.info("Loading installed KVM modules")
+            _load_kvm_modules(mod_list, module_dir=self.mod_install_dir)
+        else:
+            logging.info("Loading stock KVM modules")
+            _load_kvm_modules(mod_list, load_stock=True)
+
+
+    def install(self):
+        self._build()
+        self._install()
+        self.reload_modules_if_needed()
+        if self.save_results:
+            save_build(self.srcdir, self.results_dir)
+
+
+class GitInstaller(SourceDirInstaller):
+    def _pull_code(self):
+        """
+        Retrieves code from git repositories.
+        """
+        params = self.params
+
+        kernel_repo = params.get("git_repo")
+        user_repo = params.get("user_git_repo")
+        kmod_repo = params.get("kmod_repo")
+        test_repo = params.get("test_git_repo")
+
+        kernel_branch = params.get("kernel_branch", "master")
+        user_branch = params.get("user_branch", "master")
+        kmod_branch = params.get("kmod_branch", "master")
+        test_branch = params.get("test_branch", "master")
+
+        kernel_lbranch = params.get("kernel_lbranch", "master")
+        user_lbranch = params.get("user_lbranch", "master")
+        kmod_lbranch = params.get("kmod_lbranch", "master")
+        test_lbranch = params.get("test_lbranch", "master")
+
+        kernel_commit = params.get("kernel_commit", None)
+        user_commit = params.get("user_commit", None)
+        kmod_commit = params.get("kmod_commit", None)
+        test_commit = params.get("test_commit", None)
+
+        kernel_patches = eval(params.get("kernel_patches", "[]"))
+        user_patches = eval(params.get("user_patches", "[]"))
+        kmod_patches = eval(params.get("user_patches", "[]"))
+
+        if not user_repo:
+            message = "KVM user git repository path not specified"
+            logging.error(message)
+            raise error.TestError(message)
+
+        userspace_srcdir = os.path.join(self.srcdir, "kvm_userspace")
+        kvm_utils.get_git_branch(user_repo, user_branch, userspace_srcdir,
+                                 user_commit, user_lbranch)
+        self.userspace_srcdir = userspace_srcdir
+
+        if user_patches:
+            os.chdir(self.userspace_srcdir)
+            for patch in user_patches:
+                utils.get_file(patch, os.path.join(self.userspace_srcdir,
+                                                   os.path.basename(patch)))
+                utils.system('patch -p1 %s' % os.path.basename(patch))
+
+        if test_repo:
+            test_srcdir = os.path.join(self.srcdir, "kvm-unit-tests")
+            kvm_utils.get_git_branch(test_repo, test_branch, test_srcdir,
+                                     test_commit, test_lbranch)
+            unittest_cfg = os.path.join(test_srcdir, 'x86',
+                                        'unittests.cfg')
+            self.test_srcdir = test_srcdir
+        else:
+            unittest_cfg = os.path.join(userspace_srcdir, 'kvm', 'test', 'x86',
+                                        'unittests.cfg')
+
+        self.unittest_cfg = None
+        if os.path.isfile(unittest_cfg):
+            self.unittest_cfg = unittest_cfg
+
+        if kernel_repo:
+            kernel_srcdir = os.path.join(self.srcdir, "kvm")
+            kvm_utils.get_git_branch(kernel_repo, kernel_branch, kernel_srcdir,
+                                     kernel_commit, kernel_lbranch)
+            self.kernel_srcdir = kernel_srcdir
+            if kernel_patches:
+                os.chdir(self.kernel_srcdir)
+                for patch in kernel_patches:
+                    utils.get_file(patch, os.path.join(self.userspace_srcdir,
+                                                       os.path.basename(patch)))
+                    utils.system('patch -p1 %s' % os.path.basename(patch))
+        else:
+            self.kernel_srcdir = None
+
+        if kmod_repo:
+            kmod_srcdir = os.path.join (self.srcdir, "kvm_kmod")
+            kvm_utils.get_git_branch(kmod_repo, kmod_branch, kmod_srcdir,
+                                     kmod_commit, kmod_lbranch)
+            self.kmod_srcdir = kmod_srcdir
+            if kmod_patches:
+                os.chdir(self.kmod_srcdir)
+                for patch in kmod_patches:
+                    utils.get_file(patch, os.path.join(self.userspace_srcdir,
+                                                       os.path.basename(patch)))
+                    utils.system('patch -p1 %s' % os.path.basename(patch))
+        else:
+            self.kmod_srcdir = None
+
+        configure_script = os.path.join(self.userspace_srcdir, 'configure')
+        self.configure_options = check_configure_options(configure_script)
+
+
+    def _build(self):
+        make_jobs = utils.count_cpus()
+        cfg = './configure'
+        if self.kmod_srcdir:
+            logging.info('Building KVM modules')
+            os.chdir(self.kmod_srcdir)
+            module_build_steps = [cfg,
+                                  'make clean',
+                                  'make sync LINUX=%s' % self.kernel_srcdir,
+                                  'make']
+        elif self.kernel_srcdir:
+            logging.info('Building KVM modules')
+            os.chdir(self.userspace_srcdir)
+            cfg += ' --kerneldir=%s' % self.host_kernel_srcdir
+            module_build_steps = [cfg,
+                            'make clean',
+                            'make -C kernel LINUX=%s sync' % self.kernel_srcdir]
+        else:
+            module_build_steps = []
+
+        for step in module_build_steps:
+            utils.run(step)
+
+        logging.info('Building KVM userspace code')
+        os.chdir(self.userspace_srcdir)
+        cfg += ' --prefix=%s' % self.prefix
+        if "--disable-strip" in self.configure_options:
+            cfg += ' --disable-strip'
+        if self.extra_configure_options:
+            cfg += ' %s' % self.extra_configure_options
+        utils.system(cfg)
+        utils.system('make clean')
+        utils.system('make -j %s' % make_jobs)
+
+        self.unittest_prefix = None
+        if self.unittest_cfg:
+            os.chdir(os.path.dirname(os.path.dirname(self.unittest_cfg)))
+            utils.system('./configure --prefix=%s' % self.prefix)
+            utils.system('make')
+            utils.system('make install')
+            self.unittest_prefix = os.path.join(self.prefix, 'share', 'qemu',
+                                                'tests')
+
+
+    def _install(self):
+        if self.kernel_srcdir:
+            os.chdir(self.userspace_srcdir)
+            # the kernel module install with --prefix doesn't work, and DESTDIR
+            # wouldn't work for the userspace stuff, so we clear WANT_MODULE:
+            utils.system('make install WANT_MODULE=')
+            # and install the old-style-kmod modules manually:
+            self._install_kmods_old_userspace(self.userspace_srcdir)
+        elif self.kmod_srcdir:
+            # if we have a kmod repository, it is easier:
+            # 1) install userspace:
+            os.chdir(self.userspace_srcdir)
+            utils.system('make install')
+            # 2) install kmod:
+            self._install_kmods(self.kmod_srcdir)
+        else:
+            # if we don't have kmod sources, we just install
+            # userspace:
+            os.chdir(self.userspace_srcdir)
+            utils.system('make install')
+
+        if self.path_to_roms:
+            install_roms(self.path_to_roms, self.prefix)
+        create_symlinks(test_bindir=self.test_bindir, prefix=self.prefix,
+                        bin_list=None,
+                        unittest=self.unittest_prefix)
+
+
+    def install(self):
+        self._pull_code()
+        self._build()
+        self._install()
+        self.reload_modules_if_needed()
+        if self.save_results:
+            save_build(self.srcdir, self.results_dir)
+
+
+class PreInstalledKvm(BaseInstaller):
+    # load_modules() will use the stock modules:
+    load_stock_modules = True
+    def install(self):
+        logging.info("Expecting KVM to be already installed. Doing nothing")
+
+
+class FailedInstaller:
+    """
+    Class used to be returned instead of the installer if a installation fails
+
+    Useful to make sure no installer object is used if KVM installation fails.
+    """
+    def __init__(self, msg="KVM install failed"):
+        self._msg = msg
+
+
+    def load_modules(self):
+        """Will refuse to load the KVM modules as install failed"""
+        raise FailedKvmInstall("KVM modules not available. reason: %s" % (self._msg))
+
+
+installer_classes = {
+    'localsrc': SourceDirInstaller,
+    'localtar': SourceDirInstaller,
+    'release': SourceDirInstaller,
+    'snapshot': SourceDirInstaller,
+    'git': GitInstaller,
+    'yum': YumInstaller,
+    'koji': KojiInstaller,
+    'preinstalled': PreInstalledKvm,
+}
+
+
+def _installer_class(install_mode):
+    c = installer_classes.get(install_mode)
+    if c is None:
+        raise error.TestError('Invalid or unsupported'
+                              ' install mode: %s' % install_mode)
+    return c
+
+
+def make_installer(params):
+    # priority:
+    # - 'install_mode' param
+    # - 'mode' param
+    mode = params.get("install_mode", params.get("mode"))
+    klass = _installer_class(mode)
+    return klass(mode)
diff --git a/client/tests/kvm/kvm.py b/client/tests/kvm/kvm.py
index f656238..dbe29c3 100644
--- a/client/tests/kvm/kvm.py
+++ b/client/tests/kvm/kvm.py
@@ -21,9 +21,12 @@
             (Online doc - Getting started with KVM testing)
     """
     version = 1
-    env_version = 0
+    env_version = 1
 
     def run_once(self, params):
+        # Convert params to a Params object
+        params = kvm_utils.Params(params)
+
         # Report the parameters we've received and write them as keyvals
         logging.debug("Test parameters:")
         keys = params.keys()
@@ -40,8 +43,7 @@
         logging.info("Unpickling env. You may see some harmless error "
                      "messages.")
         env_filename = os.path.join(self.bindir, params.get("env", "env"))
-        env = kvm_utils.load_env(env_filename, self.env_version)
-        logging.debug("Contents of environment: %s", env)
+        env = kvm_utils.Env(env_filename, self.env_version)
 
         test_passed = False
 
@@ -66,13 +68,13 @@
                     try:
                         kvm_preprocessing.preprocess(self, params, env)
                     finally:
-                        kvm_utils.dump_env(env, env_filename)
+                        env.save()
                     # Run the test function
                     run_func = getattr(test_module, "run_%s" % t_type)
                     try:
                         run_func(self, params, env)
                     finally:
-                        kvm_utils.dump_env(env, env_filename)
+                        env.save()
                     test_passed = True
 
                 except Exception, e:
@@ -82,7 +84,7 @@
                         kvm_preprocessing.postprocess_on_error(
                             self, params, env)
                     finally:
-                        kvm_utils.dump_env(env, env_filename)
+                        env.save()
                     raise
 
             finally:
@@ -96,15 +98,14 @@
                         logging.error("Exception raised during "
                                       "postprocessing: %s", e)
                 finally:
-                    kvm_utils.dump_env(env, env_filename)
-                    logging.debug("Contents of environment: %s", env)
+                    env.save()
 
         except Exception, e:
             if params.get("abort_on_error") != "yes":
                 raise
             # Abort on error
             logging.info("Aborting job (%s)", e)
-            for vm in kvm_utils.env_get_all_vms(env):
+            for vm in env.get_all_vms():
                 if vm.is_dead():
                     continue
                 logging.info("VM '%s' is alive.", vm.name)
diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py
index 1ddf99b..4daafec 100644
--- a/client/tests/kvm/kvm_preprocessing.py
+++ b/client/tests/kvm/kvm_preprocessing.py
@@ -50,16 +50,18 @@
     @param name: The name of the VM object.
     """
     logging.debug("Preprocessing VM '%s'..." % name)
-    vm = kvm_utils.env_get_vm(env, name)
+    vm = env.get_vm(name)
     if vm:
         logging.debug("VM object found in environment")
     else:
         logging.debug("VM object does not exist; creating it")
         vm = kvm_vm.VM(name, params, test.bindir, env.get("address_cache"))
-        kvm_utils.env_register_vm(env, name, vm)
+        env.register_vm(name, vm)
 
     start_vm = False
 
+    migration_mode = params.get("migration_mode", None)
+
     if params.get("restart_vm") == "yes":
         logging.debug("'restart_vm' specified; (re)starting VM...")
         start_vm = True
@@ -72,11 +74,19 @@
             logging.debug("VM's qemu command differs from requested one; "
                           "restarting it...")
             start_vm = True
+    elif migration_mode is not None:
+        logging.debug("Starting VM on migration incoming mode...")
+        start_vm = True
 
     if start_vm:
-        # Start the VM (or restart it if it's already up)
-        if not vm.create(name, params, test.bindir):
-            raise error.TestError("Could not start VM")
+        if migration_mode is not None:
+            if not vm.create(name, params, test.bindir,
+                             migration_mode=migration_mode):
+                raise error.TestError("Could not start VM for migration")
+        else:
+            # Start the VM (or restart it if it's already up)
+            if not vm.create(name, params, test.bindir):
+                raise error.TestError("Could not start VM")
     else:
         # Don't start the VM, just update its params
         vm.params = params
@@ -112,7 +122,7 @@
     @param name: The name of the VM object.
     """
     logging.debug("Postprocessing VM '%s'..." % name)
-    vm = kvm_utils.env_get_vm(env, name)
+    vm = env.get_vm(name)
     if vm:
         logging.debug("VM object found in environment")
     else:
@@ -173,13 +183,11 @@
     @param vm_func: A function to call for each VM.
     """
     # Get list of VMs specified for this test
-    vm_names = kvm_utils.get_sub_dict_names(params, "vms")
-    for vm_name in vm_names:
-        vm_params = kvm_utils.get_sub_dict(params, vm_name)
+    for vm_name in params.objects("vms"):
+        vm_params = params.object_params(vm_name)
         # Get list of images specified for this VM
-        image_names = kvm_utils.get_sub_dict_names(vm_params, "images")
-        for image_name in image_names:
-            image_params = kvm_utils.get_sub_dict(vm_params, image_name)
+        for image_name in vm_params.objects("images"):
+            image_params = vm_params.object_params(image_name)
             # Call image_func for each image
             image_func(test, image_params)
         # Call vm_func for each vm
@@ -204,7 +212,7 @@
     if "tcpdump" not in env and params.get("run_tcpdump", "yes") == "yes":
         cmd = "%s -npvi any 'dst port 68'" % kvm_utils.find_command("tcpdump")
         logging.debug("Starting tcpdump (%s)...", cmd)
-        env["tcpdump"] = kvm_subprocess.kvm_tail(
+        env["tcpdump"] = kvm_subprocess.Tail(
             command=cmd,
             output_func=_update_address_cache,
             output_params=(env["address_cache"],))
@@ -216,7 +224,7 @@
                 env["tcpdump"].get_output()))
 
     # Destroy and remove VMs that are no longer needed in the environment
-    requested_vms = kvm_utils.get_sub_dict_names(params, "vms")
+    requested_vms = params.objects("vms")
     for key in env.keys():
         vm = env[key]
         if not kvm_utils.is_vm(vm):
@@ -330,7 +338,7 @@
     if params.get("kill_unresponsive_vms") == "yes":
         logging.debug("'kill_unresponsive_vms' specified; killing all VMs "
                       "that fail to respond to a remote login request...")
-        for vm in kvm_utils.env_get_all_vms(env):
+        for vm in env.get_all_vms():
             if vm.is_alive():
                 session = vm.remote_login()
                 if session:
@@ -342,7 +350,7 @@
     kvm_subprocess.kill_tail_threads()
 
     # Terminate tcpdump if no VMs are alive
-    living_vms = [vm for vm in kvm_utils.env_get_all_vms(env) if vm.is_alive()]
+    living_vms = [vm for vm in env.get_all_vms() if vm.is_alive()]
     if not living_vms and "tcpdump" in env:
         env["tcpdump"].close()
         del env["tcpdump"]
@@ -362,7 +370,7 @@
     @param params: A dict containing all VM and image parameters.
     @param env: The environment (a dict-like object).
     """
-    params.update(kvm_utils.get_sub_dict(params, "on_error"))
+    params.update(params.object_params("on_error"))
 
 
 def _update_address_cache(address_cache, line):
@@ -374,9 +382,11 @@
         matches = re.findall(r"\w*:\w*:\w*:\w*:\w*:\w*", line)
         if matches and address_cache.get("last_seen"):
             mac_address = matches[0].lower()
-            logging.debug("(address cache) Adding cache entry: %s ---> %s",
-                          mac_address, address_cache.get("last_seen"))
+            if time.time() - address_cache.get("time_%s" % mac_address, 0) > 5:
+                logging.debug("(address cache) Adding cache entry: %s ---> %s",
+                              mac_address, address_cache.get("last_seen"))
             address_cache[mac_address] = address_cache.get("last_seen")
+            address_cache["time_%s" % mac_address] = time.time()
             del address_cache["last_seen"]
 
 
@@ -398,7 +408,7 @@
     cache = {}
 
     while True:
-        for vm in kvm_utils.env_get_all_vms(env):
+        for vm in env.get_all_vms():
             if not vm.is_alive():
                 continue
             try:
diff --git a/client/tests/kvm/kvm_scheduler.py b/client/tests/kvm/kvm_scheduler.py
index f1adb39..95282e4 100644
--- a/client/tests/kvm/kvm_scheduler.py
+++ b/client/tests/kvm/kvm_scheduler.py
@@ -74,13 +74,13 @@
             # The scheduler wants this worker to free its used resources
             elif cmd[0] == "cleanup":
                 env_filename = os.path.join(self.bindir, self_dict["env"])
-                env = kvm_utils.load_env(env_filename, {})
+                env = kvm_utils.Env(env_filename)
                 for obj in env.values():
                     if isinstance(obj, kvm_vm.VM):
                         obj.destroy()
-                    elif isinstance(obj, kvm_subprocess.kvm_spawn):
+                    elif isinstance(obj, kvm_subprocess.Spawn):
                         obj.close()
-                kvm_utils.dump_env(env, env_filename)
+                env.save()
                 w.write("cleanup_done\n")
                 w.write("ready\n")
 
diff --git a/client/tests/kvm/kvm_subprocess.py b/client/tests/kvm/kvm_subprocess.py
index 8321bb3..c3e2dd7 100755
--- a/client/tests/kvm/kvm_subprocess.py
+++ b/client/tests/kvm/kvm_subprocess.py
@@ -189,6 +189,88 @@
 import common, kvm_utils
 
 
+class ExpectError(Exception):
+    def __init__(self, patterns, output):
+        Exception.__init__(self, patterns, output)
+        self.patterns = patterns
+        self.output = output
+
+    def _pattern_str(self):
+        if len(self.patterns) == 1:
+            return "pattern %r" % self.patterns[0]
+        else:
+            return "patterns %r" % self.patterns
+
+    def __str__(self):
+        return ("Unknown error occurred while looking for %s (output: %r)" %
+                (self._pattern_str(), self.output))
+
+
+class ExpectTimeoutError(ExpectError):
+    def __str__(self):
+        return ("Timeout expired while looking for %s (output: %r)" %
+                (self._pattern_str(), self.output))
+
+
+class ExpectProcessTerminatedError(ExpectError):
+    def __init__(self, patterns, status, output):
+        ExpectError.__init__(self, patterns, output)
+        self.status = status
+
+    def __str__(self):
+        return ("Process terminated while looking for %s (status: %s, output: "
+                "%r)" % (self._pattern_str(), self.status, self.output))
+
+
+class ShellError(Exception):
+    def __init__(self, cmd, output):
+        Exception.__init__(self, cmd, output)
+        self.cmd = cmd
+        self.output = output
+
+    def __str__(self):
+        return ("Could not execute shell command %r (output: %r)" %
+                (self.cmd, self.output))
+
+
+class ShellTimeoutError(ShellError):
+    def __str__(self):
+        return ("Timeout expired while waiting for shell command %r to "
+                "complete (output: %r)" % (self.cmd, self.output))
+
+
+class ShellProcessTerminatedError(ShellError):
+    # Raised when the shell process itself (e.g. ssh, netcat, telnet)
+    # terminates unexpectedly
+    def __init__(self, cmd, status, output):
+        ShellError.__init__(self, cmd, output)
+        self.status = status
+
+    def __str__(self):
+        return ("Shell process terminated while waiting for command %r to "
+                "complete (status: %s, output: %r)" %
+                (self.cmd, self.status, self.output))
+
+
+class ShellCmdError(ShellError):
+    # Raised when a command executed in a shell terminates with a nonzero
+    # exit code (status)
+    def __init__(self, cmd, status, output):
+        ShellError.__init__(self, cmd, output)
+        self.status = status
+
+    def __str__(self):
+        return ("Shell command %r failed with status %d (output: %r)" %
+                (self.cmd, self.status, self.output))
+
+
+class ShellStatusError(ShellError):
+    # Raised when the command's exit status cannot be obtained
+    def __str__(self):
+        return ("Could not get exit status of command %r (output: %r)" %
+                (self.cmd, self.output))
+
+
 def run_bg(command, termination_func=None, output_func=None, output_prefix="",
            timeout=1.0):
     """
@@ -210,12 +292,12 @@
     @param timeout: Time duration (in seconds) to wait for the subprocess to
             terminate before returning
 
-    @return: A kvm_tail object.
+    @return: A Tail object.
     """
-    process = kvm_tail(command=command,
-                       termination_func=termination_func,
-                       output_func=output_func,
-                       output_prefix=output_prefix)
+    process = Tail(command=command,
+                   termination_func=termination_func,
+                   output_func=output_func,
+                   output_prefix=output_prefix)
 
     end_time = time.time() + timeout
     while time.time() < end_time and process.is_alive():
@@ -256,7 +338,7 @@
     return (status, output)
 
 
-class kvm_spawn:
+class Spawn:
     """
     This class is used for spawning and controlling a child process.
 
@@ -268,7 +350,7 @@
     The text file can be accessed at any time using get_output().
     In addition, the server opens as many pipes as requested by the client and
     writes the output to them.
-    The pipes are requested and accessed by classes derived from kvm_spawn.
+    The pipes are requested and accessed by classes derived from Spawn.
     These pipes are referred to as "readers".
     The server also receives input from the client and sends it to the child
     process.
@@ -552,7 +634,7 @@
 
 def kill_tail_threads():
     """
-    Kill all kvm_tail threads.
+    Kill all Tail threads.
 
     After calling this function no new threads should be started.
     """
@@ -564,12 +646,12 @@
     _thread_kill_requested = False
 
 
-class kvm_tail(kvm_spawn):
+class Tail(Spawn):
     """
     This class runs a child process in the background and sends its output in
     real time, line-by-line, to a callback function.
 
-    See kvm_spawn's docstring.
+    See Spawn's docstring.
 
     This class uses a single pipe reader to read data in real time from the
     child process and report it to a given callback function.
@@ -610,10 +692,10 @@
         """
         # Add a reader and a close hook
         self._add_reader("tail")
-        self._add_close_hook(kvm_tail._join_thread)
+        self._add_close_hook(Tail._join_thread)
 
         # Init the superclass
-        kvm_spawn.__init__(self, command, id, auto_close, echo, linesep)
+        Spawn.__init__(self, command, id, auto_close, echo, linesep)
 
         # Remember some attributes
         self.termination_func = termination_func
@@ -629,11 +711,11 @@
 
 
     def __getinitargs__(self):
-        return kvm_spawn.__getinitargs__(self) + (self.termination_func,
-                                                  self.termination_params,
-                                                  self.output_func,
-                                                  self.output_params,
-                                                  self.output_prefix)
+        return Spawn.__getinitargs__(self) + (self.termination_func,
+                                              self.termination_params,
+                                              self.output_func,
+                                              self.output_params,
+                                              self.output_prefix)
 
 
     def set_termination_func(self, termination_func):
@@ -765,15 +847,15 @@
             t.join()
 
 
-class kvm_expect(kvm_tail):
+class Expect(Tail):
     """
     This class runs a child process in the background and provides expect-like
     services.
 
-    It also provides all of kvm_tail's functionality.
+    It also provides all of Tail's functionality.
     """
 
-    def __init__(self, command=None, id=None, auto_close=False, echo=False,
+    def __init__(self, command=None, id=None, auto_close=True, echo=False,
                  linesep="\n", termination_func=None, termination_params=(),
                  output_func=None, output_params=(), output_prefix=""):
         """
@@ -806,13 +888,13 @@
         self._add_reader("expect")
 
         # Init the superclass
-        kvm_tail.__init__(self, command, id, auto_close, echo, linesep,
-                          termination_func, termination_params,
-                          output_func, output_params, output_prefix)
+        Tail.__init__(self, command, id, auto_close, echo, linesep,
+                      termination_func, termination_params,
+                      output_func, output_params, output_prefix)
 
 
     def __getinitargs__(self):
-        return kvm_tail.__getinitargs__(self)
+        return Tail.__getinitargs__(self)
 
 
     def read_nonblocking(self, timeout=None):
@@ -858,7 +940,7 @@
 
 
     def read_until_output_matches(self, patterns, filter=lambda x: x,
-                                  timeout=30.0, internal_timeout=None,
+                                  timeout=60, internal_timeout=None,
                                   print_func=None):
         """
         Read using read_nonblocking until a match is found using match_patterns,
@@ -876,13 +958,14 @@
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
-        @return: Tuple containing the match index (or None if no match was
-                found) and the data read so far.
+        @return: Tuple containing the match index and the data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the child process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
-        match = None
-        data = ""
-
         fd = self._get_fd("expect")
+        o = ""
         end_time = time.time() + timeout
         while True:
             try:
@@ -890,41 +973,31 @@
                                         max(0, end_time - time.time()))
             except (select.error, TypeError):
                 break
-            if fd not in r:
-                break
+            if not r:
+                raise ExpectTimeoutError(patterns, o)
             # Read data from child
-            newdata = self.read_nonblocking(internal_timeout)
+            data = self.read_nonblocking(internal_timeout)
+            if not data:
+                break
             # Print it if necessary
-            if print_func and newdata:
-                str = newdata
-                if str.endswith("\n"):
-                    str = str[:-1]
-                for line in str.split("\n"):
+            if print_func:
+                for line in data.splitlines():
                     print_func(line)
-            data += newdata
-
-            done = False
             # Look for patterns
-            match = self.match_patterns(filter(data), patterns)
+            o += data
+            match = self.match_patterns(filter(o), patterns)
             if match is not None:
-                done = True
-            # Check if child has died
-            if not self.is_alive():
-                logging.debug("Process terminated with status %s" %
-                              self.get_status())
-                done = True
-            # Are we done?
-            if done: break
+                return match, o
 
-        # Print some debugging info
-        if match is None and (self.is_alive() or self.get_status() != 0):
-            logging.debug("Timeout elapsed or process terminated. Output:" +
-                          kvm_utils.format_str_for_message(data.strip()))
-
-        return (match, data)
+        # Check if the child has terminated
+        if kvm_utils.wait_for(lambda: not self.is_alive(), 5, 0, 0.1):
+            raise ExpectProcessTerminatedError(patterns, self.get_status(), o)
+        else:
+            # This shouldn't happen
+            raise ExpectError(patterns, o)
 
 
-    def read_until_last_word_matches(self, patterns, timeout=30.0,
+    def read_until_last_word_matches(self, patterns, timeout=60,
                                      internal_timeout=None, print_func=None):
         """
         Read using read_nonblocking until the last word of the output matches
@@ -936,8 +1009,11 @@
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
-        @return: A tuple containing the match index (or None if no match was
-                found) and the data read so far.
+        @return: A tuple containing the match index and the data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the child process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
         def get_last_word(str):
             if str:
@@ -950,7 +1026,7 @@
                                               print_func)
 
 
-    def read_until_last_line_matches(self, patterns, timeout=30.0,
+    def read_until_last_line_matches(self, patterns, timeout=60,
                                      internal_timeout=None, print_func=None):
         """
         Read using read_nonblocking until the last non-empty line of the output
@@ -967,6 +1043,11 @@
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
+        @return: A tuple containing the match index and the data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the child process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
         def get_last_nonempty_line(str):
             nonempty_lines = [l for l in str.splitlines() if l.strip()]
@@ -980,12 +1061,12 @@
                                               print_func)
 
 
-class kvm_shell_session(kvm_expect):
+class ShellSession(Expect):
     """
     This class runs a child process in the background.  It it suited for
     processes that provide an interactive shell, such as SSH and Telnet.
 
-    It provides all services of kvm_expect and kvm_tail.  In addition, it
+    It provides all services of Expect and Tail.  In addition, it
     provides command running services, and a utility function to test the
     process for responsiveness.
     """
@@ -1022,12 +1103,12 @@
         @param prompt: Regular expression describing the shell's prompt line.
         @param status_test_command: Command to be used for getting the last
                 exit status of commands run inside the shell (used by
-                get_command_status_output() and friends).
+                cmd_status_output() and friends).
         """
         # Init the superclass
-        kvm_expect.__init__(self, command, id, auto_close, echo, linesep,
-                            termination_func, termination_params,
-                            output_func, output_params, output_prefix)
+        Expect.__init__(self, command, id, auto_close, echo, linesep,
+                        termination_func, termination_params,
+                        output_func, output_params, output_prefix)
 
         # Remember some attributes
         self.prompt = prompt
@@ -1035,8 +1116,8 @@
 
 
     def __getinitargs__(self):
-        return kvm_expect.__getinitargs__(self) + (self.prompt,
-                                                   self.status_test_command)
+        return Expect.__getinitargs__(self) + (self.prompt,
+                                               self.status_test_command)
 
 
     def set_prompt(self, prompt):
@@ -1085,7 +1166,7 @@
         return False
 
 
-    def read_up_to_prompt(self, timeout=30.0, internal_timeout=None,
+    def read_up_to_prompt(self, timeout=60, internal_timeout=None,
                           print_func=None):
         """
         Read using read_nonblocking until the last non-empty line of the output
@@ -1101,31 +1182,34 @@
         @param print_func: A function to be used to print the data being
                 read (should take a string parameter)
 
-        @return: A tuple containing True/False indicating whether the prompt
-                was found, and the data read so far.
+        @return: The data read so far
+        @raise ExpectTimeoutError: Raised if timeout expires
+        @raise ExpectProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ExpectError: Raised if an unknown error occurs
         """
-        (match, output) = self.read_until_last_line_matches([self.prompt],
-                                                            timeout,
-                                                            internal_timeout,
-                                                            print_func)
-        return (match is not None, output)
+        m, o = self.read_until_last_line_matches([self.prompt], timeout,
+                                                 internal_timeout, print_func)
+        return o
 
 
-    def get_command_status_output(self, command, timeout=30.0,
-                                  internal_timeout=None, print_func=None):
+    def cmd_output(self, cmd, timeout=60, internal_timeout=None,
+                   print_func=None):
         """
-        Send a command and return its exit status and output.
+        Send a command and return its output.
 
-        @param command: Command to send (must not contain newline characters)
-        @param timeout: The duration (in seconds) to wait until a match is
-                found
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
 
-        @return: A tuple (status, output) where status is the exit status or
-                None if no exit status is available (e.g. timeout elapsed), and
-                output is the output of command.
+        @return: The output of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellError: Raised if an unknown error occurs
         """
         def remove_command_echo(str, cmd):
             if str and str.splitlines()[0] == cmd:
@@ -1135,79 +1219,132 @@
         def remove_last_nonempty_line(str):
             return "".join(str.rstrip().splitlines(True)[:-1])
 
-        # Print some debugging info
-        logging.debug("Sending command: %s" % command)
-
-        # Read everything that's waiting to be read
+        logging.debug("Sending command: %s" % cmd)
         self.read_nonblocking(timeout=0)
+        self.sendline(cmd)
+        try:
+            o = self.read_up_to_prompt(timeout, internal_timeout, print_func)
+        except ExpectError, e:
+            o = remove_command_echo(e.output, cmd)
+            if isinstance(e, ExpectTimeoutError):
+                raise ShellTimeoutError(cmd, o)
+            elif isinstance(e, ExpectProcessTerminatedError):
+                raise ShellProcessTerminatedError(cmd, e.status, o)
+            else:
+                raise ShellError(cmd, o)
 
-        # Send the command and get its output
-        self.sendline(command)
-        (match, output) = self.read_up_to_prompt(timeout, internal_timeout,
-                                                 print_func)
-        # Remove the echoed command from the output
-        output = remove_command_echo(output, command)
-        # If the prompt was not found, return the output so far
-        if not match:
-            return (None, output)
-        # Remove the final shell prompt from the output
-        output = remove_last_nonempty_line(output)
+        # Remove the echoed command and the final shell prompt
+        return remove_last_nonempty_line(remove_command_echo(o, cmd))
 
-        # Send the 'echo ...' command to get the last exit status
-        self.sendline(self.status_test_command)
-        (match, status) = self.read_up_to_prompt(10.0, internal_timeout)
-        if not match:
-            return (None, output)
-        status = remove_command_echo(status, self.status_test_command)
-        status = remove_last_nonempty_line(status)
+
+    def cmd_status_output(self, cmd, timeout=60, internal_timeout=None,
+                          print_func=None):
+        """
+        Send a command and return its exit status and output.
+
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
+        @param internal_timeout: The timeout to pass to read_nonblocking
+        @param print_func: A function to be used to print the data being read
+                (should take a string parameter)
+
+        @return: A tuple (status, output) where status is the exit status and
+                output is the output of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellStatusError: Raised if the exit status cannot be obtained
+        @raise ShellError: Raised if an unknown error occurs
+        """
+        o = self.cmd_output(cmd, timeout, internal_timeout, print_func)
+        try:
+            # Send the 'echo $?' (or equivalent) command to get the exit status
+            s = self.cmd_output(self.status_test_command, 10, internal_timeout)
+        except ShellError:
+            raise ShellStatusError(cmd, o)
+
         # Get the first line consisting of digits only
-        digit_lines = [l for l in status.splitlines() if l.strip().isdigit()]
-        if not digit_lines:
-            return (None, output)
-        status = int(digit_lines[0].strip())
-
-        # Print some debugging info
-        if status != 0:
-            logging.debug("Command failed; status: %d, output:%s", status,
-                          kvm_utils.format_str_for_message(output.strip()))
-
-        return (status, output)
+        digit_lines = [l for l in s.splitlines() if l.strip().isdigit()]
+        if digit_lines:
+            return int(digit_lines[0].strip()), o
+        else:
+            raise ShellStatusError(cmd, o)
 
 
-    def get_command_status(self, command, timeout=30.0, internal_timeout=None,
-                           print_func=None):
+    def cmd_status(self, cmd, timeout=60, internal_timeout=None,
+                   print_func=None):
         """
         Send a command and return its exit status.
 
-        @param command: Command to send
-        @param timeout: The duration (in seconds) to wait until a match is
-                found
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
         @param internal_timeout: The timeout to pass to read_nonblocking
         @param print_func: A function to be used to print the data being read
                 (should take a string parameter)
 
-        @return: Exit status or None if no exit status is available (e.g.
-                timeout elapsed).
+        @return: The exit status of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellStatusError: Raised if the exit status cannot be obtained
+        @raise ShellError: Raised if an unknown error occurs
         """
-        (status, output) = self.get_command_status_output(command, timeout,
-                                                          internal_timeout,
-                                                          print_func)
-        return status
+        s, o = self.cmd_status_output(cmd, timeout, internal_timeout,
+                                      print_func)
+        return s
 
 
-    def get_command_output(self, command, timeout=30.0, internal_timeout=None,
+    def cmd(self, cmd, timeout=60, internal_timeout=None, print_func=None):
+        """
+        Send a command and return its output. If the command's exit status is
+        nonzero, raise an exception.
+
+        @param cmd: Command to send (must not contain newline characters)
+        @param timeout: The duration (in seconds) to wait for the prompt to
+                return
+        @param internal_timeout: The timeout to pass to read_nonblocking
+        @param print_func: A function to be used to print the data being read
+                (should take a string parameter)
+
+        @return: The output of cmd
+        @raise ShellTimeoutError: Raised if timeout expires
+        @raise ShellProcessTerminatedError: Raised if the shell process
+                terminates while waiting for output
+        @raise ShellError: Raised if the exit status cannot be obtained or if
+                an unknown error occurs
+        @raise ShellStatusError: Raised if the exit status cannot be obtained
+        @raise ShellError: Raised if an unknown error occurs
+        @raise ShellCmdError: Raised if the exit status is nonzero
+        """
+        s, o = self.cmd_status_output(cmd, timeout, internal_timeout,
+                                      print_func)
+        if s != 0:
+            raise ShellCmdError(cmd, s, o)
+        return o
+
+
+    def get_command_output(self, cmd, timeout=60, internal_timeout=None,
                            print_func=None):
         """
-        Send a command and return its output.
-
-        @param command: Command to send
-        @param timeout: The duration (in seconds) to wait until a match is
-                found
-        @param internal_timeout: The timeout to pass to read_nonblocking
-        @param print_func: A function to be used to print the data being read
-                (should take a string parameter)
+        Alias for cmd_output() for backward compatibility.
         """
-        (status, output) = self.get_command_status_output(command, timeout,
-                                                          internal_timeout,
-                                                          print_func)
-        return output
+        return self.cmd_output(cmd, timeout, internal_timeout, print_func)
+
+
+    def get_command_status_output(self, cmd, timeout=60, internal_timeout=None,
+                                  print_func=None):
+        """
+        Alias for cmd_status_output() for backward compatibility.
+        """
+        return self.cmd_status_output(cmd, timeout, internal_timeout,
+                                      print_func)
+
+
+    def get_command_status(self, cmd, timeout=60, internal_timeout=None,
+                           print_func=None):
+        """
+        Alias for cmd_status() for backward compatibility.
+        """
+        return self.cmd_status(cmd, timeout, internal_timeout, print_func)
diff --git a/client/tests/kvm/kvm_test_utils.py b/client/tests/kvm/kvm_test_utils.py
index 014f265..7ed3330 100644
--- a/client/tests/kvm/kvm_test_utils.py
+++ b/client/tests/kvm/kvm_test_utils.py
@@ -21,7 +21,7 @@
 @copyright: 2008-2009 Red Hat Inc.
 """
 
-import time, os, logging, re, commands, signal
+import time, os, logging, re, commands, signal, threading
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
 import kvm_utils, kvm_vm, kvm_subprocess, scan_results
@@ -35,7 +35,7 @@
     @param vm_name: Name of the desired VM object.
     @return: A VM object.
     """
-    vm = kvm_utils.env_get_vm(env, vm_name)
+    vm = env.get_vm(vm_name)
     if not vm:
         raise error.TestError("VM '%s' not found in environment" % vm_name)
     if not vm.is_alive():
@@ -44,21 +44,33 @@
     return vm
 
 
-def wait_for_login(vm, nic_index=0, timeout=240, start=0, step=2):
+def wait_for_login(vm, nic_index=0, timeout=240, start=0, step=2, serial=None):
     """
     Try logging into a VM repeatedly.  Stop on success or when timeout expires.
 
     @param vm: VM object.
     @param nic_index: Index of NIC to access in the VM.
     @param timeout: Time to wait before giving up.
+    @param serial: Whether to use a serial connection instead of a remote
+            (ssh, rss) one.
     @return: A shell session object.
     """
-    logging.info("Trying to log into guest '%s', timeout %ds", vm.name, timeout)
-    session = kvm_utils.wait_for(lambda: vm.remote_login(nic_index=nic_index),
-                                 timeout, start, step)
+    type = 'remote'
+    if serial:
+        type = 'serial'
+        logging.info("Trying to log into guest %s using serial connection,"
+                     " timeout %ds", vm.name, timeout)
+        session = kvm_utils.wait_for(lambda: vm.serial_login(), timeout,
+                                     start, step)
+    else:
+        logging.info("Trying to log into guest %s using remote connection,"
+                     " timeout %ds", vm.name, timeout)
+        session = kvm_utils.wait_for(lambda: vm.remote_login(
+                  nic_index=nic_index), timeout, start, step)
     if not session:
-        raise error.TestFail("Could not log into guest '%s'" % vm.name)
-    logging.info("Logged into guest '%s'" % vm.name)
+        raise error.TestFail("Could not log into guest %s using %s connection" %
+                             (vm.name, type))
+    logging.info("Logged into guest %s using %s connection", vm.name, type)
     return session
 
 
@@ -121,7 +133,8 @@
 
 
 def migrate(vm, env=None, mig_timeout=3600, mig_protocol="tcp",
-            mig_cancel=False):
+            mig_cancel=False, offline=False, stable_check=False,
+            clean=False, save_path=None, dest_host='localhost', mig_port=None):
     """
     Migrate a VM locally and re-register it in the environment.
 
@@ -131,7 +144,10 @@
     @param mig_timeout: timeout value for migration.
     @param mig_protocol: migration protocol
     @param mig_cancel: Test migrate_cancel or not when protocol is tcp.
-    @return: The post-migration VM.
+    @param dest_host: Destination host (defaults to 'localhost').
+    @param mig_port: Port that will be used for migration.
+    @return: The post-migration VM, in case of same host migration, True in
+            case of multi-host migration.
     """
     def mig_finished():
         o = vm.monitor.info("migrate")
@@ -169,38 +185,32 @@
             raise error.TestFail("Timeout expired while waiting for migration "
                                  "to finish")
 
-    dest_vm = vm.clone()
+    if dest_host == 'localhost':
+        dest_vm = vm.clone()
 
-    if mig_protocol == "exec":
-        # Exec is a little different from other migrate methods - first we
-        # ask the monitor the migration, then the vm state is dumped to a
-        # compressed file, then we start the dest vm with -incoming pointing
-        # to it
-        try:
-            exec_file = "/tmp/exec-%s.gz" % kvm_utils.generate_random_string(8)
-            exec_cmd = "gzip -c -d %s" % exec_file
-            uri = '"exec:gzip -c > %s"' % exec_file
-            vm.monitor.cmd("stop")
-            vm.monitor.migrate(uri)
-            wait_for_migration()
+    if (dest_host == 'localhost') and stable_check:
+        # Pause the dest vm after creation
+        dest_vm.params['extra_params'] = (dest_vm.params.get('extra_params','')
+                                          + ' -S')
 
-            if not dest_vm.create(migration_mode=mig_protocol,
-                                  migration_exec_cmd=exec_cmd, mac_source=vm):
-                raise error.TestError("Could not create dest VM")
-        finally:
-            logging.debug("Removing migration file %s", exec_file)
-            try:
-                os.remove(exec_file)
-            except OSError:
-                pass
-    else:
+    if dest_host == 'localhost':
         if not dest_vm.create(migration_mode=mig_protocol, mac_source=vm):
             raise error.TestError("Could not create dest VM")
+
+    try:
         try:
             if mig_protocol == "tcp":
-                uri = "tcp:localhost:%d" % dest_vm.migration_port
+                if dest_host == 'localhost':
+                    uri = "tcp:localhost:%d" % dest_vm.migration_port
+                else:
+                    uri = 'tcp:%s:%d' % (dest_host, mig_port)
             elif mig_protocol == "unix":
                 uri = "unix:%s" % dest_vm.migration_file
+            elif mig_protocol == "exec":
+                uri = '"exec:nc localhost %s"' % dest_vm.migration_port
+
+            if offline:
+                vm.monitor.cmd("stop")
             vm.monitor.migrate(uri)
 
             if mig_cancel:
@@ -210,14 +220,43 @@
                                           "Waiting for migration "
                                           "cancellation"):
                     raise error.TestFail("Failed to cancel migration")
-                dest_vm.destroy(gracefully=False)
+                if offline:
+                    vm.monitor.cmd("cont")
+                if dest_host == 'localhost':
+                    dest_vm.destroy(gracefully=False)
                 return vm
             else:
                 wait_for_migration()
+                if (dest_host == 'localhost') and stable_check:
+                    save_path = None or "/tmp"
+                    save1 = os.path.join(save_path, "src")
+                    save2 = os.path.join(save_path, "dst")
+
+                    vm.save_to_file(save1)
+                    dest_vm.save_to_file(save2)
+
+                    # Fail if we see deltas
+                    md5_save1 = utils.hash_file(save1)
+                    md5_save2 = utils.hash_file(save2)
+                    if md5_save1 != md5_save2:
+                        raise error.TestFail("Mismatch of VM state before "
+                                             "and after migration")
+
+                if (dest_host == 'localhost') and offline:
+                    dest_vm.monitor.cmd("cont")
         except:
-            dest_vm.destroy()
+            if dest_host == 'localhost':
+                dest_vm.destroy()
             raise
 
+    finally:
+        if (dest_host == 'localhost') and stable_check and clean:
+            logging.debug("Cleaning the state files")
+            if os.path.isfile(save1):
+                os.remove(save1)
+            if os.path.isfile(save2):
+                os.remove(save2)
+
     # Report migration status
     if mig_succeeded():
         logging.info("Migration finished successfully")
@@ -226,19 +265,23 @@
     else:
         raise error.TestFail("Migration ended with unknown status")
 
-    if "paused" in dest_vm.monitor.info("status"):
-        logging.debug("Destination VM is paused, resuming it...")
-        dest_vm.monitor.cmd("cont")
+    if dest_host == 'localhost':
+        if "paused" in dest_vm.monitor.info("status"):
+            logging.debug("Destination VM is paused, resuming it...")
+            dest_vm.monitor.cmd("cont")
 
     # Kill the source VM
     vm.destroy(gracefully=False)
 
     # Replace the source VM with the new cloned VM
-    if env is not None:
-        kvm_utils.env_register_vm(env, vm.name, dest_vm)
+    if (dest_host == 'localhost') and (env is not None):
+        env.register_vm(vm.name, dest_vm)
 
     # Return the new cloned VM
-    return dest_vm
+    if dest_host == 'localhost':
+        return dest_vm
+    else:
+        return vm
 
 
 def stop_windows_service(session, service, timeout=120):
@@ -252,7 +295,7 @@
     """
     end_time = time.time() + timeout
     while time.time() < end_time:
-        o = session.get_command_output("sc stop %s" % service, timeout=60)
+        o = session.cmd_output("sc stop %s" % service, timeout=60)
         # FAILED 1060 means the service isn't installed.
         # FAILED 1062 means the service hasn't been started.
         if re.search(r"\bFAILED (1060|1062)\b", o, re.I):
@@ -274,7 +317,7 @@
     """
     end_time = time.time() + timeout
     while time.time() < end_time:
-        o = session.get_command_output("sc start %s" % service, timeout=60)
+        o = session.cmd_output("sc start %s" % service, timeout=60)
         # FAILED 1060 means the service isn't installed.
         if re.search(r"\bFAILED 1060\b", o, re.I):
             raise error.TestError("Could not start service '%s' "
@@ -306,10 +349,7 @@
     """
     if len(re.findall("ntpdate|w32tm", time_command)) == 0:
         host_time = time.time()
-        session.sendline(time_command)
-        (match, s) = session.read_up_to_prompt()
-        if not match:
-            raise error.TestError("Could not get guest time")
+        s = session.cmd_output(time_command)
 
         try:
             s = re.findall(time_filter_re, s)[0]
@@ -323,9 +363,7 @@
 
         guest_time = time.mktime(time.strptime(s, time_format))
     else:
-        s , o = session.get_command_status_output(time_command)
-        if s != 0:
-            raise error.TestError("Could not get guest time")
+        o = session.cmd(time_command)
         if re.match('ntpdate', time_command):
             offset = re.findall('offset (.*) sec',o)[0]
             host_main, host_mantissa = re.findall(time_filter_re, o)[0]
@@ -403,7 +441,7 @@
         copy = False
         local_hash = utils.hash_file(local_path)
         basename = os.path.basename(local_path)
-        output = session.get_command_output("md5sum %s" % remote_path)
+        output = session.cmd_output("md5sum %s" % remote_path)
         if "such file" in output:
             remote_hash = "0"
         elif output:
@@ -435,10 +473,7 @@
         basename = os.path.basename(remote_path)
         logging.info("Extracting %s...", basename)
         e_cmd = "tar xjvf %s -C %s" % (remote_path, dest_dir)
-        s, o = session.get_command_status_output(e_cmd, timeout=120)
-        if s != 0:
-            logging.error("Uncompress output:\n%s", o)
-            raise error.TestFail("Failed to extract %s on guest" % basename)
+        session.cmd(e_cmd, timeout=120)
 
 
     def get_results():
@@ -459,7 +494,7 @@
         Get the status of the tests that were executed on the host and close
         the session where autotest was being executed.
         """
-        output = session.get_command_output("cat results/*/status")
+        output = session.cmd_output("cat results/*/status")
         try:
             results = scan_results.parse_results(output)
             # Report test results
@@ -508,26 +543,32 @@
     # Run the test
     logging.info("Running autotest control file %s on guest, timeout %ss",
                  os.path.basename(control_path), timeout)
-    session.get_command_output("cd %s" % autotest_path)
-    session.get_command_output("rm -f control.state")
-    session.get_command_output("rm -rf results/*")
-    logging.info("---------------- Test output ----------------")
-    status = session.get_command_status("bin/autotest control",
-                                        timeout=timeout,
-                                        print_func=logging.info)
-    logging.info("------------- End of test output ------------")
-    if status is None:
-        if not vm.is_alive():
+    session.cmd("cd %s" % autotest_path)
+    try:
+        session.cmd("rm -f control.state")
+        session.cmd("rm -rf results/*")
+    except kvm_subprocess.ShellError:
+        pass
+    try:
+        try:
+            logging.info("---------------- Test output ----------------")
+            session.cmd_output("bin/autotest control", timeout=timeout,
+                               print_func=logging.info)
+        finally:
+            logging.info("------------- End of test output ------------")
+    except kvm_subprocess.ShellTimeoutError:
+        if vm.is_alive():
+            get_results()
+            get_results_summary()
+            raise error.TestError("Timeout elapsed while waiting for job to "
+                                  "complete")
+        else:
             raise error.TestError("Autotest job on guest failed "
                                   "(VM terminated during job)")
-        if not session.is_alive():
-            get_results()
-            raise error.TestError("Autotest job on guest failed "
-                                  "(Remote session terminated during job)")
+    except kvm_subprocess.ShellProcessTerminatedError:
         get_results()
-        get_results_summary()
-        raise error.TestError("Timeout elapsed while waiting for job to "
-                              "complete")
+        raise error.TestError("Autotest job on guest failed "
+                              "(Remote session terminated during job)")
 
     results = get_results_summary()
     get_results()
@@ -589,21 +630,24 @@
         process.close()
         return status, output
     else:
-        session.sendline(command)
-        status, output = session.read_up_to_prompt(timeout=timeout,
-                                                   print_func=output_func)
-        if not status:
+        try:
+            output = session.cmd_output(command, timeout=timeout,
+                                        print_func=output_func)
+        except kvm_subprocess.ShellTimeoutError:
             # Send ctrl+c (SIGINT) through ssh session
             session.send("\003")
-            status, output2 = session.read_up_to_prompt(print_func=output_func)
-            output += output2
-            if not status:
+            try:
+                output2 = session.read_up_to_prompt(print_func=output_func)
+                output += output2
+            except kvm_subprocess.ExpectTimeoutError, e:
+                output += e.output
                 # We also need to use this session to query the return value
                 session.send("\003")
 
         session.sendline(session.status_test_command)
-        s2, o2 = session.read_up_to_prompt()
-        if not s2:
+        try:
+            o2 = session.read_up_to_prompt()
+        except kvm_subprocess.ExpectError:
             status = -1
         else:
             try:
@@ -670,7 +714,7 @@
     @mac_address: the macaddress of nic
     """
 
-    output = session.get_command_output("ifconfig -a")
+    output = session.cmd_output("ifconfig -a")
 
     try:
         ethname = re.findall("(\w+)\s+Link.*%s" % mac_address, output,
diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
index b849b37..d135979 100644
--- a/client/tests/kvm/kvm_utils.py
+++ b/client/tests/kvm/kvm_utils.py
@@ -5,7 +5,7 @@
 """
 
 import time, string, random, socket, os, signal, re, logging, commands, cPickle
-import fcntl, shelve, ConfigParser
+import fcntl, shelve, ConfigParser, rss_file_transfer, threading, sys, UserDict
 from autotest_lib.client.bin import utils, os_dep
 from autotest_lib.client.common_lib import error, logging_config
 import kvm_subprocess
@@ -27,74 +27,152 @@
     f.close()
 
 
-def dump_env(obj, filename):
+def is_vm(obj):
     """
-    Dump KVM test environment to a file.
+    Tests whether a given object is a VM object.
 
-    @param filename: Path to a file where the environment will be dumped to.
+    @param obj: Python object.
     """
-    file = open(filename, "w")
-    cPickle.dump(obj, file)
-    file.close()
+    return obj.__class__.__name__ == "VM"
 
 
-def load_env(filename, version):
+class Env(UserDict.IterableUserDict):
     """
-    Load KVM test environment from an env file.
-    If the version recorded in the file is lower than version, return an empty
-    env.  If some other error occurs during unpickling, return an empty env.
-
-    @param filename: Path to an env file.
+    A dict-like object containing global objects used by tests.
     """
-    default = {"version": version}
-    try:
-        file = open(filename, "r")
-        env = cPickle.load(file)
-        file.close()
-        if env.get("version", 0) < version:
-            logging.warn("Incompatible env file found. Not using it.")
-            return default
-        return env
-    # Almost any exception can be raised during unpickling, so let's catch
-    # them all
-    except Exception, e:
-        logging.warn(e)
-        return default
+    def __init__(self, filename=None, version=0):
+        """
+        Create an empty Env object or load an existing one from a file.
+
+        If the version recorded in the file is lower than version, or if some
+        error occurs during unpickling, or if filename is not supplied,
+        create an empty Env object.
+
+        @param filename: Path to an env file.
+        @param version: Required env version (int).
+        """
+        UserDict.IterableUserDict.__init__(self)
+        empty = {"version": version}
+        if filename:
+            self._filename = filename
+            try:
+                f = open(filename, "r")
+                env = cPickle.load(f)
+                f.close()
+                if env.get("version", 0) >= version:
+                    self.data = env
+                else:
+                    logging.warn("Incompatible env file found. Not using it.")
+                    self.data = empty
+            # Almost any exception can be raised during unpickling, so let's
+            # catch them all
+            except Exception, e:
+                logging.warn(e)
+                self.data = empty
+        else:
+            self.data = empty
 
 
-def get_sub_dict(dict, name):
+    def save(self, filename=None):
+        """
+        Pickle the contents of the Env object into a file.
+
+        @param filename: Filename to pickle the dict into.  If not supplied,
+                use the filename from which the dict was loaded.
+        """
+        filename = filename or self._filename
+        f = open(filename, "w")
+        cPickle.dump(self.data, f)
+        f.close()
+
+
+    def get_all_vms(self):
+        """
+        Return a list of all VM objects in this Env object.
+        """
+        return [o for o in self.values() if is_vm(o)]
+
+
+    def get_vm(self, name):
+        """
+        Return a VM object by its name.
+
+        @param name: VM name.
+        """
+        return self.get("vm__%s" % name)
+
+
+    def register_vm(self, name, vm):
+        """
+        Register a VM in this Env object.
+
+        @param name: VM name.
+        @param vm: VM object.
+        """
+        self["vm__%s" % name] = vm
+
+
+    def unregister_vm(self, name):
+        """
+        Remove a given VM.
+
+        @param name: VM name.
+        """
+        del self["vm__%s" % name]
+
+
+    def register_installer(self, installer):
+        """
+        Register a installer that was just run
+
+        The installer will be available for other tests, so that
+        information about the installed KVM modules and qemu-kvm can be used by
+        them.
+        """
+        self['last_installer'] = installer
+
+
+    def previous_installer(self):
+        """
+        Return the last installer that was registered
+        """
+        return self.get('last_installer')
+
+
+class Params(UserDict.IterableUserDict):
     """
-    Return a "sub-dict" corresponding to a specific object.
-
-    Operate on a copy of dict: for each key that ends with the suffix
-    "_" + name, strip the suffix from the key, and set the value of
-    the stripped key to that of the key. Return the resulting dict.
-
-    @param name: Suffix of the key we want to set the value.
+    A dict-like object passed to every test.
     """
-    suffix = "_" + name
-    new_dict = dict.copy()
-    for key in dict.keys():
-        if key.endswith(suffix):
-            new_key = key.split(suffix)[0]
-            new_dict[new_key] = dict[key]
-    return new_dict
+    def objects(self, key):
+        """
+        Return the names of objects defined using a given key.
+
+        @param key: The name of the key whose value lists the objects
+                (e.g. 'nics').
+        """
+        return self.get(key, "").split()
 
 
-def get_sub_dict_names(dict, keyword):
-    """
-    Return a list of "sub-dict" names that may be extracted with get_sub_dict.
+    def object_params(self, obj_name):
+        """
+        Return a dict-like object containing the parameters of an individual
+        object.
 
-    This function may be modified to change the behavior of all functions that
-    deal with multiple objects defined in dicts (e.g. VMs, images, NICs).
+        This method behaves as follows: the suffix '_' + obj_name is removed
+        from all key names that have it.  Other key names are left unchanged.
+        The values of keys with the suffix overwrite the values of their
+        suffixless versions.
 
-    @param keyword: A key in dict (e.g. "vms", "images", "nics").
-    """
-    names = dict.get(keyword)
-    if names:
-        return names.split()
-    else:
-        return []
+        @param obj_name: The name of the object (objects are listed by the
+                objects() method).
+        """
+        suffix = "_" + obj_name
+        new_dict = self.copy()
+        for key in self:
+            if key.endswith(suffix):
+                new_key = key.split(suffix)[0]
+                new_dict[new_key] = self[key]
+        return new_dict
 
 
 # Functions related to MAC/IP addresses
@@ -240,60 +318,6 @@
     return bool(regex.search(o))
 
 
-# Functions for working with the environment (a dict-like object)
-
-def is_vm(obj):
-    """
-    Tests whether a given object is a VM object.
-
-    @param obj: Python object (pretty much everything on python).
-    """
-    return obj.__class__.__name__ == "VM"
-
-
-def env_get_all_vms(env):
-    """
-    Return a list of all VM objects on a given environment.
-
-    @param env: Dictionary with environment items.
-    """
-    vms = []
-    for obj in env.values():
-        if is_vm(obj):
-            vms.append(obj)
-    return vms
-
-
-def env_get_vm(env, name):
-    """
-    Return a VM object by its name.
-
-    @param name: VM name.
-    """
-    return env.get("vm__%s" % name)
-
-
-def env_register_vm(env, name, vm):
-    """
-    Register a given VM in a given env.
-
-    @param env: Environment where we will register the VM.
-    @param name: VM name.
-    @param vm: VM object.
-    """
-    env["vm__%s" % name] = vm
-
-
-def env_unregister_vm(env, name):
-    """
-    Remove a given VM from a given env.
-
-    @param env: Environment where we will un-register the VM.
-    @param name: VM name.
-    """
-    del env["vm__%s" % name]
-
-
 # Utility functions for dealing with external processes
 
 def find_command(cmd):
@@ -421,7 +445,7 @@
     os.chdir(source_dir)
     has_qemu_dir = os.path.isdir('qemu')
     has_kvm_dir = os.path.isdir('kvm')
-    if has_qemu_dir and not has_kvm_dir:
+    if has_qemu_dir:
         logging.debug("qemu directory detected, source dir layout 1")
         return 1
     if has_kvm_dir and not has_qemu_dir:
@@ -442,7 +466,7 @@
 
     @brief: Log into a remote host (guest) using SSH or Telnet.
 
-    @param session: A kvm_expect or kvm_shell_session instance to operate on
+    @param session: An Expect or ShellSession instance to operate on
     @param username: The username to send in reply to a login prompt
     @param password: The password to send in reply to a password prompt
     @param prompt: The shell prompt that indicates a successful login
@@ -456,48 +480,52 @@
     login_prompt_count = 0
 
     while True:
-        (match, text) = session.read_until_last_line_matches(
+        try:
+            match, text = session.read_until_last_line_matches(
                 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$",
                  r"[Cc]onnection.*closed", r"[Cc]onnection.*refused",
                  r"[Pp]lease wait", prompt],
-                 timeout=timeout, internal_timeout=0.5)
-        if match == 0:  # "Are you sure you want to continue connecting"
-            logging.debug("Got 'Are you sure...'; sending 'yes'")
-            session.sendline("yes")
-            continue
-        elif match == 1:  # "password:"
-            if password_prompt_count == 0:
-                logging.debug("Got password prompt; sending '%s'" % password)
-                session.sendline(password)
-                password_prompt_count += 1
+                timeout=timeout, internal_timeout=0.5)
+            if match == 0:  # "Are you sure you want to continue connecting"
+                logging.debug("Got 'Are you sure...'; sending 'yes'")
+                session.sendline("yes")
                 continue
-            else:
-                logging.debug("Got password prompt again")
+            elif match == 1:  # "password:"
+                if password_prompt_count == 0:
+                    logging.debug("Got password prompt; sending '%s'" % password)
+                    session.sendline(password)
+                    password_prompt_count += 1
+                    continue
+                else:
+                    logging.debug("Got password prompt again")
+                    return False
+            elif match == 2:  # "login:"
+                if login_prompt_count == 0:
+                    logging.debug("Got username prompt; sending '%s'" % username)
+                    session.sendline(username)
+                    login_prompt_count += 1
+                    continue
+                else:
+                    logging.debug("Got username prompt again")
+                    return False
+            elif match == 3:  # "Connection closed"
+                logging.debug("Got 'Connection closed'")
                 return False
-        elif match == 2:  # "login:"
-            if login_prompt_count == 0:
-                logging.debug("Got username prompt; sending '%s'" % username)
-                session.sendline(username)
-                login_prompt_count += 1
+            elif match == 4:  # "Connection refused"
+                logging.debug("Got 'Connection refused'")
+                return False
+            elif match == 5:  # "Please wait"
+                logging.debug("Got 'Please wait'")
+                timeout = 30
                 continue
-            else:
-                logging.debug("Got username prompt again")
-                return False
-        elif match == 3:  # "Connection closed"
-            logging.debug("Got 'Connection closed'")
+            elif match == 6:  # prompt
+                logging.debug("Got shell prompt -- logged in")
+                return True
+        except kvm_subprocess.ExpectTimeoutError, e:
+            logging.debug("Timeout elapsed (output so far: %r)" % e.output)
             return False
-        elif match == 4:  # "Connection refused"
-            logging.debug("Got 'Connection refused'")
-            return False
-        elif match == 5:  # "Please wait"
-            logging.debug("Got 'Please wait'")
-            timeout = 30
-            continue
-        elif match == 6:  # prompt
-            logging.debug("Got shell prompt -- logged in")
-            return session
-        else:  # match == None
-            logging.debug("Timeout elapsed or process terminated")
+        except kvm_subprocess.ExpectProcessTerminatedError, e:
+            logging.debug("Process terminated (output so far: %r)" % e.output)
             return False
 
 
@@ -510,7 +538,7 @@
 
     @brief: Transfer files using SCP, given a command line.
 
-    @param session: A kvm_expect or kvm_shell_session instance to operate on
+    @param session: An Expect or ShellSession instance to operate on
     @param password: The password to send in reply to a password prompt.
     @param transfer_timeout: The time duration (in seconds) to wait for the
             transfer to complete.
@@ -524,34 +552,33 @@
     timeout = login_timeout
 
     while True:
-        (match, text) = session.read_until_last_line_matches(
+        try:
+            match, text = session.read_until_last_line_matches(
                 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
                 timeout=timeout, internal_timeout=0.5)
-        if match == 0:  # "Are you sure you want to continue connecting"
-            logging.debug("Got 'Are you sure...'; sending 'yes'")
-            session.sendline("yes")
-            continue
-        elif match == 1:  # "password:"
-            if password_prompt_count == 0:
-                logging.debug("Got password prompt; sending '%s'" % password)
-                session.sendline(password)
-                password_prompt_count += 1
-                timeout = transfer_timeout
+            if match == 0:  # "Are you sure you want to continue connecting"
+                logging.debug("Got 'Are you sure...'; sending 'yes'")
+                session.sendline("yes")
                 continue
-            else:
-                logging.debug("Got password prompt again")
+            elif match == 1:  # "password:"
+                if password_prompt_count == 0:
+                    logging.debug("Got password prompt; sending '%s'" % password)
+                    session.sendline(password)
+                    password_prompt_count += 1
+                    timeout = transfer_timeout
+                    continue
+                else:
+                    logging.debug("Got password prompt again")
+                    return False
+            elif match == 2:  # "lost connection"
+                logging.debug("Got 'lost connection'")
                 return False
-        elif match == 2:  # "lost connection"
-            logging.debug("Got 'lost connection'")
+        except kvm_subprocess.ExpectTimeoutError, e:
+            logging.debug("Timeout expired")
             return False
-        else:  # match == None
-            if session.is_alive():
-                logging.debug("Timeout expired")
-                return False
-            else:
-                status = session.get_status()
-                logging.debug("SCP process terminated with status %s", status)
-                return status == 0
+        except kvm_subprocess.ExpectProcessTerminatedError, e:
+            logging.debug("SCP process terminated with status %s", e.status)
+            return e.status == 0
 
 
 def remote_login(client, host, port, username, password, prompt, linesep="\n",
@@ -572,7 +599,7 @@
             each step of the login procedure (i.e. the "Are you sure" prompt
             or the password prompt)
 
-    @return: kvm_shell_session object on success and None on failure.
+    @return: ShellSession object on success and None on failure.
     """
     if client == "ssh":
         cmd = ("ssh -o UserKnownHostsFile=/dev/null "
@@ -587,8 +614,7 @@
         return
 
     logging.debug("Trying to login with command '%s'" % cmd)
-    session = kvm_subprocess.kvm_shell_session(cmd, linesep=linesep,
-                                               prompt=prompt)
+    session = kvm_subprocess.ShellSession(cmd, linesep=linesep, prompt=prompt)
     if _remote_login(session, username, password, prompt, timeout):
         if log_filename:
             session.set_output_func(log_line)
@@ -627,19 +653,83 @@
         output_func = None
         output_params = ()
 
-    session = kvm_subprocess.kvm_expect(command,
-                                        output_func=output_func,
-                                        output_params=output_params)
+    session = kvm_subprocess.Expect(command,
+                                    output_func=output_func,
+                                    output_params=output_params)
     try:
         return _remote_scp(session, password, transfer_timeout, login_timeout)
     finally:
         session.close()
 
 
+def copy_files_to(address, client, username, password, port, local_path,
+                  remote_path, log_filename=None, timeout=600):
+    """
+    Decide the transfer cleint and copy file to a remote host (guest).
+
+    @param client: Type of transfer client
+    @param username: Username (if required)
+    @param password: Password (if requried)
+    @param local_path: Path on the local machine where we are copying from
+    @param remote_path: Path on the remote machine where we are copying to
+    @param address: Address of remote host(guest)
+    @param log_filename: If specified, log all output to this file
+    @param timeout: The time duration (in seconds) to wait for the transfer to
+    complete.
+
+    @return: True on success and False on failure.
+    """
+
+    if not address or not port:
+        logging.debug("IP address or port unavailable")
+        return None
+
+    if client == "scp":
+        return scp_to_remote(address, port, username, password, local_path,
+                             remote_path, log_filename, timeout)
+    elif client == "rss":
+        c = rss_file_transfer.FileUploadClient(address, port)
+        c.upload(local_path, remote_path, timeout)
+        c.close()
+        return True
+
+
+def copy_files_from(address, client, username, password, port, local_path,
+                  remote_path, log_filename=None, timeout=600):
+    """
+    Decide the transfer cleint and copy file from a remote host (guest).
+
+    @param client: Type of transfer client
+    @param username: Username (if required)
+    @param password: Password (if requried)
+    @param local_path: Path on the local machine where we are copying from
+    @param remote_path: Path on the remote machine where we are copying to
+    @param address: Address of remote host(guest)
+    @param log_filename: If specified, log all output to this file
+    @param timeout: The time duration (in seconds) to wait for the transfer to
+    complete.
+
+    @return: True on success and False on failure.
+    """
+
+    if not address or not port:
+        logging.debug("IP address or port unavailable")
+        return None
+
+    if client == "scp":
+        return scp_from_remote(address, port, username, password, remote_path,
+                             local_path, log_filename, timeout)
+    elif client == "rss":
+        c = rss_file_transfer.FileDownloadClient(address, port)
+        c.download(remote_path, local_path, timeout)
+        c.close()
+        return True
+
+
 def scp_to_remote(host, port, username, password, local_path, remote_path,
                   log_filename=None, timeout=600):
     """
-    Copy files to a remote host (guest).
+    Copy files to a remote host (guest) through scp.
 
     @param host: Hostname or IP address
     @param username: Username (if required)
@@ -978,6 +1068,86 @@
     return re.sub(":", " ", commands.getoutput(cmd))
 
 
+class Thread(threading.Thread):
+    """
+    Run a function in a background thread.
+    """
+    def __init__(self, target, args=(), kwargs={}):
+        """
+        Initialize the instance.
+
+        @param target: Function to run in the thread.
+        @param args: Arguments to pass to target.
+        @param kwargs: Keyword arguments to pass to target.
+        """
+        threading.Thread.__init__(self)
+        self._target = target
+        self._args = args
+        self._kwargs = kwargs
+
+
+    def run(self):
+        """
+        Run target (passed to the constructor).  No point in calling this
+        function directly.  Call start() to make this function run in a new
+        thread.
+        """
+        self._e = None
+        self._retval = None
+        try:
+            try:
+                self._retval = self._target(*self._args, **self._kwargs)
+            except:
+                self._e = sys.exc_info()
+                raise
+        finally:
+            # Avoid circular references (start() may be called only once so
+            # it's OK to delete these)
+            del self._target, self._args, self._kwargs
+
+
+    def join(self, timeout=None):
+        """
+        Join the thread.  If target raised an exception, re-raise it.
+        Otherwise, return the value returned by target.
+
+        @param timeout: Timeout value to pass to threading.Thread.join().
+        """
+        threading.Thread.join(self, timeout)
+        try:
+            if self._e:
+                raise self._e[0], self._e[1], self._e[2]
+            else:
+                return self._retval
+        finally:
+            # Avoid circular references (join() may be called multiple times
+            # so we can't delete these)
+            self._e = None
+            self._retval = None
+
+
+def parallel(targets):
+    """
+    Run multiple functions in parallel.
+
+    @param targets: A sequence of tuples or functions.  If it's a sequence of
+            tuples, each tuple will be interpreted as (target, args, kwargs) or
+            (target, args) or (target,) depending on its length.  If it's a
+            sequence of functions, the functions will be called without
+            arguments.
+    @return: A list of the values returned by the functions called.
+    """
+    threads = []
+    for target in targets:
+        if isinstance(target, tuple) or isinstance(target, list):
+            t = Thread(*target)
+        else:
+            t = Thread(target)
+        threads.append(t)
+        t.start()
+    return [t.join() for t in threads]
+
+
 class KvmLoggingConfig(logging_config.LoggingConfig):
     """
     Used with the sole purpose of providing convenient logging setup
@@ -1347,7 +1517,7 @@
                              "provide an appropriate tag or build name.")
 
         if not build:
-            builds = self.session.listTagged(tag, latest=True,
+            builds = self.session.listTagged(tag, latest=True, inherit=True,
                                              package=src_package)
             if not builds:
                 raise ValueError("Tag %s has no builds of %s" % (tag,
@@ -1390,3 +1560,58 @@
                 rpm_paths.append(r)
 
         return rpm_paths
+
+
+def umount(src, mount_point, type):
+    """
+    Umount the src mounted in mount_point.
+
+    @src: mount source
+    @mount_point: mount point
+    @type: file system type
+    """
+
+    mount_string = "%s %s %s" % (src, mount_point, type)
+    if mount_string in file("/etc/mtab").read():
+        umount_cmd = "umount %s" % mount_point
+        try:
+            utils.system(umount_cmd)
+            return True
+        except error.CmdError:
+            return False
+    else:
+        logging.debug("%s is not mounted under %s" % (src, mount_point))
+        return True
+
+
+def mount(src, mount_point, type, perm="rw"):
+    """
+    Mount the src into mount_point of the host.
+
+    @src: mount source
+    @mount_point: mount point
+    @type: file system type
+    @perm: mount premission
+    """
+    umount(src, mount_point, type)
+    mount_string = "%s %s %s %s" % (src, mount_point, type, perm)
+
+    if mount_string in file("/etc/mtab").read():
+        logging.debug("%s is already mounted in %s with %s" %
+                      (src, mount_point, perm))
+        return True
+
+    mount_cmd = "mount -t %s %s %s -o %s" % (type, src, mount_point, perm)
+    try:
+        utils.system(mount_cmd)
+    except error.CmdError:
+        return False
+
+    logging.debug("Verify the mount through /etc/mtab")
+    if mount_string in file("/etc/mtab").read():
+        logging.debug("%s is successfully mounted" % src)
+        return True
+    else:
+        logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s" %
+                      file("/etc/mtab").read())
+        return False
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index a860437..f6f1684 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -24,6 +24,8 @@
     """
     image_name = params.get("image_name", "image")
     image_format = params.get("image_format", "qcow2")
+    if params.get("image_raw_device") == "yes":
+        return image_name
     image_filename = "%s.%s" % (image_name, image_format)
     image_filename = kvm_utils.get_path(root_dir, image_filename)
     return image_filename
@@ -95,7 +97,7 @@
     This class handles all basic VM operations.
     """
 
-    def __init__(self, name, params, root_dir, address_cache):
+    def __init__(self, name, params, root_dir, address_cache, state=None):
         """
         Initialize the object and set a few attributes.
 
@@ -104,30 +106,35 @@
                 (see method make_qemu_command for a full description)
         @param root_dir: Base directory for relative filenames
         @param address_cache: A dict that maps MAC addresses to IP addresses
+        @param state: If provided, use this as self.__dict__
         """
-        self.process = None
-        self.serial_console = None
-        self.redirs = {}
-        self.vnc_port = 5900
-        self.monitors = []
-        self.pci_assignable = None
-        self.netdev_id = []
-        self.uuid = None
+        if state:
+            self.__dict__ = state
+        else:
+            self.process = None
+            self.serial_console = None
+            self.redirs = {}
+            self.vnc_port = 5900
+            self.monitors = []
+            self.pci_assignable = None
+            self.netdev_id = []
+            self.uuid = None
+
+            # Find a unique identifier for this VM
+            while True:
+                self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
+                                 kvm_utils.generate_random_string(4))
+                if not glob.glob("/tmp/*%s" % self.instance):
+                    break
 
         self.name = name
         self.params = params
         self.root_dir = root_dir
         self.address_cache = address_cache
 
-        # Find a unique identifier for this VM
-        while True:
-            self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
-                             kvm_utils.generate_random_string(4))
-            if not glob.glob("/tmp/*%s" % self.instance):
-                break
 
-
-    def clone(self, name=None, params=None, root_dir=None, address_cache=None):
+    def clone(self, name=None, params=None, root_dir=None, address_cache=None,
+              copy_state=False):
         """
         Return a clone of the VM object with optionally modified parameters.
         The clone is initially not alive and needs to be started using create().
@@ -138,6 +145,8 @@
         @param params: Optional new VM creation parameters
         @param root_dir: Optional new base directory for relative filenames
         @param address_cache: A dict that maps MAC addresses to IP addresses
+        @param copy_state: If True, copy the original VM's state to the clone.
+                Mainly useful for make_qemu_command().
         """
         if name is None:
             name = self.name
@@ -147,7 +156,11 @@
             root_dir = self.root_dir
         if address_cache is None:
             address_cache = self.address_cache
-        return VM(name, params, root_dir, address_cache)
+        if copy_state:
+            state = self.__dict__.copy()
+        else:
+            state = None
+        return VM(name, params, root_dir, address_cache, state)
 
 
     def make_qemu_command(self, name=None, params=None, root_dir=None):
@@ -236,25 +249,22 @@
 
         def add_nic(help, vlan, model=None, mac=None, netdev_id=None,
                     nic_extra_params=None):
+            if has_option(help, "netdev"):
+                netdev_vlan_str = ",netdev=%s" % netdev_id
+            else:
+                netdev_vlan_str = ",vlan=%d" % vlan
             if has_option(help, "device"):
-                if model == "virtio":
-                    model="virtio-net-pci"
                 if not model:
-                    model= "rtl8139"
-                cmd = " -device %s" % model
+                    model = "rtl8139"
+                elif model == "virtio":
+                    model = "virtio-net-pci"
+                cmd = " -device %s" % model + netdev_vlan_str
                 if mac:
-                    cmd += ",mac=%s" % mac
-                if has_option(help, "netdev"):
-                    cmd += ",netdev=%s" % netdev_id
-                else:
-                    cmd += "vlan=%d,"  % vlan
+                    cmd += ",mac='%s'" % mac
                 if nic_extra_params:
                     cmd += ",%s" % nic_extra_params
             else:
-                if has_option(help, "netdev"):
-                    cmd = " -net nic,netdev=%s" % netdev_id
-                else:
-                    cmd = " -net nic,vlan=%d" % vlan
+                cmd = " -net nic" + netdev_vlan_str
                 if model:
                     cmd += ",model=%s" % model
                 if mac:
@@ -263,11 +273,11 @@
 
         def add_net(help, vlan, mode, ifname=None, script=None,
                     downscript=None, tftp=None, bootfile=None, hostfwd=[],
-                    netdev_id=None, vhost=False):
+                    netdev_id=None, netdev_extra_params=None):
             if has_option(help, "netdev"):
                 cmd = " -netdev %s,id=%s" % (mode, netdev_id)
-                if vhost:
-                    cmd +=",vhost=on"
+                if netdev_extra_params:
+                    cmd += ",%s" % netdev_extra_params
             else:
                 cmd = " -net %s,vlan=%d" % (mode, vlan)
             if mode == "tap":
@@ -351,6 +361,9 @@
         if params is None: params = self.params
         if root_dir is None: root_dir = self.root_dir
 
+        # Clone this VM using the new params
+        vm = self.clone(name, params, root_dir, copy_state=True)
+
         qemu_binary = kvm_utils.get_path(root_dir, params.get("qemu_binary",
                                                               "qemu"))
         # Get the output of 'qemu -help' (log a message in case this call never
@@ -368,19 +381,19 @@
         # Add the VM's name
         qemu_cmd += add_name(help, name)
         # Add monitors
-        for monitor_name in kvm_utils.get_sub_dict_names(params, "monitors"):
-            monitor_params = kvm_utils.get_sub_dict(params, monitor_name)
-            monitor_filename = self.get_monitor_filename(monitor_name)
+        for monitor_name in params.objects("monitors"):
+            monitor_params = params.object_params(monitor_name)
+            monitor_filename = vm.get_monitor_filename(monitor_name)
             if monitor_params.get("monitor_type") == "qmp":
                 qemu_cmd += add_qmp_monitor(help, monitor_filename)
             else:
                 qemu_cmd += add_human_monitor(help, monitor_filename)
 
         # Add serial console redirection
-        qemu_cmd += add_serial(help, self.get_serial_console_filename())
+        qemu_cmd += add_serial(help, vm.get_serial_console_filename())
 
-        for image_name in kvm_utils.get_sub_dict_names(params, "images"):
-            image_params = kvm_utils.get_sub_dict(params, image_name)
+        for image_name in params.objects("images"):
+            image_params = params.object_params(image_name)
             if image_params.get("boot_drive") == "no":
                 continue
             qemu_cmd += add_drive(help,
@@ -394,20 +407,23 @@
                                   image_params.get("image_boot") == "yes")
 
         redirs = []
-        for redir_name in kvm_utils.get_sub_dict_names(params, "redirs"):
-            redir_params = kvm_utils.get_sub_dict(params, redir_name)
+        for redir_name in params.objects("redirs"):
+            redir_params = params.object_params(redir_name)
             guest_port = int(redir_params.get("guest_port"))
-            host_port = self.redirs.get(guest_port)
+            host_port = vm.redirs.get(guest_port)
             redirs += [(host_port, guest_port)]
 
         vlan = 0
-        for nic_name in kvm_utils.get_sub_dict_names(params, "nics"):
-            nic_params = kvm_utils.get_sub_dict(params, nic_name)
+        for nic_name in params.objects("nics"):
+            nic_params = params.object_params(nic_name)
+            try:
+                netdev_id = vm.netdev_id[vlan]
+            except IndexError:
+                netdev_id = None
             # Handle the '-net nic' part
-            mac = self.get_mac_address(vlan)
+            mac = vm.get_mac_address(vlan)
             qemu_cmd += add_nic(help, vlan, nic_params.get("nic_model"), mac,
-                                self.netdev_id[vlan],
-                                nic_params.get("nic_extra_params"))
+                                netdev_id, nic_params.get("nic_extra_params"))
             # Handle the '-net tap' or '-net user' part
             script = nic_params.get("nic_script")
             downscript = nic_params.get("nic_downscript")
@@ -419,11 +435,10 @@
             if tftp:
                 tftp = kvm_utils.get_path(root_dir, tftp)
             qemu_cmd += add_net(help, vlan, nic_params.get("nic_mode", "user"),
-                                self.get_ifname(vlan),
+                                vm.get_ifname(vlan),
                                 script, downscript, tftp,
-                                nic_params.get("bootp"), redirs,
-                                self.netdev_id[vlan],
-                                nic_params.get("vhost")=="yes")
+                                nic_params.get("bootp"), redirs, netdev_id,
+                                nic_params.get("netdev_extra_params"))
             # Proceed to next NIC
             vlan += 1
 
@@ -435,9 +450,8 @@
         if smp:
             qemu_cmd += add_smp(help, smp)
 
-        cdroms = kvm_utils.get_sub_dict_names(params, "cdroms")
-        for cdrom in cdroms:
-            cdrom_params = kvm_utils.get_sub_dict(params, cdrom)
+        for cdrom in params.objects("cdroms"):
+            cdrom_params = params.object_params(cdrom)
             iso = cdrom_params.get("cdrom")
             if iso:
                 qemu_cmd += add_cdrom(help, kvm_utils.get_path(root_dir, iso),
@@ -477,27 +491,27 @@
             qemu_cmd += add_tcp_redir(help, host_port, guest_port)
 
         if params.get("display") == "vnc":
-            qemu_cmd += add_vnc(help, self.vnc_port)
+            qemu_cmd += add_vnc(help, vm.vnc_port)
         elif params.get("display") == "sdl":
             qemu_cmd += add_sdl(help)
         elif params.get("display") == "nographic":
             qemu_cmd += add_nographic(help)
 
         if params.get("uuid") == "random":
-            qemu_cmd += add_uuid(help, self.uuid)
+            qemu_cmd += add_uuid(help, vm.uuid)
         elif params.get("uuid"):
             qemu_cmd += add_uuid(help, params.get("uuid"))
 
         if params.get("testdev") == "yes":
-            qemu_cmd += add_testdev(help, self.get_testlog_filename())
+            qemu_cmd += add_testdev(help, vm.get_testlog_filename())
 
         if params.get("disable_hpet") == "yes":
             qemu_cmd += add_no_hpet(help)
 
         # If the PCI assignment step went OK, add each one of the PCI assigned
         # devices to the qemu command line.
-        if self.pci_assignable:
-            for pci_id in self.pa_pci_ids:
+        if vm.pci_assignable:
+            for pci_id in vm.pa_pci_ids:
                 qemu_cmd += add_pcidevice(help, pci_id)
 
         extra_params = params.get("extra_params")
@@ -508,7 +522,7 @@
 
 
     def create(self, name=None, params=None, root_dir=None, timeout=5.0,
-               migration_mode=None, migration_exec_cmd=None, mac_source=None):
+               migration_mode=None, mac_source=None):
         """
         Start the VM by running a qemu command.
         All parameters are optional. If name, params or root_dir are not
@@ -536,38 +550,40 @@
         params = self.params
         root_dir = self.root_dir
 
-        # Verify the md5sum of the ISO image
-        iso = params.get("cdrom")
-        if iso:
-            iso = kvm_utils.get_path(root_dir, iso)
-            if not os.path.exists(iso):
-                logging.error("ISO file not found: %s" % iso)
-                return False
-            compare = False
-            if params.get("md5sum_1m"):
-                logging.debug("Comparing expected MD5 sum with MD5 sum of "
-                              "first MB of ISO file...")
-                actual_hash = utils.hash_file(iso, 1048576, method="md5")
-                expected_hash = params.get("md5sum_1m")
-                compare = True
-            elif params.get("md5sum"):
-                logging.debug("Comparing expected MD5 sum with MD5 sum of ISO "
-                              "file...")
-                actual_hash = utils.hash_file(iso, method="md5")
-                expected_hash = params.get("md5sum")
-                compare = True
-            elif params.get("sha1sum"):
-                logging.debug("Comparing expected SHA1 sum with SHA1 sum of "
-                              "ISO file...")
-                actual_hash = utils.hash_file(iso, method="sha1")
-                expected_hash = params.get("sha1sum")
-                compare = True
-            if compare:
-                if actual_hash == expected_hash:
-                    logging.debug("Hashes match")
-                else:
-                    logging.error("Actual hash differs from expected one")
+        # Verify the md5sum of the ISO images
+        for cdrom in params.objects("cdroms"):
+            cdrom_params = params.object_params(cdrom)
+            iso = cdrom_params.get("cdrom")
+            if iso:
+                iso = kvm_utils.get_path(root_dir, iso)
+                if not os.path.exists(iso):
+                    logging.error("ISO file not found: %s" % iso)
                     return False
+                compare = False
+                if cdrom_params.get("md5sum_1m"):
+                    logging.debug("Comparing expected MD5 sum with MD5 sum of "
+                                  "first MB of ISO file...")
+                    actual_hash = utils.hash_file(iso, 1048576, method="md5")
+                    expected_hash = cdrom_params.get("md5sum_1m")
+                    compare = True
+                elif cdrom_params.get("md5sum"):
+                    logging.debug("Comparing expected MD5 sum with MD5 sum of "
+                                  "ISO file...")
+                    actual_hash = utils.hash_file(iso, method="md5")
+                    expected_hash = cdrom_params.get("md5sum")
+                    compare = True
+                elif cdrom_params.get("sha1sum"):
+                    logging.debug("Comparing expected SHA1 sum with SHA1 sum "
+                                  "of ISO file...")
+                    actual_hash = utils.hash_file(iso, method="sha1")
+                    expected_hash = cdrom_params.get("sha1sum")
+                    compare = True
+                if compare:
+                    if actual_hash == expected_hash:
+                        logging.debug("Hashes match")
+                    else:
+                        logging.error("Actual hash differs from expected one")
+                        return False
 
         # Make sure the following code is not executed by more than one thread
         # at the same time
@@ -576,15 +592,17 @@
 
         try:
             # Handle port redirections
-            redir_names = kvm_utils.get_sub_dict_names(params, "redirs")
+            redir_names = params.objects("redirs")
             host_ports = kvm_utils.find_free_ports(5000, 6000, len(redir_names))
             self.redirs = {}
             for i in range(len(redir_names)):
-                redir_params = kvm_utils.get_sub_dict(params, redir_names[i])
+                redir_params = params.object_params(redir_names[i])
                 guest_port = int(redir_params.get("guest_port"))
                 self.redirs[guest_port] = host_ports[i]
 
-            for nic in kvm_utils.get_sub_dict_names(params, "nics"):
+            # Generate netdev IDs for all NICs
+            self.netdev_id = []
+            for nic in params.objects("nics"):
                 self.netdev_id.append(kvm_utils.generate_random_id())
 
             # Find available VNC port, if needed
@@ -598,13 +616,19 @@
                 f.close()
 
             # Generate or copy MAC addresses for all NICs
-            num_nics = len(kvm_utils.get_sub_dict_names(params, "nics"))
+            num_nics = len(params.objects("nics"))
             for vlan in range(num_nics):
-                mac = mac_source and mac_source.get_mac_address(vlan)
-                if mac:
+                nic_name = params.objects("nics")[vlan]
+                nic_params = params.object_params(nic_name)
+                if nic_params.get("nic_mac", None):
+                    mac = nic_params.get("nic_mac")
                     kvm_utils.set_mac_address(self.instance, vlan, mac)
                 else:
-                    kvm_utils.generate_mac_address(self.instance, vlan)
+                    mac = mac_source and mac_source.get_mac_address(vlan)
+                    if mac:
+                        kvm_utils.set_mac_address(self.instance, vlan, mac)
+                    else:
+                        kvm_utils.generate_mac_address(self.instance, vlan)
 
             # Assign a PCI assignable device
             self.pci_assignable = None
@@ -660,7 +684,9 @@
                 self.migration_file = "/tmp/migration-unix-%s" % self.instance
                 qemu_command += " -incoming unix:%s" % self.migration_file
             elif migration_mode == "exec":
-                qemu_command += ' -incoming "exec:%s"' % migration_exec_cmd
+                self.migration_port = kvm_utils.find_free_port(5200, 6000)
+                qemu_command += (' -incoming "exec:nc -l %s"' %
+                                 self.migration_port)
 
             logging.debug("Running qemu command:\n%s", qemu_command)
             self.process = kvm_subprocess.run_bg(qemu_command, None,
@@ -678,9 +704,8 @@
 
             # Establish monitor connections
             self.monitors = []
-            for monitor_name in kvm_utils.get_sub_dict_names(params,
-                                                             "monitors"):
-                monitor_params = kvm_utils.get_sub_dict(params, monitor_name)
+            for monitor_name in params.objects("monitors"):
+                monitor_params = params.object_params(monitor_name)
                 # Wait for monitor connection to succeed
                 end_time = time.time() + timeout
                 while time.time() < end_time:
@@ -733,7 +758,7 @@
 
             # Establish a session with the serial console -- requires a version
             # of netcat that supports -U
-            self.serial_console = kvm_subprocess.kvm_shell_session(
+            self.serial_console = kvm_subprocess.ShellSession(
                 "nc -U %s" % self.get_serial_console_filename(),
                 auto_close=False,
                 output_func=kvm_utils.log_line,
@@ -826,7 +851,7 @@
                     os.unlink(self.migration_file)
                 except OSError:
                     pass
-            num_nics = len(kvm_utils.get_sub_dict_names(self.params, "nics"))
+            num_nics = len(self.params.objects("nics"))
             for vlan in range(num_nics):
                 self.free_mac_address(vlan)
 
@@ -885,7 +910,7 @@
         params).
         """
         return [self.get_monitor_filename(m) for m in
-                kvm_utils.get_sub_dict_names(self.params, "monitors")]
+                self.params.objects("monitors")]
 
 
     def get_serial_console_filename(self):
@@ -911,9 +936,9 @@
 
         @param index: Index of the NIC whose address is requested.
         """
-        nics = kvm_utils.get_sub_dict_names(self.params, "nics")
+        nics = self.params.objects("nics")
         nic_name = nics[index]
-        nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
+        nic_params = self.params.object_params(nic_name)
         if nic_params.get("nic_mode") == "tap":
             mac = self.get_mac_address(index)
             if not mac:
@@ -946,8 +971,8 @@
         @return: If port redirection is used, return the host port redirected
                 to guest port port. Otherwise return port.
         """
-        nic_name = kvm_utils.get_sub_dict_names(self.params, "nics")[nic_index]
-        nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
+        nic_name = self.params.objects("nics")[nic_index]
+        nic_params = self.params.object_params(nic_name)
         if nic_params.get("nic_mode") == "tap":
             return port
         else:
@@ -963,9 +988,9 @@
 
         @param nic_index: Index of the NIC
         """
-        nics = kvm_utils.get_sub_dict_names(self.params, "nics")
+        nics = self.params.objects("nics")
         nic_name = nics[nic_index]
-        nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
+        nic_params = self.params.object_params(nic_name)
         if nic_params.get("nic_ifname"):
             return nic_params.get("nic_ifname")
         else:
@@ -1040,7 +1065,7 @@
         @param nic_index: The index of the NIC to connect to.
         @param timeout: Time (seconds) before giving up logging into the
                 guest.
-        @return: kvm_spawn object on success and None on failure.
+        @return: ShellSession object on success and None on failure.
         """
         username = self.params.get("username", "")
         password = self.params.get("password", "")
@@ -1068,7 +1093,7 @@
 
     def copy_files_to(self, local_path, remote_path, nic_index=0, timeout=600):
         """
-        Transfer files to the guest.
+        Transfer files to the remote host(guest).
 
         @param local_path: Host path
         @param remote_path: Guest path
@@ -1082,21 +1107,12 @@
         address = self.get_address(nic_index)
         port = self.get_port(int(self.params.get("file_transfer_port")))
 
-        if not address or not port:
-            logging.debug("IP address or port unavailable")
-            return None
-
-        if client == "scp":
-            log_filename = ("scp-%s-%s.log" %
-                            (self.name, kvm_utils.generate_random_string(4)))
-            return kvm_utils.scp_to_remote(address, port, username, password,
-                                           local_path, remote_path,
-                                           log_filename, timeout)
-        elif client == "rss":
-            c = rss_file_transfer.FileUploadClient(address, port)
-            c.upload(local_path, remote_path, timeout)
-            c.close()
-            return True
+        log_filename = ("transfer-%s-to-%s-%s.log" %
+                        (self.name, address,
+                        kvm_utils.generate_random_string(4)))
+        return kvm_utils.copy_files_to(address, client, username, password,
+                                       port, local_path, remote_path,
+                                       log_filename, timeout)
 
 
     def copy_files_from(self, remote_path, local_path, nic_index=0, timeout=600):
@@ -1115,21 +1131,11 @@
         address = self.get_address(nic_index)
         port = self.get_port(int(self.params.get("file_transfer_port")))
 
-        if not address or not port:
-            logging.debug("IP address or port unavailable")
-            return None
-
-        if client == "scp":
-            log_filename = ("scp-%s-%s.log" %
-                            (self.name, kvm_utils.generate_random_string(4)))
-            return kvm_utils.scp_from_remote(address, port, username, password,
-                                             remote_path, local_path,
-                                             log_filename, timeout)
-        elif client == "rss":
-            c = rss_file_transfer.FileDownloadClient(address, port)
-            c.download(remote_path, local_path, timeout)
-            c.close()
-            return True
+        log_filename = ("transfer-%s-from-%s-%s.log" %
+                        (self.name, address,
+                        kvm_utils.generate_random_string(4)))
+        return kvm_utils.copy_files_from(address, client, username, password,
+                        port, local_path, remote_path, log_filename, timeout)
 
 
     def serial_login(self, timeout=10):
@@ -1139,7 +1145,7 @@
         password prompt or a shell prompt) -- fail.
 
         @param timeout: Time (seconds) before giving up logging into the guest.
-        @return: kvm_spawn object on success and None on failure.
+        @return: ShellSession object on success and None on failure.
         """
         username = self.params.get("username", "")
         password = self.params.get("password", "")
@@ -1213,11 +1219,7 @@
         if not session:
             return None
         try:
-            cmd = self.params.get("cpu_chk_cmd")
-            s, count = session.get_command_status_output(cmd)
-            if s == 0:
-                return int(count)
-            return None
+            return int(session.cmd(self.params.get("cpu_chk_cmd")))
         finally:
             session.close()
 
@@ -1235,9 +1237,7 @@
         try:
             if not cmd:
                 cmd = self.params.get("mem_chk_cmd")
-            s, mem_str = session.get_command_status_output(cmd)
-            if s != 0:
-                return None
+            mem_str = session.cmd(cmd)
             mem = re.findall("([0-9]+)", mem_str)
             mem_size = 0
             for m in mem:
@@ -1259,3 +1259,14 @@
         """
         cmd = self.params.get("mem_chk_cur_cmd")
         return self.get_memory_size(cmd)
+
+
+    def save_to_file(self, path):
+        """
+        Save the state of virtual machine to a file through migrate to
+        exec
+        """
+        # Make sure we only get one iteration
+        self.monitor.cmd("migrate_set_speed 1000g")
+        self.monitor.cmd("migrate_set_downtime 100000000")
+        self.monitor.migrate('"exec:cat>%s"' % path)
diff --git a/client/tests/kvm/migration_control.srv b/client/tests/kvm/migration_control.srv
new file mode 100644
index 0000000..16ada36
--- /dev/null
+++ b/client/tests/kvm/migration_control.srv
@@ -0,0 +1,122 @@
+AUTHOR = "Yolkfull Chow <yzhou@redhat.com>"
+TIME = "SHORT"
+NAME = "Migration across multiple hosts"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "Virtualization"
+TEST_TYPE = "Server"
+DOC = """
+Migrate KVM guest between two hosts. It parses the base config file, restricts
+it with appropriate parameters, generates the test dicts, modify the test_dicts
+so there's a distinction between the migration roles ('dest' or 'source').
+"""
+
+import sys, os, commands, glob, shutil, logging, random
+from autotest_lib.server import utils
+
+# Specify the directory of autotest before you start this test
+AUTOTEST_DIR = '/usr/local/autotest'
+
+# Specify the root directory that on client machines
+rootdir = '/tmp/kvm_autotest_root'
+
+# Make possible to import the KVM test APIs
+KVM_DIR = os.path.join(AUTOTEST_DIR, 'client/tests/kvm')
+sys.path.append(KVM_DIR)
+
+import common, kvm_config
+
+def generate_mac_address():
+    r = random.SystemRandom()
+    mac = "9a:%02x:%02x:%02x:%02x:%02x" % (r.randint(0x00, 0xff),
+                                           r.randint(0x00, 0xff),
+                                           r.randint(0x00, 0xff),
+                                           r.randint(0x00, 0xff),
+                                           r.randint(0x00, 0xff))
+    return mac
+
+
+def run(pair):
+    logging.info("KVM migration running on source host [%s] and destination "
+                 "host [%s]\n", pair[0], pair[1])
+
+    source = hosts.create_host(pair[0])
+    dest = hosts.create_host(pair[1])
+    source_at = autotest.Autotest(source)
+    dest_at = autotest.Autotest(dest)
+
+    cfg_file = os.path.join(KVM_DIR, "tests_base.cfg")
+
+    if not os.path.exists(cfg_file):
+        raise error.JobError("Config file %s was not found", cfg_file)
+
+    # Get test set (dictionary list) from the configuration file
+    cfg = kvm_config.config()
+    test_variants = """
+image_name(_.*)? ?<= /tmp/kvm_autotest_root/images/
+cdrom(_.*)? ?<= /tmp/kvm_autotest_root/
+floppy ?<= /tmp/kvm_autotest_root/
+Linux:
+    unattended_install:
+        kernel ?<= /tmp/kvm_autotest_root/
+        initrd ?<= /tmp/kvm_autotest_root/
+qemu_binary = /usr/libexec/qemu-kvm
+qemu_img_binary = /usr/bin/qemu-img
+only qcow2
+only virtio_net
+only virtio_blk
+only smp2
+only no_pci_assignable
+only smallpages
+only Fedora.13.64
+only migrate_multi_host
+nic_mode = tap
+nic_mac_nic1 = %s
+""" % (generate_mac_address())
+    cfg.fork_and_parse(cfg_file, test_variants)
+    test_dicts = cfg.get_list()
+
+    source_control_file = dest_control_file = """
+kvm_test_dir = os.path.join(os.environ['AUTODIR'],'tests/kvm')
+sys.path.append(kvm_test_dir)\n
+"""
+    for params in test_dicts:
+        params['srchost'] = source.ip
+        params['dsthost'] = dest.ip
+        params['rootdir'] = rootdir
+
+        source_params = params.copy()
+        source_params['role'] = "source"
+
+        dest_params = params.copy()
+        dest_params['role'] = "destination"
+        dest_params['migration_mode'] = "tcp"
+
+        # Report the parameters we've received
+        print "Test parameters:"
+        keys = params.keys()
+        keys.sort()
+        for key in keys:
+            logging.debug("    %s = %s", key, params[key])
+
+        source_control_file += "job.run_test('kvm', tag='%s', params=%s)" % (source_params['shortname'], source_params)
+        dest_control_file += "job.run_test('kvm', tag='%s', params=%s)" % (dest_params['shortname'], dest_params)
+
+        logging.info('Source control file:\n%s', source_control_file)
+        logging.info('Destination control file:\n%s', dest_control_file)
+        dest_command = subcommand(dest_at.run,
+                                  [dest_control_file, dest.hostname])
+
+        source_command = subcommand(source_at.run,
+                                    [source_control_file, source.hostname])
+
+        parallel([dest_command, source_command])
+
+# Grab the pairs (and failures)
+(pairs, failures) = utils.form_ntuples_from_machines(machines, 2)
+
+# Log the failures
+for failure in failures:
+    job.record("FAIL", failure[0], "kvm", failure[1])
+
+# Now run through each pair and run
+job.parallel_simple(run, pairs, log=False)
diff --git a/client/tests/kvm/scan_results.py b/client/tests/kvm/scan_results.py
index a339a85..97105fb 100755
--- a/client/tests/kvm/scan_results.py
+++ b/client/tests/kvm/scan_results.py
@@ -38,7 +38,7 @@
             test_status = parts[0].split()[1]
             # Remove "kvm." prefix
             if test_name.startswith("kvm."):
-                test_name = test_name.split("kvm.")[1]
+                test_name = test_name[4:]
             result_list.append((test_name, test_status,
                                 int(end_time - start_time), info))
 
diff --git a/client/tests/kvm/scripts/allocator.py b/client/tests/kvm/scripts/allocator.py
index 227745a..09dc004 100755
--- a/client/tests/kvm/scripts/allocator.py
+++ b/client/tests/kvm/scripts/allocator.py
@@ -12,7 +12,7 @@
 
 PAGE_SIZE = 4096 # machine page size
 
-TMPFS_OVERHEAD = 0.0022 # overhead on 1MB of write data 
+TMPFS_OVERHEAD = 0.0022 # overhead on 1MB of write data
 
 
 class MemFill(object):
@@ -34,7 +34,7 @@
 
         self.tmpdp = tempfile.mkdtemp()
         ret_code = os.system("mount -o size=%dM tmpfs %s -t tmpfs" %
-                             ((mem+math.ceil(mem*TMPFS_OVERHEAD)), 
+                             ((mem+math.ceil(mem*TMPFS_OVERHEAD)),
                              self.tmpdp))
         if ret_code != 0:
             if os.getuid() != 0:
diff --git a/client/tests/kvm/scripts/bonding_setup.py b/client/tests/kvm/scripts/bonding_setup.py
new file mode 100644
index 0000000..f2d4be9
--- /dev/null
+++ b/client/tests/kvm/scripts/bonding_setup.py
@@ -0,0 +1,37 @@
+import os, re, commands, sys
+"""This script is used to setup bonding, macaddr of bond0 should be assigned by
+argv1"""
+
+if len(sys.argv) != 2:
+    sys.exit(1)
+mac = sys.argv[1]
+eth_nums = 0
+ifconfig_output = commands.getoutput("ifconfig")
+re_eth = "eth[0-9]*"
+for ename in re.findall(re_eth, ifconfig_output):
+    eth_config_file = "/etc/sysconfig/network-scripts/ifcfg-%s" % ename
+    eth_config = """DEVICE=%s
+USERCTL=no
+ONBOOT=yes
+MASTER=bond0
+SLAVE=yes
+BOOTPROTO=none
+""" % ename
+    f = file(eth_config_file,'w')
+    f.write(eth_config)
+    f.close()
+
+bonding_config_file = "/etc/sysconfig/network-scripts/ifcfg-bond0"
+bond_config = """DEVICE=bond0
+BOOTPROTO=dhcp
+NETWORKING_IPV6=no
+ONBOOT=yes
+USERCTL=no
+MACADDR=%s
+""" % mac
+f = file(bonding_config_file, "w")
+f.write(bond_config)
+f.close()
+os.system("modprobe bonding")
+os.system("service NetworkManager stop")
+os.system("service network restart")
diff --git a/client/tests/kvm/scripts/join_mcast.py b/client/tests/kvm/scripts/join_mcast.py
old mode 100644
new mode 100755
diff --git a/client/tests/kvm/scripts/qemu-ifup-ipv6 b/client/tests/kvm/scripts/qemu-ifup-ipv6
old mode 100644
new mode 100755
diff --git a/client/tests/kvm/scripts/unattended.py b/client/tests/kvm/scripts/unattended.py
index 1029d1e..e9e4751 100755
--- a/client/tests/kvm/scripts/unattended.py
+++ b/client/tests/kvm/scripts/unattended.py
@@ -242,9 +242,8 @@
 class UnattendedInstall(object):
     """
     Creates a floppy disk image that will contain a config file for unattended
-    OS install. Optionally, sets up a PXE install server using qemu built in
-    TFTP and DHCP servers to install a particular operating system. The
-    parameters to the script are retrieved from environment variables.
+    OS install. The parameters to the script are retrieved from environment
+    variables.
     """
     def __init__(self):
         """
@@ -256,9 +255,9 @@
 
         attributes = ['kernel_args', 'finish_program', 'cdrom_cd1',
                       'unattended_file', 'medium', 'url', 'kernel', 'initrd',
-                      'nfs_server', 'nfs_dir', 'pxe_dir', 'pxe_image',
-                      'pxe_initrd', 'install_virtio', 'tftp',
-                      'floppy', 'cdrom_unattended']
+                      'nfs_server', 'nfs_dir', 'install_virtio', 'floppy',
+                      'cdrom_unattended', 'boot_path', 'extra_params']
+
         for a in attributes:
             self._setattr(a)
 
@@ -269,13 +268,6 @@
             for va in v_attributes:
                 self._setattr(va)
 
-        # Silly attribution just to calm pylint down...
-        self.tftp = self.tftp
-        if self.tftp:
-            self.tftp = os.path.join(KVM_TEST_DIR, self.tftp)
-            if not os.path.isdir(self.tftp):
-                os.makedirs(self.tftp)
-
         if self.cdrom_cd1:
             self.cdrom_cd1 = os.path.join(KVM_TEST_DIR, self.cdrom_cd1)
         self.cdrom_cd1_mount = tempfile.mkdtemp(prefix='cdrom_cd1_', dir='/tmp')
@@ -287,9 +279,7 @@
             if not os.path.isdir(os.path.dirname(self.floppy)):
                 os.makedirs(os.path.dirname(self.floppy))
 
-        self.image_path = KVM_TEST_DIR
-        self.kernel_path = os.path.join(self.image_path, self.kernel)
-        self.initrd_path = os.path.join(self.image_path, self.initrd)
+        self.image_path = os.path.dirname(self.kernel)
 
 
     def _setattr(self, key):
@@ -408,7 +398,7 @@
             boot_disk.setup_answer_file(dest_fname, answer_contents)
 
         elif self.unattended_file.endswith('.xml'):
-            if self.tftp:
+            if "autoyast" in self.extra_params:
                 # SUSE autoyast install
                 dest_fname = "autoinst.xml"
                 if self.cdrom_unattended:
@@ -436,87 +426,44 @@
         boot_disk.close()
 
 
-    def setup_pxe_boot(self):
+    def setup_cdrom(self):
         """
-        Sets up a PXE boot environment using the built in qemu TFTP server.
-        Copies the PXE Linux bootloader pxelinux.0 from the host (needs the
-        pxelinux package or equivalent for your distro), and vmlinuz and
-        initrd.img files from the CD to a directory that qemu will serve trough
-        TFTP to the VM.
+        Mount cdrom and copy vmlinuz and initrd.img.
         """
-        print "Setting up PXE boot using TFTP root %s" % self.tftp
-
-        pxe_file = None
-        pxe_paths = ['/usr/lib/syslinux/pxelinux.0',
-                     '/usr/share/syslinux/pxelinux.0']
-        for path in pxe_paths:
-            if os.path.isfile(path):
-                pxe_file = path
-                break
-
-        if not pxe_file:
-            raise SetupError('Cannot find PXE boot loader pxelinux.0. Make '
-                             'sure pxelinux or equivalent package for your '
-                             'distro is installed.')
-
-        pxe_dest = os.path.join(self.tftp, 'pxelinux.0')
-        shutil.copyfile(pxe_file, pxe_dest)
+        print "Copying vmlinuz and initrd.img from cdrom"
+        m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' %
+                 (self.cdrom_cd1, self.cdrom_cd1_mount))
+        run(m_cmd, info='Could not mount CD image %s.' % self.cdrom_cd1)
 
         try:
-            m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' %
-                     (self.cdrom_cd1, self.cdrom_cd1_mount))
-            run(m_cmd, info='Could not mount CD image %s.' % self.cdrom_cd1)
-
-            pxe_dir = os.path.join(self.cdrom_cd1_mount, self.pxe_dir)
-            pxe_image = os.path.join(pxe_dir, self.pxe_image)
-            pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd)
-
-            if not os.path.isdir(pxe_dir):
-                raise SetupError('The ISO image does not have a %s dir. The '
-                                 'script assumes that the cd has a %s dir '
-                                 'where to search for the vmlinuz image.' %
-                                 (self.pxe_dir, self.pxe_dir))
-
-            if not os.path.isfile(pxe_image) or not os.path.isfile(pxe_initrd):
-                raise SetupError('The location %s is lacking either a vmlinuz '
-                                 'or a initrd.img file. Cannot find a PXE '
-                                 'image to proceed.' % self.pxe_dir)
-
-            tftp_image = os.path.join(self.tftp, 'vmlinuz')
-            tftp_initrd = os.path.join(self.tftp, 'initrd.img')
-            shutil.copyfile(pxe_image, tftp_image)
-            shutil.copyfile(pxe_initrd, tftp_initrd)
-
+            img_path_cmd = ("mkdir -p %s" % self.image_path)
+            run(img_path_cmd, info=("Could not create image path dir %s" %
+                                    self.image_path))
+            kernel_fetch_cmd = ("cp %s/%s/%s %s" %
+                                (self.cdrom_cd1_mount, self.boot_path,
+                                 os.path.basename(self.kernel), self.kernel))
+            run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" %
+                                        self.cdrom_cd1_mount))
+            initrd_fetch_cmd = ("cp %s/%s/%s %s" %
+                                (self.cdrom_cd1_mount, self.boot_path,
+                                 os.path.basename(self.initrd), self.initrd))
+            run(initrd_fetch_cmd, info=("Could not copy the initrd.img from "
+                                        "%s" % self.cdrom_cd1_mount))
         finally:
             cleanup(self.cdrom_cd1_mount)
 
-        pxe_config_dir = os.path.join(self.tftp, 'pxelinux.cfg')
-        if not os.path.isdir(pxe_config_dir):
-            os.makedirs(pxe_config_dir)
-        pxe_config_path = os.path.join(pxe_config_dir, 'default')
-
-        pxe_config = open(pxe_config_path, 'w')
-        pxe_config.write('DEFAULT pxeboot\n')
-        pxe_config.write('TIMEOUT 20\n')
-        pxe_config.write('PROMPT 0\n')
-        pxe_config.write('LABEL pxeboot\n')
-        pxe_config.write('     KERNEL vmlinuz\n')
-        pxe_config.write('     APPEND initrd=initrd.img %s\n' %
-                         self.kernel_args)
-        pxe_config.close()
-
-        print "PXE boot successfuly set"
-
 
     def setup_url(self):
         """
         Download the vmlinuz and initrd.img from URL.
         """
-        print "Downloading the vmlinuz and initrd.img"
+        print "Downloading vmlinuz and initrd.img from URL"
         os.chdir(self.image_path)
 
-        kernel_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.kernel)
-        initrd_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.initrd)
+        kernel_fetch_cmd = "wget -q %s/%s/%s" % (self.url, self.boot_path,
+                                                 os.path.basename(self.kernel))
+        initrd_fetch_cmd = "wget -q %s/%s/%s" % (self.url, self.boot_path,
+                                                 os.path.basename(self.initrd))
 
         if os.path.exists(self.kernel):
             os.unlink(self.kernel)
@@ -526,7 +473,6 @@
         run(kernel_fetch_cmd, info="Could not fetch vmlinuz from %s" % self.url)
         run(initrd_fetch_cmd, info=("Could not fetch initrd.img from %s" %
                                     self.url))
-        print "Download of vmlinuz and initrd.img finished"
 
 
     def setup_nfs(self):
@@ -540,12 +486,14 @@
         run(m_cmd, info='Could not mount nfs server')
 
         try:
-            kernel_fetch_cmd = ("cp %s/isolinux/%s %s" %
-                                (self.nfs_mount, self.kernel, self.image_path))
+            kernel_fetch_cmd = ("cp %s/%s/%s %s" %
+                                (self.nfs_mount, self.boot_path,
+                                os.path.basename(self.kernel), self.image_path))
             run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" %
                                         self.nfs_mount))
-            initrd_fetch_cmd = ("cp %s/isolinux/%s %s" %
-                                (self.nfs_mount, self.initrd, self.image_path))
+            initrd_fetch_cmd = ("cp %s/%s/%s %s" %
+                                (self.nfs_mount, self.boot_path,
+                                os.path.basename(self.initrd), self.image_path))
             run(initrd_fetch_cmd, info=("Could not copy the initrd.img from "
                                         "%s" % self.nfs_mount))
         finally:
@@ -572,8 +520,8 @@
         if self.unattended_file and (self.floppy or self.cdrom_unattended):
             self.setup_boot_disk()
         if self.medium == "cdrom":
-            if self.tftp:
-                self.setup_pxe_boot()
+            if self.kernel and self.initrd:
+                self.setup_cdrom()
         elif self.medium == "url":
             self.setup_url()
         elif self.medium == "nfs":
diff --git a/client/tests/kvm/scripts/virtio_guest.py b/client/tests/kvm/scripts/virtio_guest.py
old mode 100644
new mode 100755
index 4862ef2..0038f48
--- a/client/tests/kvm/scripts/virtio_guest.py
+++ b/client/tests/kvm/scripts/virtio_guest.py
@@ -3,35 +3,23 @@
 """
 Auxiliary script used to send data between ports on guests.
 
-@copyright: 2008-2009 Red Hat Inc.
+@copyright: 2010 Red Hat, Inc.
 @author: Jiri Zupka (jzupka@redhat.com)
 @author: Lukas Doktor (ldoktor@redhat.com)
 """
-#from _pydev_SimpleXMLRPCServer import fcntl
-
-"""
-TODO:
-virt.init([consoles])   # sysfs, udev, OK
-virt.open(name)
-virt.close(name)
-virt.poll(name, eventmask, timeout) # poll.register(), poll.poll(),
-return event
-virt.send(name, length) # host disconnected
-virt.recv(name, length) # host disconnected
-virt.blocking(name, true)   # true = blocking, false = nonblocking
-virt.loopback(in_names, out_names, type="None")  # use select/poll
-"""
-
 import threading
 from threading import Thread
-import os, time, select, re, random, sys, array, fcntl, array, subprocess
+import os, time, select, re, random, sys, array
+import fcntl, array, subprocess, traceback, signal
 
 DEBUGPATH = "/sys/kernel/debug"
 SYSFSPATH = "/sys/class/virtio-ports/"
 
 
-class virtio_guest():
-
+class VirtioGuest:
+    """
+    Test tools of virtio_ports.
+    """
     LOOP_NONE = 0
     LOOP_POLL = 1
     LOOP_SELECT = 2
@@ -41,6 +29,9 @@
         self.exit_thread = threading.Event()
         self.threads = []
         self.ports = {}
+        self.poll_fds = {}
+        self.catch_signal = None
+        self.use_config = threading.Event()
 
 
     def _readfile(self, name):
@@ -125,7 +116,7 @@
         print "PASS: Init and check virtioconsole files in system."
 
 
-    class switch(Thread):
+    class Switch(Thread):
         """
         Thread that sends data between ports.
         """
@@ -137,7 +128,7 @@
             @param method: Method of read/write access.
             @param cachesize: Block to receive and send.
             """
-            Thread.__init__(self)
+            Thread.__init__(self, name="Switch")
 
             self.in_files = in_files
             self.out_files = out_files
@@ -211,15 +202,15 @@
 
 
         def run(self):
-            if (self.method == virtio_guest.LOOP_POLL):
+            if (self.method == VirtioGuest.LOOP_POLL):
                 self._poll_mode()
-            elif (self.method == virtio_guest.LOOP_SELECT):
+            elif (self.method == VirtioGuest.LOOP_SELECT):
                 self._select_mode()
             else:
                 self._none_mode()
 
 
-    class sender(Thread):
+    class Sender(Thread):
         """
         Creates a thread which sends random blocks of data to dst port.
         """
@@ -228,7 +219,7 @@
             @param port: Destination port
             @param length: Length of the random data block
             """
-            Thread.__init__(self)
+            Thread.__init__(self, name="Sender")
             self.port = port
             self.exit_thread = event
             self.data = array.array('L')
@@ -260,11 +251,33 @@
                         print os.system("stty -F %s raw -echo" % (name))
                         print os.system("stty -F %s -a" % (name))
                     f.append(self.files[name])
-                except Exception as inst:
+                except Exception, inst:
                     print "FAIL: Failed to open file %s" % (name)
                     raise inst
         return f
 
+    @staticmethod
+    def pollmask_to_str(mask):
+        """
+        Conver pool mast to string
+
+        @param mask: poll return mask
+        """
+        str = ""
+        if (mask & select.POLLIN):
+            str += "IN "
+        if (mask & select.POLLPRI):
+            str += "PRI IN "
+        if (mask & select.POLLOUT):
+            str += "OUT "
+        if (mask & select.POLLERR):
+            str += "ERR "
+        if (mask & select.POLLHUP):
+            str += "HUP "
+        if (mask & select.POLLMSG):
+            str += "MSG "
+        return str
+
 
     def poll(self, port, expected, timeout=500):
         """
@@ -279,24 +292,35 @@
 
         mask = p.poll(timeout)
 
-        str = ""
-        if (mask[0][1] & select.POLLIN):
-            str += "IN "
-        if (mask[0][1] & select.POLLPRI):
-            str += "PRI IN "
-        if (mask[0][1] & select.POLLOUT):
-            str += "OUT "
-        if (mask[0][1] & select.POLLERR):
-            str += "ERR "
-        if (mask[0][1] & select.POLLHUP):
-            str += "HUP "
-        if (mask[0][1] & select.POLLMSG):
-            str += "MSG "
-
+        maskstr = VirtioGuest.pollmask_to_str(mask[0][1])
         if (mask[0][1] & expected) == expected:
-            print "PASS: Events: " + str
+            print "PASS: Events: " + maskstr
         else:
-            print "FAIL: Events: " + str
+            emaskstr = VirtioGuest.pollmask_to_str(expected)
+            print "FAIL: Events: " + maskstr + "  Expected: " + emaskstr
+
+
+    def lseek(self, port, pos, how):
+        """
+        Use lseek on the device. The device is unseekable so PASS is returned
+        when lseek command fails and vice versa.
+
+        @param port: Name of the port
+        @param pos: Offset
+        @param how: Relativ offset os.SEEK_{SET,CUR,END}
+        """
+        fd = self._open([port])[0]
+
+        try:
+            os.lseek(fd, pos, how)
+        except Exception, inst:
+            if inst.errno == 29:
+                print "PASS: the lseek failed as expected"
+            else:
+                print inst
+                print "FAIL: unknown error"
+        else:
+            print "FAIL: the lseek unexpectedly passed"
 
 
     def blocking(self, port, mode=False):
@@ -306,8 +330,7 @@
         @param port: port to set mode
         @param mode: False to set nonblock mode, True for block mode
         """
-        path = self.ports[port]["path"]
-        fd = self.files[path]
+        fd = self._open([port])[0]
 
         try:
             fl = fcntl.fcntl(fd, fcntl.F_GETFL)
@@ -316,12 +339,115 @@
             else:
                 fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_NONBLOCK)
 
-        except Exception as inst:
+        except Exception, inst:
             print "FAIL: Setting (non)blocking mode: " + str(inst)
             return
 
-        print ("PASS: set blocking mode to %s mode" %
-               ("blocking" if mode else "nonblocking"))
+        if mode:
+            print "PASS: set to blocking mode"
+        else:
+            print "PASS: set to nonblocking mode"
+
+
+    def __call__(self, sig, frame):
+        """
+        Call function. Used for signal handle.
+        """
+        if (sig == signal.SIGIO):
+            self.sigio_handler(sig, frame)
+
+
+    def sigio_handler(self, sig, frame):
+        """
+        Handler for sigio operation.
+
+        @param sig: signal which call handler.
+        @param frame: frame of caller
+        """
+        if self.poll_fds:
+            p = select.poll()
+            map(p.register, self.poll_fds.keys())
+
+            masks = p.poll(1)
+            print masks
+            for mask in masks:
+                self.poll_fds[mask[0]][1] |= mask[1]
+
+
+    def get_sigio_poll_return(self, port):
+        """
+        Return PASS, FAIL and poll walue in string format.
+
+        @param port: Port to check poll information.
+        """
+        fd = self._open([port])[0]
+
+        maskstr = VirtioGuest.pollmask_to_str(self.poll_fds[fd][1])
+        if (self.poll_fds[fd][0] ^ self.poll_fds[fd][1]):
+            emaskstr = VirtioGuest.pollmask_to_str(self.poll_fds[fd][0])
+            print "FAIL: Events: " + maskstr + "  Expected: " + emaskstr
+        else:
+            print "PASS: Events: " + maskstr
+        self.poll_fds[fd][1] = 0
+
+
+    def set_pool_want_return(self, port, poll_value):
+        """
+        Set value to static variable.
+
+        @param port: Port which should be set excepted mask
+        @param poll_value: Value to check sigio signal.
+        """
+        fd = self._open([port])[0]
+        self.poll_fds[fd] = [poll_value, 0]
+        print "PASS: Events: " + VirtioGuest.pollmask_to_str(poll_value)
+
+
+    def catching_signal(self):
+        """
+        return: True if should set catch signal, False if ignore signal and
+                none when configuration is not changed.
+        """
+        ret = self.catch_signal
+        self.catch_signal = None
+        return ret
+
+
+    def async(self, port, mode=True, exp_val = 0):
+        """
+        Set port function mode async/sync.
+
+        @param port: port which should be pooled.
+        @param mode: False to set sync mode, True for sync mode.
+        @param exp_val: Value which should be pooled.
+        """
+        fd = self._open([port])[0]
+
+        try:
+            fcntl.fcntl(fd, fcntl.F_SETOWN, os.getpid())
+            fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+
+            self.use_config.clear()
+            if mode:
+                fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_ASYNC)
+                self.poll_fds[fd] = [exp_val, 0]
+                self.catch_signal = True
+            else:
+                del self.poll_fds[fd]
+                fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_ASYNC)
+                self.catch_signal = False
+
+            os.kill(os.getpid(), signal.SIGUSR1)
+            self.use_config.wait()
+
+        except Exception, inst:
+            print "FAIL: Setting (a)sync mode: " + str(inst)
+            return
+
+        if mode:
+            print "PASS: Set to async mode"
+        else:
+            print "PASS: Set to sync mode"
 
 
     def close(self, file):
@@ -336,26 +462,29 @@
             if path in self.files.keys():
                 descriptor = self.files[path]
                 del self.files[path]
-        try:
-            os.close(descriptor)
-        except Exception as inst:
-            print "FAIL: Closing the file: " + str(inst)
-            return
+            if descriptor != None:
+                try:
+                    os.close(descriptor)
+                except Exception, inst:
+                    print "FAIL: Closing the file: " + str(inst)
+                    return
         print "PASS: Close"
 
 
-    def open(self, in_files):
+    def open(self, in_file):
         """
         Direct open devices.
 
-        @param in_files: Array of files.
+        @param in_file: Array of files.
         @return: Array of descriptors.
         """
-        name = self.ports[in_files]["path"]
+        name = self.ports[in_file]["path"]
         try:
             self.files[name] = os.open(name, os.O_RDWR)
+            if (self.ports[in_file]["is_console"] == "yes"):
+                print os.system("stty -F %s raw -echo" % (name))
             print "PASS: Open all filles correctly."
-        except Exception as inst:
+        except Exception, inst:
             print "%s\nFAIL: Failed open file %s" % (str(inst), name)
 
 
@@ -374,7 +503,7 @@
         in_f = self._open(in_files)
         out_f = self._open(out_files)
 
-        s = self.switch(in_f, out_f, self.exit_thread, cachesize, mode)
+        s = self.Switch(in_f, out_f, self.exit_thread, cachesize, mode)
         s.start()
         self.threads.append(s)
         print "PASS: Start switch"
@@ -412,7 +541,7 @@
         self.ports = self._get_port_status()
         in_f = self._open([port])
 
-        self.threads.append(self.sender(in_f[0], self.exit_thread, length))
+        self.threads.append(self.Sender(in_f[0], self.exit_thread, length))
         print "PASS: Sender prepare"
 
 
@@ -439,7 +568,7 @@
             data += "%c" % random.randrange(255)
         try:
             writes = os.write(in_f[0], data)
-        except Exception as inst:
+        except Exception, inst:
             print inst
         if not writes:
             writes = 0
@@ -447,7 +576,7 @@
             while (writes < length):
                 try:
                     writes += os.write(in_f[0], data)
-                except Exception as inst:
+                except Exception, inst:
                     print inst
         if writes >= length:
             print "PASS: Send data length %d" % writes
@@ -469,13 +598,13 @@
         recvs = ""
         try:
             recvs = os.read(in_f[0], buffer)
-        except Exception as inst:
+        except Exception, inst:
             print inst
         if mode:
             while (len(recvs) < length):
                 try:
                     recvs += os.read(in_f[0], buffer)
-                except Exception as inst:
+                except Exception, inst:
                     print inst
         if len(recvs) >= length:
             print "PASS: Recv data length %d" % len(recvs)
@@ -484,6 +613,28 @@
                    (length, len(recvs)))
 
 
+    def clean_port(self, port, buffer=1024):
+        in_f = self._open([port])
+        ret = select.select([in_f[0]], [], [], 1.0)
+        buf = ""
+        if ret[0]:
+            buf = os.read(in_f[0], buffer)
+        print ("PASS: Rest in socket: " + buf)
+
+
+def is_alive():
+    """
+    Check is only main thread is alive and if guest react.
+    """
+    if threading.activeCount() == 2:
+        print ("PASS: Guest is ok no thread alive")
+    else:
+        threads = ""
+        for thread in threading.enumerate():
+            threads += thread.name + ", "
+        print ("FAIL: On guest run thread. Active thread:" + threads)
+
+
 def compile():
     """
     Compile virtio_guest.py to speed up.
@@ -491,22 +642,52 @@
     import py_compile
     py_compile.compile(sys.path[0] + "/virtio_guest.py")
     print "PASS: compile"
-    exit(0)
+    sys.exit()
 
 
-def main():
+def worker(virt):
     """
-    Main (infinite) loop of virtio_guest.
+    Worker thread (infinite) loop of virtio_guest.
     """
-    if (len(sys.argv) > 1) and (sys.argv[1] == "-c"):
-        compile()
-
-    virt = virtio_guest()
     print "PASS: Start"
 
     while True:
         str = raw_input()
-        exec str
+        try:
+            exec str
+        except:
+            exc_type, exc_value, exc_traceback = sys.exc_info()
+            print "On Guest exception from: \n" + "".join(
+                            traceback.format_exception(exc_type,
+                                                       exc_value,
+                                                       exc_traceback))
+    sys.exit(0)
+
+
+def sigusr_handler(sig, frame):
+    pass
+
+
+def main():
+    """
+    Main function with infinite loop to catch signal from system.
+    """
+    if (len(sys.argv) > 1) and (sys.argv[1] == "-c"):
+        compile()
+
+    virt = VirtioGuest()
+    slave = Thread(target=worker, args=(virt, ))
+    slave.start()
+    signal.signal(signal.SIGUSR1, sigusr_handler)
+    while True:
+        signal.pause()
+        catch = virt.catching_signal()
+        if catch:
+            signal.signal(signal.SIGIO, virt)
+        elif catch == False:
+            signal.signal(signal.SIGIO, signal.SIG_DFL)
+        if (catch != None):
+            virt.use_config.set()
 
 
 if __name__ == "__main__":
diff --git a/client/tests/kvm/tests.cfg.sample b/client/tests/kvm/tests.cfg.sample
index ce3e307..bde7aba 100644
--- a/client/tests/kvm/tests.cfg.sample
+++ b/client/tests/kvm/tests.cfg.sample
@@ -11,10 +11,17 @@
 # * qemu and qemu-img are expected to be found under /usr/bin/qemu-kvm and
 #   /usr/bin/qemu-img respectively.
 # * All image files are expected under /tmp/kvm_autotest_root/images/
-# * All iso files are expected under /tmp/kvm_autotest_root/isos/
-qemu_img_binary = /usr/bin/qemu-img
+# * All install iso files are expected under /tmp/kvm_autotest_root/isos/
+# * The parameters cdrom_unattended, floppy, kernel and initrd are generated
+#   by KVM autotest, so remember to put them under a writable location
+#   (for example, the cdrom share can be read only)
 image_name(_.*)? ?<= /tmp/kvm_autotest_root/images/
-cdrom(_.*)? ?<= /tmp/kvm_autotest_root/isos/
+cdrom(_.*)? ?<= /tmp/kvm_autotest_root/
+floppy ?<= /tmp/kvm_autotest_root/
+Linux:
+    unattended_install:
+        kernel ?<= /tmp/kvm_autotest_root/
+        initrd ?<= /tmp/kvm_autotest_root/
 
 # Here are the test sets variants. The variant 'qemu_kvm_windows_quick' is
 # fully commented, the following ones have comments only on noteworthy points
@@ -26,6 +33,7 @@
     - @qemu_kvm_windows_quick:
         # We want qemu-kvm for this run
         qemu_binary = /usr/bin/qemu-kvm
+        qemu_img_binary = /usr/bin/qemu-img
         # Only qcow2 file format
         only qcow2
         # Only rtl8139 for nw card (default on qemu-kvm)
@@ -43,10 +51,11 @@
         # Subtest choice. You can modify that line to add more subtests
         only unattended_install.cdrom boot shutdown
 
-    # Runs qemu, f13 64 bit guest OS, install, boot, shutdown
-    - @qemu_f13_quick:
+    # Runs qemu, f14 64 bit guest OS, install, boot, shutdown
+    - @qemu_f14_quick:
         # We want qemu for this run
         qemu_binary = /usr/bin/qemu
+        qemu_img_binary = /usr/bin/qemu-img
         only qcow2
         # The default nw card for qemu is e1000
         only e1000
@@ -55,22 +64,23 @@
         only up
         only no_pci_assignable
         only smallpages
-        only Fedora.13.64
+        only Fedora.14.64
         only unattended_install.cdrom boot shutdown
         # qemu needs -enable-kvm on the cmdline
         extra_params += ' -enable-kvm'
 
-    # Runs qemu-kvm, f13 64 bit guest OS, install, boot, shutdown
-    - @qemu_kvm_f13_quick:
+    # Runs qemu-kvm, f14 64 bit guest OS, install, boot, shutdown
+    - @qemu_kvm_f14_quick:
         # We want qemu-kvm for this run
         qemu_binary = /usr/bin/qemu-kvm
+        qemu_img_binary = /usr/bin/qemu-img
         only qcow2
         only virtio_net
         only virtio_blk
         only smp2
         only no_pci_assignable
         only smallpages
-        only Fedora.13.64
+        only Fedora.14.64
         only unattended_install.cdrom boot shutdown
 
 # You may provide information about the DTM server for WHQL tests here:
@@ -87,4 +97,4 @@
 #kill_unresponsive_vms.* ?= no
 
 # Choose your test list from the testsets defined
-only qemu_kvm_f13_quick
+only qemu_kvm_f14_quick
diff --git a/client/tests/kvm/tests/build.py b/client/tests/kvm/tests/build.py
index c4f0b18..1eef7a1 100644
--- a/client/tests/kvm/tests/build.py
+++ b/client/tests/kvm/tests/build.py
@@ -1,590 +1,4 @@
-import time, os, sys, urllib, re, signal, logging, datetime, glob, ConfigParser
-import shutil
-from autotest_lib.client.bin import utils, test, os_dep
-from autotest_lib.client.common_lib import error
-import kvm_utils
-
-
-def check_configure_options(script_path):
-    """
-    Return the list of available options (flags) of a given kvm configure build
-    script.
-
-    @param script: Path to the configure script
-    """
-    abspath = os.path.abspath(script_path)
-    help_raw = utils.system_output('%s --help' % abspath, ignore_status=True)
-    help_output = help_raw.split("\n")
-    option_list = []
-    for line in help_output:
-        cleaned_line = line.lstrip()
-        if cleaned_line.startswith("--"):
-            option = cleaned_line.split()[0]
-            option = option.split("=")[0]
-            option_list.append(option)
-
-    return option_list
-
-
-def kill_qemu_processes():
-    """
-    Kills all qemu processes, also kills all processes holding /dev/kvm down.
-    """
-    logging.debug("Killing any qemu processes that might be left behind")
-    utils.system("pkill qemu", ignore_status=True)
-    # Let's double check to see if some other process is holding /dev/kvm
-    if os.path.isfile("/dev/kvm"):
-        utils.system("fuser -k /dev/kvm", ignore_status=True)
-
-
-def load_kvm_modules(module_dir=None, load_stock=False, extra_modules=None):
-    """
-    Unload previously loaded kvm modules, then load modules present on any
-    sub directory of module_dir. Function will walk through module_dir until
-    it finds the modules.
-
-    @param module_dir: Directory where the KVM modules are located.
-    @param load_stock: Whether we are going to load system kernel modules.
-    @param extra_modules: List of extra modules to load.
-    """
-    vendor = "intel"
-    if os.system("grep vmx /proc/cpuinfo 1>/dev/null") != 0:
-        vendor = "amd"
-    logging.debug("Detected CPU vendor as '%s'" %(vendor))
-
-    kill_qemu_processes()
-
-    logging.info("Unloading previously loaded KVM modules")
-    utils.unload_module("kvm")
-    if extra_modules:
-        for module in extra_modules:
-            utils.unload_module(module)
-
-    if module_dir:
-        logging.info("Loading the built KVM modules...")
-        kvm_module_path = None
-        kvm_vendor_module_path = None
-        abort = False
-
-        list_modules = ['kvm.ko', 'kvm-%s.ko' % vendor]
-        if extra_modules:
-            for extra_module in extra_modules:
-                list_modules.append('%s.ko' % extra_module)
-
-        list_module_paths = []
-        for folder, subdirs, files in os.walk(module_dir):
-            for module in list_modules:
-                if module in files:
-                    module_path = os.path.join(folder, module)
-                    list_module_paths.append(module_path)
-
-        # We might need to arrange the modules in the correct order
-        # to avoid module load problems
-        list_modules_load = []
-        for module in list_modules:
-            for module_path in list_module_paths:
-                if os.path.basename(module_path) == module:
-                    list_modules_load.append(module_path)
-
-        if len(list_module_paths) != len(list_modules):
-            logging.error("KVM modules not found. If you don't want to use the "
-                          "modules built by this test, make sure the option "
-                          "load_modules: 'no' is marked on the test control "
-                          "file.")
-            raise error.TestError("The modules %s were requested to be loaded, "
-                                  "but the only modules found were %s" %
-                                  (list_modules, list_module_paths))
-
-        for module_path in list_modules_load:
-            try:
-                utils.system("insmod %s" % module_path)
-            except Exception, e:
-                raise error.TestFail("Failed to load KVM modules: %s" % e)
-
-    if load_stock:
-        logging.info("Loading current system KVM modules...")
-        utils.system("modprobe kvm")
-        utils.system("modprobe kvm-%s" % vendor)
-        if extra_modules:
-            for module in extra_modules:
-                utils.system("modprobe %s" % module)
-
-
-def create_symlinks(test_bindir, prefix=None, bin_list=None, unittest=None):
-    """
-    Create symbolic links for the appropriate qemu and qemu-img commands on
-    the kvm test bindir.
-
-    @param test_bindir: KVM test bindir
-    @param prefix: KVM prefix path
-    @param bin_list: List of qemu binaries to link
-    @param unittest: Path to configuration file unittests.cfg
-    """
-    qemu_path = os.path.join(test_bindir, "qemu")
-    qemu_img_path = os.path.join(test_bindir, "qemu-img")
-    qemu_unittest_path = os.path.join(test_bindir, "unittests")
-    if os.path.lexists(qemu_path):
-        os.unlink(qemu_path)
-    if os.path.lexists(qemu_img_path):
-        os.unlink(qemu_img_path)
-    if unittest and os.path.lexists(qemu_unittest_path):
-        os.unlink(qemu_unittest_path)
-
-    logging.debug("Linking qemu binaries")
-
-    if bin_list:
-        for bin in bin_list:
-            if os.path.basename(bin) == 'qemu-kvm':
-                os.symlink(bin, qemu_path)
-            elif os.path.basename(bin) == 'qemu-img':
-                os.symlink(bin, qemu_img_path)
-
-    elif prefix:
-        kvm_qemu = os.path.join(prefix, "bin", "qemu-system-x86_64")
-        if not os.path.isfile(kvm_qemu):
-            raise error.TestError('Invalid qemu path')
-        kvm_qemu_img = os.path.join(prefix, "bin", "qemu-img")
-        if not os.path.isfile(kvm_qemu_img):
-            raise error.TestError('Invalid qemu-img path')
-        os.symlink(kvm_qemu, qemu_path)
-        os.symlink(kvm_qemu_img, qemu_img_path)
-
-    if unittest:
-        logging.debug("Linking unittest dir")
-        os.symlink(unittest, qemu_unittest_path)
-
-
-def save_build(build_dir, dest_dir):
-    logging.debug('Saving the result of the build on %s', dest_dir)
-    base_name = os.path.basename(build_dir)
-    tarball_name = base_name + '.tar.bz2'
-    os.chdir(os.path.dirname(build_dir))
-    utils.system('tar -cjf %s %s' % (tarball_name, base_name))
-    shutil.move(tarball_name, os.path.join(dest_dir, tarball_name))
-
-
-class BaseInstaller(object):
-    def __init__(self, test, params):
-        load_modules = params.get('load_modules', 'no')
-        if not load_modules or load_modules == 'yes':
-            self.load_modules = True
-        elif load_modules == 'no':
-            self.load_modules = False
-        default_extra_modules = str(None)
-        self.extra_modules = eval(params.get("extra_modules",
-                                             default_extra_modules))
-
-        self.srcdir = test.srcdir
-        if not os.path.isdir(self.srcdir):
-            os.makedirs(self.srcdir)
-
-        self.test_bindir = test.bindir
-        self.results_dir = test.resultsdir
-
-        # KVM build prefix, for the modes that do need it
-        prefix = os.path.join(test.bindir, 'build')
-        self.prefix = os.path.abspath(prefix)
-
-        # Current host kernel directory
-        default_host_kernel_source = '/lib/modules/%s/build' % os.uname()[2]
-        self.host_kernel_srcdir = params.get('host_kernel_source',
-                                             default_host_kernel_source)
-
-        # Extra parameters that can be passed to the configure script
-        self.extra_configure_options = params.get('extra_configure_options',
-                                                  None)
-
-        # Do we want to save the result of the build on test.resultsdir?
-        self.save_results = True
-        save_results = params.get('save_results', 'no')
-        if save_results == 'no':
-            self.save_results = False
-
-
-class YumInstaller(BaseInstaller):
-    """
-    Class that uses yum to install and remove packages.
-    """
-    def __init__(self, test, params):
-        super(YumInstaller, self).__init__(test, params)
-        # Checking if all required dependencies are available
-        os_dep.command("rpm")
-        os_dep.command("yum")
-
-        default_pkg_list = str(['qemu-kvm', 'qemu-kvm-tools'])
-        default_qemu_bin_paths = str(['/usr/bin/qemu-kvm', '/usr/bin/qemu-img'])
-        default_pkg_path_list = str(None)
-        self.pkg_list = eval(params.get("pkg_list", default_pkg_list))
-        self.pkg_path_list = eval(params.get("pkg_path_list",
-                                             default_pkg_path_list))
-        self.qemu_bin_paths = eval(params.get("qemu_bin_paths",
-                                              default_qemu_bin_paths))
-
-
-    def _clean_previous_installs(self):
-        kill_qemu_processes()
-        removable_packages = ""
-        for pkg in self.pkg_list:
-            removable_packages += " %s" % pkg
-
-        utils.system("yum remove -y %s" % removable_packages)
-
-
-    def _get_packages(self):
-        for pkg in self.pkg_path_list:
-            utils.get_file(pkg, os.path.join(self.srcdir,
-                                             os.path.basename(pkg)))
-
-
-    def _install_packages(self):
-        """
-        Install all downloaded packages.
-        """
-        os.chdir(self.srcdir)
-        utils.system("yum install --nogpgcheck -y *.rpm")
-
-
-    def install(self):
-        self._clean_previous_installs()
-        self._get_packages()
-        self._install_packages()
-        create_symlinks(test_bindir=self.test_bindir,
-                        bin_list=self.qemu_bin_paths)
-        if self.load_modules:
-            load_kvm_modules(load_stock=True, extra_modules=self.extra_modules)
-        if self.save_results:
-            save_build(self.srcdir, self.results_dir)
-
-
-class KojiInstaller(YumInstaller):
-    """
-    Class that handles installing KVM from the fedora build service, koji.
-    It uses yum to install and remove packages.
-    """
-    def __init__(self, test, params):
-        """
-        Gets parameters and initializes the package downloader.
-
-        @param test: kvm test object
-        @param params: Dictionary with test arguments
-        """
-        super(KojiInstaller, self).__init__(test, params)
-        default_koji_cmd = '/usr/bin/koji'
-        default_src_pkg = 'qemu'
-        self.src_pkg = params.get("src_pkg", default_src_pkg)
-        self.tag = params.get("koji_tag", None)
-        self.build = params.get("koji_build", None)
-        koji_cmd = params.get("koji_cmd", default_koji_cmd)
-        self.downloader = kvm_utils.KojiDownloader(cmd=koji_cmd)
-
-
-    def _get_packages(self):
-        """
-        Downloads the specific arch RPMs for the specific build name.
-        """
-        self.downloader.get(src_package=self.src_pkg, tag=self.tag,
-                            build=self.build, dst_dir=self.srcdir)
-
-
-    def install(self):
-        super(KojiInstaller, self)._clean_previous_installs()
-        self._get_packages()
-        super(KojiInstaller, self)._install_packages()
-        create_symlinks(test_bindir=self.test_bindir,
-                        bin_list=self.qemu_bin_paths)
-        if self.load_modules:
-            load_kvm_modules(load_stock=True, extra_modules=self.extra_modules)
-        if self.save_results:
-            save_build(self.srcdir, self.results_dir)
-
-
-class SourceDirInstaller(BaseInstaller):
-    """
-    Class that handles building/installing KVM directly from a tarball or
-    a single source code dir.
-    """
-    def __init__(self, test, params):
-        """
-        Initializes class attributes, and retrieves KVM code.
-
-        @param test: kvm test object
-        @param params: Dictionary with test arguments
-        """
-        super(SourceDirInstaller, self).__init__(test, params)
-
-        install_mode = params["mode"]
-        srcdir = params.get("srcdir", None)
-
-        if install_mode == 'localsrc':
-            if srcdir is None:
-                raise error.TestError("Install from source directory specified"
-                                      "but no source directory provided on the"
-                                      "control file.")
-            else:
-                shutil.copytree(srcdir, self.srcdir)
-
-        if install_mode == 'release':
-            release_tag = params.get("release_tag")
-            release_dir = params.get("release_dir")
-            release_listing = params.get("release_listing")
-            logging.info("Installing KVM from release tarball")
-            if not release_tag:
-                release_tag = kvm_utils.get_latest_kvm_release_tag(
-                                                                release_listing)
-            tarball = os.path.join(release_dir, 'kvm', release_tag,
-                                   "kvm-%s.tar.gz" % release_tag)
-            logging.info("Retrieving release kvm-%s" % release_tag)
-            tarball = utils.unmap_url("/", tarball, "/tmp")
-
-        elif install_mode == 'snapshot':
-            logging.info("Installing KVM from snapshot")
-            snapshot_dir = params.get("snapshot_dir")
-            if not snapshot_dir:
-                raise error.TestError("Snapshot dir not provided")
-            snapshot_date = params.get("snapshot_date")
-            if not snapshot_date:
-                # Take yesterday's snapshot
-                d = (datetime.date.today() -
-                     datetime.timedelta(1)).strftime("%Y%m%d")
-            else:
-                d = snapshot_date
-            tarball = os.path.join(snapshot_dir, "kvm-snapshot-%s.tar.gz" % d)
-            logging.info("Retrieving kvm-snapshot-%s" % d)
-            tarball = utils.unmap_url("/", tarball, "/tmp")
-
-        elif install_mode == 'localtar':
-            tarball = params.get("tarball")
-            if not tarball:
-                raise error.TestError("KVM Tarball install specified but no"
-                                      " tarball provided on control file.")
-            logging.info("Installing KVM from a local tarball")
-            logging.info("Using tarball %s")
-            tarball = utils.unmap_url("/", params.get("tarball"), "/tmp")
-
-        if install_mode in ['release', 'snapshot', 'localtar']:
-            utils.extract_tarball_to_dir(tarball, self.srcdir)
-
-        if install_mode in ['release', 'snapshot', 'localtar', 'srcdir']:
-            self.repo_type = kvm_utils.check_kvm_source_dir(self.srcdir)
-            configure_script = os.path.join(self.srcdir, 'configure')
-            self.configure_options = check_configure_options(configure_script)
-
-
-    def _build(self):
-        make_jobs = utils.count_cpus()
-        os.chdir(self.srcdir)
-        # For testing purposes, it's better to build qemu binaries with
-        # debugging symbols, so we can extract more meaningful stack traces.
-        cfg = "./configure --prefix=%s" % self.prefix
-        if "--disable-strip" in self.configure_options:
-            cfg += " --disable-strip"
-        steps = [cfg, "make clean", "make -j %s" % make_jobs]
-        logging.info("Building KVM")
-        for step in steps:
-            utils.system(step)
-
-
-    def _install(self):
-        os.chdir(self.srcdir)
-        logging.info("Installing KVM userspace")
-        if self.repo_type == 1:
-            utils.system("make -C qemu install")
-        elif self.repo_type == 2:
-            utils.system("make install")
-        create_symlinks(self.test_bindir, self.prefix)
-
-
-    def _load_modules(self):
-        load_kvm_modules(module_dir=self.srcdir,
-                         extra_modules=self.extra_modules)
-
-
-    def install(self):
-        self._build()
-        self._install()
-        if self.load_modules:
-            self._load_modules()
-        if self.save_results:
-            save_build(self.srcdir, self.results_dir)
-
-
-class GitInstaller(SourceDirInstaller):
-    def __init__(self, test, params):
-        """
-        Initialize class parameters and retrieves code from git repositories.
-
-        @param test: kvm test object.
-        @param params: Dictionary with test parameters.
-        """
-        super(GitInstaller, self).__init__(test, params)
-
-        kernel_repo = params.get("git_repo")
-        user_repo = params.get("user_git_repo")
-        kmod_repo = params.get("kmod_repo")
-        test_repo = params.get("test_git_repo")
-
-        kernel_branch = params.get("kernel_branch", "master")
-        user_branch = params.get("user_branch", "master")
-        kmod_branch = params.get("kmod_branch", "master")
-        test_branch = params.get("test_branch", "master")
-
-        kernel_lbranch = params.get("kernel_lbranch", "master")
-        user_lbranch = params.get("user_lbranch", "master")
-        kmod_lbranch = params.get("kmod_lbranch", "master")
-        test_lbranch = params.get("test_lbranch", "master")
-
-        kernel_commit = params.get("kernel_commit", None)
-        user_commit = params.get("user_commit", None)
-        kmod_commit = params.get("kmod_commit", None)
-        test_commit = params.get("test_commit", None)
-
-        kernel_patches = eval(params.get("kernel_patches", "[]"))
-        user_patches = eval(params.get("user_patches", "[]"))
-        kmod_patches = eval(params.get("user_patches", "[]"))
-
-        if not user_repo:
-            message = "KVM user git repository path not specified"
-            logging.error(message)
-            raise error.TestError(message)
-
-        userspace_srcdir = os.path.join(self.srcdir, "kvm_userspace")
-        kvm_utils.get_git_branch(user_repo, user_branch, userspace_srcdir,
-                                 user_commit, user_lbranch)
-        self.userspace_srcdir = userspace_srcdir
-
-        if user_patches:
-            os.chdir(self.userspace_srcdir)
-            for patch in user_patches:
-                utils.get_file(patch, os.path.join(self.userspace_srcdir,
-                                                   os.path.basename(patch)))
-                utils.system('patch -p1 %s' % os.path.basename(patch))
-
-        if test_repo:
-            test_srcdir = os.path.join(self.srcdir, "kvm-unit-tests")
-            kvm_utils.get_git_branch(test_repo, test_branch, test_srcdir,
-                                     test_commit, test_lbranch)
-            unittest_cfg = os.path.join(test_srcdir, 'x86',
-                                        'unittests.cfg')
-            self.test_srcdir = test_srcdir
-        else:
-            unittest_cfg = os.path.join(userspace_srcdir, 'kvm', 'test', 'x86',
-                                        'unittests.cfg')
-
-        self.unittest_cfg = None
-        if os.path.isfile(unittest_cfg):
-            self.unittest_cfg = unittest_cfg
-
-        if kernel_repo:
-            kernel_srcdir = os.path.join(self.srcdir, "kvm")
-            kvm_utils.get_git_branch(kernel_repo, kernel_branch, kernel_srcdir,
-                                     kernel_commit, kernel_lbranch)
-            self.kernel_srcdir = kernel_srcdir
-            if kernel_patches:
-                os.chdir(self.kernel_srcdir)
-                for patch in kernel_patches:
-                    utils.get_file(patch, os.path.join(self.userspace_srcdir,
-                                                       os.path.basename(patch)))
-                    utils.system('patch -p1 %s' % os.path.basename(patch))
-        else:
-            self.kernel_srcdir = None
-
-        if kmod_repo:
-            kmod_srcdir = os.path.join (self.srcdir, "kvm_kmod")
-            kvm_utils.get_git_branch(kmod_repo, kmod_branch, kmod_srcdir,
-                                     kmod_commit, kmod_lbranch)
-            self.kmod_srcdir = kmod_srcdir
-            if kmod_patches:
-                os.chdir(self.kmod_srcdir)
-                for patch in kmod_patches:
-                    utils.get_file(patch, os.path.join(self.userspace_srcdir,
-                                                       os.path.basename(patch)))
-                    utils.system('patch -p1 %s' % os.path.basename(patch))
-        else:
-            self.kmod_srcdir = None
-
-        configure_script = os.path.join(self.userspace_srcdir, 'configure')
-        self.configure_options = check_configure_options(configure_script)
-
-
-    def _build(self):
-        make_jobs = utils.count_cpus()
-        cfg = './configure'
-        self.modules_build_succeed = False
-        if self.kmod_srcdir:
-            logging.info('Building KVM modules')
-            os.chdir(self.kmod_srcdir)
-            module_build_steps = [cfg,
-                                  'make clean',
-                                  'make sync LINUX=%s' % self.kernel_srcdir,
-                                  'make']
-        elif self.kernel_srcdir:
-            logging.info('Building KVM modules')
-            os.chdir(self.userspace_srcdir)
-            cfg += ' --kerneldir=%s' % self.host_kernel_srcdir
-            module_build_steps = [cfg,
-                            'make clean',
-                            'make -C kernel LINUX=%s sync' % self.kernel_srcdir]
-        else:
-            module_build_steps = []
-
-        try:
-            if module_build_steps:
-                for step in module_build_steps:
-                    utils.run(step)
-                self.modules_build_succeed = True
-        except error.CmdError, e:
-            logging.error("KVM modules build failed to build: %s" % e)
-
-        logging.info('Building KVM userspace code')
-        os.chdir(self.userspace_srcdir)
-        cfg += ' --prefix=%s' % self.prefix
-        if "--disable-strip" in self.configure_options:
-            cfg += ' --disable-strip'
-        if self.extra_configure_options:
-            cfg += ' %s' % self.extra_configure_options
-        utils.system(cfg)
-        utils.system('make clean')
-        utils.system('make -j %s' % make_jobs)
-
-        self.unittest_prefix = None
-        if self.unittest_cfg:
-            os.chdir(os.path.dirname(os.path.dirname(self.unittest_cfg)))
-            utils.system('./configure --prefix=%s' % self.prefix)
-            utils.system('make')
-            utils.system('make install')
-            self.unittest_prefix = os.path.join(self.prefix, 'share', 'qemu',
-                                                'tests')
-
-
-    def _install(self):
-        os.chdir(self.userspace_srcdir)
-        utils.system('make install')
-        create_symlinks(test_bindir=self.test_bindir, prefix=self.prefix,
-                        bin_list=None,
-                        unittest=self.unittest_prefix)
-
-
-    def _load_modules(self):
-        if self.kmod_srcdir and self.modules_build_succeed:
-            load_kvm_modules(module_dir=self.kmod_srcdir,
-                             extra_modules=self.extra_modules)
-        elif self.kernel_srcdir and self.modules_build_succeed:
-            load_kvm_modules(module_dir=self.userspace_srcdir,
-                             extra_modules=self.extra_modules)
-        else:
-            logging.info("Loading stock KVM modules")
-            load_kvm_modules(load_stock=True,
-                             extra_modules=self.extra_modules)
-
-
-    def install(self):
-        self._build()
-        self._install()
-        if self.load_modules:
-            self._load_modules()
-        if self.save_results:
-            save_build(self.srcdir, self.results_dir)
-
+import installer
 
 def run_build(test, params, env):
     """
@@ -595,20 +9,17 @@
     @param params: Dictionary with test parameters.
     @param env: Test environment.
     """
-    install_mode = params.get("mode")
     srcdir = params.get("srcdir", test.srcdir)
     params["srcdir"] = srcdir
 
-    if install_mode in ['localsrc', 'localtar', 'release', 'snapshot']:
-        installer = SourceDirInstaller(test, params)
-    elif install_mode == 'git':
-        installer = GitInstaller(test, params)
-    elif install_mode == 'yum':
-        installer = YumInstaller(test, params)
-    elif install_mode == 'koji':
-        installer = KojiInstaller(test, params)
-    else:
-        raise error.TestError('Invalid or unsupported'
-                              ' install mode: %s' % install_mode)
-
-    installer.install()
+    try:
+        installer_object = installer.make_installer(params)
+        installer_object.set_install_params(test, params)
+        installer_object.install()
+        env.register_installer(installer_object)
+    except Exception,e:
+        # if the build/install fails, don't allow other tests
+        # to get a installer.
+        msg = "KVM install failed: %s" % (e)
+        env.register_installer(installer.FailedInstaller(msg))
+        raise
diff --git a/client/tests/kvm/tests/clock_getres.py b/client/tests/kvm/tests/clock_getres.py
new file mode 100644
index 0000000..f85bb26
--- /dev/null
+++ b/client/tests/kvm/tests/clock_getres.py
@@ -0,0 +1,39 @@
+import logging, time, os
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import utils
+import kvm_test_utils, kvm_utils
+
+
+def run_clock_getres(test, params, env):
+    """
+    Verify if guests using kvm-clock as the time source have a sane clock
+    resolution.
+
+    @param test: kvm test object.
+    @param params: Dictionary with test parameters.
+    @param env: Dictionary with the test environment.
+    """
+    t_name = "test_clock_getres"
+    base_dir = "/tmp"
+
+    deps_dir = os.path.join(test.bindir, "deps", t_name)
+    os.chdir(deps_dir)
+    try:
+        utils.system("make clean")
+        utils.system("make")
+    except:
+        raise error.TestError("Failed to compile %s" % t_name)
+
+    test_clock = os.path.join(deps_dir, t_name)
+    if not os.path.isfile(test_clock):
+        raise error.TestError("Could not find %s" % t_name)
+
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = int(params.get("login_timeout", 360))
+    session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
+    if not vm.copy_files_to(test_clock, base_dir):
+        raise error.TestError("Failed to copy %s to VM" % t_name)
+    session.cmd(os.path.join(base_dir, t_name))
+    logging.info("PASS: Guest reported appropriate clock resolution")
+    logging.info("guest's dmesg:")
+    session.cmd_output("dmesg")
diff --git a/client/tests/kvm/tests/ethtool.py b/client/tests/kvm/tests/ethtool.py
index 56b1c70..8951fcb 100644
--- a/client/tests/kvm/tests/ethtool.py
+++ b/client/tests/kvm/tests/ethtool.py
@@ -1,7 +1,7 @@
 import logging, commands, re
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
-import kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_subprocess
 
 def run_ethtool(test, params, env):
     """
@@ -32,7 +32,7 @@
             'gro': 'generic.*receive.*offload',
             'lro': 'large.*receive.*offload',
             }
-        s, o = session.get_command_status_output("ethtool -k %s" % ethname)
+        o = session.cmd("ethtool -k %s" % ethname)
         try:
             return re.findall("%s: (.*)" % feature_pattern.get(type), o)[0]
         except IndexError:
@@ -51,7 +51,11 @@
             return False
         cmd = "ethtool -K %s %s %s" % (ethname, type, status)
         if ethtool_get(type) != status:
-            return session.get_command_status(cmd) == 0
+            try:
+                session.cmd(cmd)
+                return True
+            except:
+                return False
         if ethtool_get(type) != status:
             logging.error("Fail to set %s %s" % (type, status))
             return False
@@ -74,7 +78,7 @@
         logging.info("Compare md5sum of the files on guest and host")
         host_result = utils.hash_file(name, method="md5")
         try:
-            o = session.get_command_output("md5sum %s" % name)
+            o = session.cmd_output("md5sum %s" % name)
             guest_result = re.findall("\w+", o)[0]
         except IndexError:
             logging.error("Could not get file md5sum in guest")
@@ -92,13 +96,13 @@
         @param src: Source host of transfer file
         @return: Tuple (status, error msg/tcpdump result)
         """
-        session2.get_command_status("rm -rf %s" % filename)
-        dd_cmd = "dd if=/dev/urandom of=%s bs=1M count=%s" % (filename,
-                                                   params.get("filesize"))
+        session2.cmd_output("rm -rf %s" % filename)
+        dd_cmd = ("dd if=/dev/urandom of=%s bs=1M count=%s" %
+                  (filename, params.get("filesize")))
         logging.info("Creat file in source host, cmd: %s" % dd_cmd)
         tcpdump_cmd = "tcpdump -lep -s 0 tcp -vv port ssh"
         if src == "guest":
-            s = session.get_command_status(dd_cmd, timeout=360)
+            session.cmd_output(dd_cmd, timeout=360)
             tcpdump_cmd += " and src %s" % guest_ip
             copy_files_fun = vm.copy_files_from
         else:
@@ -115,18 +119,18 @@
             tcpdump_cmd += " and not port %s" % i
         logging.debug("Listen by command: %s" % tcpdump_cmd)
         session2.sendline(tcpdump_cmd)
-        if not kvm_utils.wait_for(lambda: session.get_command_status(
-                                           "pgrep tcpdump") == 0, 30):
+        if not kvm_utils.wait_for(
+                           lambda:session.cmd_status("pgrep tcpdump") == 0, 30):
             return (False, "Tcpdump process wasn't launched")
 
         logging.info("Start to transfer file")
         if not copy_files_fun(filename, filename):
             return (False, "Child process transfer file failed")
         logging.info("Transfer file completed")
-        if session.get_command_status("killall tcpdump") != 0:
-            return (False, "Could not kill all tcpdump process")
-        s, tcpdump_string = session2.read_up_to_prompt(timeout=60)
-        if not s:
+        session.cmd("killall tcpdump")
+        try:
+            tcpdump_string = session2.read_up_to_prompt(timeout=60)
+        except kvm_subprocess.ExpectError:
             return (False, "Fail to read tcpdump's output")
 
         if not compare_md5sum(filename):
@@ -173,8 +177,7 @@
     session = kvm_test_utils.wait_for_login(vm,
                   timeout=int(params.get("login_timeout", 360)))
     # Let's just error the test if we identify that there's no ethtool installed
-    if session.get_command_status("ethtool -h"):
-        raise error.TestError("Command ethtool not installed on guest")
+    session.cmd("ethtool -h")
     session2 = kvm_test_utils.wait_for_login(vm,
                   timeout=int(params.get("login_timeout", 360)))
     mtu = 1514
diff --git a/client/tests/kvm/tests/file_transfer.py b/client/tests/kvm/tests/file_transfer.py
index e872bed..f02078e 100644
--- a/client/tests/kvm/tests/file_transfer.py
+++ b/client/tests/kvm/tests/file_transfer.py
@@ -24,13 +24,17 @@
     if not session:
         raise error.TestFail("Could not log into guest '%s'" % vm.name)
 
-    dir = test.tmpdir
+    dir_name = test.tmpdir
     transfer_timeout = int(params.get("transfer_timeout"))
     transfer_type = params.get("transfer_type")
     tmp_dir = params.get("tmp_dir", "/tmp/")
     clean_cmd = params.get("clean_cmd", "rm -f")
     filesize = int(params.get("filesize", 4000))
-    cmd = "dd if=/dev/urandom of=%s/a.out bs=1M count=%d" % (dir, filesize)
+    count = int(filesize / 10)
+    if count == 0:
+        count = 1
+    cmd = "dd if=/dev/zero of=%s/a.out bs=10M count=%d" % (dir_name,
+                                                           count)
     guest_path = tmp_dir + "b.out"
 
     try:
@@ -41,7 +45,7 @@
             logging.info("Transfering file host -> guest, timeout: %ss",
                          transfer_timeout)
             t_begin = time.time()
-            success = vm.copy_files_to("%s/a.out" % dir, guest_path,
+            success = vm.copy_files_to("%s/a.out" % dir_name, guest_path,
                                        timeout=transfer_timeout)
             t_end = time.time()
             throughput = filesize / (t_end - t_begin)
@@ -53,7 +57,7 @@
             logging.info("Transfering file guest -> host, timeout: %ss",
                          transfer_timeout)
             t_begin = time.time()
-            success = vm.copy_files_from(guest_path, "%s/c.out" % dir,
+            success = vm.copy_files_from(guest_path, "%s/c.out" % dir_name,
                                          timeout=transfer_timeout)
             t_end = time.time()
             throughput = filesize / (t_end - t_begin)
@@ -66,12 +70,12 @@
                                   transfer_type)
 
         for f in ['a.out', 'c.out']:
-            p = os.path.join(dir, f)
+            p = os.path.join(dir_name, f)
             size = os.path.getsize(p)
             logging.debug('Size of %s: %sB', f, size)
 
-        md5_orig = utils.hash_file("%s/a.out" % dir, method="md5")
-        md5_new = utils.hash_file("%s/c.out" % dir, method="md5")
+        md5_orig = utils.hash_file("%s/a.out" % dir_name, method="md5")
+        md5_new = utils.hash_file("%s/c.out" % dir_name, method="md5")
 
         if md5_orig != md5_new:
             raise error.TestFail("File changed after transfer host -> guest "
@@ -80,11 +84,11 @@
     finally:
         logging.info('Cleaning temp file on guest')
         clean_cmd += " %s" % guest_path
-        s, o = session.get_command_status_output(clean_cmd)
-        if s:
-            logging.warning("Failed to clean remote file %s, output:%s",
-                            guest_path, o)
+        session.cmd(clean_cmd)
         logging.info('Cleaning temp files on host')
-        os.remove('%s/a.out' % dir)
-        os.remove('%s/c.out' % dir)
+        try:
+            os.remove('%s/a.out' % dir_name)
+            os.remove('%s/c.out' % dir_name)
+        except OSError:
+            pass
         session.close()
diff --git a/client/tests/kvm/tests/guest_s4.py b/client/tests/kvm/tests/guest_s4.py
index 2eb035b..0280f71 100644
--- a/client/tests/kvm/tests/guest_s4.py
+++ b/client/tests/kvm/tests/guest_s4.py
@@ -16,11 +16,7 @@
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
 
     logging.info("Checking whether guest OS supports suspend to disk (S4)...")
-    s, o = session.get_command_status_output(params.get("check_s4_support_cmd"))
-    if "not enough space" in o:
-        raise error.TestError("Check S4 support failed: %s" % o)
-    elif s != 0:
-        raise error.TestNAError("Guest OS does not support S4")
+    session.cmd(params.get("check_s4_support_cmd"))
 
     logging.info("Waiting until all guest OS services are fully started...")
     time.sleep(float(params.get("services_up_timeout", 30)))
@@ -36,9 +32,7 @@
 
     # Make sure the background program is running as expected
     check_s4_cmd = params.get("check_s4_cmd")
-    if session2.get_command_status(check_s4_cmd) != 0:
-        raise error.TestError("Failed to launch '%s' as a background process" %
-                              test_s4_cmd)
+    session2.cmd(check_s4_cmd)
     logging.info("Launched background command in guest: %s" % test_s4_cmd)
 
     # Suspend to disk
@@ -68,11 +62,9 @@
 
     # Check whether the test command is still alive
     logging.info("Checking if background command is still alive...")
-    if session2.get_command_status(check_s4_cmd) != 0:
-        raise error.TestFail("Background command '%s' stopped running. S4 "
-                             "failed." % test_s4_cmd)
+    session2.cmd(check_s4_cmd)
 
     logging.info("VM resumed successfuly after suspend to disk")
-    session2.get_command_output(params.get("kill_test_s4_cmd"))
+    session2.cmd_output(params.get("kill_test_s4_cmd"))
     session.close()
     session2.close()
diff --git a/client/tests/kvm/tests/guest_test.py b/client/tests/kvm/tests/guest_test.py
index b6bebc7..b9786b5 100644
--- a/client/tests/kvm/tests/guest_test.py
+++ b/client/tests/kvm/tests/guest_test.py
@@ -20,7 +20,9 @@
     reboot = params.get("reboot", "no")
 
     vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
-    session = kvm_test_utils.wait_for_login(vm, timeout=login_timeout)
+    serial_login = (params.get("serial_login", "no") == "yes")
+    session = kvm_test_utils.wait_for_login(vm, timeout=login_timeout,
+                                            serial=serial_login)
 
     if reboot == "yes":
         logging.debug("Rebooting guest before test ...")
@@ -48,34 +50,25 @@
             # Change dir to dst_rsc_dir, and remove the guest script dir there
             rm_cmd = "cd %s && (rmdir /s /q %s || del /s /q %s)" % \
                      (dst_rsc_dir, rsc_dir, rsc_dir)
-            if session.get_command_status(rm_cmd, timeout=test_timeout) != 0:
-                raise error.TestFail("Remove %s failed." % rsc_dir)
+            session.cmd(rm_cmd, timeout=test_timeout)
             logging.debug("Clean directory succeeded.")
 
             # then download the resource.
             rsc_cmd = "cd %s && %s %s" %(dst_rsc_dir, download_cmd, rsc_server)
-            if session.get_command_status(rsc_cmd, timeout=test_timeout) != 0:
-                raise error.TestFail("Download test resource failed.")
+            session.cmd(rsc_cmd, timeout=test_timeout)
             logging.info("Download resource finished.")
         else:
-            session.get_command_output("del %s" % dst_rsc_path,
-                                       internal_timeout=0)
+            session.cmd_output("del %s" % dst_rsc_path, internal_timeout=0)
             script_path = kvm_utils.get_path(test.bindir, script)
             vm.copy_files_to(script_path, dst_rsc_path, timeout=60)
 
-        command = "cmd /c %s %s %s" %(interpreter, dst_rsc_path, script_params)
+        cmd = "%s %s %s" % (interpreter, dst_rsc_path, script_params)
 
-        logging.info("---------------- Script output ----------------")
-        status = session.get_command_status(command,
-                                            print_func=logging.info,
-                                            timeout=test_timeout)
-        logging.info("---------------- End of script output ----------------")
-
-        if status is None:
-            raise error.TestFail("Timeout expired before script execution "
-                                 "completed (or something weird happened)")
-        if status != 0:
-            raise error.TestFail("Script execution failed")
+        try:
+            logging.info("------------ Script output ------------")
+            session.cmd(cmd, print_func=logging.info, timeout=test_timeout)
+        finally:
+            logging.info("------------ End of script output ------------")
 
         if reboot == "yes":
             logging.debug("Rebooting guest after test ...")
diff --git a/client/tests/kvm/tests/image_copy.py b/client/tests/kvm/tests/image_copy.py
new file mode 100644
index 0000000..87bafea
--- /dev/null
+++ b/client/tests/kvm/tests/image_copy.py
@@ -0,0 +1,46 @@
+import os, logging, commands
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.bin import utils
+import kvm_utils
+
+
+def run_image_copy(test, params, env):
+    """
+    Copy guest images from nfs server.
+    1) Mount the NFS share directory
+    2) Check the existence of source image
+    3) If it exists, copy the image from NFS
+
+    @param test: kvm test object
+    @param params: Dictionary with the test parameters
+    @param env: Dictionary with test environment.
+    """
+    mount_dest_dir = params.get('dst_dir', '/mnt/images')
+    if not os.path.exists(mount_dest_dir):
+        try:
+            os.makedirs(mount_dest_dir)
+        except OSError, err:
+            logging.warning('mkdir %s error:\n%s', mount_dest_dir, err)
+
+    if not os.path.exists(mount_dest_dir):
+        raise error.TestError('Failed to create NFS share dir %s' %
+                              mount_dest_dir)
+
+    src = params.get('images_good')
+    mnt_cmd = 'mount %s %s -o ro' % (src, mount_dest_dir)
+    image = '%s.%s' % (os.path.split(params['image_name'])[1],
+                       params['image_format'])
+    src_path = os.path.join(mount_dest_dir, image)
+    dst_path = '%s.%s' % (params['image_name'], params['image_format'])
+    cmd = 'cp %s %s' % (src_path, dst_path)
+
+    if not kvm_utils.mount(src, mount_dest_dir, 'nfs', 'ro'):
+        raise error.TestError('Could not mount NFS share %s to %s' %
+                              (src, mount_dest_dir))
+
+    # Check the existence of source image
+    if not os.path.exists(src_path):
+        raise error.TestError('Could not find %s in NFS share' % src_path)
+
+    logging.debug('Copying image %s...' % image)
+    utils.system(cmd)
diff --git a/client/tests/kvm/tests/iofuzz.py b/client/tests/kvm/tests/iofuzz.py
index 45a0eb9..e77540e 100644
--- a/client/tests/kvm/tests/iofuzz.py
+++ b/client/tests/kvm/tests/iofuzz.py
@@ -33,11 +33,10 @@
         logging.debug("outb(0x%x, 0x%x)", port, data)
         outb_cmd = ("echo -e '\\%s' | dd of=/dev/port seek=%d bs=1 count=1" %
                     (oct(data), port))
-        s, o = session.get_command_status_output(outb_cmd)
-        if s is None:
-            logging.debug("Command did not return")
-        if s != 0:
-            logging.debug("Command returned status %s", s)
+        try:
+            session.cmd(outb_cmd)
+        except kvm_subprocess.ShellError, e:
+            logging.debug(e)
 
 
     def inb(session, port):
@@ -49,11 +48,10 @@
         """
         logging.debug("inb(0x%x)", port)
         inb_cmd = "dd if=/dev/port seek=%d of=/dev/null bs=1 count=1" % port
-        s, o = session.get_command_status_output(inb_cmd)
-        if s is None:
-            logging.debug("Command did not return")
-        if s != 0:
-            logging.debug("Command returned status %s", s)
+        try:
+            session.cmd(inb_cmd)
+        except kvm_subprocess.ShellError, e:
+            logging.debug(e)
 
 
     def fuzz(session, inst_list):
@@ -100,7 +98,7 @@
         r = random.SystemRandom()
 
         logging.info("Enumerate guest devices through /proc/ioports")
-        ioports = session.get_command_output("cat /proc/ioports")
+        ioports = session.cmd_output("cat /proc/ioports")
         logging.debug(ioports)
         devices = re.findall("(\w+)-(\w+)\ : (.*)", ioports)
 
diff --git a/client/tests/kvm/tests/ioquit.py b/client/tests/kvm/tests/ioquit.py
index 8126139..25d3ac9 100644
--- a/client/tests/kvm/tests/ioquit.py
+++ b/client/tests/kvm/tests/ioquit.py
@@ -20,17 +20,13 @@
     try:
         bg_cmd = params.get("background_cmd")
         logging.info("Add IO workload for guest OS.")
-        (s, o) = session.get_command_status_output(bg_cmd, timeout=60)
+        session.cmd_output(bg_cmd, timeout=60)
         check_cmd = params.get("check_cmd")
-        (s, o) = session2.get_command_status_output(check_cmd, timeout=60)
-        if s:
-            raise error.TestError("Fail to add IO workload for Guest OS")
+        session2.cmd(check_cmd, timeout=60)
 
         logging.info("Sleep for a while")
         time.sleep(random.randrange(30,100))
-        (s, o) = session2.get_command_status_output(check_cmd, timeout=60)
-        if s:
-            logging.info("IO workload finished before the VM was killed")
+        session2.cmd(check_cmd, timeout=60)
         logging.info("Kill the virtual machine")
         vm.process.close()
     finally:
diff --git a/client/tests/kvm/tests/iozone_windows.py b/client/tests/kvm/tests/iozone_windows.py
index a96fdfc..febf898 100644
--- a/client/tests/kvm/tests/iozone_windows.py
+++ b/client/tests/kvm/tests/iozone_windows.py
@@ -28,8 +28,8 @@
     c = params.get("iozone_cmd")
     t = int(params.get("iozone_timeout"))
     logging.info("Running IOzone command on guest, timeout %ss", t)
-    results = session.get_command_output(command=c, timeout=t,
-                                         print_func=logging.debug)
+    results = session.cmd_output(command=c, timeout=t,
+                                 print_func=logging.debug)
     utils.open_write_close(results_path, results)
 
     # Postprocess the results using the IOzone postprocessing module
diff --git a/client/tests/kvm/tests/jumbo.py b/client/tests/kvm/tests/jumbo.py
index 2c91c83..1fbce8b 100644
--- a/client/tests/kvm/tests/jumbo.py
+++ b/client/tests/kvm/tests/jumbo.py
@@ -40,11 +40,7 @@
 
         logging.info("Changing the MTU of guest ...")
         guest_mtu_cmd = "ifconfig %s mtu %s" % (ethname , mtu)
-        s, o = session.get_command_status_output(guest_mtu_cmd)
-        if s != 0:
-            logging.error(o)
-            raise error.TestError("Fail to set the MTU of guest NIC: %s" %
-                                  ethname)
+        session.cmd(guest_mtu_cmd)
 
         logging.info("Chaning the MTU of host tap ...")
         host_mtu_cmd = "ifconfig %s mtu %s" % (ifname, mtu)
diff --git a/client/tests/kvm/tests/kdump.py b/client/tests/kvm/tests/kdump.py
new file mode 100644
index 0000000..ccc4307
--- /dev/null
+++ b/client/tests/kvm/tests/kdump.py
@@ -0,0 +1,74 @@
+import logging, time
+from autotest_lib.client.common_lib import error
+import kvm_subprocess, kvm_test_utils, kvm_utils
+
+
+def run_kdump(test, params, env):
+    """
+    KVM reboot test:
+    1) Log into a guest
+    2) Check and enable the kdump
+    3) For each vcpu, trigger a crash and check the vmcore
+
+    @param test: kvm test object
+    @param params: Dictionary with the test parameters
+    @param env: Dictionary with test environment.
+    """
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = float(params.get("login_timeout", 240))
+    crash_timeout = float(params.get("crash_timeout", 360))
+    session = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2)
+    def_kernel_param_cmd = ("grubby --update-kernel=`grubby --default-kernel`"
+                            " --args=crashkernel=128M")
+    kernel_param_cmd = params.get("kernel_param_cmd", def_kernel_param_cmd)
+    def_kdump_enable_cmd = "chkconfig kdump on && service kdump start"
+    kdump_enable_cmd = params.get("kdump_enable_cmd", def_kdump_enable_cmd)
+    def_crash_kernel_prob_cmd = "grep -q 1 /sys/kernel/kexec_crash_loaded"
+    crash_kernel_prob_cmd = params.get("crash_kernel_prob_cmd",
+                                       def_crash_kernel_prob_cmd)
+
+    def crash_test(vcpu):
+        """
+        Trigger a crash dump through sysrq-trigger
+
+        @param vcpu: vcpu which is used to trigger a crash
+        """
+        session = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2)
+        session.cmd_output("rm -rf /var/crash/*")
+
+        logging.info("Triggering crash on vcpu %d ...", vcpu)
+        crash_cmd = "taskset -c %d echo c > /proc/sysrq-trigger" % vcpu
+        session.sendline(crash_cmd)
+
+        if not kvm_utils.wait_for(lambda: not session.is_responsive(), 240, 0,
+                                  1):
+            raise error.TestFail("Could not trigger crash on vcpu %d" % vcpu)
+
+        logging.info("Waiting for kernel crash dump to complete")
+        session = kvm_test_utils.wait_for_login(vm, 0, crash_timeout, 0, 2)
+
+        logging.info("Probing vmcore file...")
+        session.cmd("ls -R /var/crash | grep vmcore")
+        logging.info("Found vmcore.")
+
+        session.cmd_output("rm -rf /var/crash/*")
+
+    try:
+        logging.info("Checking the existence of crash kernel...")
+        try:
+            session.cmd(crash_kernel_prob_cmd)
+        except:
+            logging.info("Crash kernel is not loaded. Trying to load it")
+            session.cmd(kernel_param_cmd)
+            session = kvm_test_utils.reboot(vm, session, timeout=timeout)
+
+        logging.info("Enabling kdump service...")
+        # the initrd may be rebuilt here so we need to wait a little more
+        session.cmd(kdump_enable_cmd, timeout=120)
+
+        nvcpu = int(params.get("smp", 1))
+        for i in range (nvcpu):
+            crash_test(i)
+
+    finally:
+        session.close()
diff --git a/client/tests/kvm/tests/ksm_overcommit.py b/client/tests/kvm/tests/ksm_overcommit.py
index dd4a30d..c6368d3 100644
--- a/client/tests/kvm/tests/ksm_overcommit.py
+++ b/client/tests/kvm/tests/ksm_overcommit.py
@@ -27,12 +27,13 @@
         """
         logging.debug("Starting allocator.py on guest %s", vm.name)
         session.sendline("python /tmp/allocator.py")
-        (match, data) = session.read_until_last_line_matches(["PASS:", "FAIL:"],
-                                                             timeout)
-        if match == 1 or match is None:
-            raise error.TestFail("Command allocator.py on guest %s failed.\n"
-                                 "return code: %s\n output:\n%s" %
-                                 (vm.name, match, data))
+        try:
+            (match, data) = session.read_until_last_line_matches(
+                                                            ["PASS:", "FAIL:"],
+                                                            timeout)
+        except kvm_subprocess.ExpectProcessTerminatedError, e:
+            raise error.TestFail("Command allocator.py on vm '%s' failed: %s" %
+                                 (vm.name, str(e)))
 
 
     def _execute_allocator(command, vm, session, timeout):
@@ -50,12 +51,14 @@
         logging.debug("Executing '%s' on allocator.py loop, vm: %s, timeout: %s",
                       command, vm.name, timeout)
         session.sendline(command)
-        (match, data) = session.read_until_last_line_matches(["PASS:","FAIL:"],
+        try:
+            (match, data) = session.read_until_last_line_matches(
+                                                             ["PASS:","FAIL:"],
                                                              timeout)
-        if match == 1 or match is None:
-            raise error.TestFail("Failed to execute '%s' on allocator.py, "
-                                 "vm: %s, output:\n%s" %
-                                 (command, vm.name, data))
+        except kvm_subprocess.ExpectProcessTerminatedError, e:
+            e_str = ("Failed to execute command '%s' on allocator.py, "
+                     "vm '%s': %s" % (command, vm.name, str(e)))
+            raise error.TestFail(e_str)
         return (match, data)
 
 
@@ -80,9 +83,7 @@
             vm = lvms[lsessions.index(session)]
 
             logging.debug("Turning off swap on vm %s" % vm.name)
-            ret = session.get_command_status("swapoff -a", timeout=300)
-            if ret is None or ret:
-                raise error.TestFail("Failed to swapoff on VM %s" % vm.name)
+            session.cmd("swapoff -a", timeout=300)
 
             # Start the allocator
             _start_allocator(vm, session, 60 * perf_ratio)
@@ -232,7 +233,7 @@
                            (mem / 200 * 50 * perf_ratio))
         logging.debug(kvm_test_utils.get_memory_info([lvms[last_vm]]))
 
-        (status, data) = lsessions[i].get_command_status_output("die()", 20)
+        lsessions[i].cmd_output("die()", 20)
         lvms[last_vm].destroy(gracefully = False)
         logging.info("Phase 3b: PASS")
 
@@ -253,9 +254,7 @@
                 raise error.TestFail("Could not log into guest %s" %
                                      vm.name)
 
-        ret = session.get_command_status("swapoff -a", timeout=300)
-        if ret != 0:
-            raise error.TestFail("Failed to turn off swap on %s" % vm.name)
+        session.cmd("swapoff -a", timeout=300)
 
         for i in range(0, max_alloc):
             # Start the allocator
@@ -360,7 +359,7 @@
 
         logging.debug("Cleaning up...")
         for i in range(0, max_alloc):
-            lsessions[i].get_command_status_output("die()", 20)
+            lsessions[i].cmd_output("die()", 20)
         session.close()
         vm.destroy(gracefully = False)
 
@@ -545,7 +544,7 @@
 
     # Creating the first guest
     kvm_preprocessing.preprocess_vm(test, params, env, vm_name)
-    lvms.append(kvm_utils.env_get_vm(env, vm_name))
+    lvms.append(env.get_vm(vm_name))
     if not lvms[0]:
         raise error.TestError("VM object not found in environment")
     if not lvms[0].is_alive():
@@ -576,7 +575,7 @@
 
         # Last VM is later used to run more allocators simultaneously
         lvms.append(lvms[0].clone(vm_name, params))
-        kvm_utils.env_register_vm(env, vm_name, lvms[i])
+        env.register_vm(vm_name, lvms[i])
         params['vms'] += " " + vm_name
 
         logging.debug("Booting guest %s" % lvms[i].name)
diff --git a/client/tests/kvm/tests/linux_s3.py b/client/tests/kvm/tests/linux_s3.py
index 4a782b8..8a0f5eb 100644
--- a/client/tests/kvm/tests/linux_s3.py
+++ b/client/tests/kvm/tests/linux_s3.py
@@ -16,16 +16,12 @@
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
 
     logging.info("Checking that VM supports S3")
-    status = session.get_command_status("grep -q mem /sys/power/state")
-    if status == None:
-        logging.error("Failed to check if S3 exists")
-    elif status != 0:
-        raise error.TestFail("Guest does not support S3")
+    session.cmd("grep -q mem /sys/power/state")
 
     logging.info("Waiting for a while for X to start")
     time.sleep(10)
 
-    src_tty = session.get_command_output("fgconsole").strip()
+    src_tty = session.cmd_output("fgconsole").strip()
     logging.info("Current virtual terminal is %s" % src_tty)
     if src_tty not in map(str, range(1,10)):
         raise error.TestFail("Got a strange current vt (%s)" % src_tty)
@@ -38,9 +34,7 @@
     command = "chvt %s && echo mem > /sys/power/state && chvt %s" % (dst_tty,
                                                                      src_tty)
     suspend_timeout = 120 + int(params.get("smp")) * 60
-    status = session.get_command_status(command, timeout=suspend_timeout)
-    if status != 0:
-        raise error.TestFail("Suspend to mem failed")
+    session.cmd(command, timeout=suspend_timeout)
 
     logging.info("VM resumed after S3")
 
diff --git a/client/tests/kvm/tests/mac_change.py b/client/tests/kvm/tests/mac_change.py
index c614e15..78fbab2 100644
--- a/client/tests/kvm/tests/mac_change.py
+++ b/client/tests/kvm/tests/mac_change.py
@@ -17,12 +17,10 @@
     """
     timeout = int(params.get("login_timeout", 360))
     vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
-    logging.info("Trying to log into guest '%s' by serial", vm.name)
-    session = kvm_utils.wait_for(lambda: vm.serial_login(),
-                                  timeout, 0, step=2)
-    if not session:
-        raise error.TestFail("Could not log into guest '%s'" % vm.name)
-
+    session_serial = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2,
+                                                   serial=True)
+    # This session will be used to assess whether the IP change worked
+    session = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2)
     old_mac = vm.get_mac_address(0)
     while True:
         vm.free_mac_address(0)
@@ -30,23 +28,21 @@
         if old_mac != new_mac:
             break
     logging.info("The initial MAC address is %s", old_mac)
-    interface = kvm_test_utils.get_linux_ifname(session, old_mac)
+    interface = kvm_test_utils.get_linux_ifname(session_serial, old_mac)
     # Start change MAC address
     logging.info("Changing MAC address to %s", new_mac)
     change_cmd = ("ifconfig %s down && ifconfig %s hw ether %s && "
                   "ifconfig %s up" % (interface, interface, new_mac, interface))
-    if session.get_command_status(change_cmd) != 0:
-        raise error.TestFail("Fail to send mac_change command")
+    session_serial.cmd(change_cmd)
 
     # Verify whether MAC address was changed to the new one
     logging.info("Verifying the new mac address")
-    if session.get_command_status("ifconfig | grep -i %s" % new_mac) != 0:
-        raise error.TestFail("Fail to change MAC address")
+    session_serial.cmd("ifconfig | grep -i %s" % new_mac)
 
     # Restart `dhclient' to regain IP for new mac address
     logging.info("Restart the network to gain new IP")
     dhclient_cmd = "dhclient -r && dhclient %s" % interface
-    session.sendline(dhclient_cmd)
+    session_serial.sendline(dhclient_cmd)
 
     # Re-log into the guest after changing mac address
     if kvm_utils.wait_for(session.is_responsive, 120, 20, 3):
diff --git a/client/tests/kvm/tests/migration.py b/client/tests/kvm/tests/migration.py
index d6f4b11..1c9f178 100644
--- a/client/tests/kvm/tests/migration.py
+++ b/client/tests/kvm/tests/migration.py
@@ -26,10 +26,12 @@
     mig_timeout = float(params.get("mig_timeout", "3600"))
     mig_protocol = params.get("migration_protocol", "tcp")
     mig_cancel = bool(params.get("mig_cancel"))
+    offline = params.get("offline", "no") == "yes"
+    check = params.get("vmstate_check", "no") == "yes"
 
     # Get the output of migration_test_command
     test_command = params.get("migration_test_command")
-    reference_output = session.get_command_output(test_command)
+    reference_output = session.cmd_output(test_command)
 
     # Start some process in the background (and leave the session open)
     background_command = params.get("migration_bg_command", "")
@@ -42,14 +44,12 @@
 
     try:
         check_command = params.get("migration_bg_check_command", "")
-        if session2.get_command_status(check_command, timeout=30) != 0:
-            raise error.TestError("Could not start background process '%s'" %
-                                  background_command)
+        session2.cmd(check_command, timeout=30)
         session2.close()
 
         # Migrate the VM
         dest_vm = kvm_test_utils.migrate(vm, env,mig_timeout, mig_protocol,
-                                         mig_cancel)
+                                         mig_cancel, offline, check)
 
         # Log into the guest again
         logging.info("Logging into guest after migration...")
@@ -59,12 +59,10 @@
         logging.info("Logged in after migration")
 
         # Make sure the background process is still running
-        if session2.get_command_status(check_command, timeout=30) != 0:
-            raise error.TestFail("Could not find running background process "
-                                 "after migration: '%s'" % background_command)
+        session2.cmd(check_command, timeout=30)
 
         # Get the output of migration_test_command
-        output = session2.get_command_output(test_command)
+        output = session2.cmd_output(test_command)
 
         # Compare output to reference output
         if output != reference_output:
@@ -81,8 +79,7 @@
     finally:
         # Kill the background process
         if session2 and session2.is_alive():
-            session2.get_command_output(params.get("migration_bg_kill_command",
-                                                   ""))
+            session2.cmd_output(params.get("migration_bg_kill_command", ""))
 
     session2.close()
     session.close()
diff --git a/client/tests/kvm/tests/migration_multi_host.py b/client/tests/kvm/tests/migration_multi_host.py
new file mode 100644
index 0000000..15af0c8
--- /dev/null
+++ b/client/tests/kvm/tests/migration_multi_host.py
@@ -0,0 +1,108 @@
+import logging, time, socket
+from autotest_lib.client.common_lib import error
+import kvm_subprocess, kvm_test_utils, kvm_utils
+
+
+def run_migration_multi_host(test, params, env):
+    """
+    KVM multi-host migration test:
+
+    Migration execution progress:
+
+    source host                       dest host
+    ----------------------------------------------------------------------------
+    log into guest
+    ----------------------------------------------------------------------------
+    start socket server
+
+    wait 30 secs -------------------- wait login_timeout+30 secs ---------------
+
+    accept connection                 connect to socket server,send mig_port
+    ----------------------------------------------------------------------------
+    start migration
+
+    wait 30 secs -------------------- wait mig_timeout+30 secs -----------------
+
+    try to log into migrated guest    check VM's status via monitor cmd
+    ----------------------------------------------------------------------------
+
+    @param test: kvm test object.
+    @param params: Dictionary with test parameters.
+    @param env: Dictionary with the test environment.
+    """
+    def guest_active(vm):
+        o = vm.monitor.info("status")
+        if isinstance(o, str):
+            return "status: running" in o
+        else:
+            return o.get("status") == "running"
+
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    login_timeout = int(params.get("login_timeout", 360))
+    role = params.get("role")
+    srchost = params.get("srchost")
+    dsthost = params.get("dsthost")
+    mig_timeout = int(params.get("mig_timeout"))
+    # Port used to communicate info between source and destination
+    comm_port = int(params.get("comm_port", 12324))
+    regain_ip_cmd = params.get("regain_ip_cmd", "dhclient")
+    if role == 'source':
+        session = kvm_test_utils.wait_for_login(vm, timeout=login_timeout)
+
+        # Listen on a port to get the migration port received from
+        # dest machine
+        s_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s_socket.bind(('', comm_port))
+        s_socket.listen(1)
+
+        # Wait 30 seconds for source and dest to reach this point
+        test.job.barrier(srchost,'socket_started', 30).rendezvous(srchost,
+                                                                  dsthost)
+
+        c_socket, addr = s_socket.accept()
+        mig_port = int(c_socket.recv(6))
+        logging.info("Received from destination the migration port %s",
+                     mig_port)
+        c_socket.close()
+
+        logging.info("Start migrating now...")
+        kvm_test_utils.migrate(vm=vm, dest_host=dsthost, mig_port=mig_port,
+                               env=env)
+
+        # Wait up to 30 seconds for dest to reach this point
+        test.job.barrier(srchost, 'mig_finished', 30).rendezvous(srchost,
+                                                                 dsthost)
+
+    elif role == 'destination':
+        # Wait up to login_timeout + 30 seconds for the source to
+        # reach this point
+        test.job.barrier(dsthost, 'socket_started',
+                         login_timeout + 30).rendezvous(srchost,
+                                                        dsthost)
+
+        c_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        c_socket.connect((srchost, comm_port))
+        logging.info("Communicating to source migration port %s",
+                     vm.migration_port)
+        c_socket.send("%d" % vm.migration_port)
+        c_socket.close()
+
+        # Wait up to mig_timeout + 30 seconds for the source to
+        # reach this point: migration finished
+        test.job.barrier(dsthost, 'mig_finished',
+                         mig_timeout + 30).rendezvous(srchost,
+                                                      dsthost)
+
+        if not guest_active(vm):
+            raise error.TestFail("Guest not active after migration")
+
+        logging.info("Migrated guest appears to be running")
+
+        # Log into the guest again
+        logging.info("Logging into migrated guest after migration...")
+        session_serial = kvm_test_utils.wait_for_login(vm, timeout=login_timeout, serial=True)
+        session_serial.cmd(regain_ip_cmd)
+        session = kvm_test_utils.wait_for_login(vm, timeout=login_timeout)
+
+    else:
+        raise error.TestError('Invalid role specified')
diff --git a/client/tests/kvm/tests/migration_with_file_transfer.py b/client/tests/kvm/tests/migration_with_file_transfer.py
new file mode 100644
index 0000000..d311350
--- /dev/null
+++ b/client/tests/kvm/tests/migration_with_file_transfer.py
@@ -0,0 +1,92 @@
+import logging, time, os
+from autotest_lib.client.common_lib import utils, error
+from autotest_lib.client.bin import utils as client_utils
+import kvm_subprocess, kvm_test_utils, kvm_utils
+
+
+def run_migration_with_file_transfer(test, params, env):
+    """
+    KVM migration test:
+    1) Get a live VM and clone it.
+    2) Verify that the source VM supports migration.  If it does, proceed with
+            the test.
+    3) Reboot the VM
+    4) Send a migration command to the source VM and wait until it's finished.
+    5) Kill off the source VM.
+    6) Log into the destination VM after the migration is finished.
+
+    @param test: kvm test object.
+    @param params: Dictionary with test parameters.
+    @param env: Dictionary with the test environment.
+    """
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = int(params.get("login_timeout", 360))
+    session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
+
+    mig_timeout = float(params.get("mig_timeout", "3600"))
+    mig_protocol = params.get("migration_protocol", "tcp")
+
+    # params of transfer test
+    username = vm.params.get("username", "")
+    password = vm.params.get("password", "")
+    client = vm.params.get("file_transfer_client")
+    address = vm.get_address(0)
+    port = vm.get_port(int(params.get("file_transfer_port")))
+    log_filename = ("migration-transfer-%s-to-%s-%s.log" %
+                    (vm.name, address,
+                     kvm_utils.generate_random_string(4)))
+    host_path = "/tmp/file-%s" % kvm_utils.generate_random_string(6)
+    host_path_returned = "%s-returned" % host_path
+    guest_path = params.get("guest_path", "/tmp/file")
+    file_size = params.get("file_size", "500")
+    transfer_timeout = int(params.get("transfer_timeout", "240"))
+
+    try:
+        utils.run("dd if=/dev/urandom of=%s bs=1M count=%s" % (host_path,
+                                                               file_size))
+
+        logging.info("Transferring file from host to guest")
+        bg = kvm_utils.Thread(kvm_utils.copy_files_to,
+                              (address, client, username, password, port,
+                               host_path, guest_path, log_filename,
+                               transfer_timeout))
+        bg.start()
+        try:
+            while bg.is_alive():
+                logging.info("File transfer not ended, starting a round of "
+                             "migration...")
+                vm = kvm_test_utils.migrate(vm, env, mig_timeout, mig_protocol)
+        finally:
+            # bg.join() returns the value returned by copy_files_to()
+            if not bg.join():
+                raise error.TestFail("File transfer from host to guest failed")
+
+        logging.info("Transferring file back from guest to host")
+        bg = kvm_utils.Thread(kvm_utils.copy_files_from,
+                              (address, client, username, password, port,
+                               host_path_returned, guest_path, log_filename,
+                               transfer_timeout))
+        bg.start()
+        try:
+            while bg.is_alive():
+                logging.info("File transfer not ended, starting a round of "
+                             "migration...")
+                vm = kvm_test_utils.migrate(vm, env, mig_timeout, mig_protocol)
+        finally:
+            if not bg.join():
+                raise error.TestFail("File transfer from guest to host failed")
+
+        # Make sure the returned file is indentical to the original one
+        orig_hash = client_utils.hash_file(host_path)
+        returned_hash = client_utils.hash_file(host_path_returned)
+        if orig_hash != returned_hash:
+            raise error.TestFail("Returned file hash (%s) differs from "
+                                 "original one (%s)" % (returned_hash,
+                                                        orig_hash))
+
+    finally:
+        session.close()
+        if os.path.isfile(host_path):
+            os.remove(host_path)
+        if os.path.isfile(host_path_returned):
+            os.remove(host_path_returned)
diff --git a/client/tests/kvm/tests/migration_with_reboot.py b/client/tests/kvm/tests/migration_with_reboot.py
new file mode 100644
index 0000000..af5de64
--- /dev/null
+++ b/client/tests/kvm/tests/migration_with_reboot.py
@@ -0,0 +1,85 @@
+import logging, time
+import threading
+from autotest_lib.client.common_lib import error
+import kvm_subprocess, kvm_utils, kvm_test_utils
+
+
+def run_migration_with_reboot(test, params, env):
+    """
+    KVM migration test:
+    1) Get a live VM and clone it.
+    2) Verify that the source VM supports migration.  If it does, proceed with
+            the test.
+    3) Reboot the VM
+    4) Send a migration command to the source VM and wait until it's finished.
+    5) Kill off the source VM.
+    6) Log into the destination VM after the migration is finished.
+
+    @param test: kvm test object.
+    @param params: Dictionary with test parameters.
+    @param env: Dictionary with the test environment.
+    """
+    def reboot_test(client, session, address, reboot_command, port, username,
+                    password, prompt, linesep, log_filename, timeout):
+        """
+        A version of reboot test which is safe to be called in the background as
+        it doesn't need a VM object.
+        """
+        # Send a reboot command to the guest's shell
+        session.sendline(reboot_command)
+        logging.info("Reboot command sent. Waiting for guest to go down...")
+
+        # Wait for the session to become unresponsive and close it
+        if not kvm_utils.wait_for(lambda: not session.is_responsive(timeout=30),
+                                  120, 0, 1):
+            raise error.TestFail("Guest refuses to go down")
+        session.close()
+
+        # Try logging into the guest until timeout expires
+        logging.info("Guest is down. Waiting for it to go up again, timeout "
+                     "%ds", timeout)
+        session = kvm_utils.wait_for(
+            lambda: kvm_utils.remote_login(client, address, port, username,
+                                           password, prompt, linesep,
+                                           log_filename), timeout, 0, 2)
+        if not session:
+            raise error.TestFail("Could not log into guest after reboot")
+        logging.info("Guest is up again")
+        session.close()
+
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = int(params.get("login_timeout", 360))
+    session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
+
+    # params of reboot
+    username = vm.params.get("username", "")
+    password = vm.params.get("password", "")
+    prompt = vm.params.get("shell_prompt", "[\#\$]")
+    linesep = eval("'%s'" % vm.params.get("shell_linesep", r"\n"))
+    client = vm.params.get("shell_client")
+    address = vm.get_address(0)
+    port = vm.get_port(int(params.get("shell_port")))
+    log_filename = ("migration-reboot-%s-%s.log" %
+                    (vm.name, kvm_utils.generate_random_string(4)))
+    reboot_command = vm.params.get("reboot_command")
+
+    mig_timeout = float(params.get("mig_timeout", "3600"))
+    mig_protocol = params.get("migration_protocol", "tcp")
+    mig_cancel = bool(params.get("mig_cancel"))
+
+    try:
+        # Reboot the VM in the background
+        bg = kvm_utils.Thread(reboot_test, (client, session, address,
+                                            reboot_command, port, username,
+                                            password, prompt, linesep,
+                                            log_filename, timeout))
+        bg.start()
+
+        try:
+            while bg.is_alive():
+                vm = kvm_test_utils.migrate(vm, env, mig_timeout, mig_protocol)
+        finally:
+            bg.join()
+
+    finally:
+        session.close()
diff --git a/client/tests/kvm/tests/module_probe.py b/client/tests/kvm/tests/module_probe.py
new file mode 100644
index 0000000..661ddca
--- /dev/null
+++ b/client/tests/kvm/tests/module_probe.py
@@ -0,0 +1,57 @@
+import re, commands, logging, os
+from autotest_lib.client.common_lib import error, utils
+import kvm_subprocess, kvm_test_utils, kvm_utils, installer
+
+
+def run_module_probe(test, params, env):
+    """
+    load/unload KVM modules several times.
+
+    The test can run in two modes:
+
+    - based on previous 'build' test: in case KVM modules were installed by a
+      'build' test, we used the modules installed by the previous test.
+
+    - based on own params: if no previous 'build' test was run,
+      we assume a pre-installed KVM module. Some parameters that
+      work for the 'build' can be used, then, such as 'extra_modules'.
+    """
+
+    installer_object = env.previous_installer()
+    if installer_object is None:
+        installer_object = installer.PreInstalledKvm()
+        installer_object.set_install_params(test, params)
+
+    logging.debug('installer object: %r', installer_object)
+
+    mod_str = params.get("mod_list")
+    if mod_str:
+        mod_list = re.split("[, ]", mod_str)
+        logging.debug("mod list will be: %r", mod_list)
+    else:
+        mod_list = installer_object.full_module_list()
+        logging.debug("mod list from installer: %r", mod_list)
+
+    # unload the modules before starting:
+    installer_object._unload_modules(mod_list)
+
+    load_count = int(params.get("load_count", 100))
+    try:
+        for i in range(load_count):
+            try:
+                installer_object.load_modules(mod_list)
+            except Exception,e:
+               raise error.TestFail("Failed to load modules [%r]: %s" %
+                                    (installer_object.full_module_list, e))
+
+            # unload using rmmod directly because utils.unload_module() (used by
+            # installer) does too much (runs lsmod, checks for dependencies),
+            # and we want to run the loop as fast as possible.
+            for mod in reversed(mod_list):
+                r = utils.system("rmmod %s" % (mod), ignore_status=True)
+                if r <> 0:
+                   raise error.TestFail("Failed to unload module %s. "
+                                        "exit status: %d" % (mod, r))
+    finally:
+        installer_object.load_modules()
+
diff --git a/client/tests/kvm/tests/multicast.py b/client/tests/kvm/tests/multicast.py
index a47779a..2a12b4f 100644
--- a/client/tests/kvm/tests/multicast.py
+++ b/client/tests/kvm/tests/multicast.py
@@ -1,7 +1,7 @@
 import logging, os, re
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
-import kvm_test_utils
+import kvm_test_utils, kvm_subprocess
 
 
 def run_multicast(test, params, env):
@@ -23,10 +23,10 @@
                                   timeout=int(params.get("login_timeout", 360)))
 
     def run_guest(cmd):
-        s, o = session.get_command_status_output(cmd)
-        if s:
-            logging.warning('Command %s executed in guest returned exit code '
-                            '%s, output: %s', cmd, s, o.strip())
+        try:
+            session.cmd(cmd)
+        except kvm_subprocess.ShellError, e:
+            logging.warn(e)
 
     def run_host_guest(cmd):
         run_guest(cmd)
@@ -56,8 +56,8 @@
     mcast_path = os.path.join(test.bindir, "scripts/join_mcast.py")
     if not vm.copy_files_to(mcast_path, "/tmp"):
         raise error.TestError("Fail to copy %s to guest" % mcast_path)
-    output = session.get_command_output("python /tmp/join_mcast.py %d %s %d" %
-                                        (mgroup_count, prefix, suffix))
+    output = session.cmd_output("python /tmp/join_mcast.py %d %s %d" %
+                                (mgroup_count, prefix, suffix))
 
     # if success to join multicast, the process will be paused, and return PID.
     try:
@@ -86,6 +86,6 @@
                                      (s, o))
 
     finally:
-        logging.debug(session.get_command_output("ipmaddr show"))
-        session.get_command_output("kill -s SIGCONT %s" % pid)
+        logging.debug(session.cmd_output("ipmaddr show"))
+        session.cmd_output("kill -s SIGCONT %s" % pid)
         session.close()
diff --git a/client/tests/kvm/tests/netperf.py b/client/tests/kvm/tests/netperf.py
index dc21e0f..7c341fa 100644
--- a/client/tests/kvm/tests/netperf.py
+++ b/client/tests/kvm/tests/netperf.py
@@ -1,7 +1,7 @@
 import logging, commands, os
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
-import kvm_test_utils
+import kvm_test_utils, kvm_subprocess
 
 def run_netperf(test, params, env):
     """
@@ -26,20 +26,19 @@
     result_file = os.path.join(test.resultsdir, "output_%s" % test.iteration)
 
     firewall_flush = "iptables -F"
-    session.get_command_output(firewall_flush)
+    session.cmd_output(firewall_flush)
 
     for i in params.get("netperf_files").split():
         if not vm.copy_files_to(os.path.join(netperf_dir, i), "/tmp"):
             raise error.TestError("Could not copy file %s to guest" % i)
 
-    if session.get_command_status(firewall_flush):
+    try:
+        session.cmd(firewall_flush)
+    except kvm_subprocess.ShellError:
         logging.warning("Could not flush firewall rules on guest")
 
-    if session.get_command_status(setup_cmd % "/tmp", timeout=200):
-        raise error.TestFail("Fail to setup netperf on guest")
-
-    if session.get_command_status(params.get("netserver_cmd") % "/tmp"):
-        raise error.TestFail("Fail to start netperf server on guest")
+    session.cmd(setup_cmd % "/tmp", timeout=200)
+    session.cmd(params.get("netserver_cmd") % "/tmp")
 
     try:
         logging.info("Setup and run netperf client on host")
@@ -49,15 +48,18 @@
         result.write("Netperf test results\n")
 
         for i in params.get("protocols").split():
-            cmd = params.get("netperf_cmd") % (netperf_dir, i, guest_ip)
-            logging.info("Netperf: protocol %s", i)
-            try:
-                netperf_output = utils.system_output(cmd,
-                                                     retain_output=True)
-                result.write("%s\n" % netperf_output)
-            except:
-                logging.error("Test of protocol %s failed", i)
-                list_fail.append(i)
+            packet_size = params.get("packet_size", "1500")
+            for size in packet_size.split():
+                cmd = params.get("netperf_cmd") % (netperf_dir, i,
+                                                   guest_ip, size)
+                logging.info("Netperf: protocol %s", i)
+                try:
+                    netperf_output = utils.system_output(cmd,
+                                                         retain_output=True)
+                    result.write("%s\n" % netperf_output)
+                except:
+                    logging.error("Test of protocol %s failed", i)
+                    list_fail.append(i)
 
         result.close()
 
@@ -66,5 +68,5 @@
                                  ", ".join(list_fail))
 
     finally:
-        session.get_command_output("killall netserver")
+        session.cmd_output("killall netserver")
         session.close()
diff --git a/client/tests/kvm/tests/nic_bonding.py b/client/tests/kvm/tests/nic_bonding.py
new file mode 100644
index 0000000..087b099
--- /dev/null
+++ b/client/tests/kvm/tests/nic_bonding.py
@@ -0,0 +1,54 @@
+import logging, time, threading
+from autotest_lib.client.common_lib import error
+from tests import file_transfer
+import kvm_test_utils, kvm_utils
+
+def run_nic_bonding(test, params, env):
+    """
+    Nic bonding test in guest.
+
+    1) Start guest with four nic models.
+    2) Setup bond0 in guest by script bonding_setup.py.
+    3) Execute file transfer test between guest and host.
+    4) Repeatedly put down/up interfaces by set_link
+    5) Execute file transfer test between guest and host.
+
+    @param test: Kvm test object.
+    @param params: Dictionary with the test parameters.
+    @param env: Dictionary with test environment.
+    """
+    def control_link_loop(vm, termination_event):
+        logging.info("Repeatedly put down/up interfaces by set_link")
+        while True:
+            for i in range(len(params.get("nics").split())):
+                linkname = "%s.%s" % (params.get("nic_model"), i)
+                cmd = "set_link %s down" % linkname
+                vm.monitor.cmd(cmd)
+                time.sleep(1)
+                cmd = "set_link %s up" % linkname
+                vm.monitor.cmd(cmd)
+            if termination_event.isSet():
+                break
+
+    timeout = int(params.get("login_timeout", 1200))
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    session_serial = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2,
+                                                   serial=True)
+    script_path = kvm_utils.get_path(test.bindir, "scripts/bonding_setup.py")
+    vm.copy_files_to(script_path, "/tmp/bonding_setup.py")
+    cmd = "python /tmp/bonding_setup.py %s" % vm.get_mac_address()
+    session_serial.cmd(cmd)
+
+    termination_event = threading.Event()
+    t = threading.Thread(target=control_link_loop,
+                         args=(vm, termination_event))
+    try:
+        logging.info("Do some basic test before testing high availability")
+        file_transfer.run_file_transfer(test, params, env)
+        t.start()
+        logging.info("Do file transfer testing")
+        file_transfer.run_file_transfer(test, params, env)
+    finally:
+        termination_event.set()
+        t.join(10)
+        session_serial.close()
diff --git a/client/tests/kvm/tests/nic_hotplug.py b/client/tests/kvm/tests/nic_hotplug.py
new file mode 100644
index 0000000..edfa980
--- /dev/null
+++ b/client/tests/kvm/tests/nic_hotplug.py
@@ -0,0 +1,143 @@
+import logging, os, commands, re, time
+from autotest_lib.client.common_lib import error
+import kvm_subprocess, kvm_test_utils, kvm_utils, kvm_vm
+
+
+def run_nic_hotplug(test, params, env):
+    """
+    Test hotplug of NIC devices
+
+    1) Boot up guest with one nic
+    2) Add a host network device through monitor cmd and check if it's added
+    3) Add nic device through monitor cmd and check if it's added
+    4) Check if new interface gets ip address
+    5) Disable primary link of guest
+    6) Ping guest new ip from host
+    7) Delete nic device and netdev
+    8) Re-enable primary link of guest
+
+    @param test:   KVM test object.
+    @param params: Dictionary with the test parameters.
+    @param env:    Dictionary with test environment.
+    """
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = int(params.get("login_timeout", 360))
+    guest_delay = int(params.get("guest_delay", 20))
+    session_serial = kvm_test_utils.wait_for_login(vm, timeout=timeout,
+                                                   serial=True)
+
+    # Modprobe the module if specified in config file
+    module = params.get("modprobe_module")
+    if module:
+        session_serial.get_command_output("modprobe %s" % module)
+
+    def netdev_add(vm):
+        netdev_id = kvm_utils.generate_random_id()
+        attach_cmd = ("netdev_add tap,id=%s,script=%s" %
+                      (netdev_id, kvm_utils.get_path(vm.root_dir,
+                                                     params.get("nic_script"))))
+        netdev_extra_params = params.get("netdev_extra_params")
+        if netdev_extra_params:
+            attach_cmd += ",%s" % netdev_extra_params
+        logging.info("Adding netdev through %s" % attach_cmd)
+        vm.monitor.cmd(attach_cmd)
+
+        network = vm.monitor.info("network")
+        if netdev_id not in network:
+            logging.error(network)
+            raise error.TestError("Fail to add netdev: %s" % netdev_id)
+        else:
+            return netdev_id
+
+    def netdev_del(vm, id):
+        vm.monitor.cmd("netdev_del %s" % id)
+
+        network = vm.monitor.info("network")
+        if id in network:
+            logging.error(network)
+            raise error.TestError("Fail to remove netdev %s" % id)
+
+    def nic_add(vm, model, netdev_id, mac):
+        """
+        Add a nic to virtual machine
+
+        @vm: VM object
+        @model: nic model
+        @netdev_id: id of netdev
+        @mac: Mac address of new nic
+        """
+        id = kvm_utils.generate_random_id()
+        if model=="virtio": model="virtio-net-pci"
+        device_add_cmd = "device_add %s,netdev=%s,mac=%s,id=%s" % (model,
+                                                                   netdev_id,
+                                                                   mac, id)
+        logging.info("Adding nic through %s" % device_add_cmd)
+        vm.monitor.cmd(device_add_cmd)
+
+        qdev = vm.monitor.info("qtree")
+        if id not in qdev:
+            logging.error(qdev)
+            raise error.TestFail("Device %s was not plugged into qdev"
+                                 "tree" % id)
+        else:
+            return id
+
+    def nic_del(vm, id, wait=True):
+        """
+        Remove the nic from pci tree.
+
+        @vm: VM object
+        @id: the nic id
+        @wait: Whether need to wait for the guest to unplug the device
+        """
+        nic_del_cmd = "device_del %s" % id
+        vm.monitor.cmd(nic_del_cmd)
+        if wait:
+            logging.info("waiting for the guest to finish the unplug")
+            if not kvm_utils.wait_for(lambda: id not in
+                                      vm.monitor.info("qtree"),
+                                      guest_delay, 5 ,1):
+                logging.error(vm.monitor.info("qtree"))
+                raise error.TestError("Device is not unplugged by "
+                                      "guest, please check whether the "
+                                      "hotplug module was loaded in guest");
+
+    logging.info("Attach a virtio nic to vm")
+    mac = kvm_utils.generate_mac_address(vm.instance, 1)
+    if not mac:
+        mac = "00:00:02:00:00:02"
+    netdev_id = netdev_add(vm)
+    device_id = nic_add(vm, "virtio", netdev_id, mac)
+
+    if "Win" not in params.get("guest_name", ""):
+        session_serial.sendline("dhclient %s &" %
+                         kvm_test_utils.get_linux_ifname(session_serial, mac))
+
+    logging.info("Shutting down the primary link")
+    vm.monitor.cmd("set_link %s down" % vm.netdev_id[0])
+
+    try:
+        logging.info("Waiting for new nic's ip address acquisition...")
+        if not kvm_utils.wait_for(lambda: (vm.address_cache.get(mac) is
+                                           not None), 10, 1):
+            raise error.TestFail("Could not get ip address of new nic")
+        ip = vm.address_cache.get(mac)
+        if not kvm_utils.verify_ip_address_ownership(ip, mac):
+            raise error.TestFail("Could not verify the ip address of new nic")
+        else:
+            logging.info("Got the ip address of new nic: %s" % ip)
+
+        logging.info("Ping test the new nic ...")
+        s, o = kvm_test_utils.ping(ip, 100)
+        if s != 0:
+            logging.error(o)
+            raise error.TestFail("New nic failed ping test")
+
+        logging.info("Detaching a virtio nic from vm")
+        nic_del(vm, device_id)
+        netdev_del(vm,netdev_id)
+
+    finally:
+        vm.free_mac_address(1)
+        logging.info("Re-enabling the primary link")
+        vm.monitor.cmd("set_link %s up" % vm.netdev_id[0])
diff --git a/client/tests/kvm/tests/nic_promisc.py b/client/tests/kvm/tests/nic_promisc.py
index 99bbf8c..f4bf1e4 100644
--- a/client/tests/kvm/tests/nic_promisc.py
+++ b/client/tests/kvm/tests/nic_promisc.py
@@ -1,4 +1,4 @@
-import logging
+import logging, threading
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
 import kvm_utils, kvm_test_utils
@@ -21,21 +21,12 @@
     timeout = int(params.get("login_timeout", 360))
     vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
-
-    logging.info("Trying to log into guest '%s' by serial", vm.name)
-    session2 = kvm_utils.wait_for(lambda: vm.serial_login(),
-                                  timeout, 0, step=2)
-    if not session2:
-        raise error.TestFail("Could not log into guest '%s'" % vm.name)
+    session_serial = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2,
+                                                   serial=True)
 
     def compare(filename):
-        cmd = "md5sum %s" % filename
         md5_host = utils.hash_file(filename, method="md5")
-        rc_guest, md5_guest = session.get_command_status_output(cmd)
-        if rc_guest:
-            logging.debug("Could not get MD5 hash for file %s on guest,"
-                          "output: %s", filename, md5_guest)
-            return False
+        md5_guest = session.cmd("md5sum %s" % filename)
         md5_guest = md5_guest.split()[0]
         if md5_host != md5_guest:
             logging.error("MD5 hash mismatch between file %s "
@@ -46,11 +37,28 @@
         return True
 
     ethname = kvm_test_utils.get_linux_ifname(session, vm.get_mac_address(0))
-    set_promisc_cmd = ("ip link set %s promisc on; sleep 0.01;"
-                       "ip link set %s promisc off; sleep 0.01" %
-                       (ethname, ethname))
-    logging.info("Set promisc change repeatedly in guest")
-    session2.sendline("while true; do %s; done" % set_promisc_cmd)
+
+    class ThreadPromiscCmd(threading.Thread):
+        def __init__(self, session, termination_event):
+            self.session = session
+            self.termination_event = termination_event
+            super(ThreadPromiscCmd, self).__init__()
+
+
+        def run(self):
+            set_promisc_cmd = ("ip link set %s promisc on; sleep 0.01;"
+                               "ip link set %s promisc off; sleep 0.01" %
+                               (ethname, ethname))
+            while True:
+                self.session.cmd_output(set_promisc_cmd)
+                if self.termination_event.isSet():
+                    break
+
+
+    logging.info("Started thread to change promisc mode in guest")
+    termination_event = threading.Event()
+    promisc_thread = ThreadPromiscCmd(session_serial, termination_event)
+    promisc_thread.start()
 
     dd_cmd = "dd if=/dev/urandom of=%s bs=%d count=1"
     filename = "/tmp/nic_promisc_file"
@@ -72,10 +80,7 @@
                 success_counter += 1
 
             logging.info("Create %s bytes file on guest" % size)
-            if session.get_command_status(dd_cmd % (filename, int(size)),
-                                                    timeout=100) != 0:
-                logging.error("Create file on guest failed")
-                continue
+            session.cmd(dd_cmd % (filename, int(size)), timeout=100)
 
             logging.info("Transfer file from guest to host")
             if not vm.copy_files_from(filename, filename):
@@ -90,12 +95,14 @@
             logging.info("Clean temporary files")
             cmd = "rm -f %s" % filename
             utils.run(cmd)
-            session.get_command_status(cmd)
+            session.cmd_output(cmd)
 
     finally:
+        logging.info("Stopping the promisc thread")
+        termination_event.set()
+        promisc_thread.join(10)
         logging.info("Restore the %s to the nonpromisc mode", ethname)
-        session2.close()
-        session.get_command_status("ip link set %s promisc off" % ethname)
+        session.cmd_output("ip link set %s promisc off" % ethname)
         session.close()
 
     if success_counter != 2 * len(file_size):
diff --git a/client/tests/kvm/tests/nicdriver_unload.py b/client/tests/kvm/tests/nicdriver_unload.py
index 47318ba..a515d67 100644
--- a/client/tests/kvm/tests/nicdriver_unload.py
+++ b/client/tests/kvm/tests/nicdriver_unload.py
@@ -20,17 +20,12 @@
     timeout = int(params.get("login_timeout", 360))
     vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
     session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
-    logging.info("Trying to log into guest '%s' by serial", vm.name)
-    session2 = kvm_utils.wait_for(lambda: vm.serial_login(),
-                                  timeout, 0, step=2)
-    if not session2:
-        raise error.TestFail("Could not log into guest '%s'" % vm.name)
+    session_serial = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2,
+                                                   serial=True)
 
     ethname = kvm_test_utils.get_linux_ifname(session, vm.get_mac_address(0))
     sys_path = "/sys/class/net/%s/device/driver" % (ethname)
-    s, o = session.get_command_status_output('readlink -e %s' % sys_path)
-    if s:
-        raise error.TestError("Could not find driver name")
+    o = session.cmd("readlink -e %s" % sys_path)
     driver = os.path.basename(o.strip())
     logging.info("driver is %s", driver)
 
@@ -45,12 +40,8 @@
                 logging.debug("Failed to transfer file %s", remote_file)
 
     def compare(origin_file, receive_file):
-        cmd = "md5sum %s"
         check_sum1 = utils.hash_file(origin_file, method="md5")
-        s, output2 = session.get_command_status_output(cmd % receive_file)
-        if s != 0:
-            logging.error("Could not get md5sum of receive_file")
-            return False
+        output2 = session.cmd("md5sum %s" % receive_file)
         check_sum2 = output2.strip().split()[0]
         logging.debug("original file md5: %s, received file md5: %s",
                       check_sum1, check_sum2)
@@ -77,9 +68,11 @@
         logging.info("Unload/load NIC driver repeatedly in guest...")
         while True:
             logging.debug("Try to unload/load nic drive once")
-            if session2.get_command_status(unload_load_cmd, timeout=120) != 0:
-                session.get_command_output("rm -rf /tmp/Thread-*")
-                raise error.TestFail("Unload/load nic driver failed")
+            try:
+                session_serial.cmd(unload_load_cmd, timeout=120)
+            except:
+                session.cmd_output("rm -rf /tmp/Thread-*")
+                raise
             pid, s = os.waitpid(pid, os.WNOHANG)
             status = os.WEXITSTATUS(s)
             if (pid, status) != (0, 0):
@@ -96,7 +89,6 @@
             t.join(timeout = scp_timeout)
         os._exit(0)
 
-    session2.close()
 
     try:
         logging.info("Check MD5 hash for received files in multi-session")
@@ -111,5 +103,5 @@
             raise error.TestFail("Test nic function after load/unload fail")
 
     finally:
-        session.get_command_output("rm -rf /tmp/Thread-*")
+        session.cmd_output("rm -rf /tmp/Thread-*")
         session.close()
diff --git a/client/tests/kvm/tests/pci_hotplug.py b/client/tests/kvm/tests/pci_hotplug.py
index 55cf666..c85ff62 100644
--- a/client/tests/kvm/tests/pci_hotplug.py
+++ b/client/tests/kvm/tests/pci_hotplug.py
@@ -26,14 +26,13 @@
     # Modprobe the module if specified in config file
     module = params.get("modprobe_module")
     if module:
-        if session.get_command_status("modprobe %s" % module):
-            raise error.TestError("Modprobe module '%s' failed" % module)
+        session.cmd("modprobe %s" % module)
 
     # Get output of command 'info pci' as reference
     info_pci_ref = vm.monitor.info("pci")
 
     # Get output of command as reference
-    reference = session.get_command_output(params.get("reference_cmd"))
+    reference = session.cmd_output(params.get("reference_cmd"))
 
     tested_model = params.get("pci_model")
     test_type = params.get("pci_type")
@@ -48,11 +47,24 @@
     else:
         raise error.TestError("Unknow version of qemu")
 
+    # Determine syntax of drive hotplug
+    # __com.redhat_drive_add == qemu-kvm-0.12 on RHEL 6
+    if len(re.findall("\n__com.redhat_drive_add", cmd_output)) > 0:
+        drive_cmd_type = "__com.redhat_drive_add"
+    # drive_add == qemu-kvm-0.13 onwards
+    elif len(re.findall("\ndrive_add", cmd_output)) > 0:
+        drive_cmd_type = "drive_add"
+    else:
+        raise error.TestError("Unknow version of qemu")
+
+    # Probe qemu for a list of supported devices
+    devices_support = vm.monitor.cmd("%s ?" % cmd_type)
+
     if cmd_type == "pci_add":
         if test_type == "nic":
             pci_add_cmd = "pci_add pci_addr=auto nic model=%s" % tested_model
         elif test_type == "block":
-            image_params = kvm_utils.get_sub_dict(params, "stg")
+            image_params = params.object_params("stg")
             image_filename = kvm_vm.get_image_filename(image_params,
                                                        test.bindir)
             pci_add_cmd = ("pci_add pci_addr=auto storage file=%s,if=%s" %
@@ -74,24 +86,40 @@
             pci_add_cmd = "device_add id=%s,driver=%s" % (id, tested_model)
 
         elif test_type == "block":
-            image_params = kvm_utils.get_sub_dict(params, "stg")
+            image_params = params.object_params("stg")
             image_filename = kvm_vm.get_image_filename(image_params,
                                                        test.bindir)
+            controller_model = None
             if tested_model == "virtio":
                 tested_model = "virtio-blk-pci"
 
             if tested_model == "scsi":
                 tested_model = "scsi-disk"
+                controller_model = "lsi53c895a"
+                if len(re.findall(controller_model, devices_support)) == 0:
+                    raise error.TestError("scsi controller device (%s) not "
+                                          "supported by qemu" %
+                                          controller_model)
 
-            driver_add_cmd = (" __com.redhat_drive_add "
-                              "file=%s,format=%s,id=%s" %
-                              (image_filename, image_format, driver_id))
+            if controller_model is not None:
+                controller_id = "controller-" + id
+                controller_add_cmd = ("device_add %s,id=%s" %
+                                      (controller_model, controller_id))
+                controller_output = vm.monitor.cmd(controller_add_cmd)
+
+            if drive_cmd_type == "drive_add":
+                driver_add_cmd = ("drive_add auto file=%s,if=none,id=%s,format=%s" %
+                                  (image_filename, driver_id, image_format))
+            elif drive_cmd_type == "__com.redhat_drive_add":
+                driver_add_cmd = ("__com.redhat_drive_add "
+                                  "file=%s,format=%s,id=%s" %
+                                  (image_filename, image_format, driver_id))
+
             pci_add_cmd = ("device_add id=%s,driver=%s,drive=%s" %
                            (id, tested_model, driver_id))
             driver_output = vm.monitor.cmd(driver_add_cmd)
 
         # Check if the device is support in qemu
-        devices_support = vm.monitor.cmd("%s ?" % cmd_type)
         if len(re.findall(tested_model, devices_support)) > 0:
             add_output = vm.monitor.cmd(pci_add_cmd)
         else:
@@ -106,8 +134,12 @@
     # Define a helper function to delete the device
     def pci_del(ignore_failure=False):
         if cmd_type == "pci_add":
-            slot_id = "0" + add_output.split(",")[2].split()[1]
-            cmd = "pci_del pci_addr=%s" % slot_id
+            result_domain, bus, slot, function = add_output.split(',')
+            domain = int(result_domain.split()[2])
+            bus = int(bus.split()[1])
+            slot = int(slot.split()[1])
+            pci_addr = "%x:%x:%x" % (domain, bus, slot)
+            cmd = "pci_del pci_addr=%s" % pci_addr
         elif cmd_type == "device_add":
             cmd = "device_del %s" % id
         # This should be replaced by a proper monitor method call
@@ -131,7 +163,7 @@
 
         # Define a helper function to compare the output
         def new_shown():
-            o = session.get_command_output(params.get("reference_cmd"))
+            o = session.cmd_output(params.get("reference_cmd"))
             return o != reference
 
         secs = int(params.get("wait_secs_for_hook_up"))
@@ -142,7 +174,7 @@
 
         # Define a helper function to catch PCI device string
         def find_pci():
-            o = session.get_command_output(params.get("find_pci_cmd"))
+            o = session.cmd_output(params.get("find_pci_cmd"))
             return params.get("match_string") in o
 
         if not kvm_utils.wait_for(find_pci, 30, 3, 3):
@@ -152,10 +184,11 @@
                                   params.get("find_pci_cmd")))
 
         # Test the newly added device
-        s, o = session.get_command_status_output(params.get("pci_test_cmd"))
-        if s != 0:
+        try:
+            session.cmd(params.get("pci_test_cmd"))
+        except kvm_subprocess.ShellError, e:
             raise error.TestFail("Check for %s device failed after PCI "
-                                 "hotplug. Output: %r" % (test_type, o))
+                                 "hotplug. Output: %r" % (test_type, e.output))
 
         session.close()
 
diff --git a/client/tests/kvm/tests/physical_resources_check.py b/client/tests/kvm/tests/physical_resources_check.py
index 682c7b2..3234da7 100644
--- a/client/tests/kvm/tests/physical_resources_check.py
+++ b/client/tests/kvm/tests/physical_resources_check.py
@@ -51,7 +51,7 @@
     # Define a function for checking number of hard drivers & NICs
     def check_num(devices, info_cmd, check_str):
         f_fail = 0
-        expected_num = kvm_utils.get_sub_dict_names(params, devices).__len__()
+        expected_num = params.objects(devices).__len__()
         try:
             o = vm.monitor.info(info_cmd)
         except kvm_monitor.MonitorError, e:
@@ -78,9 +78,9 @@
     # Define a function for checking hard drives & NICs' model
     def chk_fmt_model(device, fmt_model, info_cmd, str):
         f_fail = 0
-        devices = kvm_utils.get_sub_dict_names(params, device)
+        devices = params.objects(device)
         for chk_device in devices:
-            expected = kvm_utils.get_sub_dict(params, chk_device).get(fmt_model)
+            expected = params.object_params(chk_device).get(fmt_model)
             if not expected:
                 expected = "rtl8139"
             try:
@@ -123,7 +123,7 @@
     found_mac_addresses = re.findall("macaddr=(\S+)", o)
     logging.debug("Found MAC adresses: %s" % found_mac_addresses)
 
-    num_nics = len(kvm_utils.get_sub_dict_names(params, "nics"))
+    num_nics = len(params.objects("nics"))
     for nic_index in range(num_nics):
         mac = vm.get_mac_address(nic_index)
         if not string.lower(mac) in found_mac_addresses:
@@ -135,7 +135,7 @@
     def verify_device(expect, name, verify_cmd):
         f_fail = 0
         if verify_cmd:
-            actual = session.get_command_output(verify_cmd)
+            actual = session.cmd_output(verify_cmd)
             if not string.upper(expect) in actual:
                 f_fail += 1
                 logging.error("%s mismatch:")
diff --git a/client/tests/kvm/tests/qemu_img.py b/client/tests/kvm/tests/qemu_img.py
index d3f7ff1..6351a84 100644
--- a/client/tests/kvm/tests/qemu_img.py
+++ b/client/tests/kvm/tests/qemu_img.py
@@ -1,6 +1,6 @@
-import re, os, logging, commands
+import re, os, logging, commands, string
 from autotest_lib.client.common_lib import utils, error
-import kvm_vm, kvm_utils
+import kvm_vm, kvm_utils, kvm_test_utils, kvm_preprocessing
 
 
 def run_qemu_img(test, params, env):
@@ -243,10 +243,124 @@
     def commit_test(cmd):
         """
         Subcommand 'qemu-img commit' test.
+        1) Create a backing file of the qemu harddisk specified by image_name.
+        2) Start a VM using the backing file as its harddisk.
+        3) Touch a file "commit_testfile" in the backing_file, and shutdown the
+           VM.
+        4) Make sure touching the file does not affect the original harddisk.
+        5) Commit the change to the original harddisk by executing
+           "qemu-img commit" command.
+        6) Start the VM using the original harddisk.
+        7) Check if the file "commit_testfile" exists.
 
         @param cmd: qemu-img base command.
         """
-        pass
+        cmd += " commit"
+
+        logging.info("Commit testing started!")
+        image_name = params.get("image_name", "image")
+        image_format = params.get("image_format", "qcow2")
+        backing_file_name = "%s_bak" % (image_name)
+
+        try:
+            # Remove the existing backing file
+            backing_file = "%s.%s" % (backing_file_name, image_format)
+            if os.path.isfile(backing_file):
+                os.remove(backing_file)
+
+            # Create the new backing file
+            create_cmd = "qemu-img create -b %s.%s -f %s %s.%s" % (image_name,
+                                                                  image_format,
+                                                                  image_format,
+                                                             backing_file_name,
+                                                                  image_format)
+            try:
+                utils.system(create_cmd)
+            except error.CmdError, e:
+                raise error.TestFail("Could not create a backing file!")
+            logging.info("backing_file created!")
+
+            # Set the qemu harddisk to the backing file
+            logging.info("Original image_name is: %s", params.get('image_name'))
+            params['image_name'] = backing_file_name
+            logging.info("Param image_name changed to: %s",
+                         params.get('image_name'))
+
+            # Start a new VM, using backing file as its harddisk
+            vm_name = params.get('main_vm')
+            kvm_preprocessing.preprocess_vm(test, params, env, vm_name)
+            vm = env.get_vm(vm_name)
+            vm.create()
+            timeout = int(params.get("login_timeout", 360))
+            session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
+
+            # Do some changes to the backing_file harddisk
+            try:
+                output = session.cmd("touch /commit_testfile")
+                logging.info("Output of touch /commit_testfile: %s", output)
+                output = session.cmd("ls / | grep commit_testfile")
+                logging.info("Output of ls / | grep commit_testfile: %s",
+                             output)
+            except Exception, e:
+                raise error.TestFail("Could not create commit_testfile in the "
+                                     "backing file %s", e)
+            vm.destroy()
+
+            # Make sure there is no effect on the original harddisk
+            # First, set the harddisk back to the original one
+            logging.info("Current image_name is: %s", params.get('image_name'))
+            params['image_name'] = image_name
+            logging.info("Param image_name reverted to: %s",
+                         params.get('image_name'))
+
+            # Second, Start a new VM, using image_name as its harddisk
+            # Here, the commit_testfile should not exist
+            vm_name = params.get('main_vm')
+            kvm_preprocessing.preprocess_vm(test, params, env, vm_name)
+            vm = env.get_vm(vm_name)
+            vm.create()
+            timeout = int(params.get("login_timeout", 360))
+            session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
+            try:
+                output = session.cmd("[ ! -e /commit_testfile ] && echo $?")
+                logging.info("Output of [ ! -e /commit_testfile ] && echo $?: "
+                             "%s", output)
+            except:
+                output = session.cmd("rm -f /commit_testfile")
+                raise error.TestFail("The commit_testfile exists on the "
+                                     "original file")
+            vm.destroy()
+
+            # Excecute the commit command
+            logging.info("Commiting image")
+            cmitcmd = "%s -f %s %s.%s" % (cmd, image_format, backing_file_name,
+                                          image_format)
+            try:
+                utils.system(cmitcmd)
+            except error.CmdError, e:
+                raise error.TestFail("Could not commit the backing file")
+
+            # Start a new VM, using image_name as its harddisk
+            vm_name = params.get('main_vm')
+            kvm_preprocessing.preprocess_vm(test, params, env, vm_name)
+            vm = env.get_vm(vm_name)
+            vm.create()
+            timeout = int(params.get("login_timeout", 360))
+            session = kvm_test_utils.wait_for_login(vm, timeout=timeout)
+            try:
+                output = session.cmd("[ -e /commit_testfile ] && echo $?")
+                logging.info("Output of [ -e /commit_testfile ] && echo $?: %s",
+                             output)
+                session.cmd("rm -f /commit_testfile")
+            except:
+                raise error.TestFail("Could not find commit_testfile after a "
+                                     "commit")
+            vm.destroy()
+
+        finally:
+            # Remove the backing file
+            if os.path.isfile(backing_file):
+                os.remove(backing_file)
 
 
     def _rebase(cmd, img_name, base_img, backing_fmt, mode="unsafe"):
diff --git a/client/tests/kvm/tests/set_link.py b/client/tests/kvm/tests/set_link.py
new file mode 100644
index 0000000..e88a1ef
--- /dev/null
+++ b/client/tests/kvm/tests/set_link.py
@@ -0,0 +1,60 @@
+import logging
+from autotest_lib.client.common_lib import error
+from tests import file_transfer
+import kvm_test_utils
+
+
+def run_set_link(test, params, env):
+    """
+    KVM guest link test:
+    1) Boot up guest with one nic
+    2) Ping guest from host
+    3) Disable guest link and ping guest from host
+    4) Re-enable guest link and ping guest from host
+    5) Do file transfer test
+
+    @param test: kvm test object
+    @param params: Dictionary with the test parameters
+    @param env: Dictionary with test environment.
+    """
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = float(params.get("login_timeout", 360))
+    session = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2)
+
+    ip = vm.get_address(0)
+    linkname = vm.netdev_id[0]
+
+    logging.info("Pinging guest from host")
+    s, o = kvm_test_utils.ping(ip, count=10, timeout=20)
+    if s != 0:
+        raise error.TestFail("Ping failed, status: %s, output: %s" % (s, o))
+    ratio = kvm_test_utils.get_loss_ratio(o)
+    if ratio != 0:
+        raise error.TestFail("Loss ratio is %s, output: %s" % (ratio, o))
+
+    logging.info("Executing 'set link %s off'" % linkname)
+    vm.monitor.cmd("set_link %s off" % linkname)
+    logging.info(vm.monitor.info("network"))
+    logging.info("Pinging guest from host")
+    s, o = kvm_test_utils.ping(ip, count=10, timeout=20)
+    if s == 0:
+        raise error.TestFail("Ping unexpectedly succeeded, status: %s,"
+                             "output: %s" % (s, o))
+    ratio = kvm_test_utils.get_loss_ratio(o)
+    if ratio != 100:
+        raise error.TestFail("Loss ratio is not 100%%,"
+                             "Loss ratio is %s" % ratio)
+
+    logging.info("Executing 'set link %s on'" % linkname)
+    vm.monitor.cmd("set_link %s on" % linkname)
+    logging.info(vm.monitor.info("network"))
+    logging.info("Pinging guest from host")
+    s, o = kvm_test_utils.ping(ip, count=10, timeout=20)
+    if s != 0:
+        raise error.TestFail("Ping failed, status: %s, output: %s" % (s, o))
+    ratio = kvm_test_utils.get_loss_ratio(o)
+    if ratio != 0:
+        raise error.TestFail("Loss ratio is %s, output: %s" % (ratio, o))
+
+    file_transfer.run_file_transfer(test, params, env)
+    session.close()
diff --git a/client/tests/kvm/tests/stepmaker.py b/client/tests/kvm/tests/stepmaker.py
index ee0ed92..9f6d9b2 100755
--- a/client/tests/kvm/tests/stepmaker.py
+++ b/client/tests/kvm/tests/stepmaker.py
@@ -337,7 +337,7 @@
 
 
 def run_stepmaker(test, params, env):
-    vm = kvm_utils.env_get_vm(env, params.get("main_vm"))
+    vm = env.get_vm(params.get("main_vm"))
     if not vm:
         raise error.TestError("VM object not found in environment")
     if not vm.is_alive():
diff --git a/client/tests/kvm/tests/steps.py b/client/tests/kvm/tests/steps.py
index 6f782f5..5d4ed25 100644
--- a/client/tests/kvm/tests/steps.py
+++ b/client/tests/kvm/tests/steps.py
@@ -181,7 +181,7 @@
 
 
 def run_steps(test, params, env):
-    vm = kvm_utils.env_get_vm(env, params.get("main_vm"))
+    vm = env.get_vm(params.get("main_vm"))
     if not vm:
         raise error.TestError("VM object not found in environment")
     if not vm.is_alive():
diff --git a/client/tests/kvm/tests/stress_boot.py b/client/tests/kvm/tests/stress_boot.py
index b7916b4..37d853b 100644
--- a/client/tests/kvm/tests/stress_boot.py
+++ b/client/tests/kvm/tests/stress_boot.py
@@ -36,7 +36,7 @@
             vm_name = "vm" + str(num)
             vm_params = vm.get_params().copy()
             curr_vm = vm.clone(vm_name, vm_params)
-            kvm_utils.env_register_vm(env, vm_name, curr_vm)
+            env.register_vm(vm_name, curr_vm)
             logging.info("Booting guest #%d" % num)
             kvm_preprocessing.preprocess_vm(tests, vm_params, env, vm_name)
             params['vms'] += " " + vm_name
@@ -51,7 +51,9 @@
 
             # check whether all previous shell sessions are responsive
             for i, se in enumerate(sessions):
-                if se.get_command_status(params.get("alive_test_cmd")) != 0:
+                try:
+                    se.cmd(params.get("alive_test_cmd"))
+                except kvm_subprocess.ShellError:
                     raise error.TestFail("Session #%d is not responsive" % i)
             num += 1
 
diff --git a/client/tests/kvm/tests/timedrift.py b/client/tests/kvm/tests/timedrift.py
index a6d3076..e5aa316 100644
--- a/client/tests/kvm/tests/timedrift.py
+++ b/client/tests/kvm/tests/timedrift.py
@@ -146,7 +146,7 @@
             restore_cpu_affinity(prev_affinity)
             # Stop the guest load
             if guest_load_stop_command:
-                session.get_command_output(guest_load_stop_command)
+                session.cmd_output(guest_load_stop_command)
             # Close all load shell sessions
             for load_session in guest_load_sessions:
                 load_session.close()
diff --git a/client/tests/kvm/tests/unittest.py b/client/tests/kvm/tests/unittest.py
index 54e5f73..c724051 100644
--- a/client/tests/kvm/tests/unittest.py
+++ b/client/tests/kvm/tests/unittest.py
@@ -36,8 +36,8 @@
                               unittest_cfg)
     logging.debug('Unit test list: %s' % test_list)
 
-    if params.get('test_list', None):
-        test_list = kvm_utils.get_sub_dict_names(params, 'test_list')
+    if params.get('test_list'):
+        test_list = params.get('test_list').split()
         logging.info('Original test list overriden by user')
         logging.info('User defined unit test list: %s' % test_list)
 
@@ -88,7 +88,7 @@
             try:
                 vm_name = params.get('main_vm')
                 kvm_preprocessing.preprocess_vm(test, params, env, vm_name)
-                vm = kvm_utils.env_get_vm(env, vm_name)
+                vm = env.get_vm(vm_name)
                 vm.create()
                 vm.monitor.cmd("cont")
                 logging.info("Waiting for unittest %s to complete, timeout %s, "
diff --git a/client/tests/kvm/tests/virtio_console.py b/client/tests/kvm/tests/virtio_console.py
index 008ec63..4b269d5 100644
--- a/client/tests/kvm/tests/virtio_console.py
+++ b/client/tests/kvm/tests/virtio_console.py
@@ -1,15 +1,16 @@
 """
 virtio_console test
 
-@copyright: Red Hat 2010
+@copyright: 2010 Red Hat, Inc.
 """
 import array, logging, os, random, re, select, shutil, socket, sys, tempfile
-import threading, time
+import threading, time, traceback
 from collections import deque
 from threading import Thread
 
 import kvm_subprocess, kvm_test_utils, kvm_utils, kvm_preprocessing
 from autotest_lib.client.common_lib import error
+from autotest_lib.client.bin import utils
 
 
 def run_virtio_console(test, params, env):
@@ -30,7 +31,224 @@
     @param params: Dictionary with the test parameters
     @param env: Dictionary with test environment
     """
-    class th_send(Thread):
+    class SubTest(object):
+        """
+        Collect result of subtest of main test.
+        """
+        def __init__(self):
+            """
+            Initialize object
+            """
+            self.result = []
+            self.passed = 0
+            self.failed = 0
+            self.cleanup_func = None
+            self.cleanup_args = None
+
+
+        def set_cleanup_func(self, func, args):
+            """
+            Set cleanup function which is called when subtest fails.
+
+            @param func: Function which should be called when test fails.
+            @param args: Arguments of cleanup function.
+            """
+            self.cleanup_func = func
+            self.cleanup_args = args
+
+
+        def do_test(self, function, args=None, fatal=False, cleanup=True):
+            """
+            Execute subtest function.
+
+            @param function: Object of function.
+            @param args: List of arguments of function.
+            @param fatal: If true exception is forwarded to main test.
+            @param cleanup: If true call cleanup function after crash of test.
+            @return: Return what returned executed subtest.
+            @raise TestError: If collapse of test is fatal raise forward
+                        exception from subtest.
+            """
+            if args == None:
+                args = []
+            res = [None, function.func_name, args]
+            try:
+                logging.debug("Start test %s." % function.func_name)
+                ret = function(*args)
+                res[0] = True
+                logging.info(self.result_to_string(res))
+                self.result.append(res)
+                self.passed += 1
+                return ret
+            except:
+                exc_type, exc_value, exc_traceback = sys.exc_info()
+                logging.error("In function (" + function.func_name + "):")
+                logging.error("Call from:\n" +
+                              traceback.format_stack()[-2][:-1])
+                logging.error("Exception from:\n" +
+                              "".join(traceback.format_exception(
+                                                        exc_type, exc_value,
+                                                        exc_traceback.tb_next)))
+                # Clean up environment after subTest crash
+                if cleanup:
+                    self.cleanup_func(*self.cleanup_args)
+                res[0] = False
+                logging.info(self.result_to_string(res))
+                self.result.append(res)
+                self.failed += 1
+                if fatal:
+                    raise
+
+
+        def is_failed(self):
+            """
+            @return: If any of subtest not pass return True.
+            """
+            if self.failed > 0:
+                return True
+            else:
+                return False
+
+
+        def get_result(self):
+            """
+            @return: Result of subtests.
+               Format:
+                 tuple(pass/fail,function_name,call_arguments)
+            """
+            return self.result
+
+
+        def result_to_string_debug(self, result):
+            """
+            @param result: Result of test.
+            """
+            sargs = ""
+            for arg in result[2]:
+                sargs += str(arg) + ","
+            sargs = sargs[:-1]
+            if result[0]:
+                status = "PASS"
+            else:
+                status = "FAIL"
+            return ("Subtest (%s(%s)): --> %s") % (result[1], sargs, status)
+
+
+        def result_to_string(self, result):
+            """
+            @param result: Result of test.
+            """
+            if result[0]:
+                status = "PASS"
+            else:
+                status = "FAIL"
+            return ("Subtest (%s): --> %s") % (result[1], status)
+
+
+        def headline(self, msg):
+            """
+            Add headline to result output.
+
+            @param msg: Test of headline
+            """
+            self.result.append([msg])
+
+
+        def _gen_res(self, format_func):
+            """
+            Format result with foramting function
+
+            @param format_func: Func for formating result.
+            """
+            result = ""
+            for res in self.result:
+                if (len(res) == 3):
+                    result += format_func(res) + "\n"
+                else:
+                    result += res[0] + "\n"
+            return result
+
+
+        def get_full_text_result(self):
+            """
+            @return string with text form of result
+            """
+            return self._gen_res(lambda str: self.result_to_string_debug(str))
+
+
+        def get_text_result(self):
+            """
+            @return string with text form of result
+            """
+            return self._gen_res(lambda str: self.result_to_string(str))
+
+
+    class Port(object):
+        """
+        Define structure to keep information about used port.
+        """
+        def __init__(self, sock, name, port_type, path):
+            """
+            @param vm: virtual machine object that port owned
+            @param sock: Socket of port if port is open.
+            @param name: Name of port for guest side.
+            @param port_type: Type of port yes = console, no= serialport.
+            @param path: Path to port on host side.
+            """
+            self.sock = sock
+            self.name = name
+            self.port_type = port_type
+            self.path = path
+            self.is_open = False
+
+
+        def for_guest(self):
+            """
+            Format data for communication with guest side.
+            """
+            return [self.name, self.port_type]
+
+
+        def open(self):
+            """
+            Open port on host side.
+            """
+            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            self.sock.connect(self.path)
+            self.is_open = True
+
+
+        def clean_port(self):
+            """
+            Clean all data from opened port on host side.
+            """
+            if self.is_open:
+                self.close()
+            self.open()
+            ret = select.select([self.sock], [], [], 1.0)
+            if ret[0]:
+                buf = self.sock.recv(1024)
+                logging.debug("Rest in socket: " + buf)
+
+
+        def close(self):
+            """
+            Close port.
+            """
+            self.sock.shutdown(socket.SHUT_RDWR)
+            self.sock.close()
+            self.is_open = False
+
+
+        def __str__(self):
+            """
+            Convert to text.
+            """
+            return ("%s,%s,%s,%s,%d" % ("Socket", self.name, self.port_type,
+                                        self.path, self.is_open))
+
+
+    class ThSend(Thread):
         """
         Random data sender thread.
         """
@@ -53,14 +271,14 @@
 
 
         def run(self):
-            logging.debug("th_send %s: run", self.getName())
+            logging.debug("ThSend %s: run", self.getName())
             while not self.exitevent.isSet():
                 self.idx += self.port.send(self.data)
-            logging.debug("th_send %s: exit(%d)", self.getName(),
+            logging.debug("ThSend %s: exit(%d)", self.getName(),
                           self.idx)
 
 
-    class th_send_check(Thread):
+    class ThSendCheck(Thread):
         """
         Random data sender thread.
         """
@@ -85,7 +303,7 @@
 
 
         def run(self):
-            logging.debug("th_send_check %s: run", self.getName())
+            logging.debug("ThSendCheck %s: run", self.getName())
             too_much_data = False
             while not self.exitevent.isSet():
                 # FIXME: workaround the problem with qemu-kvm stall when too
@@ -109,14 +327,14 @@
                         idx = self.port.send(buf)
                         buf = buf[idx:]
                         self.idx += idx
-            logging.debug("th_send_check %s: exit(%d)", self.getName(),
+            logging.debug("ThSendCheck %s: exit(%d)", self.getName(),
                           self.idx)
             if too_much_data:
-                logging.error("th_send_check: workaround the 'too_much_data'"
+                logging.error("ThSendCheck: workaround the 'too_much_data'"
                               "bug")
 
 
-    class th_recv(Thread):
+    class ThRecv(Thread):
         """
         Recieves data and throws it away.
         """
@@ -134,7 +352,7 @@
             self.blocklen = blocklen
             self.idx = 0
         def run(self):
-            logging.debug("th_recv %s: run", self.getName())
+            logging.debug("ThRecv %s: run", self.getName())
             while not self.exitevent.isSet():
                 # TODO: Workaround, it didn't work with select :-/
                 try:
@@ -142,10 +360,10 @@
                 except socket.timeout:
                     pass
             self.port.settimeout(self._port_timeout)
-            logging.debug("th_recv %s: exit(%d)", self.getName(), self.idx)
+            logging.debug("ThRecv %s: exit(%d)", self.getName(), self.idx)
 
 
-    class th_recv_check(Thread):
+    class ThRecvCheck(Thread):
         """
         Random data receiver/checker thread.
         """
@@ -165,10 +383,10 @@
 
 
         def run(self):
-            logging.debug("th_recv_check %s: run", self.getName())
+            logging.debug("ThRecvCheck %s: run", self.getName())
             while not self.exitevent.isSet():
                 ret = select.select([self.port], [], [], 1.0)
-                if ret and (not self.exitevent.isSet()):
+                if ret[0] and (not self.exitevent.isSet()):
                     buf = self.port.recv(self.blocklen)
                     if buf:
                         # Compare the recvd data with the control data
@@ -186,156 +404,13 @@
                                 for buf in self.buffer:
                                     ch_ += buf
                                 logging.error("Queue = %s", repr(ch_))
-                                raise error.TestFail("th_recv_check: incorrect "
+                                raise error.TestFail("ThRecvCheck: incorrect "
                                                      "data")
                         self.idx += len(buf)
-            logging.debug("th_recv_check %s: exit(%d)", self.getName(),
+            logging.debug("ThRecvCheck %s: exit(%d)", self.getName(),
                           self.idx)
 
 
-    class cpu_load():
-        """
-        Get average cpu load between start and get_load.
-        """
-        def __init__ (self):
-            self.old_load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-            self.startTime = 0
-            self.endTime = 0
-
-
-        def _get_cpu_load(self):
-            # Let's see if we can calc system load.
-            try:
-                f = open("/proc/stat", "r")
-                tmp = f.readlines(200)
-                f.close()
-            except:
-                logging.critical("Error reading /proc/stat")
-                error.TestFail("average_cpu_load: Error reading /proc/stat")
-
-            # 200 bytes should be enough because the information we need
-            # is typically stored in the first line
-            # Info about individual processors (not yet supported) is in
-            # the second (third, ...?) line
-            for line in tmp:
-                if line[0:4] == "cpu ":
-                    reg = re.compile('[0-9]+')
-                    load_values = reg.findall(line)
-                    # extract values from /proc/stat
-                    load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-                    for i in range(8):
-                        load[i] = int(load_values[i]) - self.old_load[i]
-
-                    for i in range(8):
-                        self.old_load[i] = int(load_values[i])
-                    return load
-
-
-        def start (self):
-            """
-            Start CPU usage measurement
-            """
-            self.old_load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-            self.startTime = time.time()
-            self._get_cpu_load()
-
-
-        def get_load(self):
-            """
-            Get and reset CPU usage
-
-            @return: return group cpu (user[%], system[%], sum[%], testTime[s])
-            """
-            self.endTime = time.time()
-            testTime = self.endTime - self.startTime
-            load = self._get_cpu_load()
-
-            user = load[0] / testTime
-            system = load[2] / testTime
-            sum = user + system
-
-            return (user, system, sum, testTime)
-
-
-    class pid_load():
-        """
-        Get average process cpu load between start and get_load
-        """
-        def __init__ (self, pid, name):
-            self.old_load = [0, 0]
-            self.startTime = 0
-            self.endTime = 0
-            self.pid = pid
-            self.name = name
-
-
-        def _get_cpu_load(self, pid):
-            # Let's see if we can calc system load.
-            try:
-                f = open("/proc/%d/stat" % (pid), "r")
-                line = f.readline()
-                f.close()
-            except:
-                logging.critical("Error reading /proc/%d/stat", pid)
-                error.TestFail("average_process_cpu_load: Error reading "
-                               "/proc/stat")
-            else:
-                reg = re.compile('[0-9]+')
-                load_values = reg.findall(line)
-                del load_values[0:11]
-                # extract values from /proc/stat
-                load = [0, 0]
-                for i in range(2):
-                    load[i] = int(load_values[i]) - self.old_load[i]
-
-                for i in range(2):
-                    self.old_load[i] = int(load_values[i])
-                return load
-
-
-        def start (self):
-            """
-            Start CPU usage measurement
-            """
-            self.old_load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-            self.startTime = time.time()
-            self._get_cpu_load(self.pid)
-
-
-        def get_load(self):
-            """
-            Get and reset CPU usage.
-
-            @return: Group cpu
-                    (pid, user[%], system[%], sum[%], testTime[s])
-            """
-            self.endTime = time.time()
-            testTime = self.endTime - self.startTime
-            load = self._get_cpu_load(self.pid)
-
-            user = load[0] / testTime
-            system = load[1] / testTime
-            sum = user + system
-
-            return (self.name, self.pid, user, system, sum, testTime)
-
-
-    def print_load(process, system):
-        """
-        Print load in tabular mode.
-
-        @param process: List of process statistic tuples.
-        @param system: Tuple of system cpu usage.
-        """
-
-        logging.info("%-10s %6s %5s %5s %5s %11s",
-                     "NAME", "PID", "USER", "SYS", "SUM", "TIME")
-        for pr in process:
-            logging.info("%-10s %6d %4.0f%% %4.0f%% %4.0f%% %10.3fs" % pr)
-        logging.info("TOTAL:     ------ %4.0f%% %4.0f%% %4.0f%% %10.3fs" %
-                     system)
-
-
     def process_stats(stats, scale=1.0):
         """
         Process and print the statistic.
@@ -352,7 +427,7 @@
         return stats
 
 
-    def init_guest(vm, timeout=2):
+    def _init_guest(vm, timeout=2):
         """
         Execute virtio_guest.py on guest, wait until it is initialized.
 
@@ -361,22 +436,23 @@
                 started properly.
         """
         logging.debug("compile virtio_guest.py on guest %s", vm[0].name)
-        vm[1].sendline("python -OO /tmp/virtio_guest.py -c &&"
+
+        (match, data) = _on_guest("python -OO /tmp/virtio_guest.py -c &&"
                        "echo -n 'PASS: Compile virtio_guest finished' ||"
-                       "echo -n 'FAIL: Compile virtio_guest failed'")
-        (match, data) = vm[1].read_until_last_line_matches(["PASS:", "FAIL:"],
-                                                           timeout)
-        if match == 1 or match is None:
+                       "echo -n 'FAIL: Compile virtio_guest failed'",
+                        vm, timeout)
+
+        if match != 0:
             raise error.TestFail("Command console_switch.py on guest %s failed."
                                  "\nreturn code: %s\n output:\n%s" %
                                  (vm[0].name, match, data))
         logging.debug("Starting virtio_guest.py on guest %s", vm[0].name)
-        vm[1].sendline("python /tmp/virtio_guest.pyo &&"
+        vm[1].sendline()
+        (match, data) = _on_guest("python /tmp/virtio_guest.pyo &&"
                        "echo -n 'PASS: virtio_guest finished' ||"
-                       "echo -n 'FAIL: virtio_guest failed'")
-        (match, data) = vm[1].read_until_last_line_matches(["PASS:", "FAIL:"],
-                                                           timeout)
-        if match == 1 or match is None:
+                       "echo -n 'FAIL: virtio_guest failed'",
+                       vm, timeout)
+        if match != 0:
             raise error.TestFail("Command console_switch.py on guest %s failed."
                                  "\nreturn code: %s\n output:\n%s" %
                                  (vm[0].name, match, data))
@@ -384,6 +460,21 @@
         time.sleep(2)
 
 
+    def init_guest(vm, consoles):
+        """
+        Prepares guest, executes virtio_guest.py and initialize for testing
+
+        @param vm: Informations about the guest.
+        @param consoles: Informations about consoles
+        """
+        conss = []
+        for mode in consoles:
+            for cons in mode:
+                conss.append(cons.for_guest())
+        _init_guest(vm, 10)
+        on_guest("virt.init(%s)" % (conss), vm, 10)
+
+
     def _on_guest(command, vm, timeout=2):
         """
         Execute given command inside the script's main loop, indicating the vm
@@ -398,9 +489,13 @@
         logging.debug("Executing '%s' on virtio_guest.py loop, vm: %s," +
                       "timeout: %s", command, vm[0].name, timeout)
         vm[1].sendline(command)
-        (match, data) = vm[1].read_until_last_line_matches(["PASS:", 
-                                                    "FAIL:[Failed to execute]"],
-                                                    timeout)
+        try:
+            (match, data) = vm[1].read_until_last_line_matches(["PASS:",
+                                                                "FAIL:"],
+                                                               timeout)
+        except (kvm_subprocess.ExpectError):
+            match = None
+            data = "Timeout."
         return (match, data)
 
 
@@ -425,29 +520,6 @@
         return (match, data)
 
 
-    def socket_readall(sock, read_timeout, mesagesize):
-        """
-        Read everything from the socket.
-
-        @param sock: Socket.
-        @param read_timeout: Read timeout.
-        @param mesagesize: Size of message.
-        """
-        sock_decriptor = sock.fileno()
-        sock.settimeout(read_timeout)
-        message = ""
-        try:
-            while (len(message) < mesagesize):
-                message += sock.recv(mesagesize)
-        except Exception as inst:
-            if (inst.args[0] == "timed out"):
-                logging.debug("Reading timeout")
-            else:
-                logging.debug(inst)
-        sock.setblocking(1)
-        return message
-
-
     def _guest_exit_threads(vm, send_pts, recv_pts):
         """
         Safely executes on_guest("virt.exit_threads()") using workaround of
@@ -463,7 +535,7 @@
             logging.debug("Workaround the stuck thread on guest")
             # Thread is stucked in read/write
             for send_pt in send_pts:
-                send_pt[0].sendall(".")
+                send_pt.sock.sendall(".")
         elif match != 0:
             # Something else
             raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s"
@@ -471,8 +543,8 @@
 
         # Read-out all remaining data
         for recv_pt in recv_pts:
-            while select.select([recv_pt[0]], [], [], 0.1)[0]:
-                recv_pt[0].recv(1024)
+            while select.select([recv_pt.sock], [], [], 0.1)[0]:
+                recv_pt.sock.recv(1024)
 
         # This will cause fail in case anything went wrong.
         on_guest("print 'PASS: nothing'", vm, 10)
@@ -482,6 +554,13 @@
         """
         Creates the VM and connects the specified number of consoles and serial
         ports.
+        Ports are allocated by 2 per 1 virtio-serial-pci device starting with
+        console. (3+2 => CC|CS|S; 0+2 => SS; 3+4 => CC|CS|SS|S, ...) This way
+        it's easy to test communication on the same or different
+        virtio-serial-pci device.
+        Further in tests the consoles are being picked always from the first
+        available one (3+2: 2xC => CC|cs|s <communication on the same PCI>;
+        2xC,1xS => CC|cS|s <communication between 2 PCI devs)
 
         @param no_console: Number of desired virtconsoles.
         @param no_serialport: Number of desired virtserialports.
@@ -494,27 +573,38 @@
         tmp_dir = tempfile.mkdtemp(prefix="virtio-console-", dir="/tmp/")
         if not params.get('extra_params'):
             params['extra_params'] = ''
-        params['extra_params'] += " -device virtio-serial"
 
-        for i in  range(0, no_console):
+        for i in range(0, no_console):
+            # Spread consoles between multiple PCI devices (2 per a dev)
+            if not i % 2:
+                pci = "virtio-serial-pci%d" % (i / 2)
+                params['extra_params'] += (" -device virtio-serial-pci,id="
+                                           + pci)
+                pci += ".0"
             params['extra_params'] += (" -chardev socket,path=%s/%d,id=vc%d,"
                                        "server,nowait" % (tmp_dir, i, i))
             params['extra_params'] += (" -device virtconsole,chardev=vc%d,"
-                                      "name=console-%d,id=c%d" % (i, i, i))
+                                      "name=console-%d,id=c%d,bus=%s"
+                                      % (i, i, i, pci))
 
         for i in  range(no_console, no_console + no_serialport):
+            # Spread seroal ports between multiple PCI devices (2 per a dev)
+            if not i % 2:
+                pci = "virtio-serial-pci%d" % (i / 2)
+                params['extra_params'] += (" -device virtio-serial-pci,id="
+                                           + pci)
+                pci += ".0"
             params['extra_params'] += (" -chardev socket,path=%s/%d,id=vs%d,"
                                        "server,nowait" % (tmp_dir, i, i))
             params['extra_params'] += (" -device virtserialport,chardev=vs%d,"
-                                       "name=serialport-%d,id=p%d" % (i, i, i))
-
+                                       "name=serialport-%d,id=p%d,bus=%s"
+                                       % (i, i, i, pci))
 
         logging.debug("Booting first guest %s", params.get("main_vm"))
         kvm_preprocessing.preprocess_vm(test, params, env,
                                         params.get("main_vm"))
 
-
-        vm = kvm_utils.env_get_vm(env, params.get("main_vm"))
+        vm = env.get_vm(params.get("main_vm"))
 
         session = kvm_test_utils.wait_for_login(vm, 0,
                                          float(params.get("boot_timeout", 240)),
@@ -522,159 +612,269 @@
 
         # connect the sockets
         for i in range(0, no_console):
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            sock.connect("%s/%d" % (tmp_dir, i))
-            consoles.append([sock, "console-%d" % i, "yes"])
+            consoles.append(Port(None ,"console-%d" % i,
+                                 "yes", "%s/%d" % (tmp_dir, i)))
         for i in range(no_console, no_console + no_serialport):
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            sock.connect("%s/%d" % (tmp_dir, i))
-            serialports.append([sock, "serialport-%d" % i, "no"])
+            serialports.append(Port(None ,"serialport-%d" % i,
+                                    "no", "%s/%d" % (tmp_dir, i)))
 
         return [vm, session, tmp_dir], [consoles, serialports]
 
 
-    def test_smoke(vm, consoles, params):
+    def topen(vm, port):
         """
-        Virtio console smoke test.
+        Open virtioconsole port.
 
-        Tests the basic functionalities (poll, read/write with and without
-        connected host, etc.
-
-        @param vm: target virtual machine [vm, session, tmp_dir]
-        @param consoles: a field of virtio ports with the minimum of 2 items
-        @param params: test parameters '$console_type:$data;...'
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port identifier.
         """
-        logging.info("Smoke test: Tests the basic capabilities of "
-                     "virtio_consoles.")
-        # PREPARE
-        for param in params.split(';'):
-            if not param:
-                continue
-            logging.info("test_smoke: params: %s", param)
-            param = param.split(':')
-            if len(param) > 1:
-                data = param[1]
-            else:
-                data = "Smoke test data"
-            param = (param[0] == 'serialport')
-            send_pt = consoles[param][0]
-            recv_pt = consoles[param][1]
-
-            # TEST
-            # Poll (OUT)
-            on_guest("virt.poll('%s', %s)" % (send_pt[1], select.POLLOUT), vm,
-                     2)
-
-            # Poll (IN, OUT)
-            send_pt[0].sendall("test")
-            for test in [select.POLLIN, select.POLLOUT]:
-                on_guest("virt.poll('%s', %s)" % (send_pt[1], test), vm, 2)
-
-            # Poll (IN HUP)
-            # I store the socket informations and close the socket
-            sock = send_pt[0]
-            send_pt[0] = sock.getpeername()
-            sock.shutdown(2)
-            sock.close()
-            del sock
-            for test in [select.POLLIN, select.POLLHUP]:
-                on_guest("virt.poll('%s', %s)" % (send_pt[1], test), vm, 2)
-
-            # Poll (HUP)
-            on_guest("virt.recv('%s', 4, 1024, False)" % (send_pt[1]), vm, 2)
-            on_guest("virt.poll('%s', %s)" % (send_pt[1], select.POLLHUP), vm,
-                     2)
-
-            # Reconnect the socket
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            sock.connect(send_pt[0])
-            send_pt[0] = sock
-            # Redefine socket in consoles
-            consoles[param][0] = send_pt
-            on_guest("virt.poll('%s', %s)" % (send_pt[1], select.POLLOUT), vm,
-                     2)
-
-            # Read/write without host connected
-            # I store the socket informations and close the socket
-            sock = send_pt[0]
-            send_pt[0] = sock.getpeername()
-            sock.shutdown(2)
-            sock.close()
-            del sock
-            # Read should pass
-            on_guest("virt.recv('%s', 0, 1024, False)" % send_pt[1], vm, 2)
-            # Write should timed-out
-            match, tmp = _on_guest("virt.send('%s', 10, False)"
-                                    % send_pt[1], vm, 2)
-            if match != None:
-                raise error.TestFail("Read on guest while host disconnected "
-                                     "didn't timed out.\nOutput:\n%s"
-                                     % tmp)
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            sock.connect(send_pt[0])
-            send_pt[0] = sock
-
-            # Redefine socket in consoles
-            consoles[param][0] = send_pt
-            if (send_pt[0].recv(1024) < 10):
-                raise error.TestFail("Didn't received data from guest")
-            # Now the _on_guest("virt.send('%s'... command should be finished
-            on_guest("print 'PASS: nothing'", vm, 2)
-
-            # Non-blocking mode
-            on_guest("virt.blocking('%s', False)" % send_pt[1], vm, 2)
-            # Recv should return FAIL with 0 received data
-            match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" %
-                                   send_pt[1], vm, 2)
-            if match == 0:
-                raise error.TestFail("Received data even when non were sent\n"
-                                     "Data:\n%s" % tmp)
-            elif match == None:
-                raise error.TestFail("Timed out, probably in blocking mode\n"
-                                     "Data:\n%s" % tmp)
-            elif match != 1:
-                raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" %
-                                     (match, tmp))
-            send_pt[0].sendall("1234567890")
-            on_guest("virt.recv('%s', 10, 1024, False)" % send_pt[1], vm, 2)
-
-            # Blocking mode
-            on_guest("virt.blocking('%s', True)" % send_pt[1], vm, 2)
-            # Recv should timed out
-            match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" %
-                                   send_pt[1], vm, 2)
-            if match == 0:
-                raise error.TestFail("Received data even when non were sent\n"
-                                     "Data:\n%s" % tmp)
-            elif match != None:
-                raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" %
-                                     (match, tmp))
-            send_pt[0].sendall("1234567890")
-            # Now guest received the data end escaped from the recv()
-            on_guest("print 'PASS: nothing'", vm, 2)
-
-            # Basic loopback test
-            on_guest("virt.loopback(['%s'], ['%s'], 1024, virt.LOOP_NONE)" %
-                     (send_pt[1], recv_pt[1]), vm, 2)
-            send_pt[0].sendall(data)
-            tmp = ""
-            i = 0
-            while i <= 10:
-                i += 1
-                ret = select.select([recv_pt[0]], [], [], 1.0)
-                if ret:
-                    tmp += recv_pt[0].recv(1024)
-                if len(tmp) >= len(data):
-                    break
-            if tmp != data:
-                raise error.TestFail("Incorrect data: '%s' != '%s'",
-                                     data, tmp)
-            _guest_exit_threads(vm, [send_pt], [recv_pt])
-
-        return consoles
+        on_guest("virt.open('%s')" % (port.name), vm, 2)
+        port.open()
 
 
-    def test_loopback(vm, consoles, params):
+    def tmulti_open(vm, port):
+        """
+        Multiopen virtioconsole port.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port identifier.
+        """
+        on_guest("virt.close('%s')" % (port.name), vm, 2)
+        on_guest("virt.open('%s')" % (port.name), vm, 2)
+        (match, data) = _on_guest("virt.open('%s')" % (port.name), vm, 2)
+        # Console is permitted to open the device multiple times
+        if port.port_type == "yes": #is console?
+            if match != 0: #Multiopen not pass
+                raise error.TestFail("Unexpected fail of opening the console"
+                                     " device for the 2nd time.\n%s" % data)
+        else:
+            if match != 1: #Multiopen not fail:
+                raise error.TestFail("Unexpetded pass of opening the"
+                                     " serialport device for the 2nd time.")
+            elif not "[Errno 24]" in data:
+                raise error.TestFail("Multiple opening fail but with another"
+                                     " exception %s" % data)
+        port.open()
+
+    def tclose(vm, port):
+        """
+        Close socket.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port to open.
+        """
+        on_guest("virt.close('%s')" % (port.name), vm, 2)
+        port.close()
+
+
+    def tpooling(vm, port):
+        """
+        Test try pooling function.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        # Poll (OUT)
+        on_guest("virt.poll('%s', %s)" % (port.name, select.POLLOUT), vm,
+                 2)
+
+        # Poll (IN, OUT)
+        port.sock.sendall("test")
+        for test in [select.POLLIN, select.POLLOUT]:
+            on_guest("virt.poll('%s', %s)" % (port.name, test), vm, 2)
+
+        # Poll (IN HUP)
+        # I store the socket informations and close the socket
+        port.close()
+        for test in [select.POLLIN, select.POLLHUP]:
+            on_guest("virt.poll('%s', %s)" % (port.name, test), vm, 2)
+
+        # Poll (HUP)
+        on_guest("virt.recv('%s', 4, 1024, False)" % (port.name), vm, 2)
+        on_guest("virt.poll('%s', %s)" % (port.name, select.POLLHUP), vm,
+                 2)
+
+        # Reconnect the socket
+        port.open()
+        # Redefine socket in consoles
+        on_guest("virt.poll('%s', %s)" % (port.name, select.POLLOUT), vm,
+                 2)
+
+
+    def tsigio(vm, port):
+        """
+        Test try sigio function.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        if port.is_open:
+            port.close()
+
+        # Enable sigio on specific port
+        on_guest("virt.async('%s', True, 0)" %
+                 (port.name) , vm, 5)
+        on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 2)
+
+        #Test sigio when port open
+        on_guest("virt.set_pool_want_return('%s', select.POLLOUT)" %
+                 (port.name), vm, 2)
+        port.open()
+        match = _on_guest("virt.get_sigio_poll_return('%s')" %
+                          (port.name) , vm, 2)[0]
+        if match == 1:
+            raise error.TestFail("Problem with HUP on console port.")
+
+        #Test sigio when port receive data
+        on_guest("virt.set_pool_want_return('%s', select.POLLOUT |"
+                 " select.POLLIN)" % (port.name), vm, 2)
+        port.sock.sendall("0123456789")
+        on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 2)
+
+        #Test sigio port close event
+        on_guest("virt.set_pool_want_return('%s', select.POLLHUP |"
+                 " select.POLLIN)" % (port.name), vm, 2)
+        port.close()
+        on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 2)
+
+        #Test sigio port open event and persistence of written data on port.
+        on_guest("virt.set_pool_want_return('%s', select.POLLOUT |"
+                 " select.POLLIN)" % (port.name), vm, 2)
+        port.open()
+        on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 2)
+
+        #Test event when erase data.
+        on_guest("virt.clean_port('%s')" % (port.name), vm, 2)
+        port.close()
+        on_guest("virt.set_pool_want_return('%s', select.POLLOUT)"
+                 % (port.name), vm, 2)
+        port.open()
+        on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 2)
+
+        # Disable sigio on specific port
+        on_guest("virt.async('%s', False, 0)" %
+                 (port.name) , vm, 5)
+
+
+    def tlseek(vm, port):
+        """
+        Tests the correct handling of lseek (expected fail)
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        # The virt.lseek returns PASS when the seek fails
+        on_guest("virt.lseek('%s', 0, 0)" % (port.name), vm, 2)
+
+
+    def trw_host_offline(vm, port):
+        """
+        Guest read/write from host when host is disconnected.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        if port.is_open:
+            port.close()
+
+        on_guest("virt.recv('%s', 0, 1024, False)" % port.name, vm, 2)
+        match, tmp = _on_guest("virt.send('%s', 10, False)"
+                                % port.name, vm, 2)
+        if match != None:
+            raise error.TestFail("Write on guest while host disconnected "
+                                 "didn't timed out.\nOutput:\n%s"
+                                 % tmp)
+
+        port.open()
+
+        if (port.sock.recv(1024) < 10):
+            raise error.TestFail("Didn't received data from guest")
+        # Now the _on_guest("virt.send('%s'... command should be finished
+        on_guest("print 'PASS: nothing'", vm, 2)
+
+
+    def trw_blocking_mode(vm, port):
+        """
+        Guest read\write data in blocking mode.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        # Blocking mode
+        if not port.is_open:
+            port.open()
+        on_guest("virt.blocking('%s', True)" % port.name, vm, 2)
+        # Recv should timed out
+        match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" %
+                               port.name, vm, 2)
+        if match == 0:
+            raise error.TestFail("Received data even when non were sent\n"
+                                 "Data:\n%s" % tmp)
+        elif match != None:
+            raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" %
+                                 (match, tmp))
+        port.sock.sendall("1234567890")
+        # Now guest received the data end escaped from the recv()
+        on_guest("print 'PASS: nothing'", vm, 2)
+
+
+    def trw_nonblocking_mode(vm, port):
+        """
+        Guest read\write data in nonblocking mode.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        # Non-blocking mode
+        if not port.is_open:
+            port.open()
+        on_guest("virt.blocking('%s', False)" % port.name, vm, 2)
+        # Recv should return FAIL with 0 received data
+        match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" %
+                              port.name, vm, 2)
+        if match == 0:
+            raise error.TestFail("Received data even when non were sent\n"
+                                 "Data:\n%s" % tmp)
+        elif match == None:
+            raise error.TestFail("Timed out, probably in blocking mode\n"
+                                 "Data:\n%s" % tmp)
+        elif match != 1:
+            raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" %
+                                 (match, tmp))
+        port.sock.sendall("1234567890")
+        on_guest("virt.recv('%s', 10, 1024, False)" % port.name, vm, 2)
+
+
+    def tbasic_loopback(vm, send_port, recv_port, data="Smoke test data"):
+        """
+        Easy loop back test with loop over only two port.
+
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param port: Port used in test.
+        """
+        if not send_port.is_open:
+            send_port.open()
+        if not recv_port.is_open:
+            recv_port.open()
+        on_guest("virt.loopback(['%s'], ['%s'], 1024, virt.LOOP_NONE)" %
+                     (send_port.name, recv_port.name), vm, 2)
+        send_port.sock.sendall(data)
+        tmp = ""
+        i = 0
+        while i <= 10:
+            i += 1
+            ret = select.select([recv_port.sock], [], [], 1.0)
+            if ret:
+                tmp += recv_port.sock.recv(1024)
+            if len(tmp) >= len(data):
+                break
+        if tmp != data:
+            raise error.TestFail("Incorrect data: '%s' != '%s'",
+                                 data, tmp)
+        _guest_exit_threads(vm, [send_port], [recv_port])
+
+
+    def tloopback(vm, consoles, params):
         """
         Virtio console loopback test.
 
@@ -682,21 +882,18 @@
         ports and sends length amount of data through this connection.
         It validates the correctness of the data sent.
 
-        @param vm: target virtual machine [vm, session, tmp_dir]
-        @param consoles: a field of virtio ports with the minimum of 2 items
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param consoles: Field of virtio ports with the minimum of 2 items.
         @param params: test parameters, multiple recievers allowed.
             '$source_console_type@buffer_length:
              $destination_console_type1@$buffer_length:...:
              $loopback_buffer_length;...'
         """
-        logging.info("Loopback test: Creates a loopback between sender port "
-                     "and receiving port, send data through this connection, "
-                     "verify data correctness.")
         # PREPARE
         for param in params.split(';'):
             if not param:
                 continue
-            logging.info("test_loopback: params: %s", param)
+            logging.info("test_loopback: params: %s" % (param))
             param = param.split(':')
             idx_serialport = 0
             idx_console = 0
@@ -730,6 +927,13 @@
             if len(buf_len) == (idx_console + idx_serialport):
                 buf_len.append(1024)
 
+            for p in recv_pts:
+                if not p.is_open:
+                    p.open()
+
+            if not send_pt.is_open:
+                send_pt.open()
+
             if len(recv_pts) == 0:
                 raise error.TestFail("test_loopback: incorrect recv consoles"
                                      "definition")
@@ -739,21 +943,22 @@
             for i in range(0, len(recv_pts)):
                 queues.append(deque())
 
-            tmp = "'%s'" % recv_pts[0][1]
+            tmp = "'%s'" % recv_pts[0].name
             for recv_pt in recv_pts[1:]:
-                tmp += ", '%s'" % (recv_pt[1])
+                tmp += ", '%s'" % (recv_pt.name)
             on_guest("virt.loopback(['%s'], [%s], %d, virt.LOOP_POLL)"
-                     % (send_pt[1], tmp, buf_len[-1]), vm, 2)
+                     % (send_pt.name, tmp, buf_len[-1]), vm, 2)
 
             exit_event = threading.Event()
 
             # TEST
-            thread = th_send_check(send_pt[0], exit_event, queues, buf_len[0])
+            thread = ThSendCheck(send_pt.sock, exit_event, queues,
+                                   buf_len[0])
             thread.start()
             threads.append(thread)
 
             for i in range(len(recv_pts)):
-                thread = th_recv_check(recv_pts[i][0], queues[i], exit_event,
+                thread = ThRecvCheck(recv_pts[i].sock, queues[i], exit_event,
                                        buf_len[i + 1])
                 thread.start()
                 threads.append(thread)
@@ -770,8 +975,8 @@
 
             # Read-out all remaining data
             for recv_pt in recv_pts:
-                while select.select([recv_pt[0]], [], [], 0.1)[0]:
-                    recv_pt[0].recv(1024)
+                while select.select([recv_pt.sock], [], [], 0.1)[0]:
+                    recv_pt.sock.recv(1024)
 
             _guest_exit_threads(vm, [send_pt], recv_pts)
 
@@ -779,23 +984,21 @@
             del threads[:]
 
 
-    def test_perf(vm, consoles, params):
+    def tperf(vm, consoles, params):
         """
         Tests performance of the virtio_console tunel. First it sends the data
         from host to guest and than back. It provides informations about
         computer utilisation and statistic informations about the troughput.
 
-        @param vm: target virtual machine [vm, session, tmp_dir]
-        @param consoles: a field of virtio ports with the minimum of 2 items
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param consoles: Field of virtio ports with the minimum of 2 items.
         @param params: test parameters:
                 '$console_type@$buffer_length:$test_duration;...'
         """
-        logging.info("Performance test: Measure performance for the "
-                     "virtio console tunnel")
         for param in params.split(';'):
             if not param:
                 continue
-            logging.info("test_perf: params: %s", param)
+            logging.info("test_perf: params: %s" % (param))
             param = param.split(':')
             duration = 60.0
             if len(param) > 1:
@@ -811,39 +1014,38 @@
             param = (param[0] == 'serialport')
             port = consoles[param][0]
 
+            if not port.is_open:
+                port.open()
+
             data = ""
             for i in range(buf_len):
                 data += "%c" % random.randrange(255)
 
             exit_event = threading.Event()
-            slice = float(duration)/100
+            slice = float(duration) / 100
 
             # HOST -> GUEST
             on_guest('virt.loopback(["%s"], [], %d, virt.LOOP_NONE)' %
-                     (port[1], buf_len), vm, 2)
-            thread = th_send(port[0], data, exit_event)
+                     (port.name, buf_len), vm, 2)
+            thread = ThSend(port.sock, data, exit_event)
             stats = array.array('f', [])
-            loads = []
-            loads.append(cpu_load())
-            loads.append(pid_load(os.getpid(), 'autotest'))
-            loads.append(pid_load(vm[0].get_pid(), 'VM'))
-
-            for load in loads:
-                load.start()
+            loads = utils.SystemLoad([(os.getpid(), 'autotest'),
+                                      (vm[0].get_pid(), 'VM'), 0])
+            loads.start()
             _time = time.time()
             thread.start()
             for i in range(100):
                 stats.append(thread.idx)
                 time.sleep(slice)
             _time = time.time() - _time - duration
-            print_load([loads[1].get_load(), loads[2].get_load()],
-                       loads[0].get_load())
+            logging.info("\n" + loads.get_cpu_status_string()[:-1])
+            logging.info("\n" + loads.get_mem_status_string()[:-1])
             exit_event.set()
             thread.join()
 
             # Let the guest read-out all the remaining data
             while not _on_guest("virt.poll('%s', %s)" %
-                                (port[1], select.POLLIN), vm, 2)[0]:
+                                (port.name, select.POLLIN), vm, 2)[0]:
                 time.sleep(1)
 
             _guest_exit_threads(vm, [port], [])
@@ -853,30 +1055,29 @@
                 "Test ran %fs longer which is more than one slice", _time)
             else:
                 logging.debug("Test ran %fs longer", _time)
-            stats = process_stats(stats[1:], slice*1048576)
+            stats = process_stats(stats[1:], slice * 1048576)
             logging.debug("Stats = %s", stats)
             logging.info("Host -> Guest [MB/s] (min/med/max) = %.3f/%.3f/%.3f",
-                         stats[0], stats[len(stats)/2], stats[-1])
+                        stats[0], stats[len(stats) / 2], stats[-1])
 
             del thread
 
             # GUEST -> HOST
             exit_event.clear()
             stats = array.array('f', [])
-            on_guest("virt.send_loop_init('%s', %d)" % (port[1], buf_len),
+            on_guest("virt.send_loop_init('%s', %d)" % (port.name, buf_len),
                      vm, 30)
-            thread = th_recv(port[0], exit_event, buf_len)
+            thread = ThRecv(port.sock, exit_event, buf_len)
             thread.start()
-            for load in loads:
-                load.start()
+            loads.start()
             on_guest("virt.send_loop()", vm, 2)
             _time = time.time()
             for i in range(100):
                 stats.append(thread.idx)
                 time.sleep(slice)
             _time = time.time() - _time - duration
-            print_load([loads[1].get_load(), loads[2].get_load()],
-                       loads[0].get_load())
+            logging.info("\n" + loads.get_cpu_status_string()[:-1])
+            logging.info("\n" + loads.get_mem_status_string()[:-1])
             on_guest("virt.exit_threads()", vm, 2)
             exit_event.set()
             thread.join()
@@ -885,37 +1086,152 @@
                 "Test ran %fs longer which is more than one slice", _time)
             else:
                 logging.debug("Test ran %fs longer" % _time)
-            stats = process_stats(stats[1:], slice*1048576)
+            stats = process_stats(stats[1:], slice * 1048576)
             logging.debug("Stats = %s", stats)
             logging.info("Guest -> Host [MB/s] (min/med/max) = %.3f/%.3f/%.3f",
-                         stats[0], stats[len(stats)/2], stats[-1])
+                         stats[0], stats[len(stats) / 2], stats[-1])
 
             del thread
-
             del exit_event
-            del loads[:]
+
+
+    def clean_ports(vm, consoles):
+        """
+        Clean state of all ports and set port to default state.
+        Default state:
+           No data on port or in port buffer.
+           Read mode = blocking.
+
+        @param consoles: Consoles which should be clean.
+        """
+        # Check if python is still alive
+        print "CLEANING"
+        match, tmp = _on_guest("is_alive()", vm, 10)
+        if (match == None) or (match != 0):
+            logging.error("Python died/is stucked/have remaining threads")
+            logging.debug(tmp)
+            vm[1].close()
+            try:
+                vm[1] = kvm_test_utils.wait_for_login(vm[0], 0,
+                                        float(params.get("boot_timeout", 5)),
+                                        0, 2)
+                on_guest("killall -9 python "
+                         "&& echo -n PASS: python killed"
+                         "|| echo -n PASS: python was death",
+                         vm, 10)
+
+                init_guest(vm, consoles)
+                on_guest("virt.clean_port('%s'),1024" % consoles[0][0].name,
+                         vm, 2)
+                on_guest("virt.close('%s'),1024" %
+                         consoles[0][0].name, vm, 2)
+
+            except (error.TestFail, kvm_subprocess.ExpectError,
+                    Exception), inst:
+                logging.error(inst)
+                logging.error("Virtio-console driver is irreparably"
+                              " blocked. Every comd end with sig KILL."
+                              "Try reboot vm for continue in testing.")
+                vm[1] = kvm_test_utils.reboot(vm[0], vm[1], "system_reset")
+                init_guest(vm, consoles)
+                match = _on_guest("virt.clean_port('%s'),1024" %
+                                      consoles[0][0].name, vm, 2)[0]
+
+                if (match == None) or (match != 0):
+                    raise error.TestFail("Virtio-console driver is irrepar"
+                                         "ably blocked. Every comd end"
+                                         " with sig KILL. Neither the "
+                                         "restart did not help.")
+
+        for ctype in consoles:
+            for port in ctype:
+                openned = port.is_open
+                port.clean_port()
+                #on_guest("virt.blocking('%s', True)" % port.name, vm, 2)
+                on_guest("virt.clean_port('%s'),1024" % port.name, vm, 5)
+                if not openned:
+                    port.close()
+                    on_guest("virt.close('%s'),1024" % port.name, vm, 2)
+
+
+    def test_smoke(test, vm, consoles, params):
+        """
+        Virtio console smoke test.
+
+        Tests the basic functionalities (poll, read/write with and without
+        connected host, etc.
+
+        @param test: Main test object.
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param consoles: Field of virtio ports with the minimum of 2 items.
+        @param params: Test parameters '$console_type:$data;...'
+        """
+        # PREPARE
+        for param in params.split(';'):
+            if not param:
+                continue
+            headline = "test_smoke: params: %s" % (param)
+            logging.info(headline)
+            param = param.split(':')
+            if len(param) > 1:
+                data = param[1]
+            else:
+                data = "Smoke test data"
+            param = (param[0] == 'serialport')
+            send_pt = consoles[param][0]
+            recv_pt = consoles[param][1]
+            test.headline(headline)
+            test.do_test(topen, [vm, send_pt], True)
+            test.do_test(tclose, [vm, send_pt], True)
+            test.do_test(tmulti_open, [vm, send_pt])
+            test.do_test(tpooling, [vm, send_pt])
+            test.do_test(tsigio, [vm, send_pt])
+            test.do_test(tlseek, [vm, send_pt])
+            test.do_test(trw_host_offline, [vm, send_pt])
+            test.do_test(trw_nonblocking_mode, [vm, send_pt])
+            test.do_test(trw_blocking_mode, [vm, send_pt])
+            test.do_test(tbasic_loopback, [vm, send_pt, recv_pt, data], True)
+
+
+    def test_multiport(test, vm, consoles, params):
+        """
+        This is group of test which test virtio_console in maximal load and
+        with multiple ports.
+
+        @param test: Main test object.
+        @param vm: Target virtual machine [vm, session, tmp_dir].
+        @param consoles: Field of virtio ports with the minimum of 2 items.
+        @param params: Test parameters '$console_type:$data;...'
+        """
+        test.headline("test_multiport:")
+        #Test Loopback
+        test.do_test(tloopback, [vm, consoles, params[0]])
+
+        #Test Performance
+        test.do_test(tperf, [vm, consoles, params[1]])
 
 
     # INITIALIZE
-    test_smoke_params = params.get('virtio_console_smoke', '')
-    test_loopback_params = params.get('virtio_console_loopback', '')
-    test_perf_params = params.get('virtio_console_perf', '')
+
+    tsmoke_params = params.get('virtio_console_smoke', '')
+    tloopback_params = params.get('virtio_console_loopback', '')
+    tperf_params = params.get('virtio_console_perf', '')
 
     no_serialports = 0
     no_consoles = 0
     # consoles required for Smoke test
-    if (test_smoke_params.count('serialport')):
+    if (tsmoke_params.count('serialport')):
         no_serialports = max(2, no_serialports)
-    if (test_smoke_params.count('console')):
+    if (tsmoke_params.count('console')):
         no_consoles = max(2, no_consoles)
     # consoles required for Loopback test
-    for param in test_loopback_params.split(';'):
+    for param in tloopback_params.split(';'):
         no_serialports = max(no_serialports, param.count('serialport'))
         no_consoles = max(no_consoles, param.count('console'))
     # consoles required for Performance test
-    if (test_perf_params.count('serialport')):
+    if (tperf_params.count('serialport')):
         no_serialports = max(1, no_serialports)
-    if (test_perf_params.count('console')):
+    if (tperf_params.count('console')):
         no_consoles = max(1, no_consoles)
 
     if (no_serialports + no_consoles) == 0:
@@ -933,19 +1249,26 @@
 
     # ACTUAL TESTING
     # Defines all available consoles; tests udev and sysfs
-    conss = []
-    for mode in consoles:
-        for cons in mode:
-            conss.append(cons[1:3])
-    init_guest(vm, 10)
-    on_guest("virt.init(%s)" % (conss), vm, 10)
 
-    consoles = test_smoke(vm, consoles, test_smoke_params)
-    test_loopback(vm, consoles, test_loopback_params)
-    test_perf(vm, consoles, test_perf_params)
+    test = SubTest()
+    try:
+        init_guest(vm, consoles)
+
+        test.set_cleanup_func(clean_ports, [vm, consoles])
+        #Test Smoke
+        test_smoke(test, vm, consoles, tsmoke_params)
+
+        #Test multiport functionality and performance.
+        test_multiport(test, vm, consoles, [tloopback_params, tperf_params])
+    finally:
+        logging.info(("Summary: %d tests passed  %d test failed :\n" %
+                      (test.passed, test.failed)) + test.get_text_result())
+
+    if test.is_failed():
+        raise error.TestFail("Virtio_console test FAILED.")
+
 
     # CLEANUP
     vm[1].close()
     vm[0].destroy(gracefully=False)
     shutil.rmtree(vm[2])
-
diff --git a/client/tests/kvm/tests/vlan.py b/client/tests/kvm/tests/vlan.py
index f41ea6a..69a136b 100644
--- a/client/tests/kvm/tests/vlan.py
+++ b/client/tests/kvm/tests/vlan.py
@@ -1,6 +1,6 @@
 import logging, time, re
 from autotest_lib.client.common_lib import error
-import kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_subprocess
 
 def run_vlan(test, params, env):
     """
@@ -35,24 +35,20 @@
     vm.append(kvm_test_utils.get_living_vm(env, "vm2"))
 
     def add_vlan(session, id, iface="eth0"):
-        if session.get_command_status("vconfig add %s %s" % (iface, id)) != 0:
-            raise error.TestError("Fail to add %s.%s" % (iface, id))
+        session.cmd("vconfig add %s %s" % (iface, id))
 
     def set_ip_vlan(session, id, ip, iface="eth0"):
         iface = "%s.%s" % (iface, id)
-        if session.get_command_status("ifconfig %s %s" % (iface, ip)) != 0:
-            raise error.TestError("Fail to configure ip for %s" % iface)
+        session.cmd("ifconfig %s %s" % (iface, ip))
 
     def set_arp_ignore(session, iface="eth0"):
         ignore_cmd = "echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore"
-        if session.get_command_status(ignore_cmd) != 0:
-            raise error.TestError("Fail to set arp_ignore of %s" % session)
+        session.cmd(ignore_cmd)
 
     def rem_vlan(session, id, iface="eth0"):
         rem_vlan_cmd = "if [[ -e /proc/net/vlan/%s ]];then vconfig rem %s;fi"
         iface = "%s.%s" % (iface, id)
-        s = session.get_command_status(rem_vlan_cmd % (iface, iface))
-        return s
+        return session.cmd_status(rem_vlan_cmd % (iface, iface))
 
     def nc_transfer(src, dst):
         nc_port = kvm_utils.find_free_port(1025, 5334, vm_ip[dst])
@@ -65,15 +61,14 @@
         time.sleep(2)
         #send file from src to dst
         send_cmd = send_cmd % (vlan_ip[dst], str(nc_port), "file")
-        if session[src].get_command_status(send_cmd, timeout = 60) != 0:
-            raise error.TestFail ("Fail to send file"
-                                    " from vm%s to vm%s" % (src+1, dst+1))
-        s, o = session[dst].read_up_to_prompt(timeout=60)
-        if s != True:
+        session[src].cmd(send_cmd, timeout=60)
+        try:
+            session[dst].read_up_to_prompt(timeout=60)
+        except kvm_subprocess.ExpectError:
             raise error.TestFail ("Fail to receive file"
                                     " from vm%s to vm%s" % (src+1, dst+1))
         #check MD5 message digest of receive file in dst
-        output = session[dst].get_command_output("md5sum receive").strip()
+        output = session[dst].cmd_output("md5sum receive").strip()
         digest_receive = re.findall(r'(\w+)', output)[0]
         if digest_receive == digest_origin[src]:
             logging.info("file succeed received in vm %s" % vlan_ip[dst])
@@ -81,7 +76,7 @@
             logging.info("digest_origin is  %s" % digest_origin[src])
             logging.info("digest_receive is %s" % digest_receive)
             raise error.TestFail("File transfered differ from origin")
-        session[dst].get_command_status("rm -f receive")
+        session[dst].cmd_output("rm -f receive")
 
     for i in range(2):
         session.append(kvm_test_utils.wait_for_login(vm[i],
@@ -97,22 +92,16 @@
 
         #produce sized file in vm
         dd_cmd = "dd if=/dev/urandom of=file bs=1024k count=%s"
-        if session[i].get_command_status(dd_cmd % file_size) != 0:
-            raise error.TestFail("File producing failed")
+        session[i].cmd(dd_cmd % file_size)
         #record MD5 message digest of file
-        s, output =session[i].get_command_status_output("md5sum file",
-                                                        timeout=60)
-        if s != 0:
-            raise error.TestFail("File MD5_checking failed" )
+        output = session[i].cmd("md5sum file", timeout=60)
         digest_origin.append(re.findall(r'(\w+)', output)[0])
 
         #stop firewall in vm
-        session[i].get_command_status("/etc/init.d/iptables stop")
+        session[i].cmd_output("/etc/init.d/iptables stop")
 
         #load 8021q module for vconfig
-        load_8021q_cmd = "modprobe 8021q"
-        if session[i].get_command_status(load_8021q_cmd) != 0:
-            raise error.TestError("Fail to load 8021q module on VM%s" % i)
+        session[i].cmd("modprobe 8021q")
 
     try:
         for i in range(2):
diff --git a/client/tests/kvm/tests/vmstop.py b/client/tests/kvm/tests/vmstop.py
new file mode 100644
index 0000000..876c3ef
--- /dev/null
+++ b/client/tests/kvm/tests/vmstop.py
@@ -0,0 +1,82 @@
+import logging, time, os
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.bin import utils
+import kvm_subprocess, kvm_test_utils, kvm_utils
+
+
+def run_vmstop(test, params, env):
+    """
+    KVM guest stop test:
+    1) Log into a guest
+    2) Copy a file into guest
+    3) Stop guest
+    4) Check the status through monitor
+    5) Check the session
+    6) Migrat the vm to a file twice and compare them.
+
+    @param test: kvm test object
+    @param params: Dictionary with the test parameters
+    @param env: Dictionary with test environment.
+    """
+    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+    timeout = float(params.get("login_timeout", 240))
+    session = kvm_test_utils.wait_for_login(vm, 0, timeout, 0, 2)
+
+    save_path = params.get("save_path", "/tmp")
+    clean_save = params.get("clean_save") == "yes"
+    save1 = os.path.join(save_path, "save1")
+    save2 = os.path.join(save_path, "save2")
+
+    guest_path = params.get("guest_path", "/tmp")
+    file_size = params.get("file_size", "1000")
+
+    try:
+        utils.run("dd if=/dev/zero of=/tmp/file bs=1M count=%s" % file_size)
+        # Transfer file from host to guest, we didn't expect the finish of
+        # transfer, we just let it to be a kind of stress in guest.
+        bg = kvm_utils.Thread(vm.copy_files_to, ("/tmp/file",
+                                                 guest_path, 0, 60))
+        logging.info("Start the background transfer")
+        bg.start()
+
+        try:
+            # wait for the transfer start
+            time.sleep(5)
+            logging.info("Stop the VM")
+            vm.monitor.cmd("stop")
+
+            # check with monitor
+            logging.info("Check the status through monitor")
+            if "paused" not in vm.monitor.info("status"):
+                raise error.TestFail("Guest did not pause after sending stop")
+
+            # check through session
+            logging.info("Check the session")
+            if session.is_responsive():
+                raise error.TestFail("Session still alive after sending stop")
+
+            # Check with the migration file
+            logging.info("Save and check the state files")
+            for p in [save1, save2]:
+                vm.save_to_file(p)
+                time.sleep(1)
+                if not os.path.isfile(p):
+                    raise error.TestFail("VM failed to save state file %s" % p)
+
+            # Fail if we see deltas
+            md5_save1 = utils.hash_file(save1)
+            md5_save2 = utils.hash_file(save2)
+            if md5_save1 != md5_save2:
+                raise error.TestFail("The produced state files differ")
+        finally:
+            bg.join()
+
+    finally:
+        session.close()
+        if clean_save:
+            logging.debug("Clean the state files")
+            if os.path.isfile(save1):
+                os.remove(save1)
+            if os.path.isfile(save2):
+                os.remove(save2)
+        vm.monitor.cmd("cont")
diff --git a/client/tests/kvm/tests/whql_client_install.py b/client/tests/kvm/tests/whql_client_install.py
index 84b91bc..c2616c6 100644
--- a/client/tests/kvm/tests/whql_client_install.py
+++ b/client/tests/kvm/tests/whql_client_install.py
@@ -13,6 +13,9 @@
     5) Move the client machine into the server's workgroup
     6) Reboot the client machine
     7) Install the DTM client software
+    8) Setup auto logon for the user created by the installation
+       (normally DTMLLUAdminUser)
+    9) Reboot again
 
     @param test: kvm test object
     @param params: Dictionary with the test parameters
@@ -29,6 +32,8 @@
                                     "Microsoft Driver Test Manager\\Studio")
     server_username = params.get("server_username")
     server_password = params.get("server_password")
+    client_username = params.get("client_username")
+    client_password = params.get("client_password")
     dsso_delete_machine_binary = params.get("dsso_delete_machine_binary",
                                             "deps/whql_delete_machine_15.exe")
     dsso_delete_machine_binary = kvm_utils.get_path(test.bindir,
@@ -50,27 +55,28 @@
     server_session = kvm_utils.remote_login("nc", server_address,
                                             server_shell_port, "", "",
                                             session.prompt, session.linesep)
+    server_session.set_status_test_command(session.status_test_command)
 
     # Get server and client information
     cmd = "echo %computername%"
-    server_name = server_session.get_command_output(cmd).strip()
-    client_name = session.get_command_output(cmd).strip()
+    server_name = server_session.cmd_output(cmd).strip()
+    client_name = session.cmd_output(cmd).strip()
     cmd = "wmic computersystem get domain"
-    server_workgroup = server_session.get_command_output(cmd).strip()
+    server_workgroup = server_session.cmd_output(cmd).strip()
     server_workgroup = server_workgroup.splitlines()[-1]
     regkey = r"HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
     cmd = "reg query %s /v Domain" % regkey
-    o = server_session.get_command_output(cmd).strip().splitlines()[-1]
+    o = server_session.cmd_output(cmd).strip().splitlines()[-1]
     try:
         server_dns_suffix = o.split(None, 2)[2]
     except IndexError:
         server_dns_suffix = ""
 
     # Delete the client machine from the server's data store (if it's there)
-    server_session.get_command_output("cd %s" % server_studio_path)
+    server_session.cmd("cd %s" % server_studio_path)
     cmd = "%s %s %s" % (os.path.basename(dsso_delete_machine_binary),
                         server_name, client_name)
-    server_session.get_command_output(cmd, print_func=logging.info)
+    server_session.cmd(cmd, print_func=logging.info)
     server_session.close()
 
     # Rename the client machine
@@ -78,21 +84,18 @@
     logging.info("Renaming client machine to '%s'" % client_name)
     cmd = ('wmic computersystem where name="%%computername%%" rename name="%s"'
            % client_name)
-    if session.get_command_status(cmd, timeout=600) != 0:
-        raise error.TestError("Could not rename the client machine")
+    session.cmd(cmd, timeout=600)
 
     # Join the server's workgroup
     logging.info("Joining workgroup '%s'" % server_workgroup)
     cmd = ('wmic computersystem where name="%%computername%%" call '
            'joindomainorworkgroup name="%s"' % server_workgroup)
-    if session.get_command_status(cmd, timeout=600) != 0:
-        raise error.TestError("Could not change the client's workgroup")
+    session.cmd(cmd, timeout=600)
 
     # Set the client machine's DNS suffix
     logging.info("Setting DNS suffix to '%s'" % server_dns_suffix)
     cmd = 'reg add %s /v Domain /d "%s" /f' % (regkey, server_dns_suffix)
-    if session.get_command_status(cmd, timeout=300) != 0:
-        raise error.TestError("Could not set the client's DNS suffix")
+    session.cmd(cmd, timeout=300)
 
     # Reboot
     session = kvm_test_utils.reboot(vm, session)
@@ -103,9 +106,11 @@
                                          server_password)
     end_time = time.time() + 120
     while time.time() < end_time:
-        s = session.get_command_status(cmd)
-        if s == 0:
+        try:
+            session.cmd(cmd)
             break
+        except:
+            pass
         time.sleep(5)
     else:
         raise error.TestError("Could not access server share from client "
@@ -114,7 +119,17 @@
     # Install
     logging.info("Installing DTM client (timeout=%ds)", install_timeout)
     install_cmd = r"cmd /c \\%s\%s" % (server_name, install_cmd.lstrip("\\"))
-    if session.get_command_status(install_cmd, timeout=install_timeout) != 0:
-        raise error.TestError("Client installation failed")
+    session.cmd(install_cmd, timeout=install_timeout)
 
+    # Setup auto logon
+    logging.info("Setting up auto logon for user '%s'", client_username)
+    cmd = ('reg add '
+           '"HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\winlogon" '
+           '/v "%s" /d "%s" /t REG_SZ /f')
+    session.cmd(cmd % ("AutoAdminLogon", "1"))
+    session.cmd(cmd % ("DefaultUserName", client_username))
+    session.cmd(cmd % ("DefaultPassword", client_password))
+
+    # Reboot one more time
+    session = kvm_test_utils.reboot(vm, session)
     session.close()
diff --git a/client/tests/kvm/tests/whql_submission.py b/client/tests/kvm/tests/whql_submission.py
index 1fe27c9..6fb5d83 100644
--- a/client/tests/kvm/tests/whql_submission.py
+++ b/client/tests/kvm/tests/whql_submission.py
@@ -6,20 +6,31 @@
 def run_whql_submission(test, params, env):
     """
     WHQL submission test:
-    1) Log into the guest (the client machine) and into a DTM server machine
+    1) Log into the client machines and into a DTM server machine
     2) Copy the automation program binary (dsso_test_binary) to the server machine
     3) Run the automation program
     4) Pass the program all relevant parameters (e.g. device_data)
     5) Wait for the program to terminate
     6) Parse and report job results
-    (logs and HTML reports are placed in test.bindir)
+    (logs and HTML reports are placed in test.debugdir)
 
     @param test: kvm test object
     @param params: Dictionary with the test parameters
     @param env: Dictionary with test environment.
     """
-    vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
-    session = kvm_test_utils.wait_for_login(vm, 0, 240)
+    # Log into all client VMs
+    vms = []
+    sessions = []
+    for vm_name in params.objects("vms"):
+        vms.append(kvm_test_utils.get_living_vm(env, vm_name))
+        sessions.append(kvm_test_utils.wait_for_login(vms[-1], 0, 240))
+
+    # Make sure all NICs of all client VMs are up
+    for vm in vms:
+        nics = kvm_utils.get_sub_dict_names(vm.params, "nics")
+        for nic_index in range(len(nics)):
+            s = kvm_test_utils.wait_for_login(vm, nic_index, 600)
+            s.close()
 
     # Collect parameters
     server_address = params.get("server_address")
@@ -30,41 +41,61 @@
     dsso_test_binary = params.get("dsso_test_binary",
                                   "deps/whql_submission_15.exe")
     dsso_test_binary = kvm_utils.get_path(test.bindir, dsso_test_binary)
-    test_device = params.get("test_device")
-    job_filter = params.get("job_filter", ".*")
+    dsso_delete_machine_binary = params.get("dsso_delete_machine_binary",
+                                            "deps/whql_delete_machine_15.exe")
+    dsso_delete_machine_binary = kvm_utils.get_path(test.bindir,
+                                                    dsso_delete_machine_binary)
     test_timeout = float(params.get("test_timeout", 600))
-    wtt_services = params.get("wtt_services")
 
-    # Restart WTT service(s) on the client
-    logging.info("Restarting WTT services on client")
-    for svc in wtt_services.split():
-        kvm_test_utils.stop_windows_service(session, svc)
-    for svc in wtt_services.split():
-        kvm_test_utils.start_windows_service(session, svc)
-
-    # Copy dsso_test_binary to the server
-    rss_file_transfer.upload(server_address, server_file_transfer_port,
-                             dsso_test_binary, server_studio_path, timeout=60)
+    # Copy dsso binaries to the server
+    for filename in dsso_test_binary, dsso_delete_machine_binary:
+        rss_file_transfer.upload(server_address, server_file_transfer_port,
+                                 filename, server_studio_path, timeout=60)
 
     # Open a shell session with the server
     server_session = kvm_utils.remote_login("nc", server_address,
                                             server_shell_port, "", "",
-                                            session.prompt, session.linesep)
+                                            sessions[0].prompt,
+                                            sessions[0].linesep)
+    server_session.set_status_test_command(sessions[0].status_test_command)
 
-    # Get the computer names of the server and client
+    # Get the computer names of the server and clients
     cmd = "echo %computername%"
-    server_name = server_session.get_command_output(cmd).strip()
-    client_name = session.get_command_output(cmd).strip()
-    session.close()
+    server_name = server_session.cmd_output(cmd).strip()
+    client_names = [session.cmd_output(cmd).strip() for session in sessions]
+
+    # Delete all client machines from the server's data store
+    server_session.cmd("cd %s" % server_studio_path)
+    for client_name in client_names:
+        cmd = "%s %s %s" % (os.path.basename(dsso_delete_machine_binary),
+                            server_name, client_name)
+        server_session.cmd(cmd, print_func=logging.debug)
+
+    # Reboot the client machines
+    sessions = kvm_utils.parallel((kvm_test_utils.reboot, (vm, session))
+                                  for vm, session in zip(vms, sessions))
+
+    # Check the NICs again
+    for vm in vms:
+        nics = kvm_utils.get_sub_dict_names(vm.params, "nics")
+        for nic_index in range(len(nics)):
+            s = kvm_test_utils.wait_for_login(vm, nic_index, 600)
+            s.close()
+
+    # Run whql_pre_command and close the sessions
+    if params.get("whql_pre_command"):
+        for session in sessions:
+            session.cmd(params.get("whql_pre_command"),
+                        int(params.get("whql_pre_command_timeout", 600)))
+            session.close()
 
     # Run the automation program on the server
-    server_session.get_command_output("cd %s" % server_studio_path)
+    pool_name = "%s_pool" % client_names[0]
+    submission_name = "%s_%s" % (client_names[0],
+                                 params.get("submission_name"))
     cmd = "%s %s %s %s %s %s" % (os.path.basename(dsso_test_binary),
-                                 server_name,
-                                 client_name,
-                                 "%s_pool" % client_name,
-                                 "%s_submission" % client_name,
-                                 test_timeout)
+                                 server_name, pool_name, submission_name,
+                                 test_timeout, " ".join(client_names))
     server_session.sendline(cmd)
 
     # Helper function: wait for a given prompt and raise an exception if an
@@ -78,40 +109,69 @@
             if errors:
                 raise error.TestError(errors[0])
             else:
-                raise error.TestError("Error running automation program: could "
-                                      "not find '%s' prompt" % prompt)
+                raise error.TestError("Error running automation program: "
+                                      "could not find '%s' prompt" % prompt)
 
     # Tell the automation program which device to test
     find_prompt("Device to test:")
-    server_session.sendline(test_device)
+    server_session.sendline(params.get("test_device"))
 
     # Tell the automation program which jobs to run
     find_prompt("Jobs to run:")
-    server_session.sendline(job_filter)
+    server_session.sendline(params.get("job_filter", ".*"))
 
-    # Give the automation program all the device data supplied by the user
+    # Set submission DeviceData
     find_prompt("DeviceData name:")
-    for dd in kvm_utils.get_sub_dict_names(params, "device_data"):
-        dd_params = kvm_utils.get_sub_dict(params, dd)
+    for dd in params.objects("device_data"):
+        dd_params = params.object_params(dd)
         if dd_params.get("dd_name") and dd_params.get("dd_data"):
             server_session.sendline(dd_params.get("dd_name"))
             server_session.sendline(dd_params.get("dd_data"))
     server_session.sendline()
 
-    # Give the automation program all the descriptor information supplied by
-    # the user
+    # Set submission descriptors
     find_prompt("Descriptor path:")
-    for desc in kvm_utils.get_sub_dict_names(params, "descriptors"):
-        desc_params = kvm_utils.get_sub_dict(params, desc)
+    for desc in params.objects("descriptors"):
+        desc_params = params.object_params(desc)
         if desc_params.get("desc_path"):
             server_session.sendline(desc_params.get("desc_path"))
     server_session.sendline()
 
+    # Set machine dimensions for each client machine
+    for vm_name in params.objects("vms"):
+        vm_params = params.object_params(vm_name)
+        find_prompt(r"Dimension name\b.*:")
+        for dp in vm_params.objects("dimensions"):
+            dp_params = vm_params.object_params(dp)
+            if dp_params.get("dim_name") and dp_params.get("dim_value"):
+                server_session.sendline(dp_params.get("dim_name"))
+                server_session.sendline(dp_params.get("dim_value"))
+        server_session.sendline()
+
+    # Set extra parameters for tests that require them (e.g. NDISTest)
+    for vm_name in params.objects("vms"):
+        vm_params = params.object_params(vm_name)
+        find_prompt(r"Parameter name\b.*:")
+        for dp in vm_params.objects("device_params"):
+            dp_params = vm_params.object_params(dp)
+            if dp_params.get("dp_name") and dp_params.get("dp_regex"):
+                server_session.sendline(dp_params.get("dp_name"))
+                server_session.sendline(dp_params.get("dp_regex"))
+                # Make sure the prompt appears again (if the device isn't found
+                # the automation program will terminate)
+                find_prompt(r"Parameter name\b.*:")
+        server_session.sendline()
+
     # Wait for the automation program to terminate
-    m, o = server_session.read_up_to_prompt(print_func=logging.info,
-                                            timeout=test_timeout + 300)
-    # (test_timeout + 300 is used here because the automation program is
-    # supposed to terminate cleanly on its own when test_timeout expires)
+    try:
+        o = server_session.read_up_to_prompt(print_func=logging.info,
+                                             timeout=test_timeout + 300)
+        # (test_timeout + 300 is used here because the automation program is
+        # supposed to terminate cleanly on its own when test_timeout expires)
+        done = True
+    except kvm_subprocess.ExpectError, e:
+        o = e.output
+        done = False
     server_session.close()
 
     # Look for test results in the automation program's output
@@ -151,38 +211,63 @@
                 except (KeyError, OSError):
                     pass
 
-    # Print result summary
-    logging.info("")
-    logging.info("Result summary:")
-    name_length = max(len(r.get("job", "")) for r in results)
-    fmt = "%%-6s %%-%ds %%-15s %%-8s %%-8s %%-8s %%-15s" % name_length
-    logging.info(fmt % ("ID", "Job", "Status", "Pass", "Fail", "NotRun",
-                        "NotApplicable"))
-    logging.info(fmt % ("--", "---", "------", "----", "----", "------",
-                        "-------------"))
-    for r in results:
-        logging.info(fmt % (r.get("id"), r.get("job"), r.get("status"),
-                            r.get("pass"), r.get("fail"), r.get("notrun"),
-                            r.get("notapplicable")))
-    logging.info("(see logs and HTML reports in %s)" % test.debugdir)
+    # Print result summary (both to the regular logs and to a file named
+    # 'summary' in test.debugdir)
+    def print_summary_line(f, line):
+        logging.info(line)
+        f.write(line + "\n")
+    if results:
+        # Make sure all results have the required keys
+        for r in results:
+            r["id"] = str(r.get("id"))
+            r["job"] = str(r.get("job"))
+            r["status"] = str(r.get("status"))
+            r["pass"] = int(r.get("pass", 0))
+            r["fail"] = int(r.get("fail", 0))
+            r["notrun"] = int(r.get("notrun", 0))
+            r["notapplicable"] = int(r.get("notapplicable", 0))
+        # Sort the results by failures and total test count in descending order
+        results = [(r["fail"],
+                    r["pass"] + r["fail"] + r["notrun"] + r["notapplicable"],
+                    r) for r in results]
+        results.sort(reverse=True)
+        results = [r[-1] for r in results]
+        # Print results
+        logging.info("")
+        logging.info("Result summary:")
+        name_length = max(len(r["job"]) for r in results)
+        fmt = "%%-6s %%-%ds %%-15s %%-8s %%-8s %%-8s %%-15s" % name_length
+        f = open(os.path.join(test.debugdir, "summary"), "w")
+        print_summary_line(f, fmt % ("ID", "Job", "Status", "Pass", "Fail",
+                                     "NotRun", "NotApplicable"))
+        print_summary_line(f, fmt % ("--", "---", "------", "----", "----",
+                                     "------", "-------------"))
+        for r in results:
+            print_summary_line(f, fmt % (r["id"], r["job"], r["status"],
+                                         r["pass"], r["fail"], r["notrun"],
+                                         r["notapplicable"]))
+        f.close()
+        logging.info("(see logs and HTML reports in %s)" % test.debugdir)
 
-    # Kill the VM and fail if the automation program did not terminate on time
-    if not m:
-        vm.destroy()
+    # Kill the client VMs and fail if the automation program did not terminate
+    # on time
+    if not done:
+        kvm_utils.parallel(vm.destroy for vm in vms)
         raise error.TestFail("The automation program did not terminate "
                              "on time")
 
-    # Fail if there are failed or incomplete jobs (kill the VM if there are
-    # incomplete jobs)
-    failed_jobs = [r.get("job") for r in results
-                   if r.get("status", "").lower() == "investigate"]
-    running_jobs = [r.get("job") for r in results
-                    if r.get("status", "").lower() == "inprogress"]
+    # Fail if there are failed or incomplete jobs (kill the client VMs if there
+    # are incomplete jobs)
+    failed_jobs = [r["job"] for r in results
+                   if r["status"].lower() == "investigate"]
+    running_jobs = [r["job"] for r in results
+                    if r["status"].lower() == "inprogress"]
     errors = []
     if failed_jobs:
         errors += ["Jobs failed: %s." % failed_jobs]
     if running_jobs:
-        vm.destroy()
+        for vm in vms:
+            vm.destroy()
         errors += ["Jobs did not complete on time: %s." % running_jobs]
     if errors:
         raise error.TestFail(" ".join(errors))
diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
index eddc02b..6374923 100644
--- a/client/tests/kvm/tests_base.cfg.sample
+++ b/client/tests/kvm/tests_base.cfg.sample
@@ -61,7 +61,10 @@
 # Misc
 profilers = kvm_stat
 login_timeout = 360
+image_raw_device = no
 
+# NFS directory of guests' images
+images_good = 0.0.0.0:/autotest/images_good
 
 # Tests
 variants:
@@ -75,6 +78,12 @@
         kill_vm_timeout = 60
         kill_vm_timeout_on_error = 0
 
+    - image_copy:
+        type = image_copy
+        vms = ''
+        parallel = no
+        profilers =
+
     - setup:        install
         type = steps
         fail_if_stuck_for = 300
@@ -100,23 +109,19 @@
                 medium = cdrom
                 nic_mode = user
                 redirs += " unattended_install"
-                kernel =
-                initrd = 
             # Install guest from http/ftp url
             - url:
                 medium = url
-                extra_params += " --append ks=floppy"
                 url = REPLACE_THIS_WITH_TREE_URL
             # Install guest from nfs nfs_server:nfs_dir
             - nfs:
                 medium = nfs
-                extra_params += " --append ks=floppy"
                 nfs_server = REPLACE_THIS_WITH_NFS_SERVER
                 nfs_dir = REPLACE_THIS_WITH_NFS_DIRECTORY
             # Install guest with a remote kickstart
             - remote_ks:
                 medium = url
-                extra_params += " --append ks=REPLACE_THIS_WITH_URL_OF_KS"
+                extra_params = " --append ks=REPLACE_THIS_WITH_URL_OF_KS"
                 url = REPLACE_THIS_WITH_TREE_URL
 
     - boot:         install setup unattended_install.cdrom
@@ -141,6 +146,9 @@
         iterations = 2
         used_mem = 1024
         mig_timeout = 3600
+        # you can uncomment the following line to enable the state
+        # check
+        # vmstate_check = yes
         variants:
             - tcp:
                 migration_protocol = "tcp"
@@ -151,6 +159,27 @@
             - mig_cancel:
                 migration_protocol = "tcp"
                 mig_cancel = True
+        variants:
+            - @default:
+            - with_reboot:
+                iterations = 1
+                type = migration_with_reboot
+            - with_file_transfer:
+                iterations = 1
+                type = migration_with_file_transfer
+
+    - migrate_multi_host:      install setup unattended_install.cdrom
+        type = migration_multi_host
+        migration_test_command = help
+        migration_bg_command = "cd /tmp; nohup tcpdump -q -t ip host localhost"
+        migration_bg_check_command = pgrep tcpdump
+        migration_bg_kill_command = pkill tcpdump
+        kill_vm_on_error = yes
+        iterations = 2
+        used_mem = 1024
+        mig_timeout = 3600
+        comm_port = 13234
+        regain_ip_cmd = dhclient
 
     - boot_savevm: install setup unattended_install.cdrom
         type = boot_savevm
@@ -291,7 +320,7 @@
         reboot = yes
         variants:
             - autoit:
-                interpreter = D:\AutoIt3.exe
+                interpreter = "cmd /c D:\AutoIt3.exe"
                 variants:
                     - notepad:
                         guest_script = autoit/notepad1.au3
@@ -303,7 +332,7 @@
                         dst_rsc_dir = "C:\"
                         dst_rsc_path = "C:\autoit\stub\stub.au3"
             - powershell:
-                interpreter = "powershell.exe -File"
+                interpreter = "cmd /c powershell.exe -File"
                 variants:
                     - stub:
                         download = yes
@@ -317,7 +346,7 @@
         iozone_cmd = "D:\IOzone\iozone.exe -a"
         iozone_timeout = 3600
 
-    - @whql:         install setup unattended_install.cdrom
+    - whql:         install setup unattended_install.cdrom
         nic_mode = tap
         # Replace this with the address of an installed DTM server
         server_address = 10.20.30.40
@@ -327,11 +356,26 @@
         server_shell_port = 10022
         server_file_transfer_port = 10023
         server_studio_path = %programfiles%\Microsoft Driver Test Manager\Studio
+        dsso_test_binary = deps/whql_submission_15.exe
+        dsso_delete_machine_binary = deps/whql_delete_machine_15.exe
         wtt_services = wttsvc
         variants:
-            - whql_client_install:
+            - support_vm_install:
+                # The support VM is identical to the tested VM in every way
+                # except for the image name which ends with '-supportvm'.
+                type = unattended_install
+                pre_command += " scripts/unattended.py;"
+                extra_params += " -boot d"
+                force_create_image = yes
+                kill_vm = yes
+                nic_mode = user
+                redirs += " unattended_install"
+                guest_port_unattended_install = 12323
+                medium = cdrom
+                kernel =
+                initrd = 
+            - client_install:    support_vm_install
                 type = whql_client_install
-                dsso_delete_machine_binary = deps/whql_delete_machine_15.exe
                 # The username and password are required for accessing the DTM client
                 # installer binary shared by the server
                 server_username = administrator
@@ -340,12 +384,22 @@
                 # (the final cmd will be something like \\servername\DTMInstall\...)
                 install_cmd = \DTMInstall\Client\Setup.exe /passive
                 install_timeout = 3600
-            - whql_submission:    whql_client_install
+                # The test will setup auto logon on the client machine using the
+                # following username and password:
+                client_username = DTMLLUAdminUser
+                client_password = Testpassword,1
+                # (These are created by the DTM client installer and should probably not
+                # be changed.)
+                variants:
+                    - @original:
+                    - support_vm:
+            - submission:    client_install support_vm_install
                 type = whql_submission
                 extra_params += " -snapshot"
-                dsso_test_binary = deps/whql_submission_15.exe
+                restart_vm = yes
+                cdroms =
                 test_timeout = 3600
-                device_data = cat0 cat1 cat2 cat3 logoarch logoos whqlos whqlqual prog desc filter virt
+                device_data = cat0 cat1 cat2 cat3 prog desc virt filter logoarch logoos whqlos whqlqual
                 descriptors = desc1 desc2 desc3
                 # DeviceData names
                 dd_name_cat0     = Category
@@ -363,43 +417,105 @@
                 # Common DeviceData data
                 dd_data_filter   = FilterIfNoInf
                 dd_data_virt     = True
+                # Exclude jobs that have '(Manual)' in their names
+                job_filter = ^((?!\(Manual\)).)*$
                 variants:
-                    - keyboard:
-                        # test_device is a regular expression that should match a device's
-                        # name as it appears in device manager.  The first device that matches
-                        # is used.
-                        test_device = keyboard
-                        # Set timeout to 10 hours
-                        test_timeout = 36000
-                        dd_data_cat0 = Input\Keyboard
-                        dd_data_cat1 = Device Fundamentals
-                        dd_data_cat2 = System Fundamentals\Dynamic Partitioning
-                        dd_data_prog = InputKbd
-                        dd_data_desc = Input > Keyboard
-                    - hdd:
-                        test_device = qemu harddisk
-                        device_data += " ex0 ex1 ex2 ex3"
-                        dd_data_cat0 = Storage\Device Class\Disk\Disk
-                        dd_data_cat1 = Storage\Device Class\Disk\Fixed
-                        dd_data_cat2 = Storage\Device Class\Disk\Bus\ATA
-                        dd_data_cat3 = Device Fundamentals
-                        dd_data_prog = StorHDD
-                        dd_data_desc = Storage > Hard Disk Drive (HDD)
-                        dd_name_ex0 = Storage_bus_type
-                        dd_data_ex0 = ATA/ATAPI
-                        dd_name_ex1 = Hybrid_HDD_Support
-                        dd_data_ex1 = 0
-                        dd_name_ex2 = Non_Rotating_Media
-                        dd_data_ex2 = 0
-                        dd_name_ex3 = Secure_Storage
-                        dd_data_ex3 = 0
+                    - unclassified:
+                        dd_data_cat0 = Device Fundamentals
+                        dd_data_cat1 = System Fundamentals\Dynamic Partitioning
+                        dd_data_prog = Unclassified
+                        dd_data_desc = Unclassified
+                        dd_data_whqlqual = Unclassified Signature
                         variants:
-                            - full:
-                                # Yes, 100 hours, this is not a mistake
-                                test_timeout = 360000
-                            - syscache_test:
-                                job_filter = syscache test
-                                test_timeout = 7200
+                            - tablet:
+                                submission_name = tablet
+                                extra_params += " -usbdevice tablet"
+                                test_device = HID-compliant mouse
+                                test_timeout = 36000
+                    - device:
+                        variants:
+                            - keyboard:
+                                submission_name = keyboard
+                                # test_device is a regular expression that should match a device's
+                                # name as it appears in device manager.  The first device that matches
+                                # is used.
+                                test_device = keyboard
+                                # Set timeout to 10 hours
+                                test_timeout = 36000
+                                dd_data_cat0 = Input\Keyboard
+                                dd_data_cat1 = Device Fundamentals
+                                dd_data_cat2 = System Fundamentals\Dynamic Partitioning
+                                dd_data_prog = InputKbd
+                                dd_data_desc = Input > Keyboard
+                            - net:
+                                submission_name = net
+                                # Add a support machine and extra NICs
+                                vms += " supportvm"
+                                nics += " nic2 nic3"
+                                test_device = RTL8139.*NIC$
+                                test_timeout = 86400
+                                dd_data_cat0 = Network\LAN (Ethernet)
+                                dd_data_cat1 = Device Fundamentals
+                                dd_data_cat2 = System Fundamentals\Dynamic Partitioning
+                                dd_data_prog = NetLan
+                                dd_data_desc = Network > LAN (Ethernet)
+                                # Machine dimensions
+                                dimensions = testrole
+                                dim_name_testrole = NetDevice\TestRole
+                                dim_value_testrole_vm1 = NdistestLanClient
+                                dim_value_testrole_supportvm = NdistestLanServer
+                                # Device selection for the NDISTest client machine
+                                device_params_vm1 = testdev clientmsgdev clientsupportdev
+                                dp_name_testdev = NdistestLanClientTestDevice
+                                dp_regex_testdev = RTL8139.*NIC$
+                                dp_name_clientmsgdev = NdistestLanClientMessageDevice
+                                dp_regex_clientmsgdev = RTL8139.*NIC #2$
+                                dp_name_clientsupportdev = NdistestLanClientSupportDevice0
+                                dp_regex_clientsupportdev = RTL8139.*NIC #3$
+                                # Device selection for the NDISTest server machine
+                                device_params_supportvm = servermsgdev serversupportdev
+                                dp_name_servermsgdev = NdistestLanServerMessageDevice
+                                dp_regex_servermsgdev = RTL8139.*NIC$
+                                dp_name_serversupportdev = NdistestLanServerSupportDevice0
+                                dp_regex_serversupportdev = RTL8139.*NIC #2$
+                            - hdd:
+                                submission_name = hdd
+                                # Run the tests on a non-system drive
+                                # (match device names that contain 'QEMU HARDDISK' and do not contain '[C]')
+                                test_device = ^(?=.*?\bQEMU HARDDISK\b)((?!\[C\]).)*$
+                                device_data += " ex0 ex1 ex2 ex3"
+                                dd_data_cat0 = Storage\Device Class\Disk\Disk
+                                dd_data_cat1 = Storage\Device Class\Disk\Fixed
+                                dd_data_cat2 = Storage\Device Class\Disk\Bus\ATA
+                                dd_data_cat3 = Device Fundamentals
+                                dd_data_prog = StorHDD
+                                dd_data_desc = Storage > Hard Disk Drive (HDD)
+                                dd_name_ex0 = Storage_bus_type
+                                dd_data_ex0 = ATA/ATAPI
+                                dd_name_ex1 = Hybrid_HDD_Support
+                                dd_data_ex1 = 0
+                                dd_name_ex2 = Non_Rotating_Media
+                                dd_data_ex2 = 0
+                                dd_name_ex3 = Secure_Storage
+                                dd_data_ex3 = 0
+                                # Add a 2nd disk which will become D:
+                                images += " tmp"
+                                image_name_tmp = tmp
+                                image_size_tmp = 4G
+                                force_create_image_tmp = yes
+                                # Run diskpart to partition the 2nd disk
+                                whql_pre_command = "echo select disk=1 > dp.txt && "
+                                whql_pre_command += "echo create partition primary >> dp.txt && "
+                                whql_pre_command += "echo assign letter=d >> dp.txt && "
+                                whql_pre_command += "diskpart /s dp.txt & "
+                                whql_pre_command += "format d: /fs:ntfs /q /y"
+                                variants:
+                                    - full:
+                                        # Yes, 100 hours, this is not a mistake
+                                        test_timeout = 360000
+                                    - syscache_test:
+                                        job_filter = syscache test
+                                        test_timeout = 7200
 
     - guest_s4:     install setup unattended_install.cdrom
         type = guest_s4
@@ -412,7 +528,6 @@
         relogin_timeout = 240
 
     - nic_hotplug:  install setup unattended_install.cdrom
-        type = pci_hotplug
         pci_type = nic
         reference_cmd = lspci
         find_pci_cmd = 'lspci | tail -n1'
@@ -428,6 +543,12 @@
             - nic_e1000:
                 pci_model = e1000
                 match_string = "Gigabit Ethernet Controller"
+        variants:
+            - default:
+                type = pci_hotplug
+            - additional:
+                type = nic_hotplug
+
 
     - block_hotplug: install setup unattended_install.cdrom
         type = pci_hotplug
@@ -449,7 +570,7 @@
                 match_string = "Virtio block device"
             - block_scsi:
                 pci_model = scsi
-                match_string = "SCSI"
+                match_string = "LSI Logic"
         variants:
             - fmt_qcow2:
                 image_format_stg = qcow2
@@ -530,17 +651,41 @@
         type = netperf
         nic_mode = tap
         netperf_files = netperf-2.4.5.tar.bz2 wait_before_data.patch
+        packet_size = 1500
         setup_cmd = "cd %s && tar xvfj netperf-2.4.5.tar.bz2 && cd netperf-2.4.5 && patch -p0 < ../wait_before_data.patch && ./configure && make"
         netserver_cmd =  %s/netperf-2.4.5/src/netserver
-        # test time is 60 seconds, set the buffer size to 1 for more hardware interrupt
-        netperf_cmd = %s/netperf-2.4.5/src/netperf -t %s -H %s -l 60 -- -m 1
-        protocols = "TCP_STREAM TCP_MAERTS TCP_RR TCP_CRR UDP_RR TCP_SENDFILE UDP_STREAM"
+	variants:
+	    - stream:
+	            netperf_cmd = %s/netperf-2.4.5/src/netperf -t %s -H %s -l 60 -- -m %s
+		    protocols = "TCP_STREAM TCP_MAERTS TCP_SENDFILE UDP_STREAM"
+ 	    - rr:
+	            netperf_cmd = %s/netperf-2.4.5/src/netperf -t %s -H %s -l 60 -- -r %s
+		    protocols = "TCP_RR TCP_CRR UDP_RR"
 
     - ethtool: install setup unattended_install.cdrom
         type = ethtool
         filesize = 512
         nic_mode = tap
 
+    - nic_bonding:
+        type = nic_bonding
+        nics += ' nic2 nic3 nic4'
+        image_snapshot = yes
+        serial_login = yes
+        test_timeout = 1000
+        filesize = 4000
+        transfer_timeout = 1000
+        transfer_type = remote
+        kill_vm = yes
+
+    - set_link:
+        type = set_link
+        test_timeout = 1000
+        filesize = 4000
+        transfer_timeout = 1000
+        transfer_type = remote
+        kill_vm =yes
+
     - physical_resources_check: install setup unattended_install.cdrom
         type = physical_resources_check
         catch_uuid_cmd = dmidecode | awk -F: '/UUID/ {print $2}'
@@ -616,6 +761,15 @@
             - vmexit:
                 case = vmexit
 
+    - module_probe:
+        type = module_probe
+        # You can specify your own module list, though it is not needed usually.
+        # mod_list = kvm
+        load_count = 100
+        vms = ''
+        profilers = ''
+        take_regular_screendumps = no
+
     - ioquit:
         type = ioquit
         background_cmd = "for i in 1 2 3 4; do (nohup dd if=/dev/urandom of=/tmp/file bs=102400 count=10000000 &) done"
@@ -664,6 +818,27 @@
                 image_name_snapshot1 = sn1
                 image_name_snapshot2 = sn2
 
+    - clock_getres: install setup unattended_install.cdrom
+        type = clock_getres
+
+    - kdump: unattended_install.cdrom
+        type = kdump
+        # time waited for the completion of crash dump
+        # crash_timeout = 360
+        # command to add the crashkernel=X@Y to kernel cmd line
+        # kernel_param_cmd = "grubby --update-kernel=`grubby --default-kernel` --args=crashkernel=128M@64M"
+        # command to enable kdump service
+        # kdump_enable_cmd = chkconfig kdump on && service kdump start
+        # command to probe the crash kernel
+        # crash_kernel_prob_cmd = "grep -q 1 /sys/kernel/kexec_crash_loaded"
+
+    - vmstop:
+        type = vmstop
+        # the path used to store the saved vm state
+        # save_path = /tmp
+        # clean the state file?
+        clean_save = yes
+
     # system_powerdown, system_reset and shutdown *must* be the last ones
     # defined (in this order), since the effect of such tests can leave
     # the VM on a bad state.
@@ -704,13 +879,24 @@
         nic_model = virtio
         # You can add advanced attributes on nic_extra_params such as mrg_rxbuf
         #nic_extra_params =
-        # You can set vhost = yes to enable the vhost kernel backend
-        # (This only works if nic_mode=tap)
-        vhost = no
+        # You can add advanced attributes through netdev_extra_params
+        # such as sndbuf, as an example, you can uncomment the
+        # following lines to enable the vhost support ( only available
+        # for tap )
+        #netdev_extra_params = "vhost=on"
         jumbo:
             mtu = 65520
         ethtool:
             supported_features = "tx sg tso gso"
+        whql.submission.device.net:
+            test_device = VirtIO Ethernet Adapter$
+            # Device selection for the NDISTest client machine
+            dp_regex_testdev = VirtIO Ethernet Adapter$
+            dp_regex_clientmsgdev = VirtIO Ethernet Adapter #2$
+            dp_regex_clientsupportdev = VirtIO Ethernet Adapter #3$
+            # Device selection for the NDISTest server machine
+            dp_regex_servermsgdev = VirtIO Ethernet Adapter$
+            dp_regex_serversupportdev = VirtIO Ethernet Adapter #2$
 
 # Guests
 variants:
@@ -729,7 +915,7 @@
         mem_chk_cmd = dmidecode -t 17 | awk -F: '/Size/ {print $2}'
         mem_chk_cur_cmd = grep MemTotal /proc/meminfo
         cpu_chk_cmd = grep -c processor /proc/cpuinfo
-        unattended_install.cdrom:
+        unattended_install:
             # If you want to use floppy to hold kickstarts,
             # comment the 3 lines below
             cdroms += " unattended"
@@ -761,162 +947,222 @@
             - Fedora:
                 no setup
                 shell_prompt = "^\[.*\][\#\$]\s*$"
-                unattended_install.cdrom:
-                    pxe_dir = "images/pxeboot"
-                    pxe_image = "vmlinuz"
-                    pxe_initrd = "initrd.img"
-                    tftp = "images/tftpboot"
-                    bootp = "/pxelinux.0"
-                    extra_params += " -boot cn"
+                unattended_install:
+                    boot_path = "images/pxeboot"
                     # You have to use ks=floppy if you want to use floppies to
                     # hold your kickstart file
-                    #kernel_args = "ks=floppy nicdelay=60 console=ttyS0,115200 console=tty0"
-                    kernel_args = "ks=cdrom nicdelay=60 console=ttyS0,115200 console=tty0"
+                    #extra_params += " --append 'ks=floppy nicdelay=60 console=ttyS0,115200 console=tty0'"
+                    extra_params += " --append 'ks=cdrom nicdelay=60 console=ttyS0,115200 console=tty0'"
 
                 variants:
                     - 8.32:
                         no setup
                         image_name = fc8-32
-                        cdrom_cd1 = linux/Fedora-8-i386-DVD.iso
-                        md5sum = dd6c79fddfff36d409d02242e7b10189
-                        md5sum_1m = dabae451bb69fbbad0e505b25144b1f9
                         install:
                             steps = Fedora-8-i386.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-8-i386-DVD.iso
+                            md5sum_cd1 = dd6c79fddfff36d409d02242e7b10189
+                            md5sum_1m_cd1 = dabae451bb69fbbad0e505b25144b1f9
+                        unattended_install:
                             unattended_file = unattended/Fedora-8.ks
-                            tftp = images/f8-32/tftpboot
                             #floppy = images/f8-32/ks.vfd
                             cdrom_unattended = images/f8-32/ks.iso
+                            kernel = images/f8-32/vmlinuz
+                            initrd = images/f8-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-8-i386-DVD.iso
+                            md5sum_cd1 = dd6c79fddfff36d409d02242e7b10189
+                            md5sum_1m_cd1 = dabae451bb69fbbad0e505b25144b1f9
 
                     - 8.64:
                         no setup
                         image_name = f8-64
-                        cdrom_cd1 = linux/Fedora-8-x86_64-DVD.iso
-                        md5sum = 2cb231a86709dec413425fd2f8bf5295
-                        md5sum_1m = 145f6414e19492649a56c89f0a45e719
                         install:
                             steps = Fedora-8-64.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-8-x86_64-DVD.iso
+                            md5sum_cd1 = 2cb231a86709dec413425fd2f8bf5295
+                            md5sum_1m_cd1 = 145f6414e19492649a56c89f0a45e719
+                        unattended_install:
                             unattended_file = unattended/Fedora-8.ks
-                            tftp = images/f8-64/tftpboot
                             #floppy = images/f8-64/ks.vfd
                             cdrom_unattended = images/f8-64/ks.iso
+                            kernel = images/f8-64/vmlinuz
+                            initrd = images/f8-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-8-x86_64-DVD.iso
+                            md5sum_cd1 = 2cb231a86709dec413425fd2f8bf5295
+                            md5sum_1m_cd1 = 145f6414e19492649a56c89f0a45e719
 
                     - 9.32:
                         image_name = f9-32
-                        cdrom_cd1 = linux/Fedora-9-i386-DVD.iso
-                        md5sum = 72601f685ea8c808c303353d8bf4d307
-                        md5sum_1m = f24fa25689e5863f1b99984c6feb787f
                         install:
                             steps = Fedora-9-i386.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-9-i386-DVD.iso
+                            md5sum_cd1 = 72601f685ea8c808c303353d8bf4d307
+                            md5sum_1m_cd1 = f24fa25689e5863f1b99984c6feb787f
+                        unattended_install:
                             unattended_file = unattended/Fedora-9.ks
-                            tftp = images/f9-32/tftpboot
                             #floppy = images/f9-32/ks.vfd
                             cdrom_unattended = images/f9-32/ks.iso
+                            kernel = images/f9-32/vmlinuz
+                            initrd = images/f9-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-9-i386-DVD.iso
+                            md5sum_cd1 = 72601f685ea8c808c303353d8bf4d307
+                            md5sum_1m_cd1 = f24fa25689e5863f1b99984c6feb787f
+
 
                     - 9.64:
                         image_name = f9-64
-                        cdrom_cd1 = linux/Fedora-9-x86_64-DVD.iso
-                        md5sum = 05b2ebeed273ec54d6f9ed3d61ea4c96
-                        md5sum_1m = 9822ab5097e37e8fe306ef2192727db4
                         install:
                             steps = Fedora-9-64.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-9-x86_64-DVD.iso
+                            md5sum_cd1 = 05b2ebeed273ec54d6f9ed3d61ea4c96
+                            md5sum_1m_cd1 = 9822ab5097e37e8fe306ef2192727db4
+                        unattended_install:
                             unattended_file = unattended/Fedora-9.ks
-                            tftp = images/f9-64/tftpboot
                             #floppy = images/f9-64/ks.vfd
                             cdrom_unattended = images/f9-64/ks.iso
+                            kernel = images/f9-64/vmlinuz
+                            initrd = images/f9-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-9-x86_64-DVD.iso
+                            md5sum_cd1 = 05b2ebeed273ec54d6f9ed3d61ea4c96
+                            md5sum_1m_cd1 = 9822ab5097e37e8fe306ef2192727db4
+
 
                     - 10.32:
                         image_name = f10-32
-                        cdrom_cd1 = linux/Fedora-10-i386-DVD.iso
-                        md5sum = 27e581edb392728c4a07d00d3fc5ced0
-                        md5sum_1m = bd67c68bdf595e4ba7131ec702159181
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-10.ks
-                            tftp = images/f10-32/tftpboot
                             #floppy = images/f10-32/ks.vfd
                             cdrom_unattended = images/f10-32/ks.iso
+                            kernel = images/f10-32/vmlinuz
+                            initrd = images/f10-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-10-i386-DVD.iso
+                            md5sum_cd1 = 27e581edb392728c4a07d00d3fc5ced0
+                            md5sum_1m_cd1 = bd67c68bdf595e4ba7131ec702159181
 
                     - 10.64:
                         image_name = f10-64
-                        cdrom_cd1 = linux/Fedora-10-x86_64-DVD.iso
-                        sha1sum = f1e5ae7db6a1ba227de7294c4112385922388648
-                        md5sum_1m = 732857cbf40c80c34683e874601d982c
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-10.ks
-                            tftp = images/f10-64/tftpboot
                             #floppy = images/f10-64/ks.vfd
                             cdrom_unattended = images/f10-64/ks.iso
+                            kernel = images/f10-64/vmlinuz
+                            initrd = images/f10-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-10-x86_64-DVD.iso
+                            sha1sum_cd1 = f1e5ae7db6a1ba227de7294c4112385922388648
+                            md5sum_1m_cd1 = 732857cbf40c80c34683e874601d982c
 
                     - 11.32:
                         image_name = f11-32
-                        cdrom_cd1 = linux/Fedora-11-i386-DVD.iso
-                        md5sum = e3b1e2d1ba42aa4705fa5f41771b3927
-                        md5sum_1m = dc8ddf90648c247339c721395aa49714
                         install:
                             steps = Fedora-11-32.steps
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-11.ks
-                            tftp = images/f11-32/tftpboot
                             #floppy = images/f11-32/ks.vfd
                             cdrom_unattended = images/f11-32/ks.iso
+                            kernel = images/f11-32/vmlinuz
+                            initrd = images/f11-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-11-i386-DVD.iso
+                            md5sum_cd1 = e3b1e2d1ba42aa4705fa5f41771b3927
+                            md5sum_1m_cd1 = dc8ddf90648c247339c721395aa49714
 
                     - 11.64:
                         image_name = f11-64
-                        cdrom_cd1 = linux/Fedora-11-x86_64-DVD.iso
-                        md5sum = 9d419844adeb93120215fe7505c9bce8
-                        md5sum_1m = 405ee05e2387a2e4328b008d5bcbdd1e
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-11.ks
-                            tftp = images/f11-64/tftpboot
                             #floppy = images/f11-64/ks.vfd
                             cdrom_unattended = images/f11-64/ks.iso
+                            kernel = images/f11-64/vmlinuz
+                            initrd = images/f11-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-11-x86_64-DVD.iso
+                            md5sum_cd1 = 9d419844adeb93120215fe7505c9bce8
+                            md5sum_1m_cd1 = 405ee05e2387a2e4328b008d5bcbdd1e
 
                     - 12.32:
                         image_name = f12-32
-                        cdrom_cd1 = linux/Fedora-12-i386-DVD.iso
-                        md5sum = 2c4c1c0d09f2fbcfd8ee6a0c5542eeb2
-                        md5sum_1m = eee935d7f0cf2ef03f6ddce3a2a50050
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-12.ks
-                            tftp = images/f12-32/tftpboot
                             #floppy = images/f12-32/ks.vfd
                             cdrom_unattended = images/f12-32/ks.iso
+                            kernel = images/f12-32/vmlinuz
+                            initrd = images/f12-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-12-i386-DVD.iso
+                            md5sum_cd1 = 2c4c1c0d09f2fbcfd8ee6a0c5542eeb2
+                            md5sum_1m_cd1 = eee935d7f0cf2ef03f6ddce3a2a50050
 
                     - 12.64:
                         image_name = f12-64
-                        cdrom_cd1 = linux/Fedora-12-x86_64-DVD.iso
-                        md5sum = 6dd31e292cc2eb1140544e9b1ba61c56
-                        md5sum_1m = 514efbd7698b55ff6768c8605438bfc5
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-12.ks
-                            tftp = images/f12-64/tftpboot
                             #floppy = images/f12-64/ks.vfd
                             cdrom_unattended = images/f12-64/ks.iso
+                            kernel = images/f12-64/vmlinuz
+                            initrd = images/f12-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-12-x86_64-DVD.iso
+                            md5sum_cd1 = 6dd31e292cc2eb1140544e9b1ba61c56
+                            md5sum_1m_cd1 = 514efbd7698b55ff6768c8605438bfc5
 
                     - 13.32:
                         image_name = f13-32
-                        cdrom_cd1 = linux/Fedora-13-i386-DVD.iso
-                        md5sum = 212fec517c2629b4b5eaf3662ac13136
-                        md5sum_1m = 4e1578a6ed5a6e7cd03b8fb074030746
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-13.ks
-                            tftp = images/f13-32/tftpboot
                             #floppy = images/f13-32/ks.vfd
                             cdrom_unattended = images/f13-32/ks.iso
+                            kernel = images/f13-32/vmlinuz
+                            initrd = images/f13-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-13-i386-DVD.iso
+                            md5sum_cd1 = 212fec517c2629b4b5eaf3662ac13136
+                            md5sum_1m_cd1 = 4e1578a6ed5a6e7cd03b8fb074030746
 
                     - 13.64:
                         image_name = f13-64
-                        cdrom_cd1 = linux/Fedora-13-x86_64-DVD.iso
-                        md5sum = 6fbae6379cf27f36e1f2c7827ba7dc35
-                        md5sum_1m = 68821b9de4d3b5975d6634334e7f47a6
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/Fedora-13.ks
-                            tftp = images/f13-64/tftpboot
                             #floppy = images/f13-64/ks.vfd
                             cdrom_unattended = images/f13-64/ks.iso
+                            kernel = images/f13-64/vmlinuz
+                            initrd = images/f13-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-13-x86_64-DVD.iso
+                            md5sum_cd1 = 6fbae6379cf27f36e1f2c7827ba7dc35
+                            md5sum_1m_cd1 = 68821b9de4d3b5975d6634334e7f47a6
+
+                    - 14.32:
+                        image_name = f14-32
+                        unattended_install:
+                            unattended_file = unattended/Fedora-14.ks
+                            #floppy = images/f14-32/ks.vfd
+                            cdrom_unattended = images/f14-32/ks.iso
+                            kernel = images/f14-32/vmlinuz
+                            initrd = images/f14-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-14-i386-DVD.iso
+                            md5sum_cd1 = 1cc67641506d2f931d669b8d3528dded
+                            md5sum_1m_cd1 = d314ab126dabab686111e6a0d71d2e67
+
+                    - 14.64:
+                        image_name = f14-64
+                        unattended_install:
+                            unattended_file = unattended/Fedora-14.ks
+                            #floppy = images/f14-64/ks.vfd
+                            cdrom_unattended = images/f14-64/ks.iso
+                            kernel = images/f14-64/vmlinuz
+                            initrd = images/f14-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/Fedora-14-x86_64-DVD.iso
+                            md5sum_cd1 = f2ebf941dc45f99ee3e8a457c9544552
+                            md5sum_1m_cd1 = df029f9cffbc3517937a91124a1e0c3a
+
 
 
             - DSL-4.2.5:
@@ -924,199 +1170,232 @@
                 image_name = dsl-4.2.5
                 install:
                     steps = DSL-4.2.5.steps
-                    cdrom_cd1 = linux/dsl-4.2.5.iso
-                    md5sum = 61694888aede3e01229865b8e6acd4a1
-                    md5sum_1m = 527f2481bd25310f2e3a6e5345ff3d12
+                    cdrom_cd1 = isos/linux/dsl-4.2.5.iso
+                    md5sum_cd1 = 61694888aede3e01229865b8e6acd4a1
+                    md5sum_1m_cd1 = 527f2481bd25310f2e3a6e5345ff3d12
 
             - Mandriva-One-2007:
                 only install
                 image_name = mandriva-one-2007
                 steps = Mandriva-One-2007-32.steps
-                cdrom_cd1 = linux/mandriva-one-2007-i386.iso
-                md5sum = 7e9e183dc11b9d39f480238e4e12bb05
-                md5sum_1m = dc7865a75db665efc86d59bca7c1fe07
+                cdrom_cd1 = isos/linux/mandriva-one-2007-i386.iso
+                md5sum_cd1 = 7e9e183dc11b9d39f480238e4e12bb05
+                md5sum_1m_cd1 = dc7865a75db665efc86d59bca7c1fe07
 
             - OpenSUSE:
                 no setup
                 shell_prompt = ".*:.*\s#"
-                unattended_install.cdrom:
-                    pxe_image = "linux"
-                    pxe_initrd = "initrd"
-                    tftp = "images/tftpboot"
-                    bootp = "/pxelinux.0"
-                    extra_params += " -boot cn"
+                unattended_install:
                     # You have to use autoyast=floppy if you want to use floppies to
                     # hold your autoyast file
-                    kernel_args = "autoyast=floppy console=ttyS0,115200 console=tty0"
-                    #kernel_args = "autoyast=cdrom console=ttyS0,115200 console=tty0"
+                    extra_params += " --append 'autoyast=floppy console=ttyS0,115200 console=tty0'"
+                    #extra_params += " --append 'autoyast=cdrom console=ttyS0,115200 console=tty0'"
                     post_install_delay = 10
 
                 variants:
                     - 11.0.32:
                         image_name = openSUSE-11.0-32
-                        cdrom_cd1 = linux/openSUSE-11.0-DVD-i386.iso
-                        md5sum = ed6a5b3feb668866df812b1c2aed9d7f
-                        md5sum_1m = c720b30557af758e69de450409516369
                         install:
                             steps = openSUSE-11.0-32.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.0-DVD-i386.iso
+                            md5sum_cd1 = ed6a5b3feb668866df812b1c2aed9d7f
+                            md5sum_1m_cd1 = c720b30557af758e69de450409516369
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-0-32/tftpboot
                             floppy = images/opensuse-11-0-32/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-0-32/autoyast.iso
-                            pxe_dir = boot/i386/loader
+                            kernel = images/opensuse-11-0-32/linux
+                            initrd = images/opensuse-11-0-32/initrd
+                            boot_path = boot/i386/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.0-DVD-i386.iso
+                            md5sum_cd1 = ed6a5b3feb668866df812b1c2aed9d7f
+                            md5sum_1m_cd1 = c720b30557af758e69de450409516369
+
 
                     - 11.0.64:
                         image_name = openSUSE-11.0-64
-                        cdrom_cd1 = linux/openSUSE-11.0-DVD-x86_64.iso
-                        md5sum = 512c8346b0f8eb35f28c4eb96454d391
-                        md5sum_1m = 661aa4cd031df2f25ea0102318a3f4d1
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-0-64/tftpboot
                             floppy = images/opensuse-11-0-64/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-0-64/autoyast.iso
-                            pxe_dir = boot/x86_64/loader
+                            kernel = images/opensuse-11-0-64/linux
+                            initrd = images/opensuse-11-0-64/initrd
+                            boot_path = boot/x86_64/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.0-DVD-x86_64.iso
+                            md5sum_cd1 = 512c8346b0f8eb35f28c4eb96454d391
+                            md5sum_1m_cd1 = 661aa4cd031df2f25ea0102318a3f4d1
 
                     - 11.1.32:
                         image_name = openSUSE-11.1-32
-                        cdrom_cd1 = linux/openSUSE-11.1-DVD-i586.iso
-                        md5sum = 8f51b278c0415be28c5699e465444bd3
-                        md5sum_1m = b70217417468389083429f81ba7ce2bd
                         install:
-                            steps=openSUSE-11.1-32-and-64.steps
-                        unattended_install.cdrom:
+                            steps = openSUSE-11.1-32-and-64.steps
+                            cdrom_cd1 = isos/linux/openSUSE-11.1-DVD-i586.iso
+                            md5sum_cd1 = 8f51b278c0415be28c5699e465444bd3
+                            md5sum_1m_cd1 = b70217417468389083429f81ba7ce2bd
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-1-32/tftpboot
                             floppy = images/opensuse-11-1-32/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-1-32/autoyast.iso
-                            pxe_dir = boot/i386/loader
+                            kernel = images/opensuse-11-1-32/linux
+                            initrd = images/opensuse-11-1-32/initrd
+                            boot_path = boot/i386/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.1-DVD-i586.iso
+                            md5sum_cd1 = 8f51b278c0415be28c5699e465444bd3
+                            md5sum_1m_cd1 = b70217417468389083429f81ba7ce2bd
 
                     - 11.1.64:
                         image_name = openSUSE-11.1-64
-                        cdrom_cd1 = linux/openSUSE-11.1-DVD-x86_64.iso
-                        md5sum = 2afee1b8a87175e6dee2b8dbbd1ad8e8
-                        md5sum_1m = 768ca32503ef92c28f2d144f2a87e4d0
                         install:
                             steps=openSUSE-11.1-32-and-64.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.1-DVD-x86_64.iso
+                            md5sum_cd1 = 2afee1b8a87175e6dee2b8dbbd1ad8e8
+                            md5sum_1m_cd1 = 768ca32503ef92c28f2d144f2a87e4d0
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-1-64/tftpboot
                             floppy = images/opensuse-11-1-64/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-1-64/autoyast.iso
-                            pxe_dir = boot/x86_64/loader
+                            kernel = images/opensuse-11-1-64/linux
+                            initrd = images/opensuse-11-1-64/initrd
+                            boot_path = boot/x86_64/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.1-DVD-x86_64.iso
+                            md5sum_cd1 = 2afee1b8a87175e6dee2b8dbbd1ad8e8
+                            md5sum_1m_cd1 = 768ca32503ef92c28f2d144f2a87e4d0
+
 
                     - 11.2.32:
                         image_name = openSUSE-11.2-32
-                        cdrom_cd1 = linux/openSUSE-11.2-DVD-i586.iso
-                        md5sum = 295d713314a30ad017948f0d542c6d92
-                        md5sum_1m = 1f8767d00acb492be5a5627c834e543f
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-2-32/tftpboot
                             floppy = images/opensuse-11-2-32/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-2-32/autoyast.iso
-                            pxe_dir = boot/i386/loader
+                            kernel = images/opensuse-11-2-32/linux
+                            initrd = images/opensuse-11-2-32/initrd
+                            boot_path = boot/i386/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.2-DVD-i586.iso
+                            md5sum_cd1 = 295d713314a30ad017948f0d542c6d92
+                            md5sum_1m_cd1 = 1f8767d00acb492be5a5627c834e543f
+
 
                     - 11.2.64:
                         image_name = openSUSE-11.2-64
-                        cdrom_cd1 = linux/openSUSE-11.2-DVD-x86_64.iso
-                        md5sum = 6a09295e34dc030319d040f67f4742c6
-                        md5sum_1m = 11fd11d39744450b898f04c371dde2e7
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-2-64/tftpboot
                             floppy = images/opensuse11-2-64/autoyast.vfd
                             #cdrom_unattended = images/opensuse11-2-64/autoyast.iso
-                            pxe_dir = boot/x86_64/loader
+                            kernel = images/opensuse-11-2-64/linux
+                            initrd = images/opensuse-11-2-64/initrd
+                            boot_path = boot/x86_64/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.2-DVD-x86_64.iso
+                            md5sum_cd1 = 6a09295e34dc030319d040f67f4742c6
+                            md5sum_1m_cd1 = 11fd11d39744450b898f04c371dde2e7
 
                     - 11.3.32:
                         image_name = openSUSE-11.3-32
-                        cdrom_cd1 = linux/openSUSE-11.3-DVD-i586.iso
-                        md5sum = 1a1da28c84e3cdad750d5cfa21c4fd17
-                        md5sum_1m = 4dd26906ce6cb3946519cb0b0de4b0f8
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-3-32/tftpboot
                             floppy = images/opensuse-11-3-32/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-3-32/autoyast.iso
-                            pxe_dir = boot/i386/loader
+                            kernel = images/opensuse-11-3-32/linux
+                            initrd = images/opensuse-11-3-32/initrd
+                            boot_path = boot/i386/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.3-DVD-i586.iso
+                            md5sum_cd1 = 1a1da28c84e3cdad750d5cfa21c4fd17
+                            md5sum_1m_cd1 = 4dd26906ce6cb3946519cb0b0de4b0f8
 
                     - 11.3.64:
                         image_name = openSUSE-11.3-64
-                        cdrom_cd1 = linux/openSUSE-11.3-DVD-x86_64.iso
-                        md5sum = adf5d2a0a03c1e3aaf102fd6a4771b87
-                        md5sum_1m = e0dd12dac30d296417256775e1234c6e
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/OpenSUSE-11.xml
-                            tftp = images/opensuse-11-3-64/tftpboot
                             floppy = images/opensuse-11-3-64/autoyast.vfd
                             #cdrom_unattended = images/opensuse-11-3-64/autoyast.iso
-                            pxe_dir = boot/x86_64/loader
+                            kernel = images/opensuse-11-3-64/linux
+                            initrd = images/opensuse-11-3-64/initrd
+                            boot_path = boot/x86_64/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/openSUSE-11.3-DVD-x86_64.iso
+                            md5sum_cd1 = adf5d2a0a03c1e3aaf102fd6a4771b87
+                            md5sum_1m_cd1 = e0dd12dac30d296417256775e1234c6e
 
             - SLES:
                 shell_prompt = "^root@.*[\#\$]\s*$|#"
-                unattended_install.cdrom:
-                    pxe_image = "linux"
-                    pxe_initrd = "initrd"
-                    bootp = "/pxelinux.0"
-                    extra_params += " -boot cn"
+                unattended_install:
                     # You have to use autoyast=floppy if you want to use floppies to
                     # hold your autoyast file
-                    kernel_args = "autoyast=floppy console=ttyS0,115200 console=tty0"
-                    #kernel_args = "autoyast=cdrom console=ttyS0,115200 console=tty0"
+                    extra_params += " --append 'autoyast=floppy console=ttyS0,115200 console=tty0'"
+                    #extra_params += " --append 'autoyast=cdrom console=ttyS0,115200 console=tty0'"
                     post_install_delay = 10
+                    kernel = linux
+                    initrd = initrd
 
                 variants:
                     - 11.0.32:
                         image_name = sles11-32
-                        cdrom_cd1 = linux/SLES-11-DVD-i586-GM-DVD1.iso
-                        md5sum = 4958d4dde2575666355c8a1c5858bab0
-                        md5sum_1m = 1f19d4eff5bcead2a3e5b8b4212b6796
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/SLES-11.xml
-                            tftp = images/sles-11-0-32/tftpboot
-                            #floppy = images/sles-11-0-32/autoyast.vfd
-                            cdrom_unattended = images/sles-11-0-32/autoyast.iso
                             floppy = images/sles-11-0-32/autoyast.vfd
                             #cdrom_unattended = images/sles-11-0-32/autoyast.iso
-                            pxe_dir = boot/i386/loader
+                            kernel = images/sles-11-0-32/linux
+                            initrd = images/sles-11-0-32/initrd
+                            boot_path = boot/i386/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/SLES-11-DVD-i586-GM-DVD1.iso
+                            md5sum_cd1 = 4958d4dde2575666355c8a1c5858bab0
+                            md5sum_1m_cd1 = 1f19d4eff5bcead2a3e5b8b4212b6796
+
 
                     - 11.0.64:
                         image_name = sles11-64
-                        cdrom_cd1 = linux/SLES-11-DVD-x86_64-GM-DVD1.iso
-                        md5sum = 50a2bd45cd12c3808c3ee48208e2586b
-                        md5sum_1m = 00000951cab7c32e332362fc424c1054
-                        unattended_install.cdrom:
+                        cdrom_cd1 = isos/linux/SLES-11-DVD-x86_64-GM-DVD1.iso
+                        md5sum_cd1 = 50a2bd45cd12c3808c3ee48208e2586b
+                        md5sum_1m_cd1 = 00000951cab7c32e332362fc424c1054
+                        unattended_install:
                             unattended_file = unattended/SLES-11.xml
-                            tftp = images/sles-11-0-64/tftpboot
                             floppy = images/sles-11-0-64/autoyast.vfd
                             #cdrom_unattended = images/sles-11-0-64/autoyast.iso
-                            pxe_dir = boot/x86_64/loader
+                            kernel = images/sles-11-0-64/linux
+                            initrd = images/sles-11-0-64/initrd
+                            boot_path = boot/x86_64/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/SLES-11-DVD-x86_64-GM-DVD1.iso
+                            md5sum_cd1 = 50a2bd45cd12c3808c3ee48208e2586b
+                            md5sum_1m_cd1 = 00000951cab7c32e332362fc424c1054
+
 
                     - 11.1.32:
                         image_name = sles11sp1-32
-                        cdrom_cd1 = linux/SLES-11-SP1-DVD-i586-GM-DVD1.iso
-                        md5sum = 0dd6886858d93501c38854552b9b1b0d
-                        md5sum_1m = a626a3d50813410e3ac42794e05773bb
                         unattended_install:
                             unattended_file = unattended/SLES-11.xml
-                            tftp = images/sles-11-1-32/tftpboot
                             floppy = images/sles-11-1-32/autoyast.vfd
                             #cdrom_unattended = images/sles-11-1-32/autoyast.iso
-                            pxe_dir = boot/i386/loader
+                            kernel = images/sles-11-1-32/linux
+                            initrd = images/sles-11-1-32/initrd
+                            boot_path = boot/i386/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/SLES-11-SP1-DVD-i586-GM-DVD1.iso
+                            md5sum_cd1 = 0dd6886858d93501c38854552b9b1b0d
+                            md5sum_1m_cd1 = a626a3d50813410e3ac42794e05773bb
 
                     - 11.1.64:
                         image_name = sles11sp1-64
-                        cdrom_cd1 = linux/SLES-11-SP1-DVD-x86_64-GM-DVD1.iso
-                        md5sum = d2e10420f3689faa49a004b60fb396b7
-                        md5sum_1m = f7f67b5da46923a9f01da8a2b6909654
                         unattended_install:
                             unattended_file = unattended/SLES-11.xml
-                            tftp = images/sles-11-1-64/tftpboot
                             floppy = images/sles-11-1-64/autoyast.vfd
                             #cdrom_unattended = images/sles-11-1-64/autoyast.iso
-                            pxe_dir = boot/x86_64/loader
+                            kernel = images/sles-11-1-64/linux
+                            initrd = images/sles-11-1-64/initrd
+                            boot_path = boot/x86_64/loader
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/SLES-11-SP1-DVD-x86_64-GM-DVD1.iso
+                            md5sum_cd1 = d2e10420f3689faa49a004b60fb396b7
+                            md5sum_1m_cd1 = f7f67b5da46923a9f01da8a2b6909654
+
 
             - @Ubuntu:
                 shell_prompt = "^root@.*[\#\$]\s*$"
@@ -1126,16 +1405,16 @@
                         only install
                         image_name = ubuntu-6.10-32
                         steps = Ubuntu-6.10-32.steps
-                        cdrom_cd1 = linux/ubuntu-6.10-desktop-i386.iso
-                        md5sum = 17fb825641571ce5888a718329efd016
-                        md5sum_1m = 7531d0a84e7451d17c5d976f1c3f8509
+                        cdrom_cd1 = isos/linux/ubuntu-6.10-desktop-i386.iso
+                        md5sum_cd1 = 17fb825641571ce5888a718329efd016
+                        md5sum_1m_cd1 = 7531d0a84e7451d17c5d976f1c3f8509
 
                     - Ubuntu-8.04-32:
                         skip = yes
                         image_name = ubuntu-8.04-32
                         install:
                             steps = Ubuntu-8.04-32.steps
-                            cdrom_cd1 = linux/ubuntu-8.04.1-desktop-i386.iso
+                            cdrom_cd1 = isos/linux/ubuntu-8.04.1-desktop-i386.iso
                         setup:
                             steps = Ubuntu-8.04-32-setupssh.steps
 
@@ -1143,9 +1422,9 @@
                         image_name = ubuntu-8.10-server-32
                         install:
                             steps = Ubuntu-8.10-server-32.steps
-                            cdrom_cd1 = linux/ubuntu-8.10-server-i386.iso
-                            md5sum = a2ec9975a91e1228c8292ed9799dc302
-                            md5sum_1m = ea493eb8ef7722ead693492fd9f8a13f
+                            cdrom_cd1 = isos/linux/ubuntu-8.10-server-i386.iso
+                            md5sum_cd1 = a2ec9975a91e1228c8292ed9799dc302
+                            md5sum_1m_cd1 = ea493eb8ef7722ead693492fd9f8a13f
                         setup:
                             steps = Ubuntu-8.10-server-32-gcc.steps
 
@@ -1156,176 +1435,252 @@
                     modprobe_module = acpiphp
                 block_hotplug:
                     modprobe_module = acpiphp
-                unattended_install.cdrom:
-                    pxe_dir = "images/pxeboot"
-                    pxe_image = "vmlinuz"
-                    pxe_initrd = "initrd.img"
-                    tftp = "images/tftpboot"
-                    bootp = "/pxelinux.0"
-                    extra_params += " -boot cn"
+                unattended_install:
+                    boot_path = images/pxeboot
                     # You have to use ks=floppy if you want to use floppies to
                     # hold your kickstart file
-                    #kernel_args = "ks=floppy nicdelay=60 console=ttyS0,115200 console=tty0"
-                    kernel_args = "ks=cdrom nicdelay=60 console=ttyS0,115200 console=tty0"
+                    #extra_params += " --append 'ks=floppy nicdelay=60 console=ttyS0,115200 console=tty0'"
+                    extra_params += " --append 'ks=cdrom nicdelay=60 console=ttyS0,115200 console=tty0'"
 
                 variants:
                     - 3.9.i386:
                         no setup autotest linux_s3 guest_s4 shutdown
                         image_name = rhel3-32
                         mem_chk_cmd = dmidecode | awk -F: '/Maximum Capacity/ {print $2}'
-                        cdrom_cd1 = linux/RHEL-3.9-i386-DVD.iso
-                        md5sum = ddd11a1cb104119039b0fa05df6d52b8
-                        md5sum_1m = 5f10c9417c7b8372b3456c1b5f3f9ed0
                         install:
                             steps=RHEL-3.9-i386.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-3.9-i386-DVD.iso
+                            md5sum_cd1 = ddd11a1cb104119039b0fa05df6d52b8
+                            md5sum_1m_cd1 = 5f10c9417c7b8372b3456c1b5f3f9ed0
+                        unattended_install:
                             unattended_file = unattended/RHEL-3-series.ks
-                            tftp = images/rhel39-32/tftpboot
                             #floppy = images/rhel39-32/ks.vfd
                             cdrom_unattended = images/rhel39-32/ks.iso
+                            kernel = images/rhel39-32/vmlinuz
+                            initrd = images/rhel39-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-3.9-i386-DVD.iso
+                            md5sum_cd1 = ddd11a1cb104119039b0fa05df6d52b8
+                            md5sum_1m_cd1 = 5f10c9417c7b8372b3456c1b5f3f9ed0
 
                     - 3.9.x86_64:
                         no setup autotest linux_s3 guest_s4 shutdown
                         image_name = rhel3-64
                         mem_chk_cmd = dmidecode | awk -F: '/Maximum Capacity/ {print $2}'
-                        cdrom_cd1 = linux/RHEL-3.9-x86_64-DVD.iso
-                        md5sum = bf4635e4a4bd3b43838e72bc8c329d55
-                        md5sum_1m = 18ecd37b639109f1b2af05cfb57dfeaf
                         install:
                             steps=RHEL-3.9-x86_64.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-3.9-x86_64-DVD.iso
+                            md5sum_cd1 = bf4635e4a4bd3b43838e72bc8c329d55
+                            md5sum_1m_cd1 = 18ecd37b639109f1b2af05cfb57dfeaf
+                        unattended_install:
                             unattended_file = unattended/RHEL-3-series.ks
-                            tftp = images/rhel39-64/tftpboot
                             #floppy = images/rhel39-64/ks.vfd
                             cdrom_unattended = images/rhel39-64/ks.iso
+                            kernel = images/rhel39-64/vmlinuz
+                            initrd = images/rhel39-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-3.9-x86_64-DVD.iso
+                            md5sum_cd1 = bf4635e4a4bd3b43838e72bc8c329d55
+                            md5sum_1m_cd1 = 18ecd37b639109f1b2af05cfb57dfeaf
+
 
                     - 4.7.i386:
                         no setup autotest
                         image_name = rhel4-32
-                        cdrom_cd1 = linux/RHEL-4.7-i386-DVD.iso
-                        md5sum = ee5092653732a88ddbaf8eef2484c500
-                        md5sum_1m = 127081cbed825d7232331a2083975528
                         install:
                             steps = RHEL-4.7-i386.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-4.7-i386-DVD.iso
+                            md5sum_cd1 = ee5092653732a88ddbaf8eef2484c500
+                            md5sum_1m_cd1 = 127081cbed825d7232331a2083975528
+                        unattended_install:
                             unattended_file = unattended/RHEL-4-series.ks
-                            tftp = images/rhel47-32/tftpboot
                             #floppy = images/rhel47-32/ks.vfd
                             cdrom_unattended = images/rhel47-32/ks.iso
+                            kernel = images/rhel47-32/vmlinuz
+                            initrd = images/rhel47-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-4.7-i386-DVD.iso
+                            md5sum_cd1 = ee5092653732a88ddbaf8eef2484c500
+                            md5sum_1m_cd1 = 127081cbed825d7232331a2083975528
 
                     - 4.7.x86_64:
                         no setup autotest
                         image_name = rhel4-64
-                        cdrom_cd1 = linux/RHEL-4.7-x86_64-DVD.iso
-                        md5sum = ea9dae16dd86f7d94092d0e672333292
-                        md5sum_1m = 58fa63eaee68e269f4cb1d2edf479792
                         install:
                             steps = RHEL-4.7-x86_64.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-4.7-x86_64-DVD.iso
+                            md5sum_cd1 = ea9dae16dd86f7d94092d0e672333292
+                            md5sum_1m_cd1 = 58fa63eaee68e269f4cb1d2edf479792
+                        unattended_install:
                             unattended_file = unattended/RHEL-4-series.ks
-                            tftp = images/rhel47-64/tftpboot
                             #floppy = images/rhel47-64/ks.vfd
                             cdrom_unattended = images/rhel47-64/ks.iso
+                            kernel = images/rhel47-64/vmlinuz
+                            initrd = images/rhel47-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-4.7-x86_64-DVD.iso
+                            md5sum_cd1 = ea9dae16dd86f7d94092d0e672333292
+                            md5sum_1m_cd1 = 58fa63eaee68e269f4cb1d2edf479792
 
                     - 4.8.i386:
                         no setup autotest
                         image_name = rhel4-32
-                        cdrom_cd1 = linux/RHEL-4.8-i386-DVD.iso
-                        md5sum = b024f0af5079539d3ef51f71fed0b194
-                        md5sum_1m = 969c197402b9058f28a278c1f807d15b
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/RHEL-4-series.ks
-                            tftp = images/rhel48-32/tftpboot
                             #floppy = images/rhel48-32/ks.vfd
                             cdrom_unattended = images/rhel48-32/ks.iso
+                            kernel = images/rhel48-32/vmlinuz
+                            initrd = images/rhel48-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-4.8-i386-DVD.iso
+                            md5sum_cd1 = b024f0af5079539d3ef51f71fed0b194
+                            md5sum_1m_cd1 = 969c197402b9058f28a278c1f807d15b
+
 
                     - 4.8.x86_64:
                         no setup autotest
                         image_name = rhel4-64
-                        cdrom_cd1 = linux/RHEL-4.8-x86_64-DVD.iso
-                        md5sum = 696bc877b0200cc942626673fcc3fc09
-                        md5sum_1m = b11ac0ef7fd345ad712966972db63886
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/RHEL-4-series.ks
-                            tftp = images/rhel48-64/tftpboot
                             #floppy = images/rhel48-64/ks.vfd
                             cdrom_unattended = images/rhel48-64/ks.iso
+                            kernel = images/rhel48-64/vmlinuz
+                            initrd = images/rhel48-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-4.8-x86_64-DVD.iso
+                            md5sum_cd1 = 696bc877b0200cc942626673fcc3fc09
+                            md5sum_1m_cd1 = b11ac0ef7fd345ad712966972db63886
+
 
                     - 5.3.i386:
                         no setup
                         image_name = rhel5-32
-                        cdrom_cd1 = linux/RHEL-5.3-i386-DVD.iso
-                        md5sum = 371c62851611fd32ead440df6f24a296
-                        md5sum_1m = 242318dd44152210f6ff6cdda1bfbf51
                         install:
                             steps = RHEL-5.3-i386.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.3-i386-DVD.iso
+                            md5sum_cd1 = 371c62851611fd32ead440df6f24a296
+                            md5sum_1m_cd1 = 242318dd44152210f6ff6cdda1bfbf51
+                        unattended_install:
                             unattended_file = unattended/RHEL-5-series.ks
-                            tftp = images/rhel53-32/tftpboot
                             #floppy = images/rhel53-32/ks.vfd
                             cdrom_unattended = images/rhel53-32/ks.iso
+                            kernel = images/rhel53-32/vmlinuz
+                            initrd = images/rhel53-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.3-i386-DVD.iso
+                            md5sum_cd1 = 371c62851611fd32ead440df6f24a296
+                            md5sum_1m_cd1 = 242318dd44152210f6ff6cdda1bfbf51
+
 
                     - 5.3.x86_64:
                         no setup
                         image_name = rhel5-64
-                        cdrom_cd1 = linux/RHEL-5.3-x86_64-DVD.iso
-                        md5sum = c5ed6b284410f4d8212cafc78fd7a8c5
-                        md5sum_1m = b999f437583098ea5bbd56fb1de1d011
                         install:
                             steps=RHEL-5.3-x86_64.steps
-                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.3-x86_64-DVD.iso
+                            md5sum_cd1 = c5ed6b284410f4d8212cafc78fd7a8c5
+                            md5sum_1m_cd1 = b999f437583098ea5bbd56fb1de1d011
+                        unattended_install:
                             unattended_file = unattended/RHEL-5-series.ks
-                            tftp = images/rhel53-64/tftpboot
                             #floppy = images/rhel53-64/ks.vfd
                             cdrom_unattended = images/rhel53-64/ks.iso
+                            kernel = images/rhel53-64/vmlinuz
+                            initrd = images/rhel53-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.3-x86_64-DVD.iso
+                            md5sum_cd1 = c5ed6b284410f4d8212cafc78fd7a8c5
+                            md5sum_1m_cd1 = b999f437583098ea5bbd56fb1de1d011
+
 
                     - 5.4.i386:
                         no setup
                         image_name = rhel5-32
-                        cdrom_cd1 = linux/RHEL-5.4-i386-DVD.iso
-                        md5sum = 7a12ec6599527e4f3d1790b51eadbfed
-                        md5sum_1m = 0dbeb8f58d213752d8c029e8601abfbb
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/RHEL-5-series.ks
-                            tftp = images/rhel54-32/tftpboot
                             #floppy = images/rhel54-32/ks.vfd
                             cdrom_unattended = images/rhel54-32/ks.iso
+                            kernel = images/rhel54-32/vmlinuz
+                            initrd = images/rhel54-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.4-i386-DVD.iso
+                            md5sum_cd1 = 7a12ec6599527e4f3d1790b51eadbfed
+                            md5sum_1m_cd1 = 0dbeb8f58d213752d8c029e8601abfbb
+
 
                     - 5.4.x86_64:
                         no setup
                         image_name = rhel5-64
-                        cdrom_cd1 = linux/RHEL-5.4-x86_64-DVD.iso
-                        md5sum = 04fe3c10202402d7b389528d2bad0210
-                        md5sum_1m = 3e74112003e88a966754849dbb8f5c3f
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/RHEL-5-series.ks
-                            tftp = images/rhel54-64/tftpboot
                             #floppy = images/rhel54-64/ks.vfd
                             cdrom_unattended = images/rhel54-64/ks.iso
+                            kernel = images/rhel54-64/vmlinuz
+                            initrd = images/rhel54-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.4-x86_64-DVD.iso
+                            md5sum_cd1 = 04fe3c10202402d7b389528d2bad0210
+                            md5sum_1m_cd1 = 3e74112003e88a966754849dbb8f5c3f
+
 
                     - 5.5.i386:
                         no setup
                         image_name = rhel5-32
-                        cdrom_cd1 = linux/RHEL-5.5-i386-DVD.iso
-                        md5sum = 148858b157f275d9153797efddfc83c3
-                        md5sum_1m = 2502cc7ddb9d0684fe08c4a83d247902
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/RHEL-5-series.ks
-                            tftp = images/rhel55-32/tftpboot
                             #floppy = images/rhel55-32/ks.vfd
                             cdrom_unattended = images/rhel55-32/ks.iso
+                            kernel = images/rhel55-32/vmlinuz
+                            initrd = images/rhel55-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.5-i386-DVD.iso
+                            md5sum_cd1 = 148858b157f275d9153797efddfc83c3
+                            md5sum_1m_cd1 = 2502cc7ddb9d0684fe08c4a83d247902
+
 
                     - 5.5.x86_64:
                         no setup
                         image_name = rhel5-64
-                        cdrom_cd1 = linux/RHEL-5.5-x86_64-DVD.iso
-                        md5sum = f3119f883257ef9041234feda2f1cad0
-                        md5sum_1m = a744084a03f6a08627f71527fc107a1e
-                        unattended_install.cdrom:
+                        unattended_install:
                             unattended_file = unattended/RHEL-5-series.ks
-                            tftp = images/rhel55-64/tftpboot
                             #floppy = images/rhel55-64/ks.vfd
                             cdrom_unattended = images/rhel55-64/ks.iso
+                            kernel = images/rhel55-64/vmlinuz
+                            initrd = images/rhel55-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-5.5-x86_64-DVD.iso
+                            md5sum_cd1 = f3119f883257ef9041234feda2f1cad0
+                            md5sum_1m_cd1 = a744084a03f6a08627f71527fc107a1e
+
+
+                    - 6.0.i386:
+                        no setup
+                        image_name = rhel6-32
+                        unattended_install:
+                            unattended_file = unattended/RHEL-6-series.ks
+                            #floppy = images/rhel60-32/ks.vfd
+                            cdrom_unattended = images/rhel60-32/ks.iso
+                            kernel = images/rhel60-32/vmlinuz
+                            initrd = images/rhel60-32/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-6.0-i386-DVD.iso
+                            md5sum_cd1 = 291d234c93442405972689b4b41c14bc
+                            md5sum_1m_cd1 = ee2cc3d3babe91a1d581a07099c4318b
+
+
+                    - 6.0.x86_64:
+                        no setup
+                        image_name = rhel6-64
+                        unattended_install:
+                            unattended_file = unattended/RHEL-6-series.ks
+                            #floppy = images/rhel60-64/ks.vfd
+                            cdrom_unattended = images/rhel60-64/ks.iso
+                            kernel = images/rhel60-64/vmlinuz
+                            initrd = images/rhel60-64/initrd.img
+                        unattended_install.cdrom:
+                            cdrom_cd1 = isos/linux/RHEL-6.0-x86_64-DVD.iso
+                            md5sum_cd1 = f7141396c6a19399d63e8c195354317d
+                            md5sum_1m_cd1 = b060eeef63e2c8700db54ae02056e80c
+
 
 
     # Windows section
@@ -1347,23 +1702,25 @@
         guest_port_file_transfer = 10023
 
         # This ISO will be used for all tests except install:
-        cdrom_cd1 = windows/winutils.iso
+        cdrom_cd1 = isos/windows/winutils.iso
 
         cpu_chk_cmd = echo %NUMBER_OF_PROCESSORS%
         mem_chk_cmd = wmic memphysical
         mem_chk_cur_cmd = wmic memphysical
 
-        unattended_install.cdrom:
+        unattended_install.cdrom|whql.support_vm_install:
             timeout = 7200
             finish_program = deps/finish.exe
             cdroms += " winutils"
-            cdrom_winutils = windows/winutils.iso
+            cdrom_winutils = isos/windows/winutils.iso
             drive_index_winutils = 2
+            kernel =
+            initrd =
             # Turn install_virtio = yes if you want to install the
             # Windows virtio drivers. It might be a lot of setup though :)
             #install_virtio = no
             #cdroms += " virtio"
-            #cdrom_virtio = windows/virtio-win.iso
+            #cdrom_virtio = isos/windows/virtio-win.iso
             #drive_index_virtio = 3
             #virtio_floppy = /usr/share/virtio-win/virtio-drivers.vfd
         migrate:
@@ -1371,6 +1728,10 @@
             migration_bg_command = start ping -t localhost
             migration_bg_check_command = tasklist | find /I "ping.exe"
             migration_bg_kill_command = taskkill /IM ping.exe /F
+        migrate.with_file_transfer:
+            guest_path = C:\tmpfile
+            rtl8139:
+                file_size = 10
         stress_boot:
             alive_test_cmd = systeminfo
         timedrift:
@@ -1398,7 +1759,6 @@
                 time_command = "echo TIME: %date% %time%"
                 time_filter_re = "(?<=TIME: \w\w\w ).{19}(?=\.\d\d)"
                 time_format = "%m/%d/%Y %H:%M:%S"
-
         guest_s4:
             check_s4_support_cmd = powercfg /hibernate on
             test_s4_cmd = start ping -t localhost
@@ -1420,11 +1780,13 @@
             find_pci_cmd = wmic diskdrive list brief
             pci_test_cmd = echo select disk 1 > dt && echo online >> dt && echo detail disk >> dt && echo exit >> dt && diskpart /s dt
         physical_resources_check:
-            catch_uuid_cmd = 
-
+            catch_uuid_cmd =
         file_transfer:
             tmp_dir = C:\
             clean_cmd = del
+        vmstop:
+            guest_path = C:\
+
         variants:
             - Win2000:
                 no reboot whql
@@ -1432,16 +1794,16 @@
                 kill_vm_gracefully = no
                 install:
                     steps = Win2000-32.steps
-                    cdrom_cd1 = windows/Windows2000_sp4.iso
-                    md5sum = dda6039f3a9173f0f6bfae40f5efdfea
-                    md5sum_1m = dd28fba196d366d56fe774bd93df5527
+                    cdrom_cd1 = isos/windows/Windows2000_sp4.iso
+                    md5sum_cd1 = dda6039f3a9173f0f6bfae40f5efdfea
+                    md5sum_1m_cd1 = dd28fba196d366d56fe774bd93df5527
                     user = user
                 setup:
                     steps = Win2000-32-rss.steps
                 unattended_install.cdrom:
-                    cdrom_cd1 = windows/Windows2000_sp4.iso
-                    md5sum = dda6039f3a9173f0f6bfae40f5efdfea
-                    md5sum_1m = dd28fba196d366d56fe774bd93df5527
+                    cdrom_cd1 = isos/windows/Windows2000_sp4.iso
+                    md5sum_cd1 = dda6039f3a9173f0f6bfae40f5efdfea
+                    md5sum_1m_cd1 = dd28fba196d366d56fe774bd93df5527
                     unattended_file = unattended/win2000-32.sif
                     floppy = images/win2000-32/answer.vfd
 
@@ -1451,17 +1813,17 @@
                     - 32:
                         image_name += -32
                         install:
-                            cdrom_cd1 = windows/WindowsXP-sp2-vlk.iso
-                            md5sum = 743450644b1d9fe97b3cf379e22dceb0
-                            md5sum_1m = b473bf75af2d1269fec8958cf0202bfd
+                            cdrom_cd1 = isos/windows/WindowsXP-sp2-vlk.iso
+                            md5sum_cd1 = 743450644b1d9fe97b3cf379e22dceb0
+                            md5sum_1m_cd1 = b473bf75af2d1269fec8958cf0202bfd
                             user = user
                             steps = WinXP-32.steps
                         setup:
                             steps = WinXP-32-rss.steps
-                        unattended_install.cdrom:
-                            cdrom_cd1 = windows/WindowsXP-sp2-vlk.iso
-                            md5sum = 743450644b1d9fe97b3cf379e22dceb0
-                            md5sum_1m = b473bf75af2d1269fec8958cf0202bfd
+                        unattended_install.cdrom|whql.support_vm_install:
+                            cdrom_cd1 = isos/windows/WindowsXP-sp2-vlk.iso
+                            md5sum_cd1 = 743450644b1d9fe97b3cf379e22dceb0
+                            md5sum_1m_cd1 = b473bf75af2d1269fec8958cf0202bfd
                             unattended_file = unattended/winxp32.sif
                             floppy = images/winXP-32/answer.vfd
                             # Uncomment virtio_network_installer_path line if
@@ -1470,28 +1832,31 @@
                             virtio_oemsetup_id = WXP32
                             virtio_network_path = 'F:\NetKVM\xp\x86'
                             #virtio_network_installer_path = 'F:\RHEV-Network32.msi'
-                        whql_submission:
+                        whql.submission:
                             desc_path_desc1 = $\WDK\Logo Type\Device Logo\Windows XP
                             desc_path_desc2 = $\WDK\Logo Type\Systems Logo\Windows XP
                             dd_data_logoarch = X86
                             dd_data_logoos = Windows XP
                             dd_data_whqlos = Windows XP
-                            dd_data_whqlqual = Basic
+                            device:
+                                dd_data_whqlqual = Basic
+                            device.net:
+                                image_name_supportvm = winXP-32-supportvm
 
                     - 64:
                         image_name += -64
                         install:
-                            cdrom_cd1 = windows/WindowsXP-64.iso
-                            md5sum = 8d3f007ec9c2060cec8a50ee7d7dc512
-                            md5sum_1m = e812363ff427effc512b7801ee70e513
+                            cdrom_cd1 = isos/windows/WindowsXP-64.iso
+                            md5sum_cd1 = 8d3f007ec9c2060cec8a50ee7d7dc512
+                            md5sum_1m_cd1 = e812363ff427effc512b7801ee70e513
                             user = user
                             steps = WinXP-64.steps
                         setup:
                             steps = WinXP-64-rss.steps
-                        unattended_install.cdrom:
-                            cdrom_cd1 = windows/WindowsXP-64.iso
-                            md5sum = 8d3f007ec9c2060cec8a50ee7d7dc512
-                            md5sum_1m = e812363ff427effc512b7801ee70e513
+                        unattended_install.cdrom|whql.support_vm_install:
+                            cdrom_cd1 = isos/windows/WindowsXP-64.iso
+                            md5sum_cd1 = 8d3f007ec9c2060cec8a50ee7d7dc512
+                            md5sum_1m_cd1 = e812363ff427effc512b7801ee70e513
                             unattended_file = unattended/winxp64.sif
                             floppy = images/winXP-64/answer.vfd
                             # Uncomment virtio_network_installer_path line if
@@ -1500,13 +1865,16 @@
                             virtio_oemsetup_id = WNET64
                             virtio_network_path = 'F:\NetKVM\xp\amd64'
                             #virtio_network_installer_path = 'F:\RHEV-Network64.msi'
-                        whql_submission:
+                        whql.submission:
                             desc_path_desc1 = $\WDK\Logo Type\Device Logo\Windows XP
                             desc_path_desc2 = $\WDK\Logo Type\Systems Logo\Windows XP
                             dd_data_logoarch = AMD64
                             dd_data_logoos = Windows XP 64-Bit Edition Version 2003
                             dd_data_whqlos = Windows XP x64
-                            dd_data_whqlqual = Basic
+                            device:
+                                dd_data_whqlqual = Basic
+                            device.net:
+                                image_name_supportvm = winXP-64-supportvm
 
             - Win2003:
                 image_name = win2003
@@ -1516,17 +1884,17 @@
                     - 32:
                         image_name += -32
                         install:
-                            cdrom_cd1 = windows/Windows2003_r2_VLK.iso
-                            md5sum = 03e921e9b4214773c21a39f5c3f42ef7
-                            md5sum_1m = 37c2fdec15ac4ec16aa10fdfdb338aa3
+                            cdrom_cd1 = isos/windows/Windows2003_r2_VLK.iso
+                            md5sum_cd1 = 03e921e9b4214773c21a39f5c3f42ef7
+                            md5sum_1m_cd1 = 37c2fdec15ac4ec16aa10fdfdb338aa3
                             user = user
                             steps = Win2003-32.steps
                         setup:
                             steps = Win2003-32-rss.steps
-                        unattended_install.cdrom:
-                            cdrom_cd1 = windows/Windows2003_r2_VLK.iso
-                            md5sum = 03e921e9b4214773c21a39f5c3f42ef7
-                            md5sum_1m = 37c2fdec15ac4ec16aa10fdfdb338aa3
+                        unattended_install.cdrom|whql.support_vm_install:
+                            cdrom_cd1 = isos/windows/Windows2003_r2_VLK.iso
+                            md5sum_cd1 = 03e921e9b4214773c21a39f5c3f42ef7
+                            md5sum_1m_cd1 = 37c2fdec15ac4ec16aa10fdfdb338aa3
                             unattended_file = unattended/win2003-32.sif
                             floppy = images/win2003-32/answer.vfd
                             # Uncomment virtio_network_installer_path line if
@@ -1535,27 +1903,30 @@
                             virtio_oemsetup_id = WNET32
                             virtio_network_path = 'F:\NetKVM\2k3\x86'
                             #virtio_network_installer_path = 'F:\RHEV-Network32.msi'
-                        whql_submission:
+                        whql.submission:
                             desc_path_desc1 = $\WDK\Logo Type\Device Logo\Windows Server 2003
                             dd_data_logoarch = X86
                             dd_data_logoos = Windows Server 2003
                             dd_data_whqlos = Windows Server 2003
-                            dd_data_whqlqual = Basic
+                            device:
+                                dd_data_whqlqual = Basic
+                            device.net:
+                                image_name_supportvm = win2003-32-supportvm
 
                     - 64:
                         image_name += -64
                         install:
-                            cdrom_cd1 = windows/Windows2003-x64.iso
-                            md5sum = 5703f87c9fd77d28c05ffadd3354dbbd
-                            md5sum_1m = 439393c384116aa09e08a0ad047dcea8
+                            cdrom_cd1 = isos/windows/Windows2003-x64.iso
+                            md5sum_cd1 = 5703f87c9fd77d28c05ffadd3354dbbd
+                            md5sum_1m_cd1 = 439393c384116aa09e08a0ad047dcea8
                             user = user
                             steps = Win2003-64.steps
                         setup:
                             steps = Win2003-64-rss.steps
-                        unattended_install.cdrom:
-                            cdrom_cd1 = windows/Windows2003-x64.iso
-                            md5sum = 5703f87c9fd77d28c05ffadd3354dbbd
-                            md5sum_1m = 439393c384116aa09e08a0ad047dcea8
+                        unattended_install.cdrom|whql.support_vm_install:
+                            cdrom_cd1 = isos/windows/Windows2003-x64.iso
+                            md5sum_cd1 = 5703f87c9fd77d28c05ffadd3354dbbd
+                            md5sum_1m_cd1 = 439393c384116aa09e08a0ad047dcea8
                             unattended_file = unattended/win2003-64.sif
                             floppy = images/win2003-64/answer.vfd
                             # Uncomment virtio_network_installer_path line if
@@ -1564,43 +1935,46 @@
                             virtio_oemsetup_id = WNET64
                             virtio_network_path = 'F:\NetKVM\2k3\amd64'
                             #virtio_network_installer_path = 'F:\RHEV-Network64.msi'
-
-                        whql_submission:
+                        whql.submission:
                             desc_path_desc1 = $\WDK\Logo Type\Device Logo\Windows Server 2003
                             dd_data_logoarch = AMD64
                             dd_data_logoos = Windows Server 2003
                             dd_data_whqlos = Windows Server 2003 x64
-                            dd_data_whqlqual = Basic
+                            device:
+                                dd_data_whqlqual = Basic
+                            device.net:
+                                image_name_supportvm = win2003-64-supportvm
 
             - WinVista:
                 image_name = winvista
                 image_size = 20G
-                whql_submission:
+                whql.submission:
                     desc_path_desc1 = $\WDK\Logo Type\Device Logo\Vista Client\Device Premium
                     desc_path_desc2 = $\WDK\Logo Type\Device Logo\Vista Client\Device Standard
                     desc_path_desc3 = $\WDK\Logo Type\Device Logo\Vista Client
 
                 variants:
                     - 32:
-                        whql_submission:
+                        whql.submission:
                             dd_data_logoarch = X86
                             dd_data_logoos = Windows Vista
                             dd_data_whqlos = Windows Vista Client
-                            dd_data_whqlqual = Premium
+                            device:
+                                dd_data_whqlqual = Premium
                         variants:
                             - sp1:
                                 image_name += -sp1-32
                                 install:
-                                    cdrom_cd1 = windows/WindowsVista-32.iso
-                                    md5sum = 1008f323d5170c8e614e52ccb85c0491
-                                    md5sum_1m = c724e9695da483bc0fd59e426eaefc72
+                                    cdrom_cd1 = isos/windows/WindowsVista-32.iso
+                                    md5sum_cd1 = 1008f323d5170c8e614e52ccb85c0491
+                                    md5sum_1m_cd1 = c724e9695da483bc0fd59e426eaefc72
                                     steps = Win-Vista-32.steps
                                 setup:
                                     steps = WinVista-32-rss.steps
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/WindowsVista-32.iso
-                                    md5sum = 1008f323d5170c8e614e52ccb85c0491
-                                    md5sum_1m = c724e9695da483bc0fd59e426eaefc72
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/WindowsVista-32.iso
+                                    md5sum_cd1 = 1008f323d5170c8e614e52ccb85c0491
+                                    md5sum_1m_cd1 = c724e9695da483bc0fd59e426eaefc72
                                     unattended_file = unattended/winvista-32-autounattend.xml
                                     floppy = images/winvista-sp1-32/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1609,15 +1983,17 @@
                                     virtio_storage_path = 'F:\viostor\w7\x86'
                                     virtio_network_path = 'F:\NetKVM\w7\x86'
                                     #virtio_network_installer_path = 'F:\RHEV-Network32.msi'
+                                whql.submission.device.net:
+                                    image_name_supportvm = winvista-sp1-32-supportvm
 
                             - sp2:
                                 image_name += -sp2-32
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/en_windows_vista_with_sp2_x86_dvd_342266.iso
-                                    md5sum = 19ca90a425667812977bab6f4ce24175
-                                    md5sum_1m = 89c15020e0e6125be19acf7a2e5dc614
-                                    sha1sum = 25ad9a776503e6a583bec07879dbcc5dfd20cd6e
-                                    sha1sum_1m = a2afa4cffdc1c362dbf9e62942337f4f875a22cf
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/en_windows_vista_with_sp2_x86_dvd_342266.iso
+                                    md5sum_cd1 = 19ca90a425667812977bab6f4ce24175
+                                    md5sum_1m_cd1 = 89c15020e0e6125be19acf7a2e5dc614
+                                    sha1sum_cd1 = 25ad9a776503e6a583bec07879dbcc5dfd20cd6e
+                                    sha1sum_1m_cd1 = a2afa4cffdc1c362dbf9e62942337f4f875a22cf
                                     unattended_file = unattended/winvista-32-autounattend.xml
                                     floppy = images/winvista-sp2-32/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1626,27 +2002,30 @@
                                     virtio_storage_path = 'F:\viostor\w7\x86'
                                     virtio_network_path = 'F:\NetKVM\w7\x86'
                                     #virtio_network_installer_path = 'F:\RHEV-Network32.msi'
+                                whql.submission.device.net:
+                                    image_name_supportvm = winvista-sp2-32-supportvm
 
                     - 64:
-                        whql_submission:
+                        whql.submission:
                             dd_data_logoarch = AMD64
                             dd_data_logoos = Windows Vista
                             dd_data_whqlos = Windows Vista Client x64
-                            dd_data_whqlqual = Premium
+                            device:
+                                dd_data_whqlqual = Premium
                         variants:
                             - sp1:
                                 image_name += -sp1-64
                                 install:
-                                    cdrom_cd1 = windows/WindowsVista-64.iso
-                                    md5sum = 11e2010d857fffc47813295e6be6d58d
-                                    md5sum_1m = 0947bcd5390546139e25f25217d6f165
+                                    cdrom_cd1 = isos/windows/WindowsVista-64.iso
+                                    md5sum_cd1 = 11e2010d857fffc47813295e6be6d58d
+                                    md5sum_1m_cd1 = 0947bcd5390546139e25f25217d6f165
                                     steps = Win-Vista-64.steps
                                 setup:
                                     steps = WinVista-64-rss.steps
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/WindowsVista-64.iso
-                                    md5sum = 11e2010d857fffc47813295e6be6d58d
-                                    md5sum_1m = 0947bcd5390546139e25f25217d6f165
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/WindowsVista-64.iso
+                                    md5sum_cd1 = 11e2010d857fffc47813295e6be6d58d
+                                    md5sum_1m_cd1 = 0947bcd5390546139e25f25217d6f165
                                     unattended_file = unattended/winvista-64-autounattend.xml
                                     floppy = images/winvista-sp1-64/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1655,14 +2034,17 @@
                                     virtio_storage_path = 'F:\viostor\w7\amd64'
                                     virtio_network_path = 'F:\NetKVM\w7\amd64'
                                     #virtio_network_installer_path = 'F:\RHEV-Network64.msi'
+                                whql.submission.device.net:
+                                    image_name_supportvm = winvista-sp1-64-supportvm
+
                             - sp2:
                                 image_name += -sp2-64
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/en_windows_vista_sp2_x64_dvd_342267.iso
-                                    md5sum = a1c024d7abaf34bac3368e88efbc2574
-                                    md5sum_1m = 3d84911a80f3df71d1026f7adedc2181
-                                    sha1sum = aaee3c04533899f9f8c4ae0c4250ef5fafbe29a3
-                                    sha1sum_1m = 1fd21bd3ce2a4de8856c7b8fe6fdf80260f6d1c7
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/en_windows_vista_sp2_x64_dvd_342267.iso
+                                    md5sum_cd1 = a1c024d7abaf34bac3368e88efbc2574
+                                    md5sum_1m_cd1 = 3d84911a80f3df71d1026f7adedc2181
+                                    sha1sum_cd1 = aaee3c04533899f9f8c4ae0c4250ef5fafbe29a3
+                                    sha1sum_1m_cd1 = 1fd21bd3ce2a4de8856c7b8fe6fdf80260f6d1c7
                                     unattended_file = unattended/winvista-64-autounattend.xml
                                     floppy = images/winvista-sp2-64/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1671,6 +2053,8 @@
                                     virtio_storage_path = 'F:\viostor\w7\amd64'
                                     virtio_network_path = 'F:\NetKVM\w7\amd64'
                                     #virtio_network_installer_path = 'F:\RHEV-Network64.msi'
+                                whql.submission.device.net:
+                                    image_name_supportvm = winvista-sp2-64-supportvm
 
             - Win2008:
                 no whql
@@ -1683,16 +2067,16 @@
                             - sp1:
                                 image_name += -sp1-32
                                 install:
-                                    cdrom_cd1 = windows/Windows2008-x86.iso
+                                    cdrom_cd1 = isos/windows/Windows2008-x86.iso
                                     #en_windows_server_2008_datacenter_enterprise_standard_x86_dvd_X14-26710.iso
                                     md5sum=0bfca49f0164de0a8eba236ced47007d
                                     md5sum_1m=07d7f5006393f74dc76e6e2e943e2440
-                                    sha1sum = 6ca018ff96f1e9b2b310a36546b6fded99a421e6
+                                    sha1sum_cd1 = 6ca018ff96f1e9b2b310a36546b6fded99a421e6
                                     steps = Win2008-32.steps
                                 setup:
                                     steps = Win2008-32-rss.steps
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/Windows2008-x86.iso
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/Windows2008-x86.iso
                                     md5sum=0bfca49f0164de0a8eba236ced47007d
                                     md5sum_1m=07d7f5006393f74dc76e6e2e943e2440
                                     unattended_file = unattended/win2008-32-autounattend.xml
@@ -1706,12 +2090,12 @@
 
                             - sp2:
                                 image_name += -sp2-32
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/en_windows_server_2008_datacenter_enterprise_standard_sp2_x86_dvd_342333.iso
-                                    md5sum = b9201aeb6eef04a3c573d036a8780bdf
-                                    md5sum_1m = b7a9d42e55ea1e85105a3a6ad4da8e04
-                                    sha1sum = 49d0d6917c1256fe81048d414fa473bbc76a8724
-                                    sha1sum_1m = 9662ff7ed715faa00407e4befc484ea52a92a9fb
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/en_windows_server_2008_datacenter_enterprise_standard_sp2_x86_dvd_342333.iso
+                                    md5sum_cd1 = b9201aeb6eef04a3c573d036a8780bdf
+                                    md5sum_1m_cd1 = b7a9d42e55ea1e85105a3a6ad4da8e04
+                                    sha1sum_cd1 = 49d0d6917c1256fe81048d414fa473bbc76a8724
+                                    sha1sum_1m_cd1 = 9662ff7ed715faa00407e4befc484ea52a92a9fb
                                     unattended_file = unattended/win2008-32-autounattend.xml
                                     floppy = images/win2008-sp2-32/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1727,16 +2111,16 @@
                                 image_name += -sp1-64
                                 install:
                                     steps = Win2008-64.steps
-                                    cdrom_cd1 = windows/Windows2008-x64.iso
+                                    cdrom_cd1 = isos/windows/Windows2008-x64.iso
                                     #en_windows_server_2008_datacenter_enterprise_standard_x64_dvd_X14-26714.iso
                                     md5sum=27c58cdb3d620f28c36333a5552f271c
                                     md5sum_1m=efdcc11d485a1ef9afa739cb8e0ca766
-                                    sha1sum = bd000374709f67e9358814db6ec8f0ddaaa16f70
+                                    sha1sum_cd1 = bd000374709f67e9358814db6ec8f0ddaaa16f70
                                     passwd = 1q2w3eP
                                 setup:
                                     steps = Win2008-64-rss.steps
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/Windows2008-x64.iso
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/Windows2008-x64.iso
                                     md5sum=27c58cdb3d620f28c36333a5552f271c
                                     md5sum_1m=efdcc11d485a1ef9afa739cb8e0ca766
                                     unattended_file = unattended/win2008-64-autounattend.xml
@@ -1750,12 +2134,12 @@
 
                             - sp2:
                                 image_name += -sp2-64
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/en_windows_server_2008_datacenter_enterprise_standard_sp2_x64_dvd_342336.iso
-                                    md5sum = e94943ef484035b3288d8db69599a6b5
-                                    md5sum_1m = ee55506823d0efffb5532ddd88a8e47b
-                                    sha1sum = 34c7d726c57b0f8b19ba3b40d1b4044c15fc2029
-                                    sha1sum_1m = 8fe08b03e3531906855a60a78020ac9577dff5ba
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/en_windows_server_2008_datacenter_enterprise_standard_sp2_x64_dvd_342336.iso
+                                    md5sum_cd1 = e94943ef484035b3288d8db69599a6b5
+                                    md5sum_1m_cd1 = ee55506823d0efffb5532ddd88a8e47b
+                                    sha1sum_cd1 = 34c7d726c57b0f8b19ba3b40d1b4044c15fc2029
+                                    sha1sum_1m_cd1 = 8fe08b03e3531906855a60a78020ac9577dff5ba
                                     unattended_file = unattended/win2008-64-autounattend.xml
                                     floppy = images/win2008-sp2-64/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1767,12 +2151,12 @@
 
                             - r2:
                                 image_name += -r2-64
-                                unattended_install.cdrom:
-                                    cdrom_cd1 = windows/en_windows_server_2008_r2_standard_enterprise_datacenter_and_web_x64_dvd_x15-59754.iso
-                                    md5sum = 0207ef392c60efdda92071b0559ca0f9
-                                    md5sum_1m = a5a22ce25008bd7109f6d830d627e3ed
-                                    sha1sum = ad855ea913aaec3f1d0e1833c1aef7a0de326b0a
-                                    sha1sum_1m = 9194a3aabae25b36e5f73cad001314b2c8d07d14
+                                unattended_install.cdrom|whql.support_vm_install:
+                                    cdrom_cd1 = isos/windows/en_windows_server_2008_r2_standard_enterprise_datacenter_and_web_x64_dvd_x15-59754.iso
+                                    md5sum_cd1 = 0207ef392c60efdda92071b0559ca0f9
+                                    md5sum_1m_cd1 = a5a22ce25008bd7109f6d830d627e3ed
+                                    sha1sum_cd1 = ad855ea913aaec3f1d0e1833c1aef7a0de326b0a
+                                    sha1sum_1m_cd1 = 9194a3aabae25b36e5f73cad001314b2c8d07d14
                                     unattended_file = unattended/win2008-r2-autounattend.xml
                                     floppy = images/win2008-r2-64/answer.vfd
                                     # Uncomment virtio_network_installer_path line if
@@ -1785,7 +2169,7 @@
             - Win7:
                 image_name = win7
                 image_size = 20G
-                whql_submission:
+                whql.submission:
                     desc_path_desc1 = $\WDK\Logo Type\Device Logo\Windows 7 Client\Logo
                     desc_path_desc2 = $\WDK\Logo Type\Device Logo\Windows 7 Client
                     device_data += " adq"
@@ -1795,12 +2179,12 @@
                 variants:
                     - 32:
                         image_name += -32
-                        unattended_install.cdrom:
-                            cdrom_cd1 = windows/en_windows_7_ultimate_x86_dvd_x15-65921.iso
-                            md5sum = d0b8b407e8a3d4b75ee9c10147266b89
-                            md5sum_1m = 2b0c2c22b1ae95065db08686bf83af93
-                            sha1sum = 5395dc4b38f7bdb1e005ff414deedfdb16dbf610
-                            sha1sum_1m = 9f9c3780aebeb28a9bf22188eed6bc15475dc9c5
+                        unattended_install.cdrom|whql.support_vm_install:
+                            cdrom_cd1 = isos/windows/en_windows_7_ultimate_x86_dvd_x15-65921.iso
+                            md5sum_cd1 = d0b8b407e8a3d4b75ee9c10147266b89
+                            md5sum_1m_cd1 = 2b0c2c22b1ae95065db08686bf83af93
+                            sha1sum_cd1 = 5395dc4b38f7bdb1e005ff414deedfdb16dbf610
+                            sha1sum_1m_cd1 = 9f9c3780aebeb28a9bf22188eed6bc15475dc9c5
                             unattended_file = unattended/win7-32-autounattend.xml
                             floppy = images/win7-32/answer.vfd
                             # Uncomment virtio_network_installer_path line if
@@ -1809,28 +2193,31 @@
                             virtio_storage_path = 'F:\viostor\w7\x86'
                             virtio_network_path = 'F:\NetKVM\w7\x86'
                             #virtio_network_installer_path = 'F:\RHEV-Network32.msi'
-                        whql_submission:
+                        whql.submission:
                             dd_data_logoarch = X86
                             dd_data_logoos = Windows 7
                             dd_data_whqlos = Windows 7 Client
-                            dd_data_whqlqual = Logo
+                            device:
+                                dd_data_whqlqual = Logo
+                            device.net:
+                                image_name_supportvm = win7-32-supportvm
 
                     - 64:
                         image_name += -64
                         install:
-                            cdrom_cd1 = windows/en_windows_7_ultimate_x64_dvd_x15-65922.iso
+                            cdrom_cd1 = isos/windows/en_windows_7_ultimate_x64_dvd_x15-65922.iso
                             md5sum=f43d22e4fb07bf617d573acd8785c028
                             md5sum_1m=b44d8cf99dbed2a5cb02765db8dfd48f
                             passwd = 1q2w3eP
                             steps = Win7-64.steps
                         setup:
                             steps = Win7-64-rss.steps
-                        unattended_install.cdrom:
-                            cdrom_cd1 = windows/en_windows_7_ultimate_x64_dvd_x15-65922.iso
-                            md5sum = f43d22e4fb07bf617d573acd8785c028
-                            md5sum_1m = b44d8cf99dbed2a5cb02765db8dfd48f
-                            sha1sum = 326327cc2ff9f05379f5058c41be6bc5e004baa7
-                            sha1sum_1m = 4a3903bd5157de54f0702e5263e0a683c5775515
+                        unattended_install.cdrom|whql.support_vm_install:
+                            cdrom_cd1 = isos/windows/en_windows_7_ultimate_x64_dvd_x15-65922.iso
+                            md5sum_cd1 = f43d22e4fb07bf617d573acd8785c028
+                            md5sum_1m_cd1 = b44d8cf99dbed2a5cb02765db8dfd48f
+                            sha1sum_cd1 = 326327cc2ff9f05379f5058c41be6bc5e004baa7
+                            sha1sum_1m_cd1 = 4a3903bd5157de54f0702e5263e0a683c5775515
                             unattended_file = unattended/win7-64-autounattend.xml
                             floppy = images/win7-64/answer.vfd
                             # Uncomment virtio_network_installer_path line if
@@ -1839,11 +2226,14 @@
                             virtio_storage_path = 'F:\viostor\w7\amd64'
                             virtio_network_path = 'F:\NetKVM\w7\amd64'
                             #virtio_network_installer_path = 'F:\RHEV-Network64.msi'
-                        whql_submission:
+                        whql.submission:
                             dd_data_logoarch = AMD64
                             dd_data_logoos = Windows 7
                             dd_data_whqlos = Windows 7 Client x64
-                            dd_data_whqlqual = Logo
+                            device:
+                                dd_data_whqlqual = Logo
+                            device.net:
+                                image_name_supportvm = win7-64-supportvm
 
 
     # Unix/BSD section
@@ -1858,16 +2248,16 @@
                 image_name = NetBSD-1.6.2
                 image_size = 4G
                 steps = NetBSD-1.6.2.steps
-                cdrom_cd1 = bsd/netbsd-1.6.2-i386.iso
+                cdrom_cd1 = isos/bsd/netbsd-1.6.2-i386.iso
                 md5sum=72eb680300f77d529bfbc880ba8208f3
                 md5sum_1m=f1a9e1e825c90adfb1be35c6177bd9ac
 
             - OpenBSD-4.1:
                 image_name = OpenBSD-4.1
                 steps = OpenBSD-4.1-32.steps
-                cdrom_cd1 = unix/openbsd41-i386-07-05-06.iso
-                md5sum = 984790db10ebdd6fc7a9cf97abc7c967
-                md5sum_1m = 8fc234b4b0ecfe56843a32ac1d26ed55
+                cdrom_cd1 = isos/unix/openbsd41-i386-07-05-06.iso
+                md5sum_cd1 = 984790db10ebdd6fc7a9cf97abc7c967
+                md5sum_1m_cd1 = 8fc234b4b0ecfe56843a32ac1d26ed55
 
     # Live CD section
     - @livecd:
@@ -1879,27 +2269,31 @@
         variants:
             - Belenix:
                 steps = Belenix-0.7.1.steps
-                cdrom_cd1 = unix/belenix_0.7.1.iso
-                md5sum = 29cea6160cf5250de138e2820e53e342
-                md5sum_1m = 427bbef1b85d6d051799b825d686ae94
+                cdrom_cd1 = isos/unix/belenix_0.7.1.iso
+                md5sum_cd1 = 29cea6160cf5250de138e2820e53e342
+                md5sum_1m_cd1 = 427bbef1b85d6d051799b825d686ae94
 
             - Slax:
                 steps = Slax-6.0.7.steps
-                cdrom_cd1 = linux/slax-6.0.7.iso
-                md5sum = cde0ecba3c8289d786e12c44666ded6e
-                md5sum_1m = ddf02bc7444f22d1160a6e5a8fc8723f
+                cdrom_cd1 = isos/linux/slax-6.0.7.iso
+                md5sum_cd1 = cde0ecba3c8289d786e12c44666ded6e
+                md5sum_1m_cd1 = ddf02bc7444f22d1160a6e5a8fc8723f
 
             - FreeSBIE-2.0.1:
                 steps = FreeSBIE-2.0.1.steps
-                cdrom_cd1 = unix/FreeSBIE-2.0.1-RELEASE.iso
-                md5sum = b2f680d27c21bbfaf4fb90dce090a118
-                md5sum_1m = 4d81ee7fe0101b0a14225963bfff60c1
+                cdrom_cd1 = isos/unix/FreeSBIE-2.0.1-RELEASE.iso
+                md5sum_cd1 = b2f680d27c21bbfaf4fb90dce090a118
+                md5sum_1m_cd1 = 4d81ee7fe0101b0a14225963bfff60c1
 
             - memtest:
                 mem = 128
                 steps = memtest86+.steps
-                cdrom_cd1 = misc/memtest86+-2.01.iso
-                md5sum = 9fae22f2666369968a76ef59e9a81ced
+                cdrom_cd1 = isos/misc/memtest86+-2.01.iso
+                md5sum_cd1 = 9fae22f2666369968a76ef59e9a81ced
+
+
+whql.support_vm_install|whql.client_install.support_vm:
+    image_name += -supportvm
 
 
 variants:
@@ -1923,9 +2317,11 @@
 
 
 virtio_net|virtio_blk|e1000|balloon_check:
-    only Fedora.11 Fedora.12 Fedora.13 RHEL.5 OpenSUSE.11 SLES.11 Ubuntu-8.10-server
-    # only WinXP Win2003 Win2008 WinVista Win7 Fedora.11 Fedora.12 Fedora.13 RHEL.5 OpenSUSE.11 SLES.11 Ubuntu-8.10-server
+    only Fedora.11 Fedora.12 Fedora.13 Fedora.14 RHEL.5 RHEL.6 OpenSUSE.11 SLES.11 Ubuntu-8.10-server
+    # only WinXP Win2003 Win2008 WinVista Win7 Fedora.11 Fedora.12 Fedora.13 Fedora.14 RHEL.5 RHEL.6 OpenSUSE.11 SLES.11 Ubuntu-8.10-server
 
+kdump:
+    only RHEL.5 RHEL.6
 
 variants:
     - @qcow2:
diff --git a/client/tests/kvm/unattended/Fedora-13.ks b/client/tests/kvm/unattended/Fedora-13.ks
index 0949e40..861546b 100644
--- a/client/tests/kvm/unattended/Fedora-13.ks
+++ b/client/tests/kvm/unattended/Fedora-13.ks
@@ -1,5 +1,5 @@
 install
-cdrom
+KVM_TEST_MEDIUM
 text
 reboot
 lang en_US
diff --git a/client/tests/kvm/unattended/Fedora-14.ks b/client/tests/kvm/unattended/Fedora-14.ks
new file mode 100644
index 0000000..9b99432
--- /dev/null
+++ b/client/tests/kvm/unattended/Fedora-14.ks
@@ -0,0 +1,37 @@
+install
+KVM_TEST_MEDIUM
+text
+reboot
+lang en_US
+keyboard us
+network --bootproto dhcp
+rootpw 123456
+firewall --enabled --ssh
+selinux --enforcing
+timezone --utc America/New_York
+firstboot --disable
+bootloader --location=mbr --append="rd_NO_PLYMOUTH console=tty0 console=ttyS0,115200"
+zerombr
+
+clearpart --all --initlabel
+autopart
+
+%packages
+@base
+@development-libs
+@development-tools
+%end
+
+%post --interpreter /usr/bin/python
+import socket, os
+os.system('dhclient')
+os.system('chkconfig sshd on')
+os.system('iptables -F')
+os.system('echo 0 > /selinux/enforce')
+server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+server.bind(('', 12323))
+server.listen(1)
+(client, addr) = server.accept()
+client.send("done")
+client.close()
+%end
diff --git a/client/tests/kvm/unattended/RHEL-5-series.ks b/client/tests/kvm/unattended/RHEL-5-series.ks
index 92ff727..3ee84f1 100644
--- a/client/tests/kvm/unattended/RHEL-5-series.ks
+++ b/client/tests/kvm/unattended/RHEL-5-series.ks
@@ -21,6 +21,7 @@
 @base
 @development-libs
 @development-tools
+kexec-tools
 
 %post --interpreter /usr/bin/python
 import socket, os
diff --git a/client/tests/kvm/unattended/RHEL-6-series.ks b/client/tests/kvm/unattended/RHEL-6-series.ks
new file mode 100644
index 0000000..16cd493
--- /dev/null
+++ b/client/tests/kvm/unattended/RHEL-6-series.ks
@@ -0,0 +1,40 @@
+install
+KVM_TEST_MEDIUM
+text
+reboot
+lang en_US.UTF-8
+keyboard us
+key --skip
+network --bootproto dhcp
+rootpw 123456
+firewall --enabled --ssh
+selinux --enforcing
+timezone --utc America/New_York
+firstboot --disable
+bootloader --location=mbr --append="console=tty0 console=ttyS0,115200"
+zerombr
+clearpart --all --initlabel
+autopart
+reboot
+
+%packages
+@base
+@core
+@development
+@additional-devel
+@debugging-tools
+@network-tools
+NetworkManager
+
+%post --interpreter /usr/bin/python
+import socket, os
+os.system('dhclient')
+os.system('chkconfig sshd on')
+os.system('iptables -F')
+os.system('echo 0 > /selinux/enforce')
+server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+server.bind(('', 12323))
+server.listen(1)
+(client, addr) = server.accept()
+client.send("done")
+client.close()
diff --git a/client/tests/kvm/unattended/win2003-32.sif b/client/tests/kvm/unattended/win2003-32.sif
index fab2cf5..207cd2b 100644
--- a/client/tests/kvm/unattended/win2003-32.sif
+++ b/client/tests/kvm/unattended/win2003-32.sif
@@ -63,4 +63,4 @@
     Command3="cmd /c net start telnet"
     Command4="cmd /c E:\setuprss.bat"
     Command5="cmd /c netsh interface ip set address local dhcp"
-    Command6="cmd /c ping 10.0.2.2 -n 20 && A:\finish.exe"
+    Command6="cmd /c A:\finish.exe"
diff --git a/client/tests/kvm/unattended/win2003-64.sif b/client/tests/kvm/unattended/win2003-64.sif
index fab2cf5..207cd2b 100644
--- a/client/tests/kvm/unattended/win2003-64.sif
+++ b/client/tests/kvm/unattended/win2003-64.sif
@@ -63,4 +63,4 @@
     Command3="cmd /c net start telnet"
     Command4="cmd /c E:\setuprss.bat"
     Command5="cmd /c netsh interface ip set address local dhcp"
-    Command6="cmd /c ping 10.0.2.2 -n 20 && A:\finish.exe"
+    Command6="cmd /c A:\finish.exe"
diff --git a/client/tests/kvm/unattended/win2008-32-autounattend.xml b/client/tests/kvm/unattended/win2008-32-autounattend.xml
index 352cb73..e33a36b 100644
--- a/client/tests/kvm/unattended/win2008-32-autounattend.xml
+++ b/client/tests/kvm/unattended/win2008-32-autounattend.xml
@@ -147,20 +147,6 @@
 				<SynchronousCommand wcm:action="add">
 					<CommandLine>%WINDIR%\System32\cmd /c net start telnet</CommandLine>
 					<Order>5</Order>
-<<<<<<< HEAD
-				</SynchronousCommand>
-				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c E:\setuprss.bat</CommandLine>
-					<Order>6</Order>
-				</SynchronousCommand>
-				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c netsh interface ip set address "Local Area Connection" dhcp</CommandLine>
-					<Order>7</Order>
-				</SynchronousCommand>
-				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
-					<Order>8</Order>
-=======
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
 					<CommandLine>%WINDIR%\System32\cmd /c bcdedit /set {current} bootstatuspolicy ignoreallfailures</CommandLine>
@@ -175,9 +161,8 @@
 					<Order>8</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
+					<CommandLine>%WINDIR%\System32\cmd /c A:\finish.exe</CommandLine>
 					<Order>9</Order>
->>>>>>> cros/upstream
 				</SynchronousCommand>
 			</FirstLogonCommands>
 			<OOBE>
diff --git a/client/tests/kvm/unattended/win2008-64-autounattend.xml b/client/tests/kvm/unattended/win2008-64-autounattend.xml
index fce6582..4ee064e 100644
--- a/client/tests/kvm/unattended/win2008-64-autounattend.xml
+++ b/client/tests/kvm/unattended/win2008-64-autounattend.xml
@@ -170,7 +170,7 @@
 					<Order>8</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
+					<CommandLine>%WINDIR%\System32\cmd /c ping A:\finish.exe</CommandLine>
 					<Order>9</Order>
 				</SynchronousCommand>
 			</FirstLogonCommands>
diff --git a/client/tests/kvm/unattended/win2008-r2-autounattend.xml b/client/tests/kvm/unattended/win2008-r2-autounattend.xml
index 7e9ab23..fce6582 100644
--- a/client/tests/kvm/unattended/win2008-r2-autounattend.xml
+++ b/client/tests/kvm/unattended/win2008-r2-autounattend.xml
@@ -114,20 +114,6 @@
 			<UILanguage>en-US</UILanguage>
 			<UserLocale>en-US</UserLocale>
 		</component>
-		<component name="Microsoft-Windows-PnpCustomizationsWinPE"
-			processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35"
-			language="neutral" versionScope="nonSxS"
-			xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
-			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-			<DriverPaths>
-				<PathAndCredentials wcm:keyValue="1" wcm:action="add">
-					<Path>KVM_TEST_STORAGE_DRIVER_PATH</Path>
-				</PathAndCredentials>
-				<PathAndCredentials wcm:keyValue="2" wcm:action="add">
-					<Path>KVM_TEST_NETWORK_DRIVER_PATH</Path>
-				</PathAndCredentials>
-			</DriverPaths>
-		</component>
 	</settings>
 	<settings pass="oobeSystem">
 		<component name="Microsoft-Windows-Shell-Setup"
diff --git a/client/tests/kvm/unattended/win7-32-autounattend.xml b/client/tests/kvm/unattended/win7-32-autounattend.xml
index 6904db1..f313f4a 100644
--- a/client/tests/kvm/unattended/win7-32-autounattend.xml
+++ b/client/tests/kvm/unattended/win7-32-autounattend.xml
@@ -156,19 +156,6 @@
 					<Order>5</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-<<<<<<< HEAD
-					<CommandLine>%WINDIR%\System32\cmd /c E:\setuprss.bat</CommandLine>
-					<Order>6</Order>
-				</SynchronousCommand>
-				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c netsh interface ip set address "Local Area Connection" dhcp</CommandLine>
-					<Order>7</Order>
-				</SynchronousCommand>
-				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
-					<Order>8</Order>
-				</SynchronousCommand>
-=======
 					<CommandLine>%WINDIR%\System32\cmd /c bcdedit /set {current} bootstatuspolicy ignoreallfailures</CommandLine>
 					<Order>6</Order>
 				</SynchronousCommand>
@@ -181,13 +168,12 @@
 					<Order>8</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
+					<CommandLine>%WINDIR%\System32\cmd /c A:\finish.exe</CommandLine>
 					<Order>9</Order>
 				</SynchronousCommand>
->>>>>>> cros/upstream
 			</FirstLogonCommands>
 		</component>
 	</settings>
 	<cpi:offlineImage cpi:source="wim:c:/install.wim#Windows Longhorn SERVERSTANDARD"
 		xmlns:cpi="urn:schemas-microsoft-com:cpi" />
-</unattend>
+</unattend>
\ No newline at end of file
diff --git a/client/tests/kvm/unattended/win7-64-autounattend.xml b/client/tests/kvm/unattended/win7-64-autounattend.xml
index e30e2c7..b42aa8f 100644
--- a/client/tests/kvm/unattended/win7-64-autounattend.xml
+++ b/client/tests/kvm/unattended/win7-64-autounattend.xml
@@ -168,7 +168,7 @@
 					<Order>8</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
+					<CommandLine>%WINDIR%\System32\cmd /c A:\finish.exe</CommandLine>
 					<Order>9</Order>
 				</SynchronousCommand>
 			</FirstLogonCommands>
@@ -176,4 +176,4 @@
 	</settings>
 	<cpi:offlineImage cpi:source="wim:c:/install.wim#Windows Longhorn SERVERSTANDARD"
 		xmlns:cpi="urn:schemas-microsoft-com:cpi" />
-</unattend>
+</unattend>
\ No newline at end of file
diff --git a/client/tests/kvm/unattended/winvista-32-autounattend.xml b/client/tests/kvm/unattended/winvista-32-autounattend.xml
index d4e8c5c..4dfe06c 100644
--- a/client/tests/kvm/unattended/winvista-32-autounattend.xml
+++ b/client/tests/kvm/unattended/winvista-32-autounattend.xml
@@ -164,7 +164,7 @@
 					<Order>7</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
+					<CommandLine>%WINDIR%\System32\cmd /c A:\finish.exe</CommandLine>
 					<Order>8</Order>
 				</SynchronousCommand>
 			</FirstLogonCommands>
diff --git a/client/tests/kvm/unattended/winvista-64-autounattend.xml b/client/tests/kvm/unattended/winvista-64-autounattend.xml
index 16d4850..5867bdb 100644
--- a/client/tests/kvm/unattended/winvista-64-autounattend.xml
+++ b/client/tests/kvm/unattended/winvista-64-autounattend.xml
@@ -165,7 +165,7 @@
 					<Order>7</Order>
 				</SynchronousCommand>
 				<SynchronousCommand wcm:action="add">
-					<CommandLine>%WINDIR%\System32\cmd /c ping 10.0.2.2 -n 20 &#38;&#38; A:\finish.exe</CommandLine>
+					<CommandLine>%WINDIR%\System32\cmd /c A:\finish.exe</CommandLine>
 					<Order>8</Order>
 				</SynchronousCommand>
 			</FirstLogonCommands>
diff --git a/client/tests/kvm/unattended/winxp32.sif b/client/tests/kvm/unattended/winxp32.sif
index b9a2ab6..e892193 100644
--- a/client/tests/kvm/unattended/winxp32.sif
+++ b/client/tests/kvm/unattended/winxp32.sif
@@ -72,4 +72,4 @@
    Command0="cmd /c KVM_TEST_VIRTIO_NETWORK_INSTALLER"
    Command1="cmd /c E:\setuprss.bat"
    Command2="cmd /c netsh interface ip set address local dhcp"
-   Command3="cmd /c ping 10.0.2.2 -n 20 && A:\finish.exe"
+   Command3="cmd /c A:\finish.exe"
diff --git a/client/tests/kvm/unattended/winxp64.sif b/client/tests/kvm/unattended/winxp64.sif
index b9a2ab6..e892193 100644
--- a/client/tests/kvm/unattended/winxp64.sif
+++ b/client/tests/kvm/unattended/winxp64.sif
@@ -72,4 +72,4 @@
    Command0="cmd /c KVM_TEST_VIRTIO_NETWORK_INSTALLER"
    Command1="cmd /c E:\setuprss.bat"
    Command2="cmd /c netsh interface ip set address local dhcp"
-   Command3="cmd /c ping 10.0.2.2 -n 20 && A:\finish.exe"
+   Command3="cmd /c A:\finish.exe"
diff --git a/client/tests/tracing_microbenchmark/tracers.py b/client/tests/tracing_microbenchmark/tracers.py
index bdd5194..08ca7e3 100644
--- a/client/tests/tracing_microbenchmark/tracers.py
+++ b/client/tests/tracing_microbenchmark/tracers.py
@@ -56,5 +56,5 @@
                 cpu_key = '%s_%s' % (cpu, key)
                 total_key = 'total_' + key
                 results[cpu_key] = val
-                results[total_key] = (results.get(total_key, 0) + 
+                results[total_key] = (results.get(total_key, 0) +
                                       results[cpu_key])
diff --git a/client/tests/unixbench/unixbench.py b/client/tests/unixbench/unixbench.py
index b3fe920..1db49ac 100644
--- a/client/tests/unixbench/unixbench.py
+++ b/client/tests/unixbench/unixbench.py
@@ -19,7 +19,7 @@
 
         utils.system('patch -p1 < ../unixbench.patch')
         utils.system('patch -p1 < ../Makefile.patch')
-        utils.system('make')
+        utils.make()
         utils.system('rm pgms/select')
 
 
diff --git a/conmux/drivers/dli-lpc b/conmux/drivers/dli-lpc
new file mode 100755
index 0000000..edf09e2
--- /dev/null
+++ b/conmux/drivers/dli-lpc
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Port control script for Digital Loggers Inc. Web Power Switch II and III
+#
+# Written by: Grant Likely <grant.likely@secretlab.ca>
+# Copyright 2010 Secret Lab Technologies Ltd.
+#
+# Usage: dli-pscontrol.sh <admin:passwd@host> <port> {on|off|cycle}
+#
+# <port> is in the range 1..8.
+# 'cycle' will turn a port off and on with a 1 second delay.
+#
+# The Web Power Switch uses a simple http request protocol for controlling
+# the port state.  The action simply gets encoded into the url in the form:
+#
+#   http://<user>:<passwd>@<host[:port]>/outlet?<port-number>={ON|OFF|CCW}
+#
+# ON and OFF are self explanatory.
+# CCW means cycle power, but only has effect when the port is already on.
+#
+# The protocol is simple enough that wget is sufficient to control ports.
+
+baseurl="http://${1}"
+porturl="${baseurl}/outlet?${2}"
+
+wget_cmd="wget --auth-no-challenge -O /dev/null"
+
+port_set() {
+	${wget_cmd} "${porturl}=${1}" > /dev/null 2>&1
+}
+
+case "$3" in
+  on)
+	port_set ON
+	;;
+  off)
+	port_set OFF
+	;;
+  cycle)
+	# The CCW command *could* be used here, but the command has no
+	# effect if the port is in the OFF state.
+	port_set OFF
+	sleep 1s
+	port_set ON
+	;;
+  *)
+	echo "Usage: $0 <admin:passwd@host> <port> {on|off|cycle}"
+	exit 1;
+	;;
+esac
+
+exit 0
diff --git a/conmux/drivers/fence_apc_snmp.py b/conmux/drivers/fence_apc_snmp.py
new file mode 100755
index 0000000..3595071
--- /dev/null
+++ b/conmux/drivers/fence_apc_snmp.py
@@ -0,0 +1,370 @@
+#!/usr/bin/python
+
+#############################################################################
+#############################################################################
+##
+##  Copyright (C) Sistina Software, Inc.  1997-2003  All rights reserved.
+##  Copyright (C) 2004-2006 Red Hat, Inc.  All rights reserved.
+##
+##  This copyrighted material is made available to anyone wishing to use,
+##  modify, copy, or redistribute it subject to the terms and conditions
+##  of the GNU General Public License v.2.
+##
+#############################################################################
+## This APC Fence script uses snmp to control the APC power
+## switch. This script requires that net-snmp-utils be installed
+## on all nodes in the cluster, and that the powernet369.mib file be
+## located in /usr/share/snmp/mibs/
+#############################################################################
+#############################################################################
+
+
+
+import getopt, sys
+import os
+import time
+import select
+import signal
+from glob import glob
+
+#BEGIN_VERSION_GENERATION
+FENCE_RELEASE_NAME=""
+REDHAT_COPYRIGHT=""
+BUILD_DATE=""
+#END_VERSION_GENERATION
+
+POWER_ON="outletOn"
+POWER_OFF="outletOff"
+POWER_REBOOT="outletReboot"
+
+def usage():
+    print "Usage:";
+    print "";
+    print "Options:";
+    print "  -a <ip>          IP address or hostname of MasterSwitch";
+    print "  -h               usage";
+    print "  -l <name>        Login name";
+    print "  -n <num>         Outlet number to change";
+    print "  -o <string>      Action: Reboot (default), Off or On";
+    print "  -p <string>      Login password";
+    print "  -q               quiet mode";
+    print "  -V               version";
+    print "  -v               Log to file /tmp/apclog";
+
+    print sys.argv
+    sys.exit(0);
+
+
+
+def main():
+    apc_base = "enterprises.apc.products.hardware."
+    apc_outletctl = "masterswitch.sPDUOutletControl.sPDUOutletControlTable.sPDUOutletControlEntry.sPDUOutletCtl."
+    apc_outletstatus = "masterswitch.sPDUOutletStatus.sPDUOutletStatusMSPTable.sPDUOutletStatusMSPEntry.sPDUOutletStatusMSP."
+
+    address = ""
+    output = ""
+    port = ""
+    action = "outletReboot"
+    status_check = False
+    verbose = False
+
+    if not glob('/usr/share/snmp/mibs/powernet*.mib'):
+        sys.stderr.write('This APC Fence script uses snmp to control the APC power switch. This script requires that net-snmp-utils be installed on all nodes in the cluster, and that the powernet369.mib file be located in /usr/share/snmp/mibs/\n')
+        sys.exit(1)
+
+    if len(sys.argv) > 1:
+        try:
+            opts, args = getopt.getopt(sys.argv[1:], "a:hl:p:n:o:vV", ["help", "output="])
+        except getopt.GetoptError:
+            #print help info and quit
+            usage()
+            sys.exit(2)
+
+        for o, a in opts:
+            if o == "-v":
+                verbose = True
+            if o == "-V":
+                print "%s\n" % FENCE_RELEASE_NAME
+                print "%s\n" % REDHAT_COPYRIGHT
+                print "%s\n" % BUILD_DATE
+                sys.exit(0)
+            if o in ("-h", "--help"):
+                usage()
+                sys.exit(0)
+            if o == "-n":
+                port = a
+            if o  == "-o":
+                lcase = a.lower() #Lower case string
+                if lcase == "off":
+                    action = "outletOff"
+                elif lcase == "on":
+                    action = "outletOn"
+                elif lcase == "reboot":
+                    action = "outletReboot"
+                elif lcase == "status":
+                    #action = "sPDUOutletStatusMSPOutletState"
+                    action = ""
+                    status_check = True
+                else:
+                    usage()
+                    sys.exit()
+            if o == "-a":
+                address = a
+
+        if address == "":
+            usage()
+            sys.exit(1)
+
+        if port == "":
+            usage()
+            sys.exit(1)
+
+    else: #Get opts from stdin
+        params = {}
+        #place params in dict
+        for line in sys.stdin:
+            val = line.split("=")
+            if len(val) == 2:
+                params[val[0].strip()] = val[1].strip()
+
+        try:
+            address = params["ipaddr"]
+        except KeyError, e:
+            sys.stderr.write("FENCE: Missing ipaddr param for fence_apc...exiting")
+            sys.exit(1)
+        try:
+            login = params["login"]
+        except KeyError, e:
+            sys.stderr.write("FENCE: Missing login param for fence_apc...exiting")
+            sys.exit(1)
+
+        try:
+            passwd = params["passwd"]
+        except KeyError, e:
+            sys.stderr.write("FENCE: Missing passwd param for fence_apc...exiting")
+            sys.exit(1)
+
+        try:
+            port = params["port"]
+        except KeyError, e:
+            sys.stderr.write("FENCE: Missing port param for fence_apc...exiting")
+            sys.exit(1)
+
+
+        try:
+            a = params["option"]
+            if a == "Off" or a == "OFF" or a == "off":
+                action = POWER_OFF
+            elif a == "On" or a == "ON" or a == "on":
+                action = POWER_ON
+            elif a == "Reboot" or a == "REBOOT" or a == "reboot":
+                action = POWER_REBOOT
+        except KeyError, e:
+            action = POWER_REBOOT
+
+        ####End of stdin section
+
+    apc_command = apc_base + apc_outletctl + port
+
+    args_status = list()
+    args_off = list()
+    args_on = list()
+
+    args_status.append("/usr/bin/snmpget")
+    args_status.append("-Oqu") #sets printing options
+    args_status.append("-v")
+    args_status.append("1")
+    args_status.append("-c")
+    args_status.append("private")
+    args_status.append("-m")
+    args_status.append("ALL")
+    args_status.append(address)
+    args_status.append(apc_command)
+
+    args_off.append("/usr/bin/snmpset")
+    args_off.append("-Oqu") #sets printing options
+    args_off.append("-v")
+    args_off.append("1")
+    args_off.append("-c")
+    args_off.append("private")
+    args_off.append("-m")
+    args_off.append("ALL")
+    args_off.append(address)
+    args_off.append(apc_command)
+    args_off.append("i")
+    args_off.append("outletOff")
+
+    args_on.append("/usr/bin/snmpset")
+    args_on.append("-Oqu") #sets printing options
+    args_on.append("-v")
+    args_on.append("1")
+    args_on.append("-c")
+    args_on.append("private")
+    args_on.append("-m")
+    args_on.append("ALL")
+    args_on.append(address)
+    args_on.append(apc_command)
+    args_on.append("i")
+    args_on.append("outletOn")
+
+    cmdstr_status = ' '.join(args_status)
+    cmdstr_off = ' '.join(args_off)
+    cmdstr_on = ' '.join(args_on)
+
+##This section issues the actual commands. Reboot is split into
+##Off, then On to make certain both actions work as planned.
+##
+##The status command just dumps the outlet status to stdout.
+##The status checks that are made when turning an outlet on or off, though,
+##use the execWithCaptureStatus so that the stdout from snmpget can be
+##examined and the desired operation confirmed.
+
+    if status_check:
+        if verbose:
+            fd = open("/tmp/apclog", "w")
+            fd.write("Attempting the following command: %s\n" % cmdstr_status)
+        strr = os.system(cmdstr_status)
+        print strr
+        if verbose:
+            fd.write("Result: %s\n" % strr)
+            fd.close()
+
+    else:
+        if action == POWER_OFF:
+            if verbose:
+                fd = open("/tmp/apclog", "w")
+                fd.write("Attempting the following command: %s\n" % cmdstr_off)
+            strr = os.system(cmdstr_off)
+            time.sleep(1)
+            strr,code = execWithCaptureStatus("/usr/bin/snmpget",args_status)
+            if verbose:
+                fd.write("Result: %s\n" % strr)
+                fd.close()
+            if strr.find(POWER_OFF) >= 0:
+                print "Success. Outlet off"
+                sys.exit(0)
+            else:
+                if verbose:
+                    fd.write("Unable to power off apc outlet")
+                    fd.close()
+                sys.exit(1)
+
+        elif action == POWER_ON:
+            if verbose:
+                fd = open("/tmp/apclog", "w")
+                fd.write("Attempting the following command: %s\n" % cmdstr_on)
+            strr = os.system(cmdstr_on)
+            time.sleep(1)
+            strr,code = execWithCaptureStatus("/usr/bin/snmpget",args_status)
+            #strr = os.system(cmdstr_status)
+            if verbose:
+                fd.write("Result: %s\n" % strr)
+            if strr.find(POWER_ON) >= 0:
+                if verbose:
+                    fd.close()
+                print "Success. Outlet On."
+                sys.exit(0)
+            else:
+                print "Unable to power on apc outlet"
+                if verbose:
+                    fd.write("Unable to power on apc outlet")
+                    fd.close()
+                sys.exit(1)
+
+        elif action == POWER_REBOOT:
+            if verbose:
+                fd = open("/tmp/apclog", "w")
+                fd.write("Attempting the following command: %s\n" % cmdstr_off)
+            strr = os.system(cmdstr_off)
+            time.sleep(1)
+            strr,code = execWithCaptureStatus("/usr/bin/snmpget",args_status)
+            #strr = os.system(cmdstr_status)
+            if verbose:
+                fd.write("Result: %s\n" % strr)
+            if strr.find(POWER_OFF) < 0:
+                print "Unable to power off apc outlet"
+                if verbose:
+                    fd.write("Unable to power off apc outlet")
+                    fd.close()
+                sys.exit(1)
+
+            if verbose:
+                fd.write("Attempting the following command: %s\n" % cmdstr_on)
+            strr = os.system(cmdstr_on)
+            time.sleep(1)
+            strr,code = execWithCaptureStatus("/usr/bin/snmpget",args_status)
+            #strr = os.system(cmdstr_status)
+            if verbose:
+                fd.write("Result: %s\n" % strr)
+            if strr.find(POWER_ON) >= 0:
+                if verbose:
+                    fd.close()
+                print "Success: Outlet Rebooted."
+                sys.exit(0)
+            else:
+                print "Unable to power on apc outlet"
+                if verbose:
+                    fd.write("Unable to power on apc outlet")
+                    fd.close()
+                sys.exit(1)
+
+def execWithCaptureStatus(command, argv, searchPath = 0, root = '/', stdin = 0,
+                          catchfd = 1, closefd = -1):
+
+    if not os.access (root + command, os.X_OK):
+        raise RuntimeError, command + " cannot be run"
+
+    (read, write) = os.pipe()
+
+    childpid = os.fork()
+    if (not childpid):
+        if (root and root != '/'): os.chroot (root)
+        if isinstance(catchfd, tuple):
+            for fd in catchfd:
+                os.dup2(write, fd)
+        else:
+            os.dup2(write, catchfd)
+        os.close(write)
+        os.close(read)
+
+        if closefd != -1:
+            os.close(closefd)
+
+        if stdin:
+            os.dup2(stdin, 0)
+            os.close(stdin)
+
+        if (searchPath):
+            os.execvp(command, argv)
+        else:
+            os.execv(command, argv)
+
+        sys.exit(1)
+
+    os.close(write)
+
+    rc = ""
+    s = "1"
+    while (s):
+        select.select([read], [], [])
+        s = os.read(read, 1000)
+        rc = rc + s
+
+    os.close(read)
+
+    pid = -1
+    status = -1
+    try:
+        (pid, status) = os.waitpid(childpid, 0)
+    except OSError, (errno, msg):
+        print __name__, "waitpid:", msg
+
+    if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
+        status = os.WEXITSTATUS(status)
+    else:
+        status = -1
+
+    return (rc, status)
+
+if __name__ == "__main__":
+    main()
diff --git a/conmux/drivers/module.mk b/conmux/drivers/module.mk
index 0cc24f9..7c36663 100644
--- a/conmux/drivers/module.mk
+++ b/conmux/drivers/module.mk
@@ -3,9 +3,9 @@
 #
 # The Console Multiplexor is released under the GNU Public License V2
 
-DRIVERS:=blade hmc reboot-netfinity reboot-newisys reboot-numaq \
+DRIVERS:=blade dli-lpc hmc reboot-netfinity reboot-newisys reboot-numaq \
 	reboot-rsa reboot-rsa2 zseries-console x3270_glue.expect \
-	reboot-acs48 reboot-apc reboot-laurel
+	reboot-acs48 reboot-apc reboot-laurel fence_apc_snmp.py
 
 install::
 	@[ -d $(BASE)/lib/drivers ] || mkdir $(BASE)/lib/drivers
diff --git a/conmux/examples/apc_snmp.cf b/conmux/examples/apc_snmp.cf
new file mode 100644
index 0000000..4164ff7
--- /dev/null
+++ b/conmux/examples/apc_snmp.cf
@@ -0,0 +1,4 @@
+listener localhost/ts63
+socket console 'console' 'localhost:13467'
+command 'config' 'Show conmux configuration' 'cat /usr/local/conmux/etc/apc_snmp.cf'
+command 'hardreset' 'initiated a hard reset' 'fence_apc_snmp.py -a pdu.xx.com -n 11 -l root'
diff --git a/conmux/start b/conmux/start
index 92402af..dabcd58 100755
--- a/conmux/start
+++ b/conmux/start
@@ -1,4 +1,4 @@
-#! /bin/sh
+#!/bin/bash
 #
 # start -- start up configured conmux servers on this host.
 #
diff --git a/frontend/afe/resources_test.py b/frontend/afe/resources_test.py
old mode 100644
new mode 100755
diff --git a/frontend/client/gwt_dir b/frontend/client/gwt_dir
old mode 100644
new mode 100755
diff --git a/frontend/client/src/autotest/EmbeddedTkoClient.gwt.xml b/frontend/client/src/autotest/EmbeddedTkoClient.gwt.xml
index 20eb6dd..3b94c39 100644
--- a/frontend/client/src/autotest/EmbeddedTkoClient.gwt.xml
+++ b/frontend/client/src/autotest/EmbeddedTkoClient.gwt.xml
@@ -12,5 +12,5 @@
   <stylesheet src='afeclient.css'/>
   <stylesheet src='tkoclient.css'/>
   
-  <!-- <set-property name="user.agent" value="gecko"/> -->
+  <!-- <set-property name="user.agent" value="gecko1_8"/> -->
 </module>
diff --git a/frontend/migrations/062_drone_sets_unique.py b/frontend/migrations/062_drone_sets_unique.py
index 3c031c6..738a0f0 100644
--- a/frontend/migrations/062_drone_sets_unique.py
+++ b/frontend/migrations/062_drone_sets_unique.py
@@ -41,9 +41,9 @@
              'GROUP BY drone_id HAVING COUNT(*) > 1')
     rows = manager.execute(query)
     if rows:
-      raise Exception('Some drones are associated with more than one drone '
-                      'set. Please remove all duplicates before running this '
-                      'migration.')
+        raise Exception('Some drones are associated with more than one drone '
+                        'set. Please remove all duplicates before running this '
+                        'migration.')
     manager.execute_script(UP_SQL)
 
     if db_utils.check_index_exists(manager, 'afe_drone_sets_drones',
diff --git a/frontend/planner/control_file_unittest.py b/frontend/planner/control_file_unittest.py
old mode 100644
new mode 100755
diff --git a/frontend/planner/execution_engine_unittest.py b/frontend/planner/execution_engine_unittest.py
old mode 100644
new mode 100755
diff --git a/frontend/planner/rpc_interface_unittest.py b/frontend/planner/rpc_interface_unittest.py
old mode 100644
new mode 100755
diff --git a/frontend/planner/rpc_utils_unittest.py b/frontend/planner/rpc_utils_unittest.py
old mode 100644
new mode 100755
diff --git a/frontend/tko/resources_test.py b/frontend/tko/resources_test.py
old mode 100644
new mode 100755
diff --git a/frontend/tko/rpc_interface_unittest.py b/frontend/tko/rpc_interface_unittest.py
old mode 100644
new mode 100755
diff --git a/scheduler/drone_manager.py b/scheduler/drone_manager.py
index 75724f3..e094f14 100644
--- a/scheduler/drone_manager.py
+++ b/scheduler/drone_manager.py
@@ -159,8 +159,7 @@
         self._results_dir = base_results_dir
 
         for hostname in drone_hostnames:
-            drone = self._add_drone(hostname)
-            drone.call('initialize', self.absolute_path(''))
+            self._add_drone(hostname)
 
         if not self._drones:
             # all drones failed to initialize
@@ -205,8 +204,9 @@
     def _add_drone(self, hostname):
         logging.info('Adding drone %s' % hostname)
         drone = drones.get_drone(hostname)
-        self._drones[drone.hostname] = drone
-        return drone
+        if drone:
+            self._drones[drone.hostname] = drone
+            drone.call('initialize', self.absolute_path(''))
 
 
     def _remove_drone(self, hostname):
diff --git a/scheduler/drones.py b/scheduler/drones.py
index 85a5ee2..b742b55 100644
--- a/scheduler/drones.py
+++ b/scheduler/drones.py
@@ -7,6 +7,11 @@
 AUTOTEST_INSTALL_DIR = global_config.global_config.get_config_value('SCHEDULER',
                                                  'drone_installation_directory')
 
+class DroneUnreachable(Exception):
+    """The drone is non-sshable."""
+    pass
+
+
 class _AbstractDrone(object):
     """
     Attributes:
@@ -111,6 +116,9 @@
         super(_RemoteDrone, self).__init__()
         self.hostname = hostname
         self._host = drone_utility.create_host(hostname)
+        if not self._host.is_up():
+            logging.error('Drone %s is unpingable, kicking out', hostname)
+            raise DroneUnreachable
         self._autotest_install_dir = AUTOTEST_INSTALL_DIR
 
 
@@ -156,4 +164,7 @@
     """
     if hostname == 'localhost':
         return _LocalDrone()
-    return _RemoteDrone(hostname)
+    try:
+        return _RemoteDrone(hostname)
+    except DroneUnreachable:
+        return None
diff --git a/scheduler/metahost_scheduler.py b/scheduler/metahost_scheduler.py
index 9588e95..98f49be 100644
--- a/scheduler/metahost_scheduler.py
+++ b/scheduler/metahost_scheduler.py
@@ -54,16 +54,16 @@
 
 
     def schedule_metahost(self, queue_entry, scheduling_utility):
-         """Schedule the given queue entry, if possible.
+        """Schedule the given queue entry, if possible.
 
-         This method should make necessary database changes culminating in
-         assigning a host to the given queue entry in the database.  It may
-         take no action if no host can be assigned currently.
+        This method should make necessary database changes culminating in
+        assigning a host to the given queue entry in the database.  It may
+        take no action if no host can be assigned currently.
 
-         @param queue_entry: a HostQueueEntry DBObject
-         @param scheduling_utility: a HostSchedulingUtility object
-         """
-         raise NotImplementedError
+        @param queue_entry: a HostQueueEntry DBObject
+        @param scheduling_utility: a HostSchedulingUtility object
+        """
+        raise NotImplementedError
 
 
     def recovery_on_startup(self):
diff --git a/server/autotest.py b/server/autotest.py
index eb25095..74a76c7 100644
--- a/server/autotest.py
+++ b/server/autotest.py
@@ -76,6 +76,7 @@
             try:
                 autotest_binary = os.path.join(path, 'bin', 'autotest')
                 host.run('test -x %s' % utils.sh_escape(autotest_binary))
+                host.run('test -w %s' % utils.sh_escape(path))
                 logging.debug('Found existing autodir at %s', path)
                 return path
             except error.AutoservRunError:
@@ -109,6 +110,7 @@
         for path in client_autodir_paths:
             try:
                 host.run('mkdir -p %s' % utils.sh_escape(path))
+                host.run('test -w %s' % utils.sh_escape(path))
                 return path
             except error.AutoservRunError:
                 logging.debug('Failed to create %s', path)
diff --git a/server/autotest_unittest.py b/server/autotest_unittest.py
index 6025af6..04023e9 100755
--- a/server/autotest_unittest.py
+++ b/server/autotest_unittest.py
@@ -256,6 +256,7 @@
         self.host.get_autodir.expect_call().and_return(None)
         self._expect_failed_run('test -x /some/path/bin/autotest')
         self.host.run.expect_call('test -x /another/path/bin/autotest')
+        self.host.run.expect_call('test -w /another/path')
 
         autodir = autotest.Autotest.get_installed_autodir(self.host)
         self.assertEquals(autodir, '/another/path')
@@ -268,6 +269,7 @@
         self._expect_failed_run('test -x /another/path/bin/autotest')
         self._expect_failed_run('mkdir -p /some/path')
         self.host.run.expect_call('mkdir -p /another/path')
+        self.host.run.expect_call('test -w /another/path')
 
         install_dir = autotest.Autotest.get_install_dir(self.host)
         self.assertEquals(install_dir, '/another/path')
diff --git a/server/base_utils.py b/server/base_utils.py
index 6d683b2..1c58609 100644
--- a/server/base_utils.py
+++ b/server/base_utils.py
@@ -244,45 +244,27 @@
     return (ntuples, failures)
 
 
-def parse_machine(machine, user = 'root', port = 22, password = ''):
+def parse_machine(machine, user='root', password='', port=22):
     """
     Parse the machine string user:pass@host:port and return it separately,
     if the machine string is not complete, use the default parameters
     when appropriate.
     """
 
-    user = user
-    port = port
-    password = password
+    if '@' in machine:
+        user, machine = machine.split('@', 1)
 
-    if re.search('@', machine):
-        machine = machine.split('@')
+    if ':' in user:
+        user, password = user.split(':', 1)
 
-        if re.search(':', machine[0]):
-            machine[0] = machine[0].split(':')
-            user = machine[0][0]
-            password = machine[0][1]
+    if ':' in machine:
+        machine, port = machine.split(':', 1)
+        port = int(port)
 
-        else:
-            user = machine[0]
+    if not machine or not user:
+        raise ValueError
 
-        if re.search(':', machine[1]):
-            machine[1] = machine[1].split(':')
-            hostname = machine[1][0]
-            port = int(machine[1][1])
-
-        else:
-            hostname = machine[1]
-
-    elif re.search(':', machine):
-        machine = machine.split(':')
-        hostname = machine[0]
-        port = int(machine[1])
-
-    else:
-        hostname = machine
-
-    return hostname, user, password, port
+    return machine, user, password, port
 
 
 def get_public_key():
diff --git a/server/base_utils_unittest.py b/server/base_utils_unittest.py
index 111dfbe..feb9ace 100755
--- a/server/base_utils_unittest.py
+++ b/server/base_utils_unittest.py
@@ -26,5 +26,37 @@
         self.assertEquals(self.failures, failures)
 
 
+    # parse_machine() test cases
+    def test_parse_machine_good(self):
+        '''test that parse_machine() is outputting the correct data'''
+        gooddata = (('host',                ('host', 'root', '', 22)),
+                    ('host:21',             ('host', 'root', '', 21)),
+                    ('user@host',           ('host', 'user', '', 22)),
+                    ('user:pass@host',      ('host', 'user', 'pass', 22)),
+                    ('user:pass@host:1234', ('host', 'user', 'pass', 1234)),
+                   )
+        for machine, result in gooddata:
+            self.assertEquals(utils.parse_machine(machine), result)
+
+
+    def test_parse_machine_override(self):
+        '''Test that parse_machine() defaults can be overridden'''
+        self.assertEquals(utils.parse_machine('host', 'bob', 'foo', 1234),
+                          ('host', 'bob', 'foo', 1234))
+
+
+    def test_parse_machine_bad(self):
+        '''test that bad data passed to parse_machine() will raise an exception'''
+        baddata = (('host:port', ValueError),   # pass a non-integer string for port
+                   ('host:22:33', ValueError),  # pass two ports
+                   (':22', ValueError),         # neglect to pass a hostname #1
+                   ('user@', ValueError),       # neglect to pass a hostname #2
+                   ('user@:22', ValueError),    # neglect to pass a hostname #3
+                   (':pass@host', ValueError),  # neglect to pass a username
+                  )
+        for machine, exception in baddata:
+            self.assertRaises(exception, utils.parse_machine, machine)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/server/control_segments/cleanup b/server/control_segments/cleanup
index 1a201bd..6ac6788 100644
--- a/server/control_segments/cleanup
+++ b/server/control_segments/cleanup
@@ -1,8 +1,5 @@
 def cleanup(machine):
-    hostname, user, passwd, port = parse_machine(machine, ssh_user,
-                                                 ssh_port, ssh_pass)
-    host = hosts.create_host(hostname, user=user, port=port, initialize=False,
-                             password=passwd, auto_monitor=False)
+    host = hosts.create_host(machine, initialize=False, auto_monitor=False)
     host.cleanup()
 
 
diff --git a/server/control_segments/client_wrapper b/server/control_segments/client_wrapper
index f8acae3..0d29c7e 100644
--- a/server/control_segments/client_wrapper
+++ b/server/control_segments/client_wrapper
@@ -2,9 +2,7 @@
 
 
 def run_client(machine):
-    hostname, user, passwd, port = parse_machine(machine, ssh_user, ssh_port,
-                                                 ssh_pass)
-    host = hosts.create_host(hostname, user=user, port=port, password=passwd)
+    host = hosts.create_host(machine)
     host.log_kernel()
     at.run(control, host=host)
 
diff --git a/server/control_segments/crashdumps b/server/control_segments/crashdumps
index 7bad63d..b5ebe78 100644
--- a/server/control_segments/crashdumps
+++ b/server/control_segments/crashdumps
@@ -2,10 +2,7 @@
 
 
 def crashdumps(machine):
-    hostname, user, passwd, port = parse_machine(machine, ssh_user,
-                                                 ssh_port, ssh_pass)
-    host = hosts.create_host(hostname, user=user, port=port, initialize=False,
-                             password=passwd, auto_monitor=False)
+    host = hosts.create_host(machine, initialize=False, auto_monitor=False)
     crashcollect.get_crashdumps(host, test_start_time)
 
 
diff --git a/server/control_segments/crashinfo b/server/control_segments/crashinfo
index c273620..c802832 100644
--- a/server/control_segments/crashinfo
+++ b/server/control_segments/crashinfo
@@ -2,10 +2,7 @@
 
 
 def crashinfo(machine):
-    hostname, user, passwd, port = parse_machine(machine, ssh_user,
-                                                 ssh_port, ssh_pass)
-    host = hosts.create_host(hostname, user=user, port=port, initialize=False,
-                             password=passwd, auto_monitor=False)
+    host = hosts.create_host(machine, initialize=False, auto_monitor=False)
     crashcollect.get_crashinfo(host, test_start_time)
 
 
diff --git a/server/control_segments/install b/server/control_segments/install
index aba79ae..20d6944 100644
--- a/server/control_segments/install
+++ b/server/control_segments/install
@@ -1,8 +1,5 @@
 def install(machine):
-    hostname, user, passwd, port = parse_machine(machine, ssh_user,
-                                                 ssh_port, ssh_pass)
-    host = hosts.create_host(hostname, user=user, port=port, initialize=False,
-                             password=passwd, auto_monitor=False)
+    host = hosts.create_host(machine, initialize=False, auto_monitor=False)
     host.machine_install()
 
 
diff --git a/server/hosts/abstract_ssh.py b/server/hosts/abstract_ssh.py
index 3d8d9e9..3723c46 100644
--- a/server/hosts/abstract_ssh.py
+++ b/server/hosts/abstract_ssh.py
@@ -115,6 +115,17 @@
                           " ".join(sources), dest)
 
 
+    def _make_ssh_cmd(self, cmd):
+        """
+        Create a base ssh command string for the host which can be used
+        to run commands directly on the machine
+        """
+        base_cmd = make_ssh_command(user=self.user, port=self.port,
+                                    opts=self.master_ssh_option,
+                                    hosts_file=self.known_hosts_fd)
+
+        return '%s %s "%s"' % (base_cmd, self.hostname, utils.sh_escape(cmd))
+
     def _make_scp_cmd(self, sources, dest):
         """
         Given a list of source paths and a destination path, produces the
diff --git a/server/hosts/factory.py b/server/hosts/factory.py
index f1a054f..7a2a724 100644
--- a/server/hosts/factory.py
+++ b/server/hosts/factory.py
@@ -68,9 +68,8 @@
     site_factory.postprocess_classes(classes, hostname,
                                      auto_monitor=auto_monitor, **args)
 
-    args['user'] = ssh_user
-    args['port'] = ssh_port
-    args['password'] = ssh_pass
+    hostname, args['user'], args['password'], args['port'] = \
+            server_utils.parse_machine(hostname, ssh_user, ssh_pass, ssh_port)
 
     # create a custom host class for this machine and return an instance of it
     host_class = type("%s_host" % hostname, tuple(classes), {})
diff --git a/server/hosts/logfile_monitor.py b/server/hosts/logfile_monitor.py
index 1608a6b..9595cc8 100644
--- a/server/hosts/logfile_monitor.py
+++ b/server/hosts/logfile_monitor.py
@@ -26,15 +26,6 @@
     """Error occurred launching followfiles remotely."""
 
 
-def run_cmd_on_host(hostname, cmd, stdin, stdout, stderr):
-    base_cmd = abstract_ssh.make_ssh_command()
-    full_cmd = "%s %s \"%s\"" % (base_cmd, hostname,
-                                 server_utils.sh_escape(cmd))
-
-    return subprocess.Popen(full_cmd, stdin=stdin, stdout=stdout,
-                            stderr=stderr, shell=True)
-
-
 def list_remote_pythons(host):
     """List out installed pythons on host."""
     result = host.run('ls /usr/bin/python[0-9]*')
@@ -72,25 +63,24 @@
             raise FollowFilesLaunchError('No supported Python on host.')
 
     remote_monitordir = copy_monitordir(host)
-    remote_script_path = os.path.join(
-        remote_monitordir, 'followfiles.py')
+    remote_script_path = os.path.join(remote_monitordir, 'followfiles.py')
 
     followfiles_cmd = '%s %s --lastlines_dirpath=%s %s' % (
         supported_python, remote_script_path,
         lastlines_dirpath, ' '.join(follow_paths))
 
-    devnull_r = open(os.devnull, 'r')
-    devnull_w = open(os.devnull, 'w')
-    remote_followfiles_proc = run_cmd_on_host(
-        host.hostname, followfiles_cmd, stdout=subprocess.PIPE,
-        stdin=devnull_r, stderr=devnull_w)
+    remote_ff_proc = subprocess.Popen(host._make_ssh_cmd(followfiles_cmd),
+                                      stdin=open(os.devnull, 'r'),
+                                      stdout=subprocess.PIPE, shell=True)
+
+
     # Give it enough time to crash if it's going to (it shouldn't).
     time.sleep(5)
-    doa = remote_followfiles_proc.poll()
+    doa = remote_ff_proc.poll()
     if doa:
         raise FollowFilesLaunchError('ssh command crashed.')
 
-    return remote_followfiles_proc
+    return remote_ff_proc
 
 
 def resolve_patterns_path(patterns_path):
diff --git a/server/hosts/remote.py b/server/hosts/remote.py
index e46bc1b..d1b4b46 100644
--- a/server/hosts/remote.py
+++ b/server/hosts/remote.py
@@ -27,7 +27,7 @@
     LAST_BOOT_TAG = object()
     DEFAULT_HALT_TIMEOUT = 2 * 60
 
-    VAR_LOG_MESSAGES_COPY_PATH = "/var/log/messages.autotest_start"
+    VAR_LOG_MESSAGES_COPY_PATH = "/var/tmp/messages.autotest_start"
 
     def _initialize(self, hostname, autodir=None, *args, **dargs):
         super(RemoteHost, self)._initialize(*args, **dargs)
@@ -230,8 +230,8 @@
             keyvals = utils.read_keyval(keyval_path)
             all_labels = keyvals.get('labels', '')
             if all_labels:
-              all_labels = all_labels.split(',')
-              return [urllib.unquote(label) for label in all_labels]
+                all_labels = all_labels.split(',')
+                return [urllib.unquote(label) for label in all_labels]
         return []
 
 
diff --git a/server/hosts/serial.py b/server/hosts/serial.py
index d514dba..d363cb7 100644
--- a/server/hosts/serial.py
+++ b/server/hosts/serial.py
@@ -165,6 +165,7 @@
                     # Run on num_attempts=1 or last retry
                     try:
                         self.wait_for_restart(timeout,
+                                              old_boot_id=old_boot_id,
                                               **wait_for_restart_kwargs)
                     except error.AutoservShutdownError:
                         logging.warning(warning_msg, num_attempts, num_attempts)
diff --git a/server/prebuild.py b/server/prebuild.py
index 4d78605..b13fd7b 100644
--- a/server/prebuild.py
+++ b/server/prebuild.py
@@ -61,4 +61,3 @@
     # instantiate a client_test instance.
     client_test = init_test(client_test_dir)
     client_setup_job.setup_test(client_test)
-
diff --git a/server/server_job.py b/server/server_job.py
index efc88d6..8a16c6a 100644
--- a/server/server_job.py
+++ b/server/server_job.py
@@ -6,7 +6,7 @@
 Copyright Martin J. Bligh, Andy Whitcroft 2007
 """
 
-import getpass, os, sys, re, stat, tempfile, time, select, subprocess
+import getpass, os, sys, re, stat, tempfile, time, select, subprocess, platform
 import traceback, shutil, warnings, fcntl, pickle, logging, itertools, errno
 from autotest_lib.client.bin import sysinfo
 from autotest_lib.client.common_lib import base_job
@@ -204,6 +204,7 @@
 
         job_data = {'label' : label, 'user' : user,
                     'hostname' : ','.join(machines),
+                    'drone' : platform.node(),
                     'status_version' : str(self._STATUS_VERSION),
                     'job_started' : str(int(time.time()))}
         if group_name:
diff --git a/server/standalone_profiler.py b/server/standalone_profiler.py
index 62a11e1..6324551 100644
--- a/server/standalone_profiler.py
+++ b/server/standalone_profiler.py
@@ -9,6 +9,7 @@
 
 __author__ = 'cranger@google.com (Colby Ranger)'
 
+import platform
 import common
 from autotest_lib.client.common_lib import barrier
 
@@ -16,7 +17,7 @@
 _RUNTEST_PATTERN = ("job.run_test('profiler_sync', timeout_sync=%r,\n"
                     "             timeout_start=%r, timeout_stop=%r,\n"
                     "             hostid='%s', masterid='%s', all_ids=%r)")
-_PROF_MASTER = "PROF_MASTER"
+_PROF_MASTER = platform.node()
 _PORT = 11920
 
 
diff --git a/server/tests/barriertest_2client/control.srv b/server/tests/barriertest_2client/control.srv
new file mode 100644
index 0000000..d6a70bb
--- /dev/null
+++ b/server/tests/barriertest_2client/control.srv
@@ -0,0 +1,78 @@
+AUTHOR = "gps@google.com (Gregory P. Smith)"
+TIME = "SHORT"
+NAME = "barrier_2client"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = 'Network'
+TEST_TYPE = "Server"
+EXPERIMENTAL = True  # This is functional a test of autotest itself.
+SYNC_COUNT = 2
+DOC = """
+A functional test of autotest's Barrier mechanisms for synchronizing
+events between two clients without the help of the server.
+"""
+
+from autotest_lib.server import utils
+
+def run(pair):
+    logging.info('Running on %s and %s', pair[0], pair[1])
+    host_objs = [hosts.create_host(machine) for machine in pair]
+    host_at_objs = [autotest.Autotest(host) for host in host_objs]
+
+    client_control_template = """
+import logging, platform, socket, traceback
+try:
+    client_hostnames = %r
+    master_hostname = client_hostnames[0]
+    client_hostname = client_hostnames[1]
+
+    logging.info('Testing hostname only barrier')
+    barrier = job.barrier(platform.node(), 'barriertest_2client', 120)
+    logging.info('rendezvous-ing')
+    barrier.rendezvous(master_hostname, client_hostname)
+    logging.info('done.')
+
+    logging.info('Testing local identifier barrier')
+    barrier = job.barrier(platform.node() + '#id0', 'barriertest_2client', 120)
+    logging.info('rendezvous-ing')
+    barrier.rendezvous(master_hostname + '#id0',
+                       client_hostname + '#id0')
+    logging.info('done.')
+
+    logging.info('Testing IP@ barrier')
+    barrier = job.barrier(socket.gethostbyname(platform.node()),
+                          'barriertest_2client', 120)
+    logging.info('rendezvous-ing')
+    barrier.rendezvous(socket.gethostbyname(master_hostname),
+                       socket.gethostbyname(client_hostname))
+    logging.info('done.')
+
+    logging.info('Testing IP@ barrier with ids')
+    barrier = job.barrier(socket.gethostbyname(platform.node()) + '#42',
+                          'barriertest_2client', 120)
+    logging.info('rendezvous-ing')
+    barrier.rendezvous(socket.gethostbyname(master_hostname) + '#42',
+                       socket.gethostbyname(client_hostname) + '#42')
+    logging.info('done.')
+except:
+    traceback.print_exc()
+    raise
+"""
+    client_controls = [client_control_template % (pair,) for host in host_objs]
+
+    subcommand_list = []
+    for host, host_at, control in zip(host_objs, host_at_objs, client_controls):
+        subcommand_list.append(subcommand(host_at.run,
+                                          (control, host.hostname)))
+
+    parallel(subcommand_list)
+
+
+# grab the pairs (and failures)
+(pairs, failures) = utils.form_ntuples_from_machines(machines, 2)
+
+# log the failures
+for failure in failures:
+    job.record("FAIL", failure[0], "barrier_2client", failure[1])
+
+# now run through each pair and run
+job.parallel_simple(run, pairs, log=False)
diff --git a/server/tests/netperf2/netperf2.py b/server/tests/netperf2/netperf2.py
index 604e4c8..108dab8 100644
--- a/server/tests/netperf2/netperf2.py
+++ b/server/tests/netperf2/netperf2.py
@@ -33,7 +33,7 @@
                             "test_time=%d, stream_list=%s, tag='%s', ",
                             "iterations=%d)"])
 
-        server_control_file = template % (server.ip, client.ip, 'server', test, 
+        server_control_file = template % (server.ip, client.ip, 'server', test,
                                           time, stream_list, test, cycles)
         client_control_file = template % (server.ip, client.ip, 'client', test,
                                           time, stream_list, test, cycles)
diff --git a/tko/job_serializer.py b/tko/job_serializer.py
old mode 100644
new mode 100755
diff --git a/tko/job_serializer_unittest.py b/tko/job_serializer_unittest.py
old mode 100644
new mode 100755
diff --git a/tko/parsers/version_1.py b/tko/parsers/version_1.py
index 111f7ef..7448c4f 100644
--- a/tko/parsers/version_1.py
+++ b/tko/parsers/version_1.py
@@ -70,11 +70,14 @@
                 val_type = "perf"
 
         # parse the actual value into a dict
-        if val_type == "attr":
-            attr_dict[key] = value
-        elif val_type == "perf" and re.search("^\d+(\.\d+)?$", value):
-            perf_dict[key] = float(value)
-        else:
+        try:
+            if val_type == "attr":
+                attr_dict[key] = value
+            elif val_type == "perf":
+                perf_dict[key] = float(value)
+            else:
+                raise ValueError
+        except ValueError:
             msg = ("WARNING: line '%s' found in test "
                    "iteration keyval could not be parsed")
             msg %= line
diff --git a/tko/parsers/version_1_unittest.py b/tko/parsers/version_1_unittest.py
index 72444c5..4114c59 100755
--- a/tko/parsers/version_1_unittest.py
+++ b/tko/parsers/version_1_unittest.py
@@ -220,8 +220,8 @@
 
 
     def test_perf_entry(self):
-        result = self.parse_line("perf-val{perf}=173")
-        self.assertEqual(({}, {"perf-val": 173}), result)
+        result = self.parse_line("perf-val{perf}=-173")
+        self.assertEqual(({}, {"perf-val": -173}), result)
 
 
     def test_attr_entry(self):
@@ -230,8 +230,8 @@
 
 
     def test_untagged_is_perf(self):
-        result = self.parse_line("untagged=678.5")
-        self.assertEqual(({}, {"untagged": 678.5}), result)
+        result = self.parse_line("untagged=-678.5e5")
+        self.assertEqual(({}, {"untagged": -678.5e5}), result)
 
 
     def test_invalid_tag_ignored(self):
@@ -240,12 +240,12 @@
 
 
     def test_non_numeric_perf_ignored(self):
-        result = self.parse_line("perf-val{perf}=NaN")
+        result = self.parse_line("perf-val{perf}=FooBar")
         self.assertEqual(({}, {}), result)
 
 
     def test_non_numeric_untagged_ignored(self):
-        result = self.parse_line("untagged=NaN")
+        result = self.parse_line("untagged=FooBar")
         self.assertEqual(({}, {}), result)
 
 
diff --git a/utils/check_patch.py b/utils/check_patch.py
index 576b97d..78af6b9 100755
--- a/utils/check_patch.py
+++ b/utils/check_patch.py
@@ -20,7 +20,7 @@
 @author: Lucas Meneghel Rodrigues <lmr@redhat.com>
 """
 
-import os, stat, logging, sys, optparse
+import os, stat, logging, sys, optparse, time
 import common
 from autotest_lib.client.common_lib import utils, error, logging_config
 from autotest_lib.client.common_lib import logging_manager
@@ -32,6 +32,20 @@
                                                                verbose=verbose)
 
 
+def ask(question, auto=False):
+    """
+    Raw input with a prompt that emulates logging.
+
+    @param question: Question to be asked
+    @param auto: Whether to return "y" instead of asking the question
+    """
+    if auto:
+        logging.info("%s (y/n) y" % question)
+        return "y"
+    return raw_input("%s INFO | %s (y/n) " %
+                     (time.strftime("%H:%M:%S", time.localtime()), question))
+
+
 class VCS(object):
     """
     Abstraction layer to the version control system.
@@ -104,6 +118,7 @@
     """
     def __init__(self):
         logging.debug("Subversion VCS backend initialized.")
+        self.ignored_extension_list = ['.orig', '.bak']
 
 
     def get_unknown_files(self):
@@ -112,7 +127,9 @@
         for line in status.split("\n"):
             status_flag = line[0]
             if line and status_flag == "?":
-                unknown_files.append(line[1:].strip())
+                for extension in self.ignored_extension_list:
+                    if not line.endswith(extension):
+                        unknown_files.append(line[1:].strip())
         return unknown_files
 
 
@@ -181,13 +198,16 @@
     Picks up a given file and performs various checks, looking after problems
     and eventually suggesting solutions.
     """
-    def __init__(self, path):
+    def __init__(self, path, confirm=False):
         """
         Class constructor, sets the path attribute.
 
         @param path: Path to the file that will be checked.
+        @param confirm: Whether to answer yes to all questions asked without
+                prompting the user.
         """
         self.path = path
+        self.confirm = confirm
         self.basename = os.path.basename(self.path)
         if self.basename.endswith('.py'):
             self.is_python = True
@@ -204,7 +224,7 @@
         self.first_line = checked_file.readline()
         checked_file.close()
         self.corrective_actions = []
-        self.indentation_exceptions = ['cli/job_unittest.py']
+        self.indentation_exceptions = ['job_unittest.py']
 
 
     def _check_indent(self):
@@ -226,8 +246,6 @@
         reindent_results = reindent_raw.split(" ")[-1].strip(".")
         if reindent_results == "changed":
             if self.basename not in self.indentation_exceptions:
-                logging.error("Possible indentation and spacing issues on "
-                              "file %s" % self.path)
                 self.corrective_actions.append("reindent.py -v %s" % self.path)
 
 
@@ -242,8 +260,7 @@
         c_cmd = 'run_pylint.py %s' % self.path
         rc = utils.system(c_cmd, ignore_status=True)
         if rc != 0:
-            logging.error("Possible syntax problems on file %s", self.path)
-            logging.error("You might want to rerun '%s'", c_cmd)
+            logging.error("Syntax issues found during '%s'", c_cmd)
 
 
     def _check_unittest(self):
@@ -260,9 +277,8 @@
                 unittest_cmd = 'python %s' % unittest_path
                 rc = utils.system(unittest_cmd, ignore_status=True)
                 if rc != 0:
-                    logging.error("Problems during unit test execution "
-                                  "for file %s", self.path)
-                    logging.error("You might want to rerun '%s'", unittest_cmd)
+                    logging.error("Unittest issues found during '%s'",
+                                  unittest_cmd)
 
 
     def _check_permissions(self):
@@ -273,14 +289,10 @@
         """
         if self.first_line.startswith("#!"):
             if not self.is_executable:
-                logging.info("File %s seems to require execution "
-                             "permissions. ", self.path)
-                self.corrective_actions.append("chmod +x %s" % self.path)
+                self.corrective_actions.append("svn propset svn:executable ON %s" % self.path)
         else:
             if self.is_executable:
-                logging.info("File %s does not seem to require execution "
-                             "permissions. ", self.path)
-                self.corrective_actions.append("chmod -x %s" % self.path)
+                self.corrective_actions.append("svn propdel svn:executable %s" % self.path)
 
 
     def report(self):
@@ -294,10 +306,9 @@
             self._check_code()
             self._check_unittest()
         if self.corrective_actions:
-            logging.info("The following corrective actions are suggested:")
             for action in self.corrective_actions:
-                logging.info(action)
-                answer = raw_input("Would you like to apply it? (y/n) ")
+                answer = ask("Would you like to execute %s?" % action,
+                             auto=self.confirm)
                 if answer == "y":
                     rc = utils.system(action, ignore_status=True)
                     if rc != 0:
@@ -305,7 +316,8 @@
 
 
 class PatchChecker(object):
-    def __init__(self, patch=None, patchwork_id=None):
+    def __init__(self, patch=None, patchwork_id=None, confirm=False):
+        self.confirm = confirm
         self.base_dir = os.getcwd()
         if patch:
             self.patch = os.path.abspath(patch)
@@ -322,7 +334,7 @@
         if changed_files_before:
             logging.error("Repository has changed files prior to patch "
                           "application. ")
-            answer = raw_input("Would you like to revert them? (y/n) ")
+            answer = ask("Would you like to revert them?", auto=self.confirm)
             if answer == "n":
                 logging.error("Not safe to proceed without reverting files.")
                 sys.exit(1)
@@ -370,20 +382,20 @@
             for untracked_file in add_to_vcs:
                 logging.info(untracked_file)
             logging.info("Might need to be added to VCS")
-            logging.info("Would you like to add them to VCS ? (y/n/abort) ")
-            answer = raw_input()
+            answer = ask("Would you like to add them to VCS ?")
             if answer == "y":
                 for untracked_file in add_to_vcs:
                     self.vcs.add_untracked_file(untracked_file)
                     modified_files_after.append(untracked_file)
             elif answer == "n":
                 pass
-            elif answer == "abort":
-                sys.exit(1)
 
         for modified_file in modified_files_after:
-            file_checker = FileChecker(modified_file)
-            file_checker.report()
+            # Additional safety check, new commits might introduce
+            # new directories
+            if os.path.isfile(modified_file):
+                file_checker = FileChecker(modified_file)
+                file_checker.report()
 
 
     def check(self):
@@ -399,20 +411,37 @@
                       help='id of a given patchwork patch')
     parser.add_option('--verbose', dest="debug", action='store_true',
                       help='include debug messages in console output')
+    parser.add_option('-f', '--full-check', dest="full_check",
+                      action='store_true',
+                      help='check the full tree for corrective actions')
+    parser.add_option('-y', '--yes', dest="confirm",
+                      action='store_true',
+                      help='Answer yes to all questions')
 
     options, args = parser.parse_args()
     local_patch = options.local_patch
     id = options.id
     debug = options.debug
+    full_check = options.full_check
+    confirm = options.confirm
 
     logging_manager.configure_logging(CheckPatchLoggingConfig(), verbose=debug)
 
-    if local_patch:
-        patch_checker = PatchChecker(patch=local_patch)
-    elif id:
-        patch_checker = PatchChecker(patchwork_id=id)
+    ignore_file_list = ['common.py']
+    if full_check:
+        for root, dirs, files in os.walk('.'):
+            if not '.svn' in root:
+                for file in files:
+                    if file not in ignore_file_list:
+                        path = os.path.join(root, file)
+                        file_checker = FileChecker(path, confirm=confirm)
+                        file_checker.report()
     else:
-        logging.error('No patch or patchwork id specified. Aborting.')
-        sys.exit(1)
-
-    patch_checker.check()
+        if local_patch:
+            patch_checker = PatchChecker(patch=local_patch, confirm=confirm)
+        elif id:
+            patch_checker = PatchChecker(patchwork_id=id, confirm=confirm)
+        else:
+            logging.error('No patch or patchwork id specified. Aborting.')
+            sys.exit(1)
+        patch_checker.check()
diff --git a/utils/external_packages.py b/utils/external_packages.py
old mode 100644
new mode 100755
index d505eef..bcfd15d
--- a/utils/external_packages.py
+++ b/utils/external_packages.py
@@ -613,7 +613,8 @@
 class ParamikoPackage(ExternalPackage):
     version = '1.7.5'
     local_filename = 'paramiko-%s.tar.gz' % version
-    urls = ('http://www.lag.net/paramiko/download/' + local_filename,)
+    urls = ('http://www.lag.net/paramiko/download/' + local_filename,
+            'ftp://mirrors.kernel.org/gentoo/distfiles/' + local_filename,)
     hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5'
 
 
diff --git a/utils/run_pylint.py b/utils/run_pylint.py
index 0bf5f95..3c3e225 100755
--- a/utils/run_pylint.py
+++ b/utils/run_pylint.py
@@ -6,10 +6,13 @@
 # do a basic check to see if pylint is even installed
 try:
     import pylint
+    from pylint.__pkginfo__ import version as pylint_version
 except ImportError:
     print "Unable to import pylint, it may need to be installed"
     sys.exit(1)
 
+major, minor, release = pylint_version.split('.')
+pylint_version = float("%s.%s" % (major, minor))
 pylintrc_path = os.path.expanduser('~/.pylintrc')
 if not os.path.exists(pylintrc_path):
     open(pylintrc_path, 'w').close()
@@ -54,10 +57,13 @@
 # * common_lib.enum.Enum objects
 # * DB model objects (scheduler models are the worst, but Django models also
 #   generate some errors)
-pylint_base_opts = ['--disable-msg-cat=warning,refactor,convention',
-                    '--disable-msg=E1101,E1103',
-                    '--reports=no',
-                    '--include-ids=y']
+if pylint_version >= 0.21:
+    pylint_base_opts = ['--disable=W,R,C,E1101,E1103']
+else:
+    pylint_base_opts = ['--disable-msg-cat=warning,refactor,convention',
+                        '--disable-msg=E1101,E1103']
+pylint_base_opts += ['--reports=no',
+                     '--include-ids=y']
 
 file_list = sys.argv[1:]
 if '--' in file_list:
diff --git a/utils/site_test_importer_attributes.py b/utils/site_test_importer_attributes.py
index f49aedb..a2a1b50 100755
--- a/utils/site_test_importer_attributes.py
+++ b/utils/site_test_importer_attributes.py
@@ -8,7 +8,6 @@
 
 
 import common, re
-from autotest_lib.frontend.afe import models
 
 
 def _set_attributes_custom(test, data):
@@ -24,9 +23,3 @@
 
     # We set verify to always False (0).
     test.run_verify = 0
-    
-    if hasattr(data, 'test_parameters'):
-        for para_name in data.test_parameters:
-            test_parameter = models.TestParameter.objects.get_or_create(
-                test=test, name=para_name)[0]
-            test_parameter.save()
diff --git a/utils/test_importer.py b/utils/test_importer.py
index 00b912c..6a63b1e 100755
--- a/utils/test_importer.py
+++ b/utils/test_importer.py
@@ -241,6 +241,12 @@
 
         _log_or_execute(repr(new_test), new_test.save)
         add_label_dependencies(new_test)
+        
+        # save TestParameter
+        for para_name in data.test_parameters:
+          test_parameter = models.TestParameter.objects.get_or_create(
+              test=new_test, name=para_name)[0]
+          test_parameter.save()
 
 
 def _set_attributes_clean(test, data):