Index: client/tests/kvm/scripts/virtio_console_guest.py |
diff --git a/client/tests/kvm/scripts/virtio_console_guest.py b/client/tests/kvm/scripts/virtio_console_guest.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..c407231aab3523ebf00bfa51beed08e9a99c89ba |
--- /dev/null |
+++ b/client/tests/kvm/scripts/virtio_console_guest.py |
@@ -0,0 +1,715 @@ |
+#!/usr/bin/python |
+# -*- coding: utf-8 -*- |
+""" |
+Auxiliary script used to send data between ports on guests. |
+ |
+@copyright: 2010 Red Hat, Inc. |
+@author: Jiri Zupka (jzupka@redhat.com) |
+@author: Lukas Doktor (ldoktor@redhat.com) |
+""" |
+import threading |
+from threading import Thread |
+import os, select, re, random, sys, array |
+import fcntl, traceback, signal |
+ |
+DEBUGPATH = "/sys/kernel/debug" |
+SYSFSPATH = "/sys/class/virtio-ports/" |
+ |
+exiting = False |
+ |
+class VirtioGuest: |
+ """ |
+ Test tools of virtio_ports. |
+ """ |
+ LOOP_NONE = 0 |
+ LOOP_POLL = 1 |
+ LOOP_SELECT = 2 |
+ |
+ def __init__(self): |
+ self.files = {} |
+ self.exit_thread = threading.Event() |
+ self.threads = [] |
+ self.ports = {} |
+ self.poll_fds = {} |
+ self.catch_signal = None |
+ self.use_config = threading.Event() |
+ |
+ |
+ def _readfile(self, name): |
+ """ |
+ 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: |
+ open_db_file = "%s/virtio-ports/%s" % (DEBUGPATH, name) |
+ f = open(open_db_file, 'r') |
+ port = {} |
+ file = [] |
+ for line in iter(f): |
+ file.append(line) |
+ try: |
+ for line in file: |
+ 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"])) |
+ except AttributeError: |
+ print ("In file " + open_db_file + |
+ " are bad data\n"+ "".join(file).strip()) |
+ print ("FAIL: Fail file data.") |
+ return |
+ |
+ ports[port['name']] = port |
+ f.close() |
+ |
+ return ports |
+ |
+ |
+ def init(self, in_files): |
+ """ |
+ Init and check port properties. |
+ """ |
+ self.ports = self._get_port_status() |
+ |
+ if self.ports == None: |
+ return |
+ 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): |
+ """ |
+ Thread that 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, name="Switch") |
+ |
+ 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) |
+ |
+ 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(): |
+ 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 == VirtioGuest.LOOP_POLL): |
+ self._poll_mode() |
+ elif (self.method == VirtioGuest.LOOP_SELECT): |
+ self._select_mode() |
+ else: |
+ self._none_mode() |
+ |
+ |
+ class Sender(Thread): |
+ """ |
+ Creates a thread which sends random blocks of data to dst port. |
+ """ |
+ def __init__(self, port, event, length): |
+ """ |
+ @param port: Destination port |
+ @param length: Length of the random data block |
+ """ |
+ Thread.__init__(self, name="Sender") |
+ 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, inst: |
+ print "FAIL: Failed to open file %s" % (name) |
+ raise inst |
+ return f |
+ |
+ @staticmethod |
+ def pollmask_to_str(mask): |
+ """ |
+ Conver pool mast to string |
+ |
+ @param mask: poll return mask |
+ """ |
+ str = "" |
+ if (mask & select.POLLIN): |
+ str += "IN " |
+ if (mask & select.POLLPRI): |
+ str += "PRI IN " |
+ if (mask & select.POLLOUT): |
+ str += "OUT " |
+ if (mask & select.POLLERR): |
+ str += "ERR " |
+ if (mask & select.POLLHUP): |
+ str += "HUP " |
+ if (mask & select.POLLMSG): |
+ str += "MSG " |
+ return str |
+ |
+ |
+ def poll(self, port, expected, timeout=500): |
+ """ |
+ 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) |
+ |
+ maskstr = VirtioGuest.pollmask_to_str(mask[0][1]) |
+ if (mask[0][1] & expected) == expected: |
+ print "PASS: Events: " + maskstr |
+ else: |
+ emaskstr = VirtioGuest.pollmask_to_str(expected) |
+ print "FAIL: Events: " + maskstr + " Expected: " + emaskstr |
+ |
+ |
+ def lseek(self, port, pos, how): |
+ """ |
+ Use lseek on the device. The device is unseekable so PASS is returned |
+ when lseek command fails and vice versa. |
+ |
+ @param port: Name of the port |
+ @param pos: Offset |
+ @param how: Relativ offset os.SEEK_{SET,CUR,END} |
+ """ |
+ fd = self._open([port])[0] |
+ |
+ try: |
+ os.lseek(fd, pos, how) |
+ except Exception, inst: |
+ if inst.errno == 29: |
+ print "PASS: the lseek failed as expected" |
+ else: |
+ print inst |
+ print "FAIL: unknown error" |
+ else: |
+ print "FAIL: the lseek unexpectedly passed" |
+ |
+ |
+ def blocking(self, port, mode=False): |
+ """ |
+ Set port function mode blocking/nonblocking |
+ |
+ @param port: port to set mode |
+ @param mode: False to set nonblock mode, True for block mode |
+ """ |
+ fd = self._open([port])[0] |
+ |
+ 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, inst: |
+ print "FAIL: Setting (non)blocking mode: " + str(inst) |
+ return |
+ |
+ if mode: |
+ print "PASS: set to blocking mode" |
+ else: |
+ print "PASS: set to nonblocking mode" |
+ |
+ |
+ def __call__(self, sig, frame): |
+ """ |
+ Call function. Used for signal handle. |
+ """ |
+ if (sig == signal.SIGIO): |
+ self.sigio_handler(sig, frame) |
+ |
+ |
+ def sigio_handler(self, sig, frame): |
+ """ |
+ Handler for sigio operation. |
+ |
+ @param sig: signal which call handler. |
+ @param frame: frame of caller |
+ """ |
+ if self.poll_fds: |
+ p = select.poll() |
+ map(p.register, self.poll_fds.keys()) |
+ |
+ masks = p.poll(1) |
+ print masks |
+ for mask in masks: |
+ self.poll_fds[mask[0]][1] |= mask[1] |
+ |
+ |
+ def get_sigio_poll_return(self, port): |
+ """ |
+ Return PASS, FAIL and poll walue in string format. |
+ |
+ @param port: Port to check poll information. |
+ """ |
+ fd = self._open([port])[0] |
+ |
+ maskstr = VirtioGuest.pollmask_to_str(self.poll_fds[fd][1]) |
+ if (self.poll_fds[fd][0] ^ self.poll_fds[fd][1]): |
+ emaskstr = VirtioGuest.pollmask_to_str(self.poll_fds[fd][0]) |
+ print "FAIL: Events: " + maskstr + " Expected: " + emaskstr |
+ else: |
+ print "PASS: Events: " + maskstr |
+ self.poll_fds[fd][1] = 0 |
+ |
+ |
+ def set_pool_want_return(self, port, poll_value): |
+ """ |
+ Set value to static variable. |
+ |
+ @param port: Port which should be set excepted mask |
+ @param poll_value: Value to check sigio signal. |
+ """ |
+ fd = self._open([port])[0] |
+ self.poll_fds[fd] = [poll_value, 0] |
+ print "PASS: Events: " + VirtioGuest.pollmask_to_str(poll_value) |
+ |
+ |
+ def catching_signal(self): |
+ """ |
+ return: True if should set catch signal, False if ignore signal and |
+ none when configuration is not changed. |
+ """ |
+ ret = self.catch_signal |
+ self.catch_signal = None |
+ return ret |
+ |
+ |
+ def async(self, port, mode=True, exp_val = 0): |
+ """ |
+ Set port function mode async/sync. |
+ |
+ @param port: port which should be pooled. |
+ @param mode: False to set sync mode, True for sync mode. |
+ @param exp_val: Value which should be pooled. |
+ """ |
+ fd = self._open([port])[0] |
+ |
+ try: |
+ fcntl.fcntl(fd, fcntl.F_SETOWN, os.getpid()) |
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL) |
+ |
+ self.use_config.clear() |
+ if mode: |
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_ASYNC) |
+ self.poll_fds[fd] = [exp_val, 0] |
+ self.catch_signal = True |
+ else: |
+ del self.poll_fds[fd] |
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_ASYNC) |
+ self.catch_signal = False |
+ |
+ os.kill(os.getpid(), signal.SIGUSR1) |
+ self.use_config.wait() |
+ |
+ except Exception, inst: |
+ print "FAIL: Setting (a)sync mode: " + str(inst) |
+ return |
+ |
+ if mode: |
+ print "PASS: Set to async mode" |
+ else: |
+ print "PASS: Set to sync mode" |
+ |
+ |
+ def close(self, file): |
+ """ |
+ 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] |
+ if descriptor != None: |
+ try: |
+ os.close(descriptor) |
+ except Exception, inst: |
+ print "FAIL: Closing the file: " + str(inst) |
+ return |
+ print "PASS: Close" |
+ |
+ |
+ def open(self, in_file): |
+ """ |
+ Direct open devices. |
+ |
+ @param in_file: Array of files. |
+ @return: Array of descriptors. |
+ """ |
+ name = self.ports[in_file]["path"] |
+ try: |
+ self.files[name] = os.open(name, os.O_RDWR) |
+ if (self.ports[in_file]["is_console"] == "yes"): |
+ print os.system("stty -F %s raw -echo" % (name)) |
+ print "PASS: Open all filles correctly." |
+ except Exception, inst: |
+ print "%s\nFAIL: Failed open file %s" % (str(inst), name) |
+ |
+ |
+ def loopback(self, in_files, out_files, cachesize=1024, mode=LOOP_NONE): |
+ """ |
+ Start a switch thread. |
+ |
+ (There is a problem with multiple opens of a single file). |
+ |
+ @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 first. |
+ """ |
+ 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, inst: |
+ print inst |
+ if not writes: |
+ writes = 0 |
+ if mode: |
+ while (writes < length): |
+ try: |
+ writes += os.write(in_f[0], data) |
+ except Exception, 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, inst: |
+ print inst |
+ if mode: |
+ while (len(recvs) < length): |
+ try: |
+ recvs += os.read(in_f[0], buffer) |
+ except Exception, 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 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: ") + str(buf[10]) |
+ |
+ |
+def is_alive(): |
+ """ |
+ Check is only main thread is alive and if guest react. |
+ """ |
+ if threading.activeCount() == 2: |
+ print ("PASS: Guest is ok no thread alive") |
+ else: |
+ threads = "" |
+ for thread in threading.enumerate(): |
+ threads += thread.name + ", " |
+ print ("FAIL: On guest run thread. Active thread:" + threads) |
+ |
+ |
+def compile(): |
+ """ |
+ Compile virtio_console_guest.py to speed up. |
+ """ |
+ import py_compile |
+ py_compile.compile(sys.path[0] + "/virtio_console_guest.py") |
+ print "PASS: compile" |
+ sys.exit() |
+ |
+ |
+def guest_exit(): |
+ global exiting |
+ exiting = True |
+ os.kill(os.getpid(), signal.SIGUSR1) |
+ |
+ |
+def worker(virt): |
+ """ |
+ Worker thread (infinite) loop of virtio_guest. |
+ """ |
+ global exiting |
+ print "PASS: Start" |
+ |
+ while not exiting: |
+ str = raw_input() |
+ 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)) |
+ |
+ |
+def sigusr_handler(sig, frame): |
+ pass |
+ |
+ |
+def main(): |
+ """ |
+ Main function with infinite loop to catch signal from system. |
+ """ |
+ if (len(sys.argv) > 1) and (sys.argv[1] == "-c"): |
+ compile() |
+ |
+ global exiting |
+ virt = VirtioGuest() |
+ slave = Thread(target=worker, args=(virt, )) |
+ slave.start() |
+ signal.signal(signal.SIGUSR1, sigusr_handler) |
+ while not exiting: |
+ signal.pause() |
+ catch = virt.catching_signal() |
+ if catch: |
+ signal.signal(signal.SIGIO, virt) |
+ elif catch == False: |
+ signal.signal(signal.SIGIO, signal.SIG_DFL) |
+ if (catch != None): |
+ virt.use_config.set() |
+ print "PASS: guest_exit" |
+ |
+ |
+if __name__ == "__main__": |
+ main() |