New version of virtio_console test is more similar to upstream C virtio_console test so it will be easier to maintain. Also we moved the scripts/console_switch.py to more sensible scripts/virtio_guest.py There are still some virtio_console bugs which restricts some usage (mostly with console). We have created workarounds which enables us to run the tests. They are marked as FIXME comments. It tests more virtio_console features: [preparation]: * Prepare for testing * test parameters '$console_type:$data;...' 1) creates GUEST machine with virtio_ports (serialport/consoles) 2) Initiate connection; defines and verifies the virtio_ports on GUEST side. [test_smoke]: * Tests the basic capabilities of virtio_consoles. * test parameters, multiple recievers allowe. '$source_console_type@$buffer_length:$destination_console_type1@buffer_length:...:$loopback_buffer_length;...' 1) POLL test (IN,OUT,HUP) 2) Read/write without connected host 3) Read in nonblocking and blocking mode 4) Basic loopback test (different from test_loopback test) [test_loopback]: *Creates a loopback between sender port and receiving port, send data through this connection, verify data correctness. * test parameters '$console_type@$buffer_length:$test_duration;...' * link checking mode is configurable by virtio_check_mode = {none, poll, select} 1) GUEST: creates loopback between specified ports 2) HOST: creates thread for sending data and storing them into verification buffer 3) HOST: creates threads for receiving data and comparing them with verification buffer 4) let it work for 60 seconds 5) Exit threads and loopback [test_perf]: * Measure performance for the virtio console tunnel ---< Host->Guest >--- 1) GUEST: creates loopback between the port and nothing (read and forget) 2) HOST: prepare for data send (sending thread, stats, cpu loads) 3) HOST: starts sending the data in loop for specified period of time 4) HOST: stop the sending and process stats/cpu loads 5) Exit threads and loopback ---< Guest->Host >--- 1) GUEST: inits sender thread 2) HOST: prepare for receiving data (recv thread, stats, cpu loads) 3) GUEST: starts sending the data 4) HOST: stop logging stats 5) GUEST: exit sending thread 6) HOST: finish receiving Signed-off-by: Lukas Doktor <ldoktor@xxxxxxxxxx> Signed-off-by: Jiri Zupka <jzupka@xxxxxxxxxx> --- client/tests/kvm/scripts/console_switch.py | 219 --------- client/tests/kvm/scripts/virtio_guest.py | 571 ++++++++++++++++++++++ client/tests/kvm/tests/virtio_console.py | 730 +++++++++++++++++----------- client/tests/kvm/tests_base.cfg.sample | 8 +- 4 files changed, 1022 insertions(+), 506 deletions(-) delete mode 100644 client/tests/kvm/scripts/console_switch.py create mode 100644 client/tests/kvm/scripts/virtio_guest.py diff --git a/client/tests/kvm/scripts/console_switch.py b/client/tests/kvm/scripts/console_switch.py deleted file mode 100644 index 3df4094..0000000 --- a/client/tests/kvm/scripts/console_switch.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" -Auxiliary script used to send data between ports on guests. - -@copyright: 2008-2009 Red Hat Inc. -@author: Jiri Zupka (jzupka@xxxxxxxxxx) -@author: Lukas Doktor (ldoktor@xxxxxxxxxx) -""" -import threading -from threading import Thread -import os,time,select,re,random,sys,array - -files = {} -ev = threading.Event() -threads = [] - -DEBUGPATH="/sys/kernel/debug" - - -class Switch(Thread): - """ - Create a thread which sends 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): - """ - Creates thread which sends random blocks 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 get_port_status(): - """ - Get info about ports from kernel debugfs. - - @return: ports dictionary of port properties - """ - ports = {} - - not_present_msg = "FAIL: There's no virtio-ports dir in debugfs" - 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 not_present_msg - except: - print not_present_msg - else: - viop_names = os.listdir('%s/virtio-ports' % DEBUGPATH) - for name in viop_names: - f = open("%s/virtio-ports/%s" % (DEBUGPATH, name), 'r') - port = {} - 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 works like a serialport - else: - port["path"] = "/dev/%s" % name - ports[port['name']] = port - f.close() - - return ports - - -def open_device(in_files, ports): - """ - Open devices and return an array of descriptors. - - @param in_files: files array - @return: array of descriptors - """ - 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 to open file %s" % name - raise inst - return f - - -def start_switch(in_files,out_files,cachesize=1): - """ - Start a switch thread - (because there is a problem with opening one file multiple times). - - @param in_files: array of input files - @param out_files: array of output files - @param cachesize: cachesize - """ - ports = get_port_status() - - in_f = open_device(in_files, ports) - out_f = open_device(out_files, ports) - - s = Switch(ev, in_f, out_f, cachesize) - s.start() - threads.append(s) - - print "PASS: Start switch" - - -def end_switches(): - """ - End all running data switches. - """ - 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) - current_pid = os.getpid() - os.kill(current_pid, 15) - - -def sender_prepare(port, length): - """ - Prepares the sender thread. Requires a clean thread structure. - """ - del threads[:] - ports = get_port_status() - in_f = open_device([port], ports) - - threads.append(Sender(in_f[0], length)) - print "PASS: Sender prepare" - - -def sender_start(): - """ - Start sender data transfer. Requires sender_prepare to run first. - """ - threads[0].start() - print "PASS: Sender start" - - -def main(): - """ - Main (infinite) loop of console_switch. - """ - print "PASS: Start" - end = False - while not end: - str = raw_input() - exec str - - -if __name__ == "__main__": - main() diff --git a/client/tests/kvm/scripts/virtio_guest.py b/client/tests/kvm/scripts/virtio_guest.py new file mode 100644 index 0000000..a0f9df8 --- /dev/null +++ b/client/tests/kvm/scripts/virtio_guest.py @@ -0,0 +1,571 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" +Auxiliary script used to send data between ports on guests. + +@copyright: 2008-2009 Red Hat Inc. +@author: Jiri Zupka (jzupka@xxxxxxxxxx) +@author: Lukas Doktor (ldoktor@xxxxxxxxxx) +""" +#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 + +DEBUGPATH = "/sys/kernel/debug" +SYSFSPATH = "/sys/class/virtio-ports/" + + +class virtio_guest(): + + LOOP_NONE = 0 + LOOP_POLL = 1 + LOOP_SELECT = 2 + + def __init__(self): + self.files = {} + self.exit_thread = threading.Event() + self.threads = [] + self.ports = {} + + def _readfile(self, name): + """ + Read file and return content as string + + @param name: Name of file + @return: Content of file as string + """ + out = "" + try: + f = open(name, "r") + out = f.read() + f.close() + except: + print "FAIL: Cannot open file %s" % (name) + + return out + + def _get_port_status(self): + """ + Get info about ports from kernel debugfs. + + @return: Ports dictionary of port properties + """ + ports = {} + not_present_msg = "FAIL: There's no virtio-ports dir in debugfs" + 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 not_present_msg + except: + print not_present_msg + else: + viop_names = os.listdir('%s/virtio-ports' % (DEBUGPATH)) + for name in viop_names: + f = open("%s/virtio-ports/%s" % (DEBUGPATH, name), 'r') + port = {} + 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 works like a serialport + else: + port["path"] = "/dev/%s" % name + + if (not os.path.exists(port['path'])): + print "FAIL: %s not exist" % port['path'] + + sysfspath = SYSFSPATH + name + if (not os.path.isdir(sysfspath)): + print "FAIL: %s not exist" % (sysfspath) + + info_name = sysfspath + "/name" + port_name = self._readfile(info_name).strip() + if (port_name != port["name"]): + print "FAIL: Port info not match \n%s - %s\n%s - %s" \ + % (info_name , port_name, \ + "%s/virtio-ports/%s" % (DEBUGPATH, name), port["name"]) + + ports[port['name']] = port + f.close() + + return ports + + def init(self, in_files): + """ + Init and check port properties + """ + self.ports = self._get_port_status() + + for item in in_files: + if (item[1] != self.ports[item[0]]["is_console"]): + print self.ports + print "FAIL: Host console is not like console on guest side\n" + print "PASS: Init and check virtioconsole files in system." + + class switch(Thread): + """ + Create a thread which sends data between ports. + """ + def __init__ (self, in_files, out_files, event, \ + cachesize=1024, method=0): + """ + @param in_files: Array of input files + @param out_files: Array of output files + @param method: Method of read/write access + @param cachesize: Block to receive and send + """ + Thread.__init__(self) + + self.in_files = in_files + self.out_files = out_files + self.exit_thread = event + self.method = method + + self.cachesize = cachesize + + def _none_mode(self): + """ + Read and write to device in blocking mode + """ + data = "" + while not self.exit_thread.isSet(): + data = "" + for desc in self.in_files: + data += os.read(desc, self.cachesize) + if data != "": + for desc in self.out_files: + os.write(desc, data) + + def _poll_mode(self): + """ + Read and write to device in polling mode + """ + + pi = select.poll() + po = select.poll() + + for fd in self.in_files: + pi.register(fd, select.POLLIN) + + for fd in self.out_files: + po.register(fd, select.POLLOUT) + readyf = [] + data = ""#array.array('B') + while not self.exit_thread.isSet(): + data = "" + t_out = self.out_files + + readyf = pi.poll(1.0) + for i in readyf: + try: + data += os.read(desc, self.cachesize) + except EOFError: + pass + + if data.count(x): + readyf = [] + while (len(t_out) != len(readyf)) and \ + not self.exit_thread.isSet(): + readyf = po.poll(1.0) + for desc in t_out: + os.write(desc, data) + + + + def _select_mode(self): + """ + Read and write to device in selecting mode + """ + data = "" + ret = [] + while not self.exit_thread.isSet(): + #TODO: Why select causes trouble? :-( + ret = select.select(self.in_files, [], [], 1.0) + data = "" + if ret[0] != []: + for desc in ret[0]: + data += os.read(desc, self.cachesize) + if data != "": + ret = select.select([], self.out_files, [], 1.0) + while (len(self.out_files) != len(ret[1])) and \ + not self.exit_thread.isSet(): + ret = select.select([], self.out_files, [], 1.0) + for desc in ret[1]: + os.write(desc, data) + + def run(self): + if (self.method == virtio_guest.LOOP_POLL): + self._poll_mode() + elif (self.method == virtio_guest.LOOP_SELECT): + self._select_mode() + else: + self._none_mode() + + def _poll_mode(self): + """ + Read and write to device in polling mode + """ + + pi = select.poll() + po = select.poll() + + for fd in self.in_files: + pi.register(fd, select.POLLIN) + + for fd in self.out_files: + po.register(fd, select.POLLOUT) + + while not self.exit_thread.isSet(): + data = "" + t_out = self.out_files + + readyf = pi.poll(1.0) + for i in readyf: + data += os.read(i[0], self.cachesize) + + if data != "": + while (len(t_out) != len(readyf)) and \ + not self.exit_thread.isSet(): + readyf = po.poll(1.0) + for desc in t_out: + os.write(desc, data) + + + + def _select_mode(self): + """ + Read and write to device in selecting mode + """ + while not self.exit_thread.isSet(): + #TODO: Why select causes trouble? :-( + ret = select.select(self.in_files, [], [], 1.0) + data = "" + if ret[0] != []: + for desc in ret[0]: + data += os.read(desc, self.cachesize) + if data != "": + ret = select.select([], self.out_files, [], 1.0) + while (len(self.out_files) != len(ret[1])) and \ + not self.exit_thread.isSet(): + ret = select.select([], self.out_files, [], 1.0) + for desc in ret[1]: + os.write(desc, data) + + def run(self): + if (self.method == virtio_guest.LOOP_POLL): + self._poll_mode() + elif (self.method == virtio_guest.LOOP_SELECT): + self._select_mode() + else: + self._none_mode() + + class sender(Thread): + """ + Creates thread which sends random blocks of data to the destination + port. + """ + def __init__(self, port, event, length): + """ + @param port: Destination port + @param length: Length of the random data block + """ + Thread.__init__(self) + self.port = port + self.exit_thread = event + 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 not self.exit_thread.isSet(): + os.write(self.port, self.data) + + + def _open(self, in_files): + """ + Open devices and return array of descriptors + + @param in_files: Files array + @return: Array of descriptor + """ + + f = [] + + for item in in_files: + name = self.ports[item]["path"] + if (name in self.files): + f.append(self.files[name]) + else: + try: + self.files[name] = os.open(name, os.O_RDWR) + if (self.ports[item]["is_console"] == "yes"): + 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: + print "FAIL: Failed to open file %s" % (name) + raise inst + return f + + + def poll(self, port, expected, timeout=500): + """ + Pool event from device and print event like text + + @param file: Device + """ + + in_f = self._open([port]) + + p = select.poll() + p.register(in_f[0]) + + 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 " + + if (mask[0][1] & expected) == expected: + print "PASS: Events: " + str + else: + print "FAIL: Events: " + str + + + def blocking(self, port, mode=False): + """ + Set port function mode blocking/nonblocking + + @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] + + try: + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + if not mode: + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) + else: + fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_NONBLOCK) + + except Exception as inst: + print "FAIL: Setting (non)blocking mode: " + str(inst) + return + + print "PASS: set blocking mode to %s mode" % \ + ("blocking" if mode else "nonblocking") + + + def close(self, file): + """ + Close open port + + @param file: file to close + """ + descriptor = None + path = self.ports[file]["path"] + if path != None: + 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 + print "PASS: Close" + + def open(self, in_files): + """ + Direct open devices. + + @param in_files: Files array + @return: Array of descriptor + """ + + name = self.ports[in_files]["path"] + try: + self.files[name] = os.open(name, os.O_RDWR) + print "PASS: Open all filles correctly." + except Exception as inst: + print "%s\nFAIL: Failed open file %s" % (str(inst), name) + + + def loopback(self, in_files, out_files, cachesize=1024, mode=LOOP_NONE): + """ + 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 + """ + + + self.ports = self._get_port_status() + + 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.start() + self.threads.append(s) + print "PASS: Start switch" + + def exit_threads(self): + """ + Function end all running data switch. + """ + self.exit_thread.set() + for th in self.threads: + print "join" + th.join() + self.exit_thread.clear() + + del self.threads[:] + for desc in self.files.itervalues(): + os.close(desc) + self.files.clear() + print "PASS: All threads finished." + + def die(self): + """ + Quit consoleswitch. + """ + self.exit_threads() + exit() + + + def send_loop_init(self, port, length): + """ + Prepares the sender thread. Requires clean thread structure. + """ + self.ports = self._get_port_status() + in_f = self._open([port]) + + self.threads.append(self.sender(in_f[0], self.exit_thread, length)) + print "PASS: Sender prepare" + + + def send_loop(self): + """ + Start sender data transfer. Requires senderprepare run firs. + """ + self.threads[0].start() + print "PASS: Sender start" + + + def send(self, port, length=1, mode=True): + """ + Send a data of some length + + @param port: Port to write data + @param length: Length of data + @param mode: True = loop mode, False = one shoot mode + """ + in_f = self._open([port]) + + data = "" + while len(data) < length: + data += "%c" % random.randrange(255) + try: + writes = os.write(in_f[0], data) + except Exception as inst: + print inst + if not writes: + writes = 0 + if mode: + while (writes < length): + try: + writes += os.write(in_f[0], data) + except Exception as inst: + print inst + if writes >= length: + print "PASS: Send data length %d" % writes + else: + print "FAIL: Partial send: desired %d, transfered %d" \ + % (length, writes) + + + def recv(self, port, length=1, buffer=1024, mode=True): + """ + Recv a data of some length + + @param port: Port to write data + @param length: Length of data + @param mode: True = loop mode, False = one shoot mode + """ + in_f = self._open([port]) + + recvs = "" + try: + recvs = os.read(in_f[0], buffer) + except Exception as inst: + print inst + if mode: + while (len(recvs) < length): + try: + recvs += os.read(in_f[0], buffer) + except Exception as inst: + print inst + if len(recvs) >= length: + print "PASS: Recv data length %d" % len(recvs) + else: + print "FAIL: Partial recv: desired %d, transfered %d" \ + % (length, len(recvs)) + +def compile(): + """ + Compile virtio_guest.py to speed up. + """ + import py_compile + py_compile.compile(sys.path[0] + "/virtio_guest.py") + print "PASS: compile" + exit(0) + +def main(): + """ + Main (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 + + +if __name__ == "__main__": + main() diff --git a/client/tests/kvm/tests/virtio_console.py b/client/tests/kvm/tests/virtio_console.py index 2e22f2f..058bb94 100644 --- a/client/tests/kvm/tests/virtio_console.py +++ b/client/tests/kvm/tests/virtio_console.py @@ -1,10 +1,10 @@ -import socket, random, array, sys, os, tempfile, shutil, threading, select, re -import logging, time -from threading import Thread +import array, logging, os, random, re, select, shutil, socket, sys, tempfile +import threading, time from collections import deque -from autotest_lib.client.common_lib import error -import kvm_subprocess, kvm_test_utils, kvm_utils, kvm_preprocessing +from threading import Thread +import kvm_subprocess, kvm_test_utils, kvm_utils, kvm_preprocessing +from autotest_lib.client.common_lib import error def run_virtio_console(test, params, env): """ @@ -28,139 +28,162 @@ def run_virtio_console(test, params, env): """ 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 + @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 + # FIXME: socket.send(data>>127998) without read blocks thread + if len(data) > 102400: + data = data[0:102400] + logging.error("Data are too long, using only first %d bytes", + len(data)) self.data = data self.exitevent = event self.idx = 0 - - def run(self): - logging.debug("th_send_loop %s: run", self.getName()) + logging.debug("th_send %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(), + logging.debug("th_send %s: exit(%d)", self.getName(), self.idx) - - class th_recv(Thread): + class th_send_check(Thread): """ - Random data reciever/checker thread + Random data sender thread """ - def __init__(self, port, buffer, length, blocklen=32): + def __init__(self, port, event, queues, blocklen=1024): """ - @param port: source port - @param buffer: control data buffer (FIFO) - @param length: amount of data we want to receive - @param blocklen: block length + @param port: Destination port + @param event: Exit event + @param queues: Queues for the control data (FIFOs) + @param blocklen: Block length """ Thread.__init__(self) - self.ExitState = True - self.port = port[0] - self.buffer = buffer - self.length = length + self.port = port + self.queues = queues + # FIXME: socket.send(data>>127998) without read blocks thread + if blocklen > 102400: + blocklen = 102400 + logging.error("Data are too long, using blocklen = %d", + blocklen) self.blocklen = blocklen - + self.exitevent = event + self.idx = 0 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 + logging.debug("th_send_check %s: run", self.getName()) + too_much_data = False + while not self.exitevent.isSet(): + # FIXME: workaround the problem with qemu-kvm stall when too + # much data send without receiving + for queue in self.queues: + while not self.exitevent.isSet() and len(queue) > 1048576: + too_much_data = True + time.sleep(0.1) + ret = select.select([], [self.port], [], 1.0) + if ret[1]: + # Generate blocklen of random data add them to the FIFO + # and send them over virtio_console + buf = "" + for i in range(self.blocklen): + ch = "%c" % random.randrange(255) + buf += ch + for queue in self.queues: + queue.append(ch) + target = self.idx + self.blocklen + while not self.exitevent.isSet() and self.idx < target: + idx = self.port.send(buf) + buf = buf[idx:] + self.idx += idx + logging.debug("th_send_check %s: exit(%d)", self.getName(), + self.idx) + if too_much_data: + logging.error("th_send_check: workaround the 'too_much_data'" + "bug") - class th_recv_null(Thread): + class th_recv(Thread): """ - Receives data and throws it away. + Recieves data and throw them away """ - def __init__(self, port, event, blocklen=32): + def __init__(self, port, event, blocklen=1024): """ @param port: Data source port. @param event: Exit event. @param blocklen: Block length. """ Thread.__init__(self) - self.port = port[0] + self.port = port 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()) + logging.debug("th_recv %s: run", self.getName()) while not self.exitevent.isSet(): - # Workaround, it didn't work with select :-/ + # TODO: 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(), + logging.debug("th_recv %s: exit(%d)", self.getName(), self.idx) + + + class th_recv_check(Thread): + """ + Random data reciever/checker thread + """ + def __init__(self, port, buffer, event, blocklen=1024): + """ + @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.port = port + self.buffer = buffer + self.exitevent = event + self.blocklen = blocklen + self.idx = 0 + def run(self): + logging.debug("th_recv_check %s: run", self.getName()) + while not self.exitevent.isSet(): + ret = select.select([self.port], [], [], 1.0) + if ret and (not self.exitevent.isSet()): + buf = self.port.recv(self.blocklen) + if buf: + # Compare the recvd data with the control data + for ch in buf: + ch_ = self.buffer.popleft() + if not ch == ch_: + self.exitevent.set() + logging.error("Failed to recv %dth character", + self.idx) + logging.error("%s != %s", repr(ch), repr(ch_)) + logging.error("Recv = %s", repr(buf)) + # sender might change the buffer :-( + time.sleep(1) + ch_ = "" + for buf in self.buffer: + ch_ += buf + logging.error("Queue = %s", repr(ch_)) + raise error.TestFail("th_recv_check: incorrect " + "data") + self.idx += len(buf) + logging.debug("th_recv_check %s: exit(%d)", self.getName(), self.idx) seqTest = threading.Lock(); - class average_cpu_load(): + class cpu_load(): """ Get average cpu load between start and get_load """ @@ -198,10 +221,11 @@ def run_virtio_console(test, params, env): return load - def start ( self ): + 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() @@ -209,10 +233,11 @@ def run_virtio_console(test, params, env): 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 + testTime = self.endTime - self.startTime load = self._get_cpu_load() user = load[0] / testTime @@ -222,7 +247,7 @@ def run_virtio_console(test, params, env): return (user, system, sum, testTime) - class average_process_cpu_load(): + class pid_load(): """ Get average process cpu load between start and get_load """ @@ -234,7 +259,7 @@ def run_virtio_console(test, params, env): self.name = name - def _get_cpu_load(self,pid): + def _get_cpu_load(self, pid): # Let's see if we can calc system load. try: f = open("/proc/%d/stat" % (pid), "r") @@ -249,19 +274,19 @@ def run_virtio_console(test, params, env): load_values = reg.findall(line) del load_values[0:11] # extract values from /proc/stat - load = [0,0] + load = [0, 0] for i in range(2): - load[i] = int(load_values[i])-self.old_load[i] + 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 ): + 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) @@ -274,7 +299,7 @@ def run_virtio_console(test, params, env): (pid, user[%], system[%], sum[%], testTime[s]) """ self.endTime = time.time() - testTime = self.endTime - self.startTime + testTime = self.endTime - self.startTime load = self._get_cpu_load(self.pid) user = load[0] / testTime @@ -288,12 +313,12 @@ def run_virtio_console(test, params, env): """ Print load in tabular mode. - @param process: list of process statistic tuples - @param system: tuple of system cpu usage + @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") + "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" % @@ -302,64 +327,96 @@ def run_virtio_console(test, params, env): def process_stats(stats, scale=1.0): """ - Process and print the stats. + 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] + 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_console_switch(vm, timeout=2): + def init_guest(vm, timeout=2): """ - Execute console_switch.py on guest, wait until it is initialized. + Execute virtio_guest.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 console_switch.py on guest %s", vm[0].name) - vm[1].sendline("python /tmp/console_switch.py") + logging.debug("compile virtio_guest.py on guest %s", vm[0].name) + vm[1].sendline("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: 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 &&" + "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: + raise error.TestFail("Command console_switch.py on guest %s failed." + "\nreturn code: %s\n output:\n%s" % + (vm[0].name, match, data)) + # Let the system rest + time.sleep(2) - def _execute_console_switch(command, vm, timeout=2): + def _on_guest(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 vm: Informations about the guest. @param timeout: Timeout used to verify expected output. @return: Tuple (match index, data) """ - logging.debug("Executing '%s' on console_switch.py loop, vm: %s," + 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:"], - timeout) + (match, data) = vm[1].read_until_last_line_matches(["PASS:", "FAIL:" \ + "[Failed to execute]"], \ + timeout) + return (match, data) + + + def on_guest(command, vm, timeout=2): + """ + Wrapper around the _on_guest command which executes the command on + guest. Unlike _on_guest command when the command fails it raises the + test error. + + @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) + """ + match, data = _on_guest(command, vm, timeout) if match == 1 or match is None: - raise error.TestFail("Failed to execute '%s' on console_switch.py, " + raise error.TestFail("Failed to execute '%s' on virtio_guest.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. + Read everything from the socket @param sock: socket @param read_timeout: read timeout @@ -380,14 +437,44 @@ def run_virtio_console(test, params, env): return message + def _guest_exit_threads(vm, send_pts, recv_pts): + """ + Safely executes on_guest("virt.exit_threads()") using workaround of + the stucked thread in loopback in mode=virt.LOOP_NONE . + + @param vm: Informations about the guest. + @param send_pts: list of possible send sockets we need to work around + @param recv_pts: list of possible recv sockets we need to read-out + """ + # in LOOP_NONE mode it might stuck in read/write + match, tmp = _on_guest("virt.exit_threads()", vm, 10) + if match == None: + logging.debug("Workaround the stucked thread on Guest") + # Thread is stucked in read/write + for send_pt in send_pts: + send_pt[0].sendall(".") + elif match != 0: + # Something else + raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n" + % (match, tmp)) + + # 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) + + # This will cause fail in case anything went wrong. + on_guest("print 'PASS: nothing'", vm, 10) + + 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 tuple with (guest information, consoles information) + @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[]] """ @@ -398,24 +485,24 @@ def run_virtio_console(test, params, env): 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=c%d," + 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=c%d," - "name=org.fedoraproject.console.%d," - "id=c%d" % (i, i, i)) + params['extra_params'] += (" -device virtconsole,chardev=%d," + "name=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=p%d," + params['extra_params'] += (" -chardev socket,path=%s/%d,id=%d," "server,nowait" % (tmp_dir, i, i)) - params['extra_params'] += (" -device virtserialport,chardev=p%d," - "name=org.fedoraproject.data.%d,id=p%d" % - (i, i, i)) + params['extra_params'] += (" -device virtserialport,chardev=%d," + "name=serialport-%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, @@ -426,12 +513,11 @@ def run_virtio_console(test, params, env): 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"]) - + consoles.append([sock, "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"]) + serialports.append([sock, "serialport-%d" % i, "no"]) return [vm, session, tmp_dir], [consoles, serialports] @@ -440,15 +526,15 @@ def run_virtio_console(test, params, env): """ Virtio console smoke test. - Creates loopback on the vm machine between the ports[>=2] provided and - sends the data + 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;...' """ - logging.info("Smoke test: Send data on the sender port, " - "verify data integrity on the receiving port") + logging.info("Smoke test: Tests the basic capabilities of " + "virtio_consoles.") # PREPARE for param in params.split(';'): if not param: @@ -462,23 +548,118 @@ def run_virtio_console(test, params, env): param = (param[0] == 'serialport') send_pt = consoles[param][0] recv_pt = consoles[param][1] - _start_console_switch(vm, 10.0) # TEST - _execute_console_switch('start_switch([%s], [%s])' % - (str(send_pt[1:3]), str(recv_pt[1:3])), - vm, 2.0) - + # 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" + % (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" + % (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) - d = socket_readall(recv_pt[0], 1.0, len(data)) - if data != d: - raise error.TestFail("test_smoke: received data on port %s " - "does not match data sent through " - "port %s" % (recv_pt, send_pt)) - - vm[1].sendline('die()') + 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]) - logging.info("test_smoke: PASS") + return consoles def test_loopback(vm, consoles, params): @@ -492,9 +673,9 @@ def run_virtio_console(test, params, env): @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;...' + '$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, " @@ -517,7 +698,7 @@ def run_virtio_console(test, params, env): if (len(param[0].split('@')) == 2): buf_len.append(int(param[0].split('@')[1])) else: - buf_len.append(32) + buf_len.append(1024) recv_pts = [] for parm in param[1:]: if (parm.isdigit()): @@ -532,201 +713,170 @@ def run_virtio_console(test, params, env): if (len(parm[0].split('@')) == 2): buf_len.append(int(parm[0].split('@')[1])) else: - buf_len.append(32) + buf_len.append(1024) # There must be sum(idx_*) consoles + last item as loopback buf_len if len(buf_len) == (idx_console + idx_serialport): - buf_len.append(32) + buf_len.append(1024) if len(recv_pts) == 0: raise error.TestFail("test_loopback: incorrect recv consoles" "definition") + threads = [] - buffers = [] + queues = [] for i in range(0, len(recv_pts)): - buffers.append(deque()) + queues.append(deque()) - _start_console_switch(vm, 10.0) - tmp = str(recv_pts[0][1:3]) + tmp = "'%s'" % recv_pts[0][1] for recv_pt in recv_pts[1:]: - tmp += ", " + str(recv_pt[1:3]) - _execute_console_switch('start_switch([%s], [%s], %d)' % - (str(send_pt[1:3]), tmp, buf_len[-1]), - vm, 2.0) + tmp += ", '%s'" % (recv_pt[1]) + on_guest("virt.loopback(['%s'], [%s], %d, virt.LOOP_POLL)" + % (send_pt[1], tmp, buf_len[-1]), vm, 2) + + exit_event = threading.Event() # TEST - thread = th_send(send_pt, 1048576, buffers, buf_len[0]) + thread = th_send_check(send_pt[0], exit_event, queues, 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 = th_recv_check(recv_pts[i][0], queues[i], exit_event, + buf_len[i + 1]) thread.start() threads.append(thread) - dead_threads = False - # Send + recv threads, DL 60s - for i in range(60): - for t in threads: - if not t.is_alive(): - if t.ExitState: - error.TestFail("test_loopback: send/recv thread " - "failed") - dead_threads = True - if dead_threads: - break - tmp = "" - for buf in buffers: - tmp += str(len(buf)) + ", " - logging.debug("test_loopback: buffer length (%s)", tmp[:-2]) - time.sleep(1) - - if not dead_threads: - 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): - dead_threads = True - for t in threads: - if t.is_alive(): - dead_threads = False - # There are no living threads - if dead_threads: - break - tmp = "" - for buf in buffers: - tmp += str(len(buf)) + ", " - logging.debug("test_loopback: buffer length (%s)", tmp[:-2]) - time.sleep(1) + time.sleep(60) + exit_event.set() + threads[0].join() + tmp = "%d data sent; " % threads[0].idx + for thread in threads[1:]: + thread.join() + tmp += "%d, " % thread.idx + logging.info("test_loopback: %s data received and verified", + tmp[:-2]) - for t in threads: - if t.ExitState: - raise error.TestFail("test_loopback: recv thread failed") + # 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) - # At least one thread is still alive - if not dead_threads: - raise error.TestFail("test_loopback: recv timeout") + _guest_exit_threads(vm, [send_pt], recv_pts) - vm[1].sendline("die()") - - logging.info("test_loopback: PASS") + del exit_event + del threads[:] def test_perf(vm, consoles, params): """ - Virtio console performance test. - Tests performance of the virtio_console tunel. First it sends the data - from host to guest and then back. It provides informations about - computer utilization and statistic information about the throughput. + 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;...' + '$console_type@$buffer_length:$test_duration;...' """ logging.info("Performance test: Measure performance for the " - "virtio console tunnel") - # PREPARE + "virtio console tunnel") for param in params.split(';'): if not param: continue - logging.info("test_perf: params: %s", param) + print "test_perf: params: %s" % param param = param.split(':') - if len(param) > 1 and param[1].isdigit(): - duration = float(param[1]) - else: - duration = 30.0 + duration = 60.0 + if len(param) > 1: + try: + duration = float(param[1]) + except: + pass 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) + buf_len = 1024 + param = (param[0] == 'serialport') + port = consoles[param][0] - _start_console_switch(vm, 10.0) + data = "" + for i in range(buf_len): + data += "%c" % random.randrange(255) - _execute_console_switch('start_switch([%s], [], %d)' % - (str(port[1:3]), buf_len), vm, 2.0) + exit_event = threading.Event() + slice = float(duration)/100 - # TEST - # Host -> Guest - load = [] + # 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) stats = array.array('f', []) - slice = float(duration)/100 - load.append(average_cpu_load()) - load.append(average_process_cpu_load(os.getpid(), 'autotest')) - load.append(average_process_cpu_load(vm[0].get_pid(), 'VM')) - for ld in load: - ld.start() + 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() _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].get_load(), load[2].get_load()], - load[0].get_load()) - ev.set() + print_load([loads[1].get_load(), loads[2].get_load()], + loads[0].get_load()) + exit_event.set() thread.join() + + _guest_exit_threads(vm, [port], []) + if (_time > slice): - logging.error("test_perf: test ran %fs longer " - "(more than 1 slice)", _time) + logging.error( + "Test ran %fs longer which is more than one slice", _time) else: - logging.debug("test_perf: test ran %fs longer " - "(less than 1 slice)", _time) - stats = process_stats(stats[1:], slice*1024*1024) - logging.info("Host -> Guest [MB/s] min/med/max = %.3f/%.3f/%.3f", + logging.debug("Test ran %fs longer", _time) + 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]) - time.sleep(5) - vm[1].sendline("die()") - # Guest -> Host - _start_console_switch(vm, 10.0) - _execute_console_switch('sender_prepare(%s, %d)' % - (str(port[1:3]), buf_len), vm, 10) + del thread + + # GUEST->HOST + exit_event.clear() stats = array.array('f', []) - ev.clear() - thread = th_recv_null(port, ev, buf_len) + on_guest("virt.send_loop_init('%s', %d)" % (port[1], buf_len), + vm, 30) + thread = th_recv(port[0], exit_event, buf_len) thread.start() - # reset load measures - for ld in load: - ld.get_load() - _execute_console_switch('sender_start()', vm, 2) + for load in loads: + load.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([load[1].get_load(), load[2].get_load()], - load[0].get_load()) - vm[1].sendline("die()") - time.sleep(5) - ev.set() + print_load([loads[1].get_load(), loads[2].get_load()], + loads[0].get_load()) + on_guest("virt.exit_threads()", vm, 2) + exit_event.set() thread.join() if (_time > slice): # Deviation is higher than 1 slice - logging.error("test_perf: test ran %fs longer " - "(more than 1 slice)", _time) + logging.error( + "Test ran %fs longer which is more than one slice", _time) else: - logging.debug("test_perf: test ran %fs longer " - "(less than 1 slice)", _time) - stats = process_stats(stats[1:], slice*1024*1024) - logging.info("Guest -> Host [MB/s] min/med/max = %.3f/%.3f/%.3f", + logging.debug("Test ran %fs longer" % _time) + 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]) - for ld in load: - del(ld) - logging.info("test_perf: PASS") + del thread + + del exit_event + del loads[:] # INITIALIZE @@ -736,35 +886,44 @@ def run_virtio_console(test, params, env): 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) + if (no_serialports + no_consoles) == 0: + raise error.TestFail("No tests defined, probably incorrect " + "configuration in tests_base.cfg") + vm, consoles = _vm_create(no_consoles, no_serialports) - # Copy console_switch.py into guests + # Copy allocator.py into guests pwd = os.path.join(os.environ['AUTODIR'], 'tests/kvm') - vksmd_src = os.path.join(pwd, "scripts/console_switch.py") + vksmd_src = os.path.join(pwd, "scripts/virtio_guest.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) + # 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) @@ -772,3 +931,4 @@ def run_virtio_console(test, params, env): 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 bef0f1c..ff0a760 100644 --- a/client/tests/kvm/tests_base.cfg.sample +++ b/client/tests/kvm/tests_base.cfg.sample @@ -502,11 +502,15 @@ variants: vms = '' type = virtio_console # smoke params - $console_type:data_string - virtio_console_smoke = "serialport;console:Custom data" + # FIXME: test_smoke doesn't work with console yet (virtio_console bug) + # "serialport;console:Custom data" + virtio_console_smoke = "serialport" # loopback params - '$source_console_type@buffer_length:$destination_console_type1@buffer_length:...:$loopback_buffer_length;...' virtio_console_loopback = "serialport:serialport;serialport@1024:serialport@32:console@1024:console@8:16" # perf params - $console_type@buffer_length:$test_duration - virtio_console_perf = "serialport;serialport@1000000:120;console@1024:60" + # FIXME: test_perf doesn't work with console yet (virtio_console bug) + # virtio_console_perf = "serialport;serialport@1000000:120;console@1024:60" + virtio_console_perf = "serialport;serialport@1000000:120" # 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) -- 1.7.2.2 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html