diff mbox

NEW virtio_console test

Message ID 1282311612-3282-2-git-send-email-ldoktor@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lukáš Doktor Aug. 20, 2010, 1:40 p.m. UTC
None
diff mbox

Patch

diff --git a/client/tests/kvm/scripts/consoleSwitch.py b/client/tests/kvm/scripts/consoleSwitch.py
new file mode 100644
index 0000000..814388f
--- /dev/null
+++ b/client/tests/kvm/scripts/consoleSwitch.py
@@ -0,0 +1,217 @@ 
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+Auxiliary script used to send data betwen ports on guests.
+
+@copyright: 2008-2009 Red Hat Inc.
+@author: Jiri Zupka (jzupka@redhat.com)
+@author: Lukas Doktor (ldoktor@redhat.com)
+"""
+
+
+import threading
+from threading import Thread
+import os,time,select,re,random,sys,array
+
+
+files = dict()
+ev = threading.Event()
+threads = []
+
+DEBUGPATH="/sys/kernel/debug"
+
+
+
+class switch(Thread):
+    """
+    Class create thread which send data between ports
+    """
+    def __init__ (self,exitevent,in_files,out_files,cachesize=1):
+        """
+        @param exitevent: event to end switch
+        @param in_files: array of input files
+        @param out_files: array of output files
+        @param cachesize: block to receive and send
+        """
+        Thread.__init__(self)
+        
+        self.in_files = in_files
+        self.out_files = out_files
+
+        self.cachesize = cachesize
+        self.exitevent = exitevent
+        
+        
+    def run(self):
+        while not self.exitevent.isSet():    
+            #TODO: Why select causes trouble? :-(
+            #ret = select.select(self.in_files,[],[],1.0)
+            data = ""
+            #if not ret[0] == []:
+            for desc in self.in_files:
+                data += os.read(desc,self.cachesize)
+            for desc in self.out_files:
+                os.write(desc,data)
+
+class sender(Thread):
+    """
+    Class creates thread which sends random block of data to the destination 
+    port
+    """
+    def __init__(self, port, length):
+        """
+        @param port: destination port
+        @param length: length of the random data block
+        """
+        Thread.__init__(self)
+        self.port = port
+        self.data = array.array('L')
+        for i in range(max(length / self.data.itemsize,1)):
+            self.data.append(random.randrange(sys.maxint))
+
+    def run(self):
+        while True:
+            os.write(self.port, self.data)
+        del threads[:]
+                 
+def getPortStatus():
+    """
+    Function get info about ports from kernel debugfs.
+    
+    @return: ports dictionary of port properties
+    """
+    ports = dict()
+    
+    if (not os.path.ismount(DEBUGPATH)):
+        os.system('mount -t debugfs none %s' % (DEBUGPATH))
+    try:
+        if not os.path.isdir('%s/virtio-ports' % (DEBUGPATH)):
+            print "FAIL: Not exist virtio-ports in debug fs"
+    except:
+        print "FAIL: virtio-ports not exist"
+    else:
+        viop_names = os.listdir('%s/virtio-ports' % (DEBUGPATH))
+        for name in viop_names:
+            f = open("%s/virtio-ports/%s" % (DEBUGPATH,name),'r')
+            port = dict()
+            for line in iter(f):
+                m = re.match("(\S+): (\S+)",line)
+                port[m.group(1)] = m.group(2)
+                
+            if (port['is_console'] == "yes"):
+                port["path"] = "/dev/hvc%s" % (port["console_vtermno"])
+                #Console work like serialport
+            else:
+                port["path"] = "/dev/%s" % (name)
+            ports[port['name']] = port
+            f.close()
+         
+    return ports    
+                    
+                    
+def opendevice(in_files,ports):
+    """
+    Open devices and return array of descriptors
+    
+    @param in_files: files array
+    @return: array of descriptor
+    """
+    
+    f = []
+    
+    for item in in_files:
+        name = ports[item[0]]["path"]
+        if (not item[1] == ports[item[0]]["is_console"]):
+            print ports
+            print "FAIL: Host console is not like console on guest side\n"
+            
+        if (name in files):
+            f.append(files[name])
+        else:
+            try:          
+                files[name] = os.open(name, os.O_RDWR)
+                if (ports[item[0]]["is_console"] == "yes"):
+                    print os.system("stty -F %s raw -echo" % (ports[item[0]]["path"]))
+                    print os.system("stty -F %s -a" % (ports[item[0]]["path"]))
+                f.append(files[name])
+            except Exception as inst:
+                print "FAIL: Failed open file %s" % (name)
+                raise inst
+    return f
+                 
+                 
+def startswitch(in_files,out_files,cachesize=1):
+    """
+    Function starts switch thread (because there is problem with multiple open 
+    of one files).
+    
+    @param in_files: array of input files
+    @param out_files: array of output files
+    @param cachesize: cachesize 
+    """
+    
+    
+    ports = getPortStatus()
+    
+    in_f = opendevice(in_files,ports)
+    out_f = opendevice(out_files,ports)
+    
+   
+    s = switch(ev,in_f,out_f,cachesize)
+    s.start()
+    threads.append(s)
+    print "PASS: Start switch"
+    
+def endswitchs():
+    """
+    Function end all running data switch.
+    """
+    ev.set()
+    for th in threads:
+        print "join"
+        th.join(3.0)
+    ev.clear()
+    
+    del threads[:]
+    print "PASS: End switch"
+
+def die():
+    """
+    Quit consoleswitch.
+    """
+    for desc in files.itervalues():
+        os.close(desc)
+    exit(0)
+
+def senderprepare(port, length):
+    """
+    Prepares the sender thread. Requires clean thread structure.
+    """
+    del threads[:]
+    ports = getPortStatus()
+    in_f = opendevice([port],ports)
+
+    threads.append(sender(in_f[0], length))
+    print "PASS: Sender prepare"
+
+def senderstart():
+    """
+    Start sender data transfer. Requires senderprepare run firs.
+    """
+    threads[0].start()
+    print "PASS: Sender start"
+
+def main():
+    """
+    Main (infinite) loop of consoleswitch.
+    """
+    print "PASS: Start"
+    end = False
+    while not end:
+        str = raw_input()
+        exec str
+            
+
+
+if __name__ == "__main__":
+    main()
diff --git a/client/tests/kvm/tests/virtio_console.py b/client/tests/kvm/tests/virtio_console.py
new file mode 100644
index 0000000..f05500d
--- /dev/null
+++ b/client/tests/kvm/tests/virtio_console.py
@@ -0,0 +1,730 @@ 
+import logging, time
+from autotest_lib.client.common_lib import error
+import kvm_subprocess, kvm_test_utils, kvm_utils, kvm_preprocessing
+import socket, random, array, sys, os, tempfile, shutil, threading, select
+from threading import Thread
+from collections import deque
+import re
+
+
+def run_virtio_console(test, params, env):
+    """
+    KVM virtio_console test
+    
+    @param test: kvm test object
+    @param params: Dictionary with the test parameters
+    @param env: Dictionary with test environment
+    """ 
+    class th_send(Thread):
+        """
+        Random data sender thread
+        """
+        def __init__(self, port, length, buffers, blocklen=32):
+            """
+            @param port: destination port
+            @param length: amount of data we want to send
+            @param buffers: buffers for the control data (FIFOs)
+            @param blocklen: block length
+            """
+            Thread.__init__(self)
+            self.ExitState = True
+            self.port = port[0]
+            self.length = length
+            self.buffers = buffers
+            self.blocklen = blocklen
+        def run(self):
+            logging.debug("th_send %s: run" % self.getName())
+            idx = 0
+            while idx < self.length:
+                ret = select.select([], [self.port], [], 1.0)
+                if ret:
+                    # Generate blocklen of random data add them to the FIFO
+                    # and send tham over virtio_console
+                    buf = ""
+                    for i in range(min(self.blocklen, self.length-idx)):
+                        ch = "%c" % random.randrange(255)
+                        buf += ch
+                        for buffer in self.buffers:
+                            buffer.append(ch)
+                    idx += len(buf)
+                    self.port.sendall(buf)
+            logging.debug("th_send %s: exit(%d)" % (self.getName(), idx))
+            if idx >= self.length:
+                self.ExitState = False
+    class th_send_loop(Thread):
+        """
+        Send data in the loop until the exit event is set
+        """
+        def __init__(self, port, data, event):
+            """
+            @param port: destination port
+            @param data: the data intend to be send in a loop
+            @param event: exit event
+            """
+            Thread.__init__(self)
+            self.port = port
+            self.data = data
+            self.exitevent = event
+            self.idx = 0
+        def run(self):
+            logging.debug("th_send_loop %s: run" % self.getName())
+            while not self.exitevent.isSet():
+                self.idx += self.port.send(self.data)
+            logging.debug("th_send_loop %s: exit(%d)" % (self.getName(), \
+                                                        self.idx))
+
+    class th_recv(Thread):
+        """
+        Random data reciever/checker thread
+        """
+        def __init__(self, port, buffer, length, blocklen=32):
+            """
+            @param port: source port
+            @param buffer: control data buffer (FIFO)
+            @param length: amount of data we want to receive
+            @param blocklen: block length
+            """
+            Thread.__init__(self)
+            self.ExitState = True
+            self.port = port[0]
+            self.buffer = buffer
+            self.length = length
+            self.blocklen = blocklen
+        def run(self):
+            logging.debug("th_recv %s: run" % self.getName())
+            idx = 0
+            while idx < self.length:
+                ret = select.select([self.port], [], [], 1.0)
+                if ret:
+                    buf = self.port.recv(self.blocklen)
+                    if buf:
+                        # Compare the recvd data with the control data
+                        for ch in buf:
+                            if not ch == self.buffer.popleft():
+                                error.TestFail("th_recv: incorrect data")
+                    idx += len(buf)
+            logging.debug("th_recv %s: exit(%d)" % (self.getName(), idx))
+            if (idx >= self.length) and (len(self.buffer) == 0):
+                self.ExitState = False
+
+    class th_recv_null(Thread):
+        """
+        Recieves data and throw them away
+        """
+        def __init__(self, port, event, blocklen=32):
+            """
+            @param port: data source port
+            @param event: exit event
+            @param blocklen: block length
+            """
+            Thread.__init__(self)
+            self.port = port[0]
+            self._port_timeout = self.port.gettimeout()
+            self.port.settimeout(0.1)
+            self.exitevent = event
+            self.blocklen = blocklen
+            self.idx = 0
+        def run(self):
+            logging.debug("th_recv_null %s: run" % self.getName())
+            while not self.exitevent.isSet():
+                # Workaround, it didn't work with select :-/
+                try:
+                    self.idx += len(self.port.recv(self.blocklen))
+                except socket.timeout:
+                    pass
+            self.port.settimeout(self._port_timeout)
+            logging.debug("th_recv_null %s: exit(%d)" % (self.getName(), \
+                                                        self.idx))
+                
+    seqTest = threading.Lock();
+
+    class averageCpuLoad():
+        """
+        Get average cpu load between start and getLoad
+        """
+        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 try if we can calc system load.
+            try:
+                f = open("/proc/stat", "r")
+                tmp = f.readlines(200)
+                f.close()
+            except:
+                self.log_entry(severity['CRIT'], "Something went terribly" \
+                                    "wrong when trying to open /proc/stat")
+                error.TestFail("averageCpuLoad: Error reading /proc/stat")
+                # 200 bytes should be enough because the information we need 
+                # ist 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.startTime = time.time();
+            self._get_cpu_load()
+                
+        def getLoad(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 averageProcessCpuLoad():
+        """
+        Get average process cpu load between start and getLoad
+        """
+        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 try if we can calc system load.
+            try:
+                f = open("/proc/%d/stat" % (pid), "r")
+                line = f.readline()
+                f.close()
+            except:
+                self.log_entry(severity['CRIT'],\
+                     "Something went terribly wrong when" + \
+                     "trying to open /proc/%d/stat" % (pid))
+                error.TestFail("averageProcessCpuLoad: 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.startTime = time.time();
+            self._get_cpu_load(self.pid)
+                
+        def getLoad(self):
+            """
+            Get and reset CPU usage
+            @return: 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
+        
+        @param stats: list of measured data
+        """
+        if not stats:
+            return None
+        for i in range((len(stats)-1),0,-1):
+            stats[i] = stats[i] - stats[i-1]
+            stats[i] /= scale
+        stats[0] /= scale
+        stats = sorted(stats)
+        return stats
+        
+    def _start_consoleSwitch(vm, timeout=2):
+        """
+        Execute consoleSwitch.py on guest; wait until it is initialized.
+    
+        @param vm: Informations about the guest.
+        @param timeout: Timeout that will be used to verify if the script
+                started properly.
+        """
+        logging.debug("Starting consoleSwitch.py on guest %s", vm[0].name)
+        vm[1].sendline("python /tmp/consoleSwitch.py")
+        (match, data) = vm[1].read_until_last_line_matches(["PASS:", "FAIL:"],
+                                                             timeout)
+        if match == 1 or match is None:
+            raise error.TestFail("Command consoleSwitch.py on guest %s failed."\
+                                 "\nreturn code: %s\n output:\n%s" \
+                                 % (vm[0].name, match, data))
+            
+    def _execute_consoleSwitch(command, vm, timeout=2):
+        """
+        Execute given command inside the script's main loop, indicating the vm
+        the command was executed on.
+    
+        @param command: Command that will be executed.
+        @param vm: Informations about the guest
+        @param timeout: Timeout used to verify expected output.
+    
+        @return: Tuple (match index, data)
+        """
+        logging.debug("Executing '%s' on consoleSwitch.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:"],
+                                                             timeout)
+        if match == 1 or match is None:
+            raise error.TestFail("Failed to execute '%s' on consoleSwitch.py, "
+                                 "vm: %s, output:\n%s" %
+                                 (command, vm[0].name, data))
+        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 _vm_create(no_console=3,no_serialport=3):
+        """
+        Creates the VM and connects the specified number of consoles and serial
+        ports
+
+        @param no_console: number of desired virtconsoles
+        @param no_serialport: number of desired virtserialports
+        @return tupple with (guest information, consoles information)
+            guest informations = [vm, session, tmp_dir]
+            consoles informations = [consoles[], serialports[]]
+        """
+        consoles = []
+        serialports = []
+        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):
+            params['extra_params'] +=\
+                    " -chardev socket,path=%s/%d,id=%d,server,nowait"\
+                    % (tmp_dir, i, i)
+            params['extra_params'] += " -device virtconsole,chardev=%d,"\
+                                      "name=org.fedoraproject.console.%d,id=c%d"\
+                                      % (i,i,i)
+
+        for i in  range(no_console, no_console + no_serialport):
+            params['extra_params'] +=\
+                    " -chardev socket,path=%s/%d,id=%d,server,nowait"\
+                    % (tmp_dir, i, i)
+            params['extra_params'] += " -device virtserialport,chardev=%d,"\
+                                      "name=org.fedoraproject.data.%d,id=p%d"\
+                                      % (i,i,i)
+
+
+        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"))
+        
+        session = kvm_test_utils.wait_for_login(vm, 0,
+                                         float(params.get("boot_timeout", 240)),
+                                         0, 2)
+
+        # 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, \
+                    "org.fedoraproject.console.%d" % i, \
+                    "yes"])
+        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, \
+                    "org.fedoraproject.data.%d" %i, \
+                    "no"])
+
+        return [vm, session, tmp_dir], [consoles, serialports]
+
+    def test_smoke(vm, consoles, params):
+        """
+        Creates loopback on the vm machine between providen ports[>=2] and 
+        sends the data
+
+        @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;...'
+        """
+        # 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]
+            _start_consoleSwitch(vm, 10.0)
+
+            # TEST
+            _execute_consoleSwitch('startswitch([%s], [%s])' % (
+                            str(send_pt[1:3]),
+                            str(recv_pt[1:3])
+                            ), vm, 2.0)
+
+            send_pt[0].sendall(data)
+            d = socket_readall(recv_pt[0], 1.0, len(data))
+            if data != d:
+                raise error.TestFail("test_smoke: Error, recieved data"\
+                                     " doesn't match the original")
+            else:
+                logging.info("test_smoke: data = %s" % d)
+
+
+            vm[1].sendline("die()")
+
+    def test_loopback(vm, consoles, params):
+        """
+        Creates loopback on the vm machine between send_pt and recv_pts
+        ports and sends length amount of data through this connection.
+        It validates the correctness of those data
+
+        @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, multiple recievers allowed.
+                '$source_console_type@buffer_length:
+                 $destination_console_type1@buffer_length:...:
+                 $loopback_buffer_length;...'
+        """
+        # PREPARE
+        for param in params.split(';'):
+            if not param:
+                continue
+            logging.info("test_loopback: PARAMS: %s" % param)
+            param = param.split(':')
+            idx_serialport = 0
+            idx_console = 0
+            buf_len = []
+            if (param[0].startswith('console')):
+                send_pt = consoles[0][idx_console]
+                idx_console += 1
+            else:
+                send_pt = consoles[1][idx_serialport]
+                idx_serialport += 1
+            if (len(param[0].split('@')) == 2):
+                buf_len.append(int(param[0].split('@')[1]))
+            else:
+                buf_len.append(32)
+            recv_pts = []
+            for parm in param[1:]:
+                if (parm.isdigit()):
+                    buf_len.append(int(parm))
+                    break   # buf_len is the last portion of param
+                if (parm.startswith('console')):
+                    recv_pts.append(consoles[0][idx_console])
+                    idx_console += 1
+                else:
+                    recv_pts.append(consoles[1][idx_serialport])
+                    idx_serialport += 1
+                if (len(parm[0].split('@')) == 2):
+                    buf_len.append(int(parm[0].split('@')[1]))
+                else:
+                    buf_len.append(32)
+            # There must be sum(idx_*) consoles + last item as loopback buf_len
+            if len(buf_len) == (idx_console + idx_serialport):
+                buf_len.append(32)
+
+            if len(recv_pts) == 0:
+                raise error.TestFail("test_loopback: incorrect recv consoles"
+                                     "definition")
+            threads = []
+            buffers = []
+            for i in range(0, len(recv_pts)):
+                buffers.append(deque())
+
+            _start_consoleSwitch(vm, 10.0)
+            tmp = str(recv_pts[0][1:3])
+            for recv_pt in recv_pts[1:]:
+                tmp += ", " + str(recv_pt[1:3])
+            _execute_consoleSwitch('startswitch([%s], [%s], %d)' % (
+                            str(send_pt[1:3]), tmp, buf_len[-1]
+                            ), vm, 2.0)
+
+            # TEST
+            thread = th_send(send_pt, 1048576, buffers, buf_len[0])
+            thread.start()
+            threads.append(thread)
+
+            for i in range(len(recv_pts)):
+                thread = th_recv(recv_pts[i], buffers[i], 1048576, 
+                                 buf_len[i+1])
+                thread.start()
+                threads.append(thread)
+
+            death = False
+            # Send+recv threads, DL 60s
+            for i in range(60):
+                for thread in threads:
+                    if not thread.is_alive():
+                        if thread.ExitState:
+                            error.TestFail("test_loopback send/recv thread" \
+                                            " failed")
+                        death = True
+                if death:
+                    break
+                tmp = ""
+                for buf in buffers:
+                    tmp += str(len(buf)) + ", "
+                logging.debug("test_loopback: buffer length (%s)" % (tmp[:-2]))
+                time.sleep(1)
+
+            if not death:
+                raise error.TestFail("test_loopback send/recv timeout")
+            # at this point at least one thread died. It should be the send one.
+    
+            # Wait for recv threads to finish their's work
+            for i in range(60):
+                death = True
+                for thread in threads:
+                    if thread.is_alive():
+                        death = False
+                # There are no living threads
+                if death:
+                    break
+                tmp = ""
+                for buf in buffers:
+                    tmp += str(len(buf)) + ", "
+                logging.debug("test_loopback: buffer length (%s)" % (tmp[:-2]))
+                time.sleep(1)
+
+            for thread in threads:
+                if thread.ExitState:
+                    error.TestFail("test_loopback recv thread failed")
+
+            # At least one thread is still alive
+            if not death:
+                raise error.TestFail("test_loopback recv timeout")
+
+            vm[1].sendline("die()")
+
+    def test_perf(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 params: test parameters: 
+                '$console_type@buffer_length:$test_duration;...'
+        """
+        # PREPARE
+        for param in params.split(';'):
+            if not param:
+                continue
+            logging.info("test_perf: PARAMS:%s" % param)
+            param = param.split(':')
+            if len(param) > 1 and param[1].isdigit():
+                duration = float(param[1])
+            else:
+                duration = 30.0
+            param = param[0].split('@')
+            if len(param) > 1 and param[1].isdigit():
+                buf_len = int(param[1])
+            else:
+                buf_len = 32
+            if param[0] == "serialport":
+                port = consoles[1][0]
+            else:
+                port = consoles[0][0]
+            data = array.array("L")
+            for i in range(max((buf_len / data.itemsize), 1)):
+                data.append(random.randrange(sys.maxint))
+
+            ev = threading.Event()
+            thread = th_send_loop(port[0], data.tostring(), ev)
+        
+            _start_consoleSwitch(vm, 10.0)
+
+            _execute_consoleSwitch('startswitch([%s], [], %d)' % (
+                            str(port[1:3]),
+                            buf_len,
+                            ), vm, 2.0)
+
+            # TEST
+            # Host -> Guest
+            load = []
+            stats = array.array('f', [])
+            slice = float(duration)/100
+            load.append(averageCpuLoad())
+            load.append(averageProcessCpuLoad(os.getpid(), 'autotest'))
+            load.append(averageProcessCpuLoad(vm[0].get_pid(), 'VM'))
+            for ld in load:
+                ld.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([load[1].getLoad(), load[2].getLoad()], \
+                            load[0].getLoad())
+            ev.set()
+            thread.join()
+            if (_time > slice):
+                logging.error(
+                    "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*1024*1024)
+            logging.info("Host->Guest [mb/s] min/med/max = %.3f/%.3f/%.3f" \
+                        % (stats[0], stats[len(stats)/2], stats[-1]))
+            time.sleep(5)
+            vm[1].sendline("die()")
+
+            # Guest -> Host
+            _start_consoleSwitch(vm, 10.0)
+            _execute_consoleSwitch('senderprepare(%s, %d)' % (
+                                    str(port[1:3]),
+                                    buf_len,
+                                    ), vm, 10)
+            stats = array.array('f', [])
+            ev.clear()
+            thread = th_recv_null(port, ev, buf_len)
+            thread.start()
+            # reset load measures
+            for ld in load:
+                ld.getLoad()
+            _execute_consoleSwitch('senderstart()', vm, 2)
+            _time = time.time()
+            for i in range(100):
+                stats.append(thread.idx)
+                time.sleep(slice)
+            _time = time.time() - _time - duration
+            print_load([load[1].getLoad(), load[2].getLoad()], \
+                            load[0].getLoad())
+            vm[1].sendline("die()")
+            time.sleep(5)
+            ev.set()
+            thread.join()
+            if (_time > slice): # Deviation is higher than 1 slice
+                logging.error(
+                    "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*1024*1024)
+            logging.info("Guest->Host [mb/s] min/med/max = %.3f/%.3f/%.3f" \
+                        % (stats[0], stats[len(stats)/2], stats[-1]))
+            for ld in load:
+                del(ld)
+
+
+    # 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', '')
+    
+    no_serialports = 0
+    no_consoles = 0
+    # consoles required for Smoke test
+    if (test_smoke_params.count('serialport')):
+        no_serialports = max(2, no_serialports)
+    if (test_smoke_params.count('console')):
+        no_consoles = max(2, no_consoles)
+    # consoles required for Loopback test
+    for param in test_loopback_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')):
+        no_serialports = max(1, no_serialports)
+    if (test_perf_params.count('console')):
+        no_consoles = max(1, no_consoles)
+
+    vm, consoles = _vm_create(no_consoles, no_serialports)
+    
+    # Copy allocator.py into guests
+    pwd = os.path.join(os.environ['AUTODIR'],'tests/kvm')
+    vksmd_src = os.path.join(pwd, "scripts/consoleSwitch.py")
+    dst_dir = "/tmp"
+    if not vm[0].copy_files_to(vksmd_src, dst_dir):
+        raise error.TestFail("copy_files_to failed %s" % vm[0].name)
+    
+    # ACTUAL TESTING
+    test_smoke(vm, consoles, test_smoke_params)
+    test_loopback(vm, consoles, test_loopback_params)
+    test_perf(vm, consoles, test_perf_params)
+    
+    
+    # CLEANUP
+    vm[1].close()
+    vm[0].destroy(gracefully = False)
+    shutil.rmtree(vm[2])
diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
index cb727c4..2773ea2 100644
--- a/client/tests/kvm/tests_base.cfg.sample
+++ b/client/tests/kvm/tests_base.cfg.sample
@@ -380,6 +380,13 @@  variants:
                 ksm_mode = "parallel"
     - iofuzz:
         type = iofuzz
+        
+    - virtio_console:        
+        vms = ''
+        type = virtio_console
+        virtio_console_smoke = "serialport;console:Custom data"
+        virtio_console_loopback = "serialport:serialport;serialport@1024:serialport@32:console@1024:console@8:16"
+        virtio_console_perf = "serialport;serialport@1000000:120;console@1024:60"
 
     # This unit test module is for older branches of KVM that use the
     # kvmctl test harness (such as the code shipped with RHEL 5.x)