blob: 8f0bb9f522ece7d6dd2a0fc22e703f04c6163256 [file] [log] [blame]
#!/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, stat
import fcntl, traceback, signal, time
DEBUGPATH = "/sys/kernel/debug"
SYSFSPATH = "/sys/class/virtio-ports/"
DEVPATH = "/dev/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, in_files=None):
"""
Get info about ports from kernel debugfs.
@param in_files: Array of input files.
@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))
if in_files is not None:
dev_names = os.listdir('/dev')
rep = re.compile(r"vport[0-9]p[0-9]+")
dev_names = filter(lambda x: rep.match(x) is not None, dev_names)
if len(dev_names) != len(in_files):
print ("FAIL: Not all ports were successfully initialized "
"in /dev, only %d from %d." % (len(dev_names),
len(in_files)))
return
if len(viop_names) != len(in_files):
print ("FAIL: Not all ports were successfuly initialized "
"in debugfs, only %d from %d." % (len(viop_names),
len(in_files)))
return
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 does not match "
"\n%s - %s\n%s - %s" %
(info_name , port_name,
"%s/virtio-ports/%s" % (DEBUGPATH, name),
port["name"]))
dev_ppath = DEVPATH + port_name
if not os.path.exists(dev_ppath):
print "FAIL: Symlink %s does not exist." % dev_ppath
if not os.path.realpath(dev_ppath) != "/dev/name":
print "FAIL: Symlink %s is not correct." % dev_ppath
except AttributeError:
print ("Bad data on file %s:\n%s. " %
(open_db_file, "".join(file).strip()))
print "FAIL: Bad data on file %s." % open_db_file
return
ports[port['name']] = port
f.close()
return ports
def check_zero_sym(self):
"""
Check if port /dev/vport0p0 was created.
"""
symlink = "/dev/vport0p0"
if os.path.exists(symlink):
print "PASS: Symlink %s exists." % symlink
else:
print "FAIL: Symlink %s does not exist." % symlink
def init(self, in_files):
"""
Init and check port properties.
"""
self.ports = self._get_port_status(in_files)
if self.ports is 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"
return
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 is not None:
if path in self.files.keys():
descriptor = self.files[path]
del self.files[path]
if descriptor is not 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.
@param mode: Mode of switch.
"""
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, is_static=False):
"""
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 = ""
writes = 0
if not is_static:
while len(data) < length:
data += "%c" % random.randrange(255)
try:
writes = os.write(in_f[0], data)
except Exception, inst:
print inst
else:
while len(data) < 4096:
data += "%c" % random.randrange(255)
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
def worker(virt):
"""
Worker thread (infinite) loop of virtio_guest.
"""
global exiting
print "PASS: Daemon start."
p = select.poll()
p.register(sys.stdin.fileno())
while not exiting:
d = p.poll()
if (d[0][1] == select.POLLIN):
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))
print "FAIL: Guest command exception."
elif (d[0][1] & select.POLLHUP):
time.sleep(0.5)
def sigusr_handler(sig, frame):
pass
class Daemon:
"""
Daemonize guest
"""
def __init__(self, stdin, stdout, stderr):
"""
Init daemon.
@param stdin: path to stdin file.
@param stdout: path to stdout file.
@param stderr: path to stderr file.
"""
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
@staticmethod
def is_file_open(path):
"""
Determine process which open file.
@param path: Path to file.
@return [[pid,mode], ... ].
"""
opens = []
pids = os.listdir('/proc')
for pid in sorted(pids):
try:
int(pid)
except ValueError:
continue
fd_dir = os.path.join('/proc', pid, 'fd')
try:
for file in os.listdir(fd_dir):
try:
p = os.path.join(fd_dir, file)
link = os.readlink(os.path.join(fd_dir, file))
if link == path:
mode = os.lstat(p).st_mode
opens.append([pid, mode])
except OSError:
continue
except OSError, e:
if e.errno == 2:
continue
raise
return opens
def daemonize(self):
"""
Run guest as a daemon.
"""
try:
pid = os.fork()
if pid > 0:
return False
except OSError, e:
sys.stderr.write("Daemonize failed: %s\n" % (e))
sys.exit(1)
os.chdir("/")
os.setsid()
os.umask(0)
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError, e:
sys.stderr.write("Daemonize failed: %s\n" % (e))
sys.exit(1)
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin,'r')
so = file(self.stdout,'w')
se = file(self.stderr,'w')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
return True
def start(self):
"""
Start the daemon
@return: PID of daemon.
"""
# Check for a pidfile to see if the daemon already runs
openers = self.is_file_open(self.stdout)
rundaemon = False
if len(openers) > 0:
for i in openers:
if i[1] & stat.S_IWUSR:
rundaemon = True
openers.remove(i)
if len(openers) > 0:
for i in openers:
os.kill(int(i[0]), 9)
time.sleep(0.3)
# Start the daemon
if not rundaemon:
if self.daemonize():
self.run()
def run(self):
"""
Run guest main thread
"""
global exiting
virt = VirtioGuest()
slave = Thread(target=worker, args=(virt, ))
slave.start()
signal.signal(signal.SIGUSR1, sigusr_handler)
signal.signal(signal.SIGALRM, sigusr_handler)
while not exiting:
signal.alarm(1)
signal.pause()
catch = virt.catching_signal()
if catch:
signal.signal(signal.SIGIO, virt)
elif catch is False:
signal.signal(signal.SIGIO, signal.SIG_DFL)
if catch is not None:
virt.use_config.set()
print "PASS: guest_exit"
sys.exit(0)
def main():
"""
Main function with infinite loop to catch signal from system.
"""
if (len(sys.argv) > 1) and (sys.argv[1] == "-c"):
compile()
stdin = "/tmp/guest_daemon_pi"
stdout = "/tmp/guest_daemon_po"
stderr = "/tmp/guest_daemon_pe"
for f in [stdin, stdout, stderr]:
try:
os.mkfifo(f)
except OSError, e:
if e.errno == 17:
pass
daemon = Daemon(stdin,
stdout,
stderr)
daemon.start()
d_stdin = os.open(stdin, os.O_WRONLY)
d_stdout = os.open(stdout, os.O_RDONLY)
d_stderr = os.open(stderr, os.O_RDONLY)
s_stdin = sys.stdin.fileno()
s_stdout = sys.stdout.fileno()
s_stderr = sys.stderr.fileno()
pid = filter(lambda x: x[0] != str(os.getpid()),
daemon.is_file_open(stdout))[0][0]
print "PASS: Start"
while 1:
ret = select.select([d_stderr,
d_stdout,
s_stdin],
[], [], 1.0)
if s_stdin in ret[0]:
os.write(d_stdin,os.read(s_stdin, 1))
if d_stdout in ret[0]:
os.write(s_stdout,os.read(d_stdout, 1024))
if d_stderr in ret[0]:
os.write(s_stderr,os.read(d_stderr, 1024))
if not os.path.exists("/proc/" + pid):
sys.exit(0)
os.close(d_stdin)
os.close(d_stdout)
os.close(d_stderr)
if __name__ == "__main__":
main()