From: Jiri Zupka <jzupka@xxxxxxxxxx> This patch changes structure of the virtio_console test and prepares this test for simpler porting of another required tests. It creates a new layer for running the elementary tests allowing them to non-critically fail and let the test to finish the whole test loop. Tests are divided into parts but their behavior is preserved. The patch also includes some code correction. Class which makes statistic and start subtest is named SubTest. Usage: def test_func(parma1, param2): if somethink: raise error.TestFail("Somethink") def clean_func(param1, param2, param3): clean test = SubTest() #set cleanup function when subtest crash. test.set_cleanup_func(clean_func, [param1, param2, ..]) #start test test.do_test(test_func,[params..]) test.do_test(test_func,[params..],true) ^^^^ - test is critical. #return statistic print test.get_text_result() if any non-critical subtests fails the particular test finishes and calls the clean-up function. Then the test-loop continues with the next elementary subtest. Although SubTest class displays information about the subtest problem. If the clean-up function also failed or subtest was critical, then the whole test will be interrupted. At the end of the test-loop it summarizes the passed/failed tests and print out the possible subtest trace-backs. Changes from v1: Fix some py 2.6 only syntax present on test Signed-off-by: Jiri Zupka <jzupka@xxxxxxxxxx> Signed-off-by: Lukas Doktor <ldoktor@xxxxxxxxxx> --- client/tests/kvm/scripts/virtio_guest.py | 113 +++-- client/tests/kvm/tests/virtio_console.py | 951 ++++++++++++++++++------------ 2 files changed, 648 insertions(+), 416 deletions(-) diff --git a/client/tests/kvm/scripts/virtio_guest.py b/client/tests/kvm/scripts/virtio_guest.py index 4862ef2..87727c7 100755 --- a/client/tests/kvm/scripts/virtio_guest.py +++ b/client/tests/kvm/scripts/virtio_guest.py @@ -3,35 +3,23 @@ """ Auxiliary script used to send data between ports on guests. -@copyright: 2008-2009 Red Hat Inc. +@copyright: 2010 Red Hat, Inc. @author: Jiri Zupka (jzupka@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 +import os, time, select, re, random, sys, array +import fcntl, array, subprocess, traceback DEBUGPATH = "/sys/kernel/debug" SYSFSPATH = "/sys/class/virtio-ports/" -class virtio_guest(): - +class VirtioGuest: + """ + Test tools of virtio_ports. + """ LOOP_NONE = 0 LOOP_POLL = 1 LOOP_SELECT = 2 @@ -125,7 +113,7 @@ class virtio_guest(): print "PASS: Init and check virtioconsole files in system." - class switch(Thread): + class Switch(Thread): """ Thread that sends data between ports. """ @@ -137,7 +125,7 @@ class virtio_guest(): @param method: Method of read/write access. @param cachesize: Block to receive and send. """ - Thread.__init__(self) + Thread.__init__(self, name="Switch") self.in_files = in_files self.out_files = out_files @@ -211,15 +199,15 @@ class virtio_guest(): def run(self): - if (self.method == virtio_guest.LOOP_POLL): + if (self.method == VirtioGuest.LOOP_POLL): self._poll_mode() - elif (self.method == virtio_guest.LOOP_SELECT): + elif (self.method == VirtioGuest.LOOP_SELECT): self._select_mode() else: self._none_mode() - class sender(Thread): + class Sender(Thread): """ Creates a thread which sends random blocks of data to dst port. """ @@ -228,7 +216,7 @@ class virtio_guest(): @param port: Destination port @param length: Length of the random data block """ - Thread.__init__(self) + Thread.__init__(self, name="Sender") self.port = port self.exit_thread = event self.data = array.array('L') @@ -296,7 +284,20 @@ class virtio_guest(): if (mask[0][1] & expected) == expected: print "PASS: Events: " + str else: - print "FAIL: Events: " + str + estr = "" + if (expected & select.POLLIN): + estr += "IN " + if (expected & select.POLLPRI): + estr += "PRI IN " + if (expected & select.POLLOUT): + estr += "OUT " + if (expected & select.POLLERR): + estr += "ERR " + if (expected & select.POLLHUP): + estr += "HUP " + if (expected & select.POLLMSG): + estr += "MSG " + print "FAIL: Events: " + str + " Expected: " + estr def blocking(self, port, mode=False): @@ -306,8 +307,7 @@ class virtio_guest(): @param port: port to set mode @param mode: False to set nonblock mode, True for block mode """ - path = self.ports[port]["path"] - fd = self.files[path] + fd = self._open([port])[0] try: fl = fcntl.fcntl(fd, fcntl.F_GETFL) @@ -336,24 +336,28 @@ class virtio_guest(): if path in self.files.keys(): descriptor = self.files[path] del self.files[path] - try: - os.close(descriptor) - except Exception as inst: - print "FAIL: Closing the file: " + str(inst) - return + if descriptor != None: + try: + os.close(descriptor) + except Exception as inst: + print "FAIL: Closing the file: " + str(inst) + return print "PASS: Close" - def open(self, in_files): + def open(self, in_file): """ Direct open devices. - @param in_files: Array of files. + @param in_file: Array of files. @return: Array of descriptors. """ - name = self.ports[in_files]["path"] + name = self.ports[in_file]["path"] try: self.files[name] = os.open(name, os.O_RDWR) + if (self.ports[in_file]["is_console"] == "yes"): + print os.system("stty -F %s raw -echo" % (name)) + print os.system("stty -F %s -a" % (name)) print "PASS: Open all filles correctly." except Exception as inst: print "%s\nFAIL: Failed open file %s" % (str(inst), name) @@ -374,7 +378,7 @@ class virtio_guest(): in_f = self._open(in_files) out_f = self._open(out_files) - s = self.switch(in_f, out_f, self.exit_thread, cachesize, mode) + s = self.Switch(in_f, out_f, self.exit_thread, cachesize, mode) s.start() self.threads.append(s) print "PASS: Start switch" @@ -412,7 +416,7 @@ class virtio_guest(): self.ports = self._get_port_status() in_f = self._open([port]) - self.threads.append(self.sender(in_f[0], self.exit_thread, length)) + self.threads.append(self.Sender(in_f[0], self.exit_thread, length)) print "PASS: Sender prepare" @@ -484,6 +488,28 @@ class virtio_guest(): (length, len(recvs))) + def clean_port(self, port, buffer=1024): + in_f = self._open([port]) + ret = select.select([in_f[0]], [], [], 1.0) + buf = "" + if ret[0]: + buf = os.read(in_f[0], buffer) + print ("PASS: Rest in socket: " + buf) + + +def is_alive(): + """ + Check is only main thread is alive and if guest react. + """ + if threading.activeCount() == 1: + print ("PASS: Guest is ok no thread alive") + else: + threads = "" + for thread in threading.enumerate(): + threads += thread.name + ", " + print ("FAIL: On guest run thread. Active thread:" + threads) + + def compile(): """ Compile virtio_guest.py to speed up. @@ -501,12 +527,19 @@ def main(): if (len(sys.argv) > 1) and (sys.argv[1] == "-c"): compile() - virt = virtio_guest() + virt = VirtioGuest() print "PASS: Start" while True: str = raw_input() - exec str + try: + exec str + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + print "On Guest exception from: \n" + "".join( + traceback.format_exception(exc_type, + exc_value, + exc_traceback)) if __name__ == "__main__": diff --git a/client/tests/kvm/tests/virtio_console.py b/client/tests/kvm/tests/virtio_console.py index aa668e1..bb6bafa 100644 --- a/client/tests/kvm/tests/virtio_console.py +++ b/client/tests/kvm/tests/virtio_console.py @@ -1,15 +1,16 @@ """ virtio_console test -@copyright: Red Hat 2010 +@copyright: 2010 Red Hat, Inc. """ import array, logging, os, random, re, select, shutil, socket, sys, tempfile -import threading, time +import threading, time, traceback from collections import deque from threading import Thread import kvm_subprocess, kvm_test_utils, kvm_utils, kvm_preprocessing from autotest_lib.client.common_lib import error +from autotest_lib.client.bin import utils def run_virtio_console(test, params, env): @@ -30,7 +31,204 @@ def run_virtio_console(test, params, env): @param params: Dictionary with the test parameters @param env: Dictionary with test environment """ - class th_send(Thread): + class SubTest(object): + """ + Collect result of subtest of main test. + """ + def __init__(self): + """ + Initialize object + """ + self.result = [] + self.passed = 0 + self.failed = 0 + self.cleanup_func = None + self.cleanup_args = None + + + def set_cleanup_func(self, func, args): + """ + Set cleanup function which is called when subtest fails. + + @param func: Function which should be called when test fails. + @param args: Arguments of cleanup function. + """ + self.cleanup_func = func + self.cleanup_args = args + + + def do_test(self, function, args=None, fatal=False, cleanup=True): + """ + Execute subtest function. + + @param function: Object of function. + @param args: List of arguments of function. + @param fatal: If true exception is forwarded to main test. + @return: Return what returned executed subtest. + @raise TestError: If collapse of test is fatal raise forward + exception from subtest. + """ + if args == None: + args = [] + try: + logging.debug("Start test %s." % function.func_name) + ret = function(*args) + logging.info(self.result_to_string((True, function.func_name, + args))) + self.result.append((True, function.func_name, args)) + self.passed += 1 + return ret + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + logging.error("In function (" + function.func_name + "):") + logging.error("Call from:\n" + + traceback.format_stack()[-2][:-1]) + logging.error("Exception from:\n" + + "".join(traceback.format_exception( + exc_type, exc_value, + exc_traceback.tb_next))) + # Clean up environment after subTest crash + if cleanup: + self.cleanup_func(*self.cleanup_args) + logging.info(self.result_to_string((False, function.func_name, + args))) + self.result.append((False, function.func_name, args)) + self.failed += 1 + if fatal: + raise + + + def is_failed(self): + """ + @return: If any of subtest not pass return True. + """ + if self.failed > 0: + return True + else: + return False + + + def get_result(self): + """ + @return: Result of subtests. + Format: + tuple(pass/fail,function_name,call_arguments) + """ + return self.result + + + def result_to_string_debug(self, result): + """ + @param result: Result of test. + """ + sargs = "" + for arg in result[2]: + sargs += str(arg) + "," + sargs = sargs[:-1] + if result[0]: + status = "PASS" + else: + status = "FAIL" + return ("Subtest (%s(%s)): --> %s") % (result[1], sargs, status) + + + def result_to_string(self, result): + """ + @param result: Result of test. + """ + if result[0]: + status = "PASS" + else: + status = "FAIL" + return ("Subtest (%s): --> %s") % (result[1], status) + + + def get_full_text_result(self): + """ + @return string with text form of result + """ + result = "" + for res in self.result: + result += self.result_to_string_debug(res) + "\n" + return result + + + def get_text_result(self): + """ + @return string with text form of result + """ + result = "" + for res in self.result: + result += self.result_to_string(res) + "\n" + return result + + + class Port(object): + """ + Define structure to keep information about used port. + """ + def __init__(self, sock, name, port_type, path): + """ + @param vm: virtual machine object that port owned + @param sock: Socket of port if port is open. + @param name: Name of port for guest side. + @param port_type: Type of port yes = console, no= serialport. + @param path: Path to port on host side. + """ + self.sock = sock + self.name = name + self.port_type = port_type + self.path = path + self.is_open = False + + + def for_guest(self): + """ + Format data for communication with guest side. + """ + return [self.name, self.port_type] + + + def open(self): + """ + Open port on host side. + """ + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(self.path) + self.is_open = True + + + def clean_port(self): + """ + Clean all data from opened port on host side. + """ + if self.is_open: + self.close() + self.open() + ret = select.select([self.sock], [], [], 1.0) + if ret[0]: + buf = self.sock.recv(1024) + logging.debug("Rest in socket: " + buf) + + + def close(self): + """ + Close port. + """ + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + self.is_open = False + + + def __str__(self): + """ + Convert to text. + """ + return ("%s,%s,%s,%s,%d" % ("Socket", self.name, self.port_type, + self.path, self.is_open)) + + + class ThSend(Thread): """ Random data sender thread. """ @@ -53,14 +251,14 @@ def run_virtio_console(test, params, env): def run(self): - logging.debug("th_send %s: run", self.getName()) + logging.debug("ThSend %s: run", self.getName()) while not self.exitevent.isSet(): self.idx += self.port.send(self.data) - logging.debug("th_send %s: exit(%d)", self.getName(), + logging.debug("ThSend %s: exit(%d)", self.getName(), self.idx) - class th_send_check(Thread): + class ThSendCheck(Thread): """ Random data sender thread. """ @@ -85,7 +283,7 @@ def run_virtio_console(test, params, env): def run(self): - logging.debug("th_send_check %s: run", self.getName()) + logging.debug("ThSendCheck %s: run", self.getName()) too_much_data = False while not self.exitevent.isSet(): # FIXME: workaround the problem with qemu-kvm stall when too @@ -109,14 +307,14 @@ def run_virtio_console(test, params, env): idx = self.port.send(buf) buf = buf[idx:] self.idx += idx - logging.debug("th_send_check %s: exit(%d)", self.getName(), + logging.debug("ThSendCheck %s: exit(%d)", self.getName(), self.idx) if too_much_data: - logging.error("th_send_check: workaround the 'too_much_data'" + logging.error("ThSendCheck: workaround the 'too_much_data'" "bug") - class th_recv(Thread): + class ThRecv(Thread): """ Recieves data and throws it away. """ @@ -134,7 +332,7 @@ def run_virtio_console(test, params, env): self.blocklen = blocklen self.idx = 0 def run(self): - logging.debug("th_recv %s: run", self.getName()) + logging.debug("ThRecv %s: run", self.getName()) while not self.exitevent.isSet(): # TODO: Workaround, it didn't work with select :-/ try: @@ -142,10 +340,10 @@ def run_virtio_console(test, params, env): except socket.timeout: pass self.port.settimeout(self._port_timeout) - logging.debug("th_recv %s: exit(%d)", self.getName(), self.idx) + logging.debug("ThRecv %s: exit(%d)", self.getName(), self.idx) - class th_recv_check(Thread): + class ThRecvCheck(Thread): """ Random data receiver/checker thread. """ @@ -165,10 +363,10 @@ def run_virtio_console(test, params, env): def run(self): - logging.debug("th_recv_check %s: run", self.getName()) + logging.debug("ThRecvCheck %s: run", self.getName()) while not self.exitevent.isSet(): ret = select.select([self.port], [], [], 1.0) - if ret and (not self.exitevent.isSet()): + if ret[0] and (not self.exitevent.isSet()): buf = self.port.recv(self.blocklen) if buf: # Compare the recvd data with the control data @@ -186,156 +384,13 @@ def run_virtio_console(test, params, env): for buf in self.buffer: ch_ += buf logging.error("Queue = %s", repr(ch_)) - raise error.TestFail("th_recv_check: incorrect " + raise error.TestFail("ThRecvCheck: incorrect " "data") self.idx += len(buf) - logging.debug("th_recv_check %s: exit(%d)", self.getName(), + logging.debug("ThRecvCheck %s: exit(%d)", self.getName(), self.idx) - class cpu_load(): - """ - Get average cpu load between start and get_load. - """ - def __init__ (self): - self.old_load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - self.startTime = 0 - self.endTime = 0 - - - def _get_cpu_load(self): - # Let's see if we can calc system load. - try: - f = open("/proc/stat", "r") - tmp = f.readlines(200) - f.close() - except: - logging.critical("Error reading /proc/stat") - error.TestFail("average_cpu_load: Error reading /proc/stat") - - # 200 bytes should be enough because the information we need - # is typically stored in the first line - # Info about individual processors (not yet supported) is in - # the second (third, ...?) line - for line in tmp: - if line[0:4] == "cpu ": - reg = re.compile('[0-9]+') - load_values = reg.findall(line) - # extract values from /proc/stat - load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - for i in range(8): - load[i] = int(load_values[i]) - self.old_load[i] - - for i in range(8): - self.old_load[i] = int(load_values[i]) - return load - - - def start (self): - """ - Start CPU usage measurement - """ - self.old_load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - self.startTime = time.time() - self._get_cpu_load() - - - def get_load(self): - """ - Get and reset CPU usage - - @return: return group cpu (user[%], system[%], sum[%], testTime[s]) - """ - self.endTime = time.time() - testTime = self.endTime - self.startTime - load = self._get_cpu_load() - - user = load[0] / testTime - system = load[2] / testTime - sum = user + system - - return (user, system, sum, testTime) - - - class pid_load(): - """ - Get average process cpu load between start and get_load - """ - def __init__ (self, pid, name): - self.old_load = [0, 0] - self.startTime = 0 - self.endTime = 0 - self.pid = pid - self.name = name - - - def _get_cpu_load(self, pid): - # Let's see if we can calc system load. - try: - f = open("/proc/%d/stat" % (pid), "r") - line = f.readline() - f.close() - except: - logging.critical("Error reading /proc/%d/stat", pid) - error.TestFail("average_process_cpu_load: Error reading " - "/proc/stat") - else: - reg = re.compile('[0-9]+') - load_values = reg.findall(line) - del load_values[0:11] - # extract values from /proc/stat - load = [0, 0] - for i in range(2): - load[i] = int(load_values[i]) - self.old_load[i] - - for i in range(2): - self.old_load[i] = int(load_values[i]) - return load - - - def start (self): - """ - Start CPU usage measurement - """ - self.old_load = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - self.startTime = time.time() - self._get_cpu_load(self.pid) - - - def get_load(self): - """ - Get and reset CPU usage. - - @return: Group cpu - (pid, user[%], system[%], sum[%], testTime[s]) - """ - self.endTime = time.time() - testTime = self.endTime - self.startTime - load = self._get_cpu_load(self.pid) - - user = load[0] / testTime - system = load[1] / testTime - sum = user + system - - return (self.name, self.pid, user, system, sum, testTime) - - - def print_load(process, system): - """ - Print load in tabular mode. - - @param process: List of process statistic tuples. - @param system: Tuple of system cpu usage. - """ - - logging.info("%-10s %6s %5s %5s %5s %11s", - "NAME", "PID", "USER", "SYS", "SUM", "TIME") - for pr in process: - logging.info("%-10s %6d %4.0f%% %4.0f%% %4.0f%% %10.3fs" % pr) - logging.info("TOTAL: ------ %4.0f%% %4.0f%% %4.0f%% %10.3fs" % - system) - - def process_stats(stats, scale=1.0): """ Process and print the statistic. @@ -352,7 +407,7 @@ def run_virtio_console(test, params, env): return stats - def init_guest(vm, timeout=2): + def _init_guest(vm, timeout=2): """ Execute virtio_guest.py on guest, wait until it is initialized. @@ -384,6 +439,21 @@ def run_virtio_console(test, params, env): time.sleep(2) + def init_guest(vm, consoles): + """ + Prepares guest, executes virtio_guest.py and initialize for testing + + @param vm: Informations about the guest. + @param consoles: Informations about consoles + """ + conss = [] + for mode in consoles: + for cons in mode: + conss.append(cons.for_guest()) + _init_guest(vm, 10) + on_guest("virt.init(%s)" % (conss), vm, 10) + + def _on_guest(command, vm, timeout=2): """ Execute given command inside the script's main loop, indicating the vm @@ -425,29 +495,6 @@ def run_virtio_console(test, params, env): return (match, data) - def socket_readall(sock, read_timeout, mesagesize): - """ - Read everything from the socket. - - @param sock: Socket. - @param read_timeout: Read timeout. - @param mesagesize: Size of message. - """ - sock_decriptor = sock.fileno() - sock.settimeout(read_timeout) - message = "" - try: - while (len(message) < mesagesize): - message += sock.recv(mesagesize) - except Exception as inst: - if (inst.args[0] == "timed out"): - logging.debug("Reading timeout") - else: - logging.debug(inst) - sock.setblocking(1) - return message - - def _guest_exit_threads(vm, send_pts, recv_pts): """ Safely executes on_guest("virt.exit_threads()") using workaround of @@ -463,7 +510,7 @@ def run_virtio_console(test, params, env): logging.debug("Workaround the stuck thread on guest") # Thread is stucked in read/write for send_pt in send_pts: - send_pt[0].sendall(".") + send_pt.sock.sendall(".") elif match != 0: # Something else raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" @@ -471,8 +518,8 @@ def run_virtio_console(test, params, env): # Read-out all remaining data for recv_pt in recv_pts: - while select.select([recv_pt[0]], [], [], 0.1)[0]: - recv_pt[0].recv(1024) + while select.select([recv_pt.sock], [], [], 0.1)[0]: + recv_pt.sock.recv(1024) # This will cause fail in case anything went wrong. on_guest("print 'PASS: nothing'", vm, 10) @@ -482,6 +529,13 @@ def run_virtio_console(test, params, env): """ Creates the VM and connects the specified number of consoles and serial ports. + Ports are allocated by 2 per 1 virtio-serial-pci device starting with + console. (3+2 => CC|CS|S; 0+2 => SS; 3+4 => CC|CS|SS|S, ...) This way + it's easy to test communication on the same or different + virtio-serial-pci device. + Further in tests the consoles are being picked always from the first + available one (3+2: 2xC => CC|cs|s <communication on the same PCI>; + 2xC,1xS => CC|cS|s <communication between 2 PCI devs) @param no_console: Number of desired virtconsoles. @param no_serialport: Number of desired virtserialports. @@ -508,12 +562,10 @@ def run_virtio_console(test, params, env): params['extra_params'] += (" -device virtserialport,chardev=vs%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, @@ -522,30 +574,219 @@ def run_virtio_console(test, params, env): # connect the sockets for i in range(0, no_console): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect("%s/%d" % (tmp_dir, i)) - consoles.append([sock, "console-%d" % i, "yes"]) + consoles.append(Port(None ,"console-%d" % i, + "yes", "%s/%d" % (tmp_dir, i))) for i in range(no_console, no_console + no_serialport): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect("%s/%d" % (tmp_dir, i)) - serialports.append([sock, "serialport-%d" % i, "no"]) + serialports.append(Port(None ,"serialport-%d" % i, + "no", "%s/%d" % (tmp_dir, i))) return [vm, session, tmp_dir], [consoles, serialports] - def test_smoke(vm, consoles, params): + def topen(vm, port): + """ + Open virtioconsole port. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port identifier. + """ + on_guest("virt.open('%s')" % (port.name), vm, 2) + port.open() + + + def tmulti_open(vm, port): + """ + Multiopen virtioconsole port. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port identifier. + """ + on_guest("virt.close('%s')" % (port.name), vm, 2) + on_guest("virt.open('%s')" % (port.name), vm, 2) + match = _on_guest("virt.open('%s')" % (port.name), vm, 2)[0] + # Console is permitted to open the device multiple times + if match != 0 and port.port_type == "yes": + raise error.TestFail("Unexpected fail of openning the console" + " device for the 2nd time.") + # Serial port is forbidden to open the device multiple times + elif match != 1 and port.port_type == "no": + raise error.TestFail("Unexpetded pass of oppening the serialport" + " device for the 2nd time.") + port.open() + + def tclose(vm, port): + """ + Close socket. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port to open. + """ + on_guest("virt.close('%s')" % (port.name), vm, 2) + port.close() + + + def tpooling(vm, port): + """ + Test try pooling function. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port used in test. + """ + # Poll (OUT) + on_guest("virt.poll('%s', %s)" % (port.name, select.POLLOUT), vm, + 2) + + # Poll (IN, OUT) + port.sock.sendall("test") + for test in [select.POLLIN, select.POLLOUT]: + on_guest("virt.poll('%s', %s)" % (port.name, test), vm, 2) + + # Poll (IN HUP) + # I store the socket informations and close the socket + port.close() + for test in [select.POLLIN, select.POLLHUP]: + on_guest("virt.poll('%s', %s)" % (port.name, test), vm, 2) + + # Poll (HUP) + on_guest("virt.recv('%s', 4, 1024, False)" % (port.name), vm, 2) + on_guest("virt.poll('%s', %s)" % (port.name, select.POLLHUP), vm, + 2) + + # Reconnect the socket + port.open() + # Redefine socket in consoles + on_guest("virt.poll('%s', %s)" % (port.name, select.POLLOUT), vm, + 2) + + + def trw_host_offline(vm, port): + """ + Guest read/write from host when host is disconnected. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port used in test. + """ + if port.is_open: + port.close() + + # Read should pass + # FIXME: Console doesn't support polling (POLLHUP without host shoots + # the guest OS) + if port.port_type != "yes": + on_guest("virt.recv('%s', 0, 1024, False)" % port.name, vm, 2) + else: # Skip this test on virtio-console port + raise error.TestFail("This test is unable to run with virtio" + " console. Please see the in_code FIXME" + " note for more info.\n" + "Skipping this round.") + # Write should timed-out + match, tmp = _on_guest("virt.send('%s', 10, False)" + % port.name, vm, 2) + if match != None: + raise error.TestFail("Write on guest while host disconnected " + "didn't timed out.\nOutput:\n%s" + % tmp) + + port.open() + + if (port.sock.recv(1024) < 10): + raise error.TestFail("Didn't received data from guest") + # Now the _on_guest("virt.send('%s'... command should be finished + on_guest("print 'PASS: nothing'", vm, 2) + + + def trw_blocking_mode(vm, port): + """ + Guest read\write data in blocking mode. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port used in test. + """ + # Blocking mode + if not port.is_open: + port.open() + on_guest("virt.blocking('%s', True)" % port.name, vm, 2) + # Recv should timed out + match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" % + port.name, vm, 2) + if match == 0: + raise error.TestFail("Received data even when non were sent\n" + "Data:\n%s" % tmp) + elif match != None: + raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" % + (match, tmp)) + port.sock.sendall("1234567890") + # Now guest received the data end escaped from the recv() + on_guest("print 'PASS: nothing'", vm, 2) + + + def trw_nonblocking_mode(vm, port): + """ + Guest read\write data in nonblocking mode. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port used in test. + """ + # Non-blocking mode + if not port.is_open: + port.open() + on_guest("virt.blocking('%s', False)" % port.name, vm, 2) + # Recv should return FAIL with 0 received data + match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" % + port.name, vm, 2) + if match == 0: + raise error.TestFail("Received data even when non were sent\n" + "Data:\n%s" % tmp) + elif match == None: + raise error.TestFail("Timed out, probably in blocking mode\n" + "Data:\n%s" % tmp) + elif match != 1: + raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" % + (match, tmp)) + port.sock.sendall("1234567890") + on_guest("virt.recv('%s', 10, 1024, False)" % port.name, vm, 2) + + + def tbasic_loopback(vm, send_port, recv_port, data="Smoke test data"): + """ + Easy loop back test with loop over only two port. + + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param port: Port used in test. + """ + if not send_port.is_open: + send_port.open() + if not recv_port.is_open: + recv_port.open() + on_guest("virt.loopback(['%s'], ['%s'], 1024, virt.LOOP_NONE)" % + (send_port.name, recv_port.name), vm, 2) + send_port.sock.sendall(data) + tmp = "" + i = 0 + while i <= 10: + i += 1 + ret = select.select([recv_port.sock], [], [], 1.0) + if ret: + tmp += recv_port.sock.recv(1024) + if len(tmp) >= len(data): + break + if tmp != data: + raise error.TestFail("Incorrect data: '%s' != '%s'", + data, tmp) + _guest_exit_threads(vm, [send_port], [recv_port]) + + + def test_smoke(test, vm, consoles, params): """ Virtio console smoke test. Tests the basic functionalities (poll, read/write with and without connected host, etc. - @param vm: target virtual machine [vm, session, tmp_dir] - @param consoles: a field of virtio ports with the minimum of 2 items - @param params: test parameters '$console_type:$data;...' + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param consoles: Field of virtio ports with the minimum of 2 items. + @param params: Test parameters '$console_type:$data;...' """ - logging.info("Smoke test: Tests the basic capabilities of " - "virtio_consoles.") # PREPARE for param in params.split(';'): if not param: @@ -560,121 +801,18 @@ def run_virtio_console(test, params, env): send_pt = consoles[param][0] recv_pt = consoles[param][1] - # TEST - # Poll (OUT) - on_guest("virt.poll('%s', %s)" % (send_pt[1], select.POLLOUT), vm, - 2) - - # Poll (IN, OUT) - send_pt[0].sendall("test") - for test in [select.POLLIN, select.POLLOUT]: - on_guest("virt.poll('%s', %s)" % (send_pt[1], test), vm, 2) - - # Poll (IN HUP) - # I store the socket informations and close the socket - sock = send_pt[0] - send_pt[0] = sock.getpeername() - sock.shutdown(2) - sock.close() - del sock - for test in [select.POLLIN, select.POLLHUP]: - on_guest("virt.poll('%s', %s)" % (send_pt[1], test), vm, 2) - - # Poll (HUP) - on_guest("virt.recv('%s', 4, 1024, False)" % (send_pt[1]), vm, 2) - on_guest("virt.poll('%s', %s)" % (send_pt[1], select.POLLHUP), vm, - 2) - - # Reconnect the socket - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(send_pt[0]) - send_pt[0] = sock - # Redefine socket in consoles - consoles[param][0] = send_pt - on_guest("virt.poll('%s', %s)" % (send_pt[1], select.POLLOUT), vm, - 2) - - # Read/write without host connected - # I store the socket informations and close the socket - sock = send_pt[0] - send_pt[0] = sock.getpeername() - sock.shutdown(2) - sock.close() - del sock - # Read should pass - on_guest("virt.recv('%s', 0, 1024, False)" % send_pt[1], vm, 2) - # Write should timed-out - match, tmp = _on_guest("virt.send('%s', 10, False)" - % send_pt[1], vm, 2) - if match != None: - raise error.TestFail("Read on guest while host disconnected " - "didn't timed out.\nOutput:\n%s" - % tmp) - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(send_pt[0]) - send_pt[0] = sock - - # Redefine socket in consoles - consoles[param][0] = send_pt - if (send_pt[0].recv(1024) < 10): - raise error.TestFail("Didn't received data from guest") - # Now the _on_guest("virt.send('%s'... command should be finished - on_guest("print 'PASS: nothing'", vm, 2) - - # Non-blocking mode - on_guest("virt.blocking('%s', False)" % send_pt[1], vm, 2) - # Recv should return FAIL with 0 received data - match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" % - send_pt[1], vm, 2) - if match == 0: - raise error.TestFail("Received data even when non were sent\n" - "Data:\n%s" % tmp) - elif match == None: - raise error.TestFail("Timed out, probably in blocking mode\n" - "Data:\n%s" % tmp) - elif match != 1: - raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" % - (match, tmp)) - send_pt[0].sendall("1234567890") - on_guest("virt.recv('%s', 10, 1024, False)" % send_pt[1], vm, 2) - - # Blocking mode - on_guest("virt.blocking('%s', True)" % send_pt[1], vm, 2) - # Recv should timed out - match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" % - send_pt[1], vm, 2) - if match == 0: - raise error.TestFail("Received data even when non were sent\n" - "Data:\n%s" % tmp) - elif match != None: - raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s" % - (match, tmp)) - send_pt[0].sendall("1234567890") - # Now guest received the data end escaped from the recv() - on_guest("print 'PASS: nothing'", vm, 2) - - # Basic loopback test - on_guest("virt.loopback(['%s'], ['%s'], 1024, virt.LOOP_NONE)" % - (send_pt[1], recv_pt[1]), vm, 2) - send_pt[0].sendall(data) - tmp = "" - i = 0 - while i <= 10: - i += 1 - ret = select.select([recv_pt[0]], [], [], 1.0) - if ret: - tmp += recv_pt[0].recv(1024) - if len(tmp) >= len(data): - break - if tmp != data: - raise error.TestFail("Incorrect data: '%s' != '%s'", - data, tmp) - _guest_exit_threads(vm, [send_pt], [recv_pt]) - - return consoles - - - def test_loopback(vm, consoles, params): + test.do_test(topen, [vm, send_pt] , True) + test.do_test(tclose, [vm, send_pt], True) + test.do_test(tmulti_open, [vm, send_pt], True) + test.do_test(tpooling, [vm, send_pt]) + test.do_test(trw_host_offline, [vm, send_pt]) + test.do_test(trw_nonblocking_mode, [vm, send_pt]) + test.do_test(trw_blocking_mode, [vm, send_pt]) + test.do_test(tbasic_loopback, [vm, send_pt, recv_pt], data) + + + + def tloopback(vm, consoles, params): """ Virtio console loopback test. @@ -682,16 +820,13 @@ def run_virtio_console(test, params, env): ports and sends length amount of data through this connection. It validates the correctness of the data sent. - @param vm: target virtual machine [vm, session, tmp_dir] - @param consoles: a field of virtio ports with the minimum of 2 items + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param consoles: Field of virtio ports with the minimum of 2 items. @param params: test parameters, multiple recievers allowed. '$source_console_type@buffer_length: $destination_console_type1@$buffer_length:...: $loopback_buffer_length;...' """ - logging.info("Loopback test: Creates a loopback between sender port " - "and receiving port, send data through this connection, " - "verify data correctness.") # PREPARE for param in params.split(';'): if not param: @@ -730,6 +865,13 @@ def run_virtio_console(test, params, env): if len(buf_len) == (idx_console + idx_serialport): buf_len.append(1024) + for p in recv_pts: + if not p.is_open: + p.open() + + if not send_pt.is_open: + send_pt.open() + if len(recv_pts) == 0: raise error.TestFail("test_loopback: incorrect recv consoles" "definition") @@ -739,21 +881,22 @@ def run_virtio_console(test, params, env): for i in range(0, len(recv_pts)): queues.append(deque()) - tmp = "'%s'" % recv_pts[0][1] + tmp = "'%s'" % recv_pts[0].name for recv_pt in recv_pts[1:]: - tmp += ", '%s'" % (recv_pt[1]) + tmp += ", '%s'" % (recv_pt.name) on_guest("virt.loopback(['%s'], [%s], %d, virt.LOOP_POLL)" - % (send_pt[1], tmp, buf_len[-1]), vm, 2) + % (send_pt.name, tmp, buf_len[-1]), vm, 2) exit_event = threading.Event() # TEST - thread = th_send_check(send_pt[0], exit_event, queues, buf_len[0]) + thread = ThSendCheck(send_pt.sock, exit_event, queues, + buf_len[0]) thread.start() threads.append(thread) for i in range(len(recv_pts)): - thread = th_recv_check(recv_pts[i][0], queues[i], exit_event, + thread = ThRecvCheck(recv_pts[i].sock, queues[i], exit_event, buf_len[i + 1]) thread.start() threads.append(thread) @@ -770,8 +913,8 @@ def run_virtio_console(test, params, env): # Read-out all remaining data for recv_pt in recv_pts: - while select.select([recv_pt[0]], [], [], 0.1)[0]: - recv_pt[0].recv(1024) + while select.select([recv_pt.sock], [], [], 0.1)[0]: + recv_pt.sock.recv(1024) _guest_exit_threads(vm, [send_pt], recv_pts) @@ -779,19 +922,17 @@ def run_virtio_console(test, params, env): del threads[:] - def test_perf(vm, consoles, params): + def tperf(vm, consoles, params): """ Tests performance of the virtio_console tunel. First it sends the data from host to guest and than back. It provides informations about computer utilisation and statistic informations about the troughput. - @param vm: target virtual machine [vm, session, tmp_dir] - @param consoles: a field of virtio ports with the minimum of 2 items + @param vm: Target virtual machine [vm, session, tmp_dir]. + @param consoles: Field of virtio ports with the minimum of 2 items. @param params: test parameters: '$console_type@$buffer_length:$test_duration;...' """ - logging.info("Performance test: Measure performance for the " - "virtio console tunnel") for param in params.split(';'): if not param: continue @@ -811,39 +952,38 @@ def run_virtio_console(test, params, env): param = (param[0] == 'serialport') port = consoles[param][0] + if not port.is_open: + port.open() + data = "" for i in range(buf_len): data += "%c" % random.randrange(255) exit_event = threading.Event() - slice = float(duration)/100 + slice = float(duration) / 100 # HOST -> GUEST on_guest('virt.loopback(["%s"], [], %d, virt.LOOP_NONE)' % - (port[1], buf_len), vm, 2) - thread = th_send(port[0], data, exit_event) + (port.name, buf_len), vm, 2) + thread = ThSend(port.sock, data, exit_event) stats = array.array('f', []) - loads = [] - loads.append(cpu_load()) - loads.append(pid_load(os.getpid(), 'autotest')) - loads.append(pid_load(vm[0].get_pid(), 'VM')) - - for load in loads: - load.start() + loads = utils.SystemLoad([(os.getpid(), 'autotest'), + (vm[0].get_pid(), 'VM'), 0]) + loads.start() _time = time.time() thread.start() for i in range(100): stats.append(thread.idx) time.sleep(slice) _time = time.time() - _time - duration - print_load([loads[1].get_load(), loads[2].get_load()], - loads[0].get_load()) + logging.info("\n" + loads.get_cpu_status_string()[:-1]) + logging.info("\n" + loads.get_mem_status_string()[:-1]) exit_event.set() thread.join() # Let the guest read-out all the remaining data while not _on_guest("virt.poll('%s', %s)" % - (port[1], select.POLLIN), vm, 2)[0]: + (port.name, select.POLLIN), vm, 2)[0]: time.sleep(1) _guest_exit_threads(vm, [port], []) @@ -853,30 +993,29 @@ def run_virtio_console(test, params, env): "Test ran %fs longer which is more than one slice", _time) else: logging.debug("Test ran %fs longer", _time) - stats = process_stats(stats[1:], slice*1048576) + stats = process_stats(stats[1:], slice * 1048576) logging.debug("Stats = %s", stats) logging.info("Host -> Guest [MB/s] (min/med/max) = %.3f/%.3f/%.3f", - stats[0], stats[len(stats)/2], stats[-1]) + stats[0], stats[len(stats) / 2], stats[-1]) del thread # GUEST -> HOST exit_event.clear() stats = array.array('f', []) - on_guest("virt.send_loop_init('%s', %d)" % (port[1], buf_len), + on_guest("virt.send_loop_init('%s', %d)" % (port.name, buf_len), vm, 30) - thread = th_recv(port[0], exit_event, buf_len) + thread = ThRecv(port.sock, exit_event, buf_len) thread.start() - for load in loads: - load.start() + loads.start() on_guest("virt.send_loop()", vm, 2) _time = time.time() for i in range(100): stats.append(thread.idx) time.sleep(slice) _time = time.time() - _time - duration - print_load([loads[1].get_load(), loads[2].get_load()], - loads[0].get_load()) + logging.info("\n" + loads.get_cpu_status_string()[:-1]) + logging.info("\n" + loads.get_mem_status_string()[:-1]) on_guest("virt.exit_threads()", vm, 2) exit_event.set() thread.join() @@ -885,37 +1024,86 @@ def run_virtio_console(test, params, env): "Test ran %fs longer which is more than one slice", _time) else: logging.debug("Test ran %fs longer" % _time) - stats = process_stats(stats[1:], slice*1048576) + stats = process_stats(stats[1:], slice * 1048576) logging.debug("Stats = %s", stats) logging.info("Guest -> Host [MB/s] (min/med/max) = %.3f/%.3f/%.3f", - stats[0], stats[len(stats)/2], stats[-1]) + stats[0], stats[len(stats) / 2], stats[-1]) del thread - del exit_event - del loads[:] + + + def clean_ports(vm, consoles): + """ + Clean state of all ports and set port to default state. + Default state: + No data on port or in port buffer. + Read mode = blocking. + + @param consoles: Consoles which should be clean. + """ + # Check if python is still alive + print "CLEANED" + match, tmp = _on_guest("is_alive()", vm, 10) + if (match == None) or (match != 0): + logging.error("Python died/is stucked/have remaining threads") + vm[1].close() + vm[1] = kvm_test_utils.wait_for_login(vm[0], 0, + float(params.get("boot_timeout", 240)), + 0, 2) + (match, data) = _on_guest("killall -9 python " + "&& echo -n PASS: python killed" + "|| echo -n PASS: python was death", + vm, 30) + if (match == None): + # killall -9 command didn't finished - python stucked + raise error.TestFail("Python is really stucked - " + "can't kill -9 it") + + on_guest("rmmod -f virtio_console && echo -n PASS: rmmod " + "|| echo -n FAIL: rmmod", vm, 10) + on_guest("modprobe virtio_console " + "&& echo -n PASS: modprobe || echo -n FAIL: modprobe", + vm, 10) + + init_guest(vm, consoles) + (match, data) = _on_guest("virt.clean_port('%s'),1024" % + consoles[0][0].name, vm, 2) + if (match == None) or (match != 0): + raise error.TestFail("Virtio-console driver is irreparably" + " blocked. Every comd end with sig KILL.") + + for ctype in consoles: + for port in ctype: + openned = port.is_open + port.clean_port() + #on_guest("virt.blocking('%s', True)" % port.name, vm, 2) + on_guest("virt.clean_port('%s'),1024" % port.name, vm, 2) + if not openned: + port.close() # INITIALIZE - test_smoke_params = params.get('virtio_console_smoke', '') - test_loopback_params = params.get('virtio_console_loopback', '') - test_perf_params = params.get('virtio_console_perf', '') + + tsmoke_params = params.get('virtio_console_smoke', '') + tloopback_params = params.get('virtio_console_loopback', '') + tperf_params = params.get('virtio_console_perf', '') no_serialports = 0 no_consoles = 0 # consoles required for Smoke test - if (test_smoke_params.count('serialport')): + if (tsmoke_params.count('serialport')): no_serialports = max(2, no_serialports) - if (test_smoke_params.count('console')): + if (tsmoke_params.count('console')): no_consoles = max(2, no_consoles) # consoles required for Loopback test - for param in test_loopback_params.split(';'): + for param in tloopback_params.split(';'): no_serialports = max(no_serialports, param.count('serialport')) no_consoles = max(no_consoles, param.count('console')) # consoles required for Performance test - if (test_perf_params.count('serialport')): + if (tperf_params.count('serialport')): no_serialports = max(1, no_serialports) - if (test_perf_params.count('console')): + if (tperf_params.count('console')): no_consoles = max(1, no_consoles) if (no_serialports + no_consoles) == 0: @@ -933,16 +1121,27 @@ def run_virtio_console(test, params, env): # ACTUAL TESTING # Defines all available consoles; tests udev and sysfs - conss = [] - for mode in consoles: - for cons in mode: - conss.append(cons[1:3]) - init_guest(vm, 10) - on_guest("virt.init(%s)" % (conss), vm, 10) - - consoles = test_smoke(vm, consoles, test_smoke_params) - test_loopback(vm, consoles, test_loopback_params) - test_perf(vm, consoles, test_perf_params) + init_guest(vm, consoles) + + test = SubTest() + test.set_cleanup_func(clean_ports, [vm, consoles]) + #Test Smoke + test_smoke(test, vm, consoles, tsmoke_params) + + #Test Loopback + test.do_test(tloopback, [vm, consoles, tloopback_params]) + + #Test Performance + test.do_test(tperf, [vm, consoles, tperf_params]) + + logging.info(("Summary: %d tests passed %d test failed :\n" % + (test.passed, test.failed)) + test.get_text_result()) + logging.debug(("Summary: %d tests passed %d test failed :\n" % + (test.passed, test.failed)) + test.get_full_text_result()) + + if test.is_failed(): + raise error.TestFail("Virtio_console test FAILED.") + # CLEANUP vm[1].close() -- 1.7.2.3 -- 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