blob: 07c502a84db03e863fc6aa61fdaa169d2adec566 [file] [log] [blame]
"""
virtio_console test
@copyright: 2010 Red Hat, Inc.
"""
import array, logging, os, random, re, select, shutil, socket, sys, tempfile
import threading, time, traceback
from collections import deque
from threading import Thread
from autotest_lib.client.common_lib import error
from autotest_lib.client.bin import utils
from autotest_lib.client.virt import virt_utils, virt_test_utils, kvm_monitor
from autotest_lib.client.virt import virt_env_process, aexpect
def run_virtio_console(test, params, env):
"""
KVM virtio_console test
1) Starts VMs with the specified number of virtio console devices
2) Start smoke test
3) Start loopback test
4) Start performance test
This test uses an auxiliary script, virtio_console_guest.py, that is copied
to guests. This script has functions to send and write data to virtio
console ports. Details of each test can be found on the docstrings for the
test_* functions.
@param test: kvm test object
@param params: Dictionary with the test parameters
@param env: Dictionary with test environment
"""
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 get_cleanup_func(self):
"""
Returns the tupple of cleanup_func and clenaup_args
@return: Tupple of self.cleanup_func and self.cleanup_args
"""
return (self.cleanup_func, self.cleanup_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.
@param cleanup: If true call cleanup function after crash of test.
@return: Return what returned executed subtest.
@raise TestError: If collapse of test is fatal raise forward
exception from subtest.
"""
if args is None:
args = []
res = [None, function.func_name, args]
try:
logging.info("Starting test %s" % function.func_name)
ret = function(*args)
res[0] = True
logging.info(self.result_to_string(res))
self.result.append(res)
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
res[0] = False
logging.info(self.result_to_string(res))
self.result.append(res)
self.failed += 1
if cleanup:
try:
self.cleanup_func(*self.cleanup_args)
except:
error.TestFail("Cleanup function crashed as well")
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 headline(self, msg):
"""
Add headline to result output.
@param msg: Test of headline
"""
self.result.append([msg])
def _gen_res(self, format_func):
"""
Format result with formatting function
@param format_func: Func for formating result.
"""
result = ""
for res in self.result:
if (len(res) == 3):
result += format_func(res) + "\n"
else:
result += res[0] + "\n"
return result
def get_full_text_result(self):
"""
@return string with text form of result
"""
return self._gen_res(lambda str: self.result_to_string_debug(str))
def get_text_result(self):
"""
@return string with text form of result
"""
return self._gen_res(lambda str: self.result_to_string(str))
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.
"""
attempt = 11
while attempt > 0:
try:
self.sock = socket.socket(socket.AF_UNIX,
socket.SOCK_STREAM)
self.sock.connect(self.path)
self.sock.setsockopt(1,socket.SO_SNDBUF, 2048)
self.is_open = True
return
except Exception, inst:
attempt -= 1
time.sleep(1)
raise error.TestFail("Can't open the %s sock" % self.name)
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.
"""
def __init__(self, port, data, event, quiet=False):
"""
@param port: Destination port.
@param data: The data intend to be send in a loop.
@param event: Exit event.
@param quiet: If true don't raise event when crash.
"""
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 is too long, using only first %d bytes",
len(data))
self.data = data
self.exitevent = event
self.idx = 0
self.quiet = quiet
def run(self):
logging.debug("ThSend %s: run", self.getName())
try:
while not self.exitevent.isSet():
self.idx += self.port.send(self.data)
logging.debug("ThSend %s: exit(%d)", self.getName(),
self.idx)
except Exception, ints:
if not self.quiet:
raise ints
logging.debug(ints)
class ThSendCheck(Thread):
"""
Random data sender thread.
"""
def __init__(self, port, event, queues, blocklen=1024):
"""
@param port: Destination port
@param event: Exit event
@param queues: Queues for the control data (FIFOs)
@param blocklen: Block length
"""
Thread.__init__(self)
self.port = port
self.queues = queues
# FIXME: socket.send(data>>127998) without read blocks thread
if blocklen > 102400:
blocklen = 102400
logging.error("Data is too long, using blocklen = %d",
blocklen)
self.blocklen = blocklen
self.exitevent = event
self.idx = 0
def run(self):
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
# much data is sent 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.sock], [], 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:
try:
idx = self.port.sock.send(buf)
except Exception, inst:
# Broken pipe
if inst.errno == 32:
logging.debug("ThSendCheck %s: Broken pipe "
"(migration?), reconnecting",
self.getName())
attempt = 10
while (attempt > 1
and not self.exitevent.isSet()):
self.port.is_open = False
self.port.open()
try:
idx = self.port.sock.send(buf)
except:
attempt += 1
time.sleep(10)
else:
attempt = 0
buf = buf[idx:]
self.idx += idx
logging.debug("ThSendCheck %s: exit(%d)", self.getName(),
self.idx)
if too_much_data:
logging.error("ThSendCheck: working around the 'too_much_data'"
"bug")
class ThRecv(Thread):
"""
Recieves data and throws it away.
"""
def __init__(self, port, event, blocklen=1024, quiet=False):
"""
@param port: Data source port.
@param event: Exit event.
@param blocklen: Block length.
@param quiet: If true don't raise event when crash.
"""
Thread.__init__(self)
self.port = port
self._port_timeout = self.port.gettimeout()
self.port.settimeout(0.1)
self.exitevent = event
self.blocklen = blocklen
self.idx = 0
self.quiet = quiet
def run(self):
logging.debug("ThRecv %s: run", self.getName())
try:
while not self.exitevent.isSet():
# 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("ThRecv %s: exit(%d)", self.getName(), self.idx)
except Exception, ints:
if not self.quiet:
raise ints
logging.debug(ints)
class ThRecvCheck(Thread):
"""
Random data receiver/checker thread.
"""
def __init__(self, port, buffer, event, blocklen=1024, sendlen=0):
"""
@param port: Source port.
@param buffer: Control data buffer (FIFO).
@param length: Amount of data we want to receive.
@param blocklen: Block length.
@param sendlen: Block length of the send function (on guest)
"""
Thread.__init__(self)
self.port = port
self.buffer = buffer
self.exitevent = event
self.blocklen = blocklen
self.idx = 0
self.sendlen = sendlen + 1 # >=
def run(self):
logging.debug("ThRecvCheck %s: run", self.getName())
attempt = 10
sendidx = -1
minsendidx = self.sendlen
while not self.exitevent.isSet():
ret = select.select([self.port.sock], [], [], 1.0)
if ret[0] and (not self.exitevent.isSet()):
buf = self.port.sock.recv(self.blocklen)
if buf:
# Compare the received data with the control data
for ch in buf:
ch_ = self.buffer.popleft()
if ch == ch_:
self.idx += 1
else:
# TODO BUG: data from the socket on host can
# be lost during migration
while ch != ch_:
if sendidx > 0:
sendidx -= 1
ch_ = self.buffer.popleft()
else:
self.exitevent.set()
logging.error("ThRecvCheck %s: "
"Failed to recv %dth "
"character",
self.getName(), self.idx)
logging.error("ThRecvCheck %s: "
"%s != %s",
self.getName(),
repr(ch), repr(ch_))
logging.error("ThRecvCheck %s: "
"Recv = %s",
self.getName(), repr(buf))
# sender might change the buffer :-(
time.sleep(1)
ch_ = ""
for buf in self.buffer:
ch_ += buf
ch_ += ' '
logging.error("ThRecvCheck %s: "
"Queue = %s",
self.getName(), repr(ch_))
logging.info("ThRecvCheck %s: "
"MaxSendIDX = %d",
self.getName(),
(self.sendlen - sendidx))
raise error.TestFail("ThRecvCheck %s: "
"incorrect data",
self.getName())
attempt = 10
else: # ! buf
# Broken socket
if attempt > 0:
attempt -= 1
logging.debug("ThRecvCheck %s: Broken pipe "
"(migration?), reconnecting. ",
self.getName())
# TODO BUG: data from the socket on host can be lost
if sendidx >= 0:
minsendidx = min(minsendidx, sendidx)
logging.debug("ThRecvCheck %s: Previous data "
"loss was %d.",
self.getName(),
(self.sendlen - sendidx))
sendidx = self.sendlen
self.port.is_open = False
self.port.open()
if sendidx >= 0:
minsendidx = min(minsendidx, sendidx)
if (self.sendlen - minsendidx):
logging.error("ThRecvCheck %s: Data loss occured during socket"
"reconnection. Maximal loss was %d per one "
"migration.", self.getName(),
(self.sendlen - minsendidx))
logging.debug("ThRecvCheck %s: exit(%d)", self.getName(),
self.idx)
def process_stats(stats, scale=1.0):
"""
Process and print the stats.
@param stats: List of measured data.
"""
if not stats:
return None
for i in range((len(stats) - 1), 0, -1):
stats[i] = stats[i] - stats[i - 1]
stats[i] /= scale
stats[0] /= scale
stats = sorted(stats)
return stats
def _init_guest(vm, timeout=10):
"""
Execute virtio_console_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("compile virtio_console_guest.py on guest %s",
vm[0].name)
(match, data) = _on_guest("python -OO /tmp/virtio_console_guest.py -c"
"&& echo -n 'PASS: Compile virtio_guest finished' ||"
"echo -n 'FAIL: Compile virtio_guest failed'",
vm, timeout)
if match != 0:
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_console_guest.py on guest %s",
vm[0].name)
vm[1].sendline()
(match, data) = _on_guest("python /tmp/virtio_console_guest.pyo &&"
"echo -n 'PASS: virtio_guest finished' ||"
"echo -n 'FAIL: virtio_guest failed'",
vm, timeout)
if match != 0:
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 init_guest(vm, consoles):
"""
Prepares guest, executes virtio_console_guest.py and initializes test.
@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 _search_kernel_crashlog(vm_port, timeout=2):
"""
Find kernel crash message.
@param vm_port : Guest output port.
@param timeout: Timeout used to verify expected output.
@return: Kernel crash log or None.
"""
data = vm_port.read_nonblocking()
match = re.search("BUG:", data, re.MULTILINE)
if match is None:
return None
match = re.search(r"BUG:.*---\[ end trace .* \]---",
data, re.DOTALL |re.MULTILINE)
if match is None:
data += vm_port.read_until_last_line_matches(
["---\[ end trace .* \]---"],timeout)
match = re.search(r"(BUG:.*---\[ end trace .* \]---)",
data, re.DOTALL |re.MULTILINE)
return match.group(0)
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 timeout: Timeout used to verify expected output.
@return: Tuple (match index, data, kernel_crash)
"""
logging.debug("Executing '%s' on virtio_console_guest.py loop," +
" vm: %s, timeout: %s", command, vm[0].name, timeout)
vm[1].sendline(command)
try:
(match, data) = vm[1].read_until_last_line_matches(["PASS:",
"FAIL:"],
timeout)
except aexpect.ExpectError, e:
match = None
data = "Cmd process timeout. Data in console: " + e.output
kcrash_data = _search_kernel_crashlog(vm[3])
if kcrash_data is not None:
logging.error(kcrash_data)
vm[4] = True
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"
" virtio_console_guest.py, "
"vm: %s, output:\n%s" %
(command, vm[0].name, data))
return (match, data)
def _guest_exit_threads(vm, send_pts, recv_pts):
"""
Safely executes on_guest("virt.exit_threads()") using workaround of
the stuck 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 is None:
logging.debug("Workaround the stuck thread on guest")
# Thread is stucked in read/write
for send_pt in send_pts:
send_pt.sock.sendall(".")
elif match != 0:
# Something else
raise error.TestFail("Unexpected fail\nMatch: %s\nData:\n%s"
% (match, tmp))
# Read-out all remaining data
for recv_pt in recv_pts:
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)
def _vm_create(no_console=3, no_serialport=3, spread=True):
"""
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.
@return: Tuple with (guest information, consoles information)
guest informations = [vm, session, tmp_dir, kcrash]
consoles informations = [consoles[], serialports[]]
"""
consoles = []
serialports = []
tmp_dir = tempfile.mkdtemp(prefix="virtio-console-", dir="/tmp/")
params['extra_params'] = standard_extra_params
if not spread:
pci = "virtio-serial-pci0"
params['extra_params'] += (" -device virtio-serial-pci,id="
+ pci)
pci += ".0"
for i in range(0, no_console):
# Spread consoles between multiple PCI devices (2 per a dev)
if not i % 2 and spread:
pci = "virtio-serial-pci%d" % (i / 2)
params['extra_params'] += (" -device virtio-serial-pci,id="
+ pci)
pci += ".0"
params['extra_params'] += (" -chardev socket,path=%s/%d,id=vc%d,"
"server,nowait" % (tmp_dir, i, i))
params['extra_params'] += (" -device virtconsole,chardev=vc%d,"
"name=console-%d,id=console-%d,bus=%s"
% (i, i, i, pci))
for i in range(no_console, no_console + no_serialport):
# Spread serial ports between multiple PCI devices (2 per each dev)
if not i % 2 and spread:
pci = "virtio-serial-pci%d" % (i / 2)
params['extra_params'] += (" -device virtio-serial-pci,id="
+ pci)
pci += ".0"
params['extra_params'] += (" -chardev socket,path=%s/%d,id=vs%d,"
"server,nowait" % (tmp_dir, i, i))
params['extra_params'] += (" -device virtserialport,chardev=vs%d,"
"name=serialport-%d,id=serialport-%d,"
"bus=%s" % (i, i, i, pci))
(vm, session, sserial) = _restore_vm()
# connect the sockets
for i in range(0, no_console):
consoles.append(Port(None ,"console-%d" % i,
"yes", "%s/%d" % (tmp_dir, i)))
for i in range(no_console, no_console + no_serialport):
serialports.append(Port(None ,"serialport-%d" % i,
"no", "%s/%d" % (tmp_dir, i)))
kcrash = False
return [vm, session, tmp_dir, sserial, kcrash], [consoles, serialports]
def _restore_vm():
"""
Restore old virtual machine when VM is destroyed.
"""
logging.debug("Booting guest %s", params.get("main_vm"))
virt_env_process.preprocess_vm(test, params, env,
params.get("main_vm"))
vm = env.get_vm(params.get("main_vm"))
kernel_bug = None
try:
session = virt_test_utils.wait_for_login(vm, 0,
float(params.get("boot_timeout", 100)),
0, 2)
except (error.TestFail):
kernel_bug = _search_kernel_crashlog(vm.serial_console, 10)
if kernel_bug is not None:
logging.error(kernel_bug)
raise
kernel_bug = _search_kernel_crashlog(vm.serial_console, 10)
if kernel_bug is not None:
logging.error(kernel_bug)
sserial = virt_test_utils.wait_for_login(vm, 0,
float(params.get("boot_timeout", 20)),
0, 2, serial=True)
return [vm, session, sserial]
def topen(vm, port):
"""
Open virtioconsole port.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port identifier.
"""
on_guest("virt.open('%s')" % (port.name), vm, 10)
port.open()
def tcheck_zero_sym(vm):
"""
Check if port /dev/vport0p0 was created.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
"""
on_guest("virt.check_zero_sym()", vm, 10)
def tmulti_open(vm, port):
"""
Multiopen virtioconsole port.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port identifier.
"""
on_guest("virt.close('%s')" % (port.name), vm, 10)
on_guest("virt.open('%s')" % (port.name), vm, 10)
(match, data) = _on_guest("virt.open('%s')" % (port.name), vm, 10)
# Console is permitted to open the device multiple times
if port.port_type == "yes": #is console?
if match != 0: #Multiopen not pass
raise error.TestFail("Unexpected fail of opening the console"
" device for the 2nd time.\n%s" % data)
else:
if match != 1: #Multiopen not fail:
raise error.TestFail("Unexpetded pass of opening the"
" serialport device for the 2nd time.")
elif not "[Errno 24]" in data:
raise error.TestFail("Multiple opening fail but with another"
" exception %s" % data)
port.open()
def tclose(vm, port):
"""
Close socket.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port to open.
"""
on_guest("virt.close('%s')" % (port.name), vm, 10)
port.close()
def tpolling(vm, port):
"""
Test try polling function.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@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, 10)
# 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, 10)
# Poll (HUP)
on_guest("virt.recv('%s', 4, 1024, False)" % (port.name), vm, 10)
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 tsigio(vm, port):
"""
Test try sigio function.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port used in test.
"""
if port.is_open:
port.close()
# Enable sigio on specific port
on_guest("virt.async('%s', True, 0)" %
(port.name) , vm, 10)
on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 10)
#Test sigio when port open
on_guest("virt.set_pool_want_return('%s', select.POLLOUT)" %
(port.name), vm, 10)
port.open()
match = _on_guest("virt.get_sigio_poll_return('%s')" %
(port.name) , vm, 10)[0]
if match == 1:
raise error.TestFail("Problem with HUP on console port.")
#Test sigio when port receive data
on_guest("virt.set_pool_want_return('%s', select.POLLOUT |"
" select.POLLIN)" % (port.name), vm, 10)
port.sock.sendall("0123456789")
on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 10)
#Test sigio port close event
on_guest("virt.set_pool_want_return('%s', select.POLLHUP |"
" select.POLLIN)" % (port.name), vm, 10)
port.close()
on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 10)
#Test sigio port open event and persistence of written data on port.
on_guest("virt.set_pool_want_return('%s', select.POLLOUT |"
" select.POLLIN)" % (port.name), vm, 10)
port.open()
on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 10)
#Test event when erase data.
on_guest("virt.clean_port('%s')" % (port.name), vm, 10)
port.close()
on_guest("virt.set_pool_want_return('%s', select.POLLOUT)"
% (port.name), vm, 10)
port.open()
on_guest("virt.get_sigio_poll_return('%s')" % (port.name) , vm, 10)
# Disable sigio on specific port
on_guest("virt.async('%s', False, 0)" %
(port.name) , vm, 10)
def tlseek(vm, port):
"""
Tests the correct handling of lseek (expected fail)
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port used in test.
"""
# The virt.lseek returns PASS when the seek fails
on_guest("virt.lseek('%s', 0, 0)" % (port.name), vm, 10)
def trw_host_offline(vm, port):
"""
Guest read/write from host when host is disconnected.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port used in test.
"""
if port.is_open:
port.close()
on_guest("virt.recv('%s', 0, 1024, False)" % port.name, vm, 10)
match, tmp = _on_guest("virt.send('%s', 10, True)" % port.name,
vm, 10)
if match is not None:
raise error.TestFail("Write on guest while host disconnected "
"didn't time 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, 10)
def trw_host_offline_big_data(vm, port):
"""
Guest read/write from host when host is disconnected.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port used in test.
"""
if port.is_open:
port.close()
port.clean_port()
port.close()
on_guest("virt.clean_port('%s'),1024" % port.name, vm, 10)
match, tmp = _on_guest("virt.send('%s', (1024**3)*3, True, "
"is_static=True)" % port.name, vm, 30)
if match is None:
raise error.TestFail("Write on guest while host disconnected "
"didn't time out.\nOutput:\n%s"
% tmp)
time.sleep(20)
port.open()
rlen = 0
while rlen < (1024**3*3):
ret = select.select([port.sock], [], [], 10.0)
if (ret[0] != []):
rlen += len(port.sock.recv(((4096))))
elif rlen != (1024**3*3):
raise error.TestFail("Not all data was received,"
"only %d from %d" % (rlen, 1024**3*3))
on_guest("print('PASS: nothing')", vm, 10)
def trw_notconnect_guest(vm, port, consoles):
"""
Host send data to guest port and guest not read any data from port.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port used in test.
"""
vm[0].destroy(gracefully = False)
(vm[0], vm[1], vm[3]) = _restore_vm()
if not port.is_open:
port.open()
else:
port.close()
port.open()
port.sock.settimeout(20.0)
loads = utils.SystemLoad([(os.getpid(), 'autotest'),
(vm[0].get_pid(), 'VM'), 0])
loads.start()
try:
sent1 = 0
for i in range(1000000):
sent1 += port.sock.send("a")
except socket.timeout:
logging.info("Data sending to closed port timed out.")
logging.info("Bytes sent to client: %d" % (sent1))
logging.info("\n" + loads.get_cpu_status_string()[:-1])
on_guest('echo -n "PASS:"', vm, 10)
logging.info("Open and then close port %s" % (port.name))
init_guest(vm, consoles)
# Test of live and open and close port again
_clean_ports(vm, consoles)
on_guest("virt.close('%s')" % (port.name), vm, 10)
# With serialport it is a different behavior
on_guest("guest_exit()", vm, 10)
port.sock.settimeout(20.0)
loads.start()
try:
sent2 = 0
for i in range(40000):
sent2 = port.sock.send("a")
except socket.timeout:
logging.info("Data sending to closed port timed out.")
logging.info("Bytes sent to client: %d" % (sent2))
logging.info("\n" + loads.get_cpu_status_string()[:-1])
loads.stop()
if (sent1 != sent2):
logging.warning("Inconsistent behavior: First sent %d bytes and "
"second sent %d bytes" % (sent1, sent2))
port.sock.settimeout(None)
(vm[0], vm[1], vm[3]) = _restore_vm()
init_guest(vm, consoles)
_clean_ports(vm, consoles)
def trw_blocking_mode(vm, port):
"""
Guest read\write data in blocking mode.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param port: Port used in test.
"""
# Blocking mode
if not port.is_open:
port.open()
on_guest("virt.blocking('%s', True)" % port.name, vm, 10)
# Recv should timed out
match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" %
port.name, vm, 10)
if match == 0:
raise error.TestFail("Received data even when none was sent\n"
"Data:\n%s" % tmp)
elif match is not 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, 10)
def trw_nonblocking_mode(vm, port):
"""
Guest read\write data in nonblocking mode.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@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, 10)
# Recv should return FAIL with 0 received data
match, tmp = _on_guest("virt.recv('%s', 10, 1024, False)" %
port.name, vm, 10)
if match == 0:
raise error.TestFail("Received data even when none was sent\n"
"Data:\n%s" % tmp)
elif match is 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, 10)
def tbasic_loopback(vm, send_port, recv_port, data="Smoke test data"):
"""
Easy loop back test with loop over only two ports.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@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, 10)
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 trmmod(vm, consoles):
"""
Remove and load virtio_console kernel modules.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
"""
on_guest("guest_exit()", vm, 5)
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)
try:
cname = consoles[0][0].name
except (IndexError):
cname = consoles[1][0].name
on_guest("virt.clean_port('%s'),1024" % cname, vm, 2)
def tmax_serial_ports(vm, consoles):
"""
Test maximum count of ports in guest machine.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
"""
logging.debug("Count of serial ports: 30")
vm[0].destroy(gracefully = False)
(vm, consoles) = _vm_create(0, 30, False)
try:
init_guest(vm, consoles)
except error.TestFail, ints:
logging.info("Count of serial ports: 30")
raise ints
clean_reload_vm(vm, consoles, expected=True)
def tmax_console_ports(vm, consoles):
"""
Test maximum count of ports in guest machine.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
"""
logging.debug("Count of console ports: 30")
vm[0].destroy(gracefully = False)
(vm, consoles) = _vm_create(30, 0, False)
try:
init_guest(vm, consoles)
except error.TestFail, ints:
logging.info("Count of console ports: 30")
raise ints
clean_reload_vm(vm, consoles, expected=True)
def tmax_mix_serial_conosle_port(vm, consoles):
"""
Test maximim count of ports in guest machine.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
"""
logging.debug("Count of ports (serial+console): 30")
vm[0].destroy(gracefully = False)
(vm, consoles) = _vm_create(15, 15, False)
try:
init_guest(vm, consoles)
except error.TestFail, ints:
logging.info("Count of ports (serial+console): 30")
raise ints
clean_reload_vm(vm, consoles, expected=True)
def tshutdown(vm, consoles):
"""
Try to gently shutdown the machine. Virtio_console shouldn't block this.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
"""
ports = []
for console in consoles[0]:
ports.append(console)
for console in consoles[1]:
ports.append(console)
for port in ports:
port.open()
# If more than one, send data on the other ports
for port in ports[1:]:
on_guest("virt.close('%s')" % (port.name), vm, 2)
on_guest("virt.open('%s')" % (port.name), vm, 2)
try:
os.system("dd if=/dev/random of='%s' bs=4096 &>/dev/null &"
% port.path)
except:
pass
# Just start sending, it won't finish anyway...
_on_guest("virt.send('%s', 1024**3, True, is_static=True)"
% ports[0].name, vm, 1)
# Let the computer transfer some bytes :-)
time.sleep(2)
# Power off the computer
vm[0].destroy(gracefully=True)
clean_reload_vm(vm, consoles, expected=True)
def __tmigrate(vm, consoles, parms, offline=True):
"""
An actual migration test. It creates loopback on guest from first port
to all remaining ports. Than it sends and validates the data.
During this it tries to migrate the vm n-times.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param parms: [media, no_migration, send-, recv-, loopback-buffer_len]
"""
# PREPARE
send_pt = consoles[parms[0]][0]
recv_pts = consoles[parms[0]][1:]
# TODO BUG: sendlen = max allowed data to be lost per one migration
# TODO BUG: using SMP the data loss is upto 4 buffers
# 2048 = char. dev. socket size, parms[2] = host->guest send buffer size
sendlen = 2*2*max(2048, parms[2])
if not offline: # TODO BUG: online migration causes more loses
# TODO: Online migration lose n*buffer. n depends on the console
# troughput. FIX or analyse it's cause.
sendlen = 1000 * sendlen
for p in recv_pts:
if not p.is_open:
p.open()
if not send_pt.is_open:
send_pt.open()
threads = []
queues = []
verified = []
for i in range(0, len(recv_pts)):
queues.append(deque())
verified.append(0)
tmp = "'%s'" % recv_pts[0].name
for recv_pt in recv_pts[1:]:
tmp += ", '%s'" % (recv_pt.name)
on_guest("virt.loopback(['%s'], [%s], %d, virt.LOOP_POLL)"
% (send_pt.name, tmp, parms[4]), vm, 10)
exit_event = threading.Event()
# TEST
thread = ThSendCheck(send_pt, exit_event, queues,
parms[2])
thread.start()
threads.append(thread)
for i in range(len(recv_pts)):
thread = ThRecvCheck(recv_pts[i], queues[i], exit_event,
parms[3], sendlen=sendlen)
thread.start()
threads.append(thread)
i=0
while i < 6:
tmp = "%d data sent; " % threads[0].idx
for thread in threads[1:]:
tmp += "%d, " % thread.idx
logging.debug("test_loopback: %s data received and verified",
tmp[:-2])
i+=1
time.sleep(2)
for j in range(parms[1]):
vm[0] = virt_test_utils.migrate(vm[0], env, 3600, "exec", 0,
offline)
if not vm[1]:
raise error.TestFail("Could not log into guest after migration")
vm[1] = virt_test_utils.wait_for_login(vm[0], 0,
float(params.get("boot_timeout", 100)),
0, 2)
# OS is sometime a bit dizzy. DL=30
_init_guest(vm, 30)
i=0
while i < 6:
tmp = "%d data sent; " % threads[0].idx
for thread in threads[1:]:
tmp += "%d, " % thread.idx
logging.debug("test_loopback: %s data received and verified",
tmp[:-2])
i+=1
time.sleep(2)
if not threads[0].is_alive():
if exit_event.isSet():
raise error.TestFail("Exit event emited, check the log for"
"send/recv thread failure.")
else:
raise error.TestFail("Send thread died unexpectedly in "
"migration %d", (j+1))
for i in range(0, len(recv_pts)):
if not threads[i+1].is_alive():
raise error.TestFail("Recv thread %d died unexpectedly in "
"migration %d", i, (j+1))
if verified[i] == threads[i+1].idx:
raise error.TestFail("No new data in %d console were "
"transfered after migration %d"
, i, (j+1))
verified[i] = threads[i+1].idx
logging.info("%d out of %d migration(s) passed" % ((j+1), parms[1]))
# TODO detect recv-thread failure and throw out whole test
# FINISH
exit_event.set()
# Send thread might fail to exit when the guest stucks
i = 30
while threads[0].is_alive():
if i <= 0:
raise error.TestFail("Send thread did not finish")
time.sleep(1)
i -= 1
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 during %d "
"migrations", tmp[:-2], parms[1])
# CLEANUP
_guest_exit_threads(vm, [send_pt], recv_pts)
del exit_event
del threads[:]
def _tmigrate(vm, consoles, parms, offline):
"""
Wrapper which parses the params for __migrate test.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param parms: test parameters, multiple recievers allowed.
'[{serialport,console}]:$no_migrations:send_buf_len:recv_buf_len:
loopback_buf_len;...'
"""
for param in parms.split(';'):
if not param:
continue
if offline:
logging.info("test_migrate_offline: params: %s", param)
else:
logging.info("test_migrate_online: params: %s", param)
param = param.split(':')
media = 1
if param[0].isalpha():
if param[0] == "console":
param[0] = 0
else:
param[0] = 1
else:
param = [0] + param
for i in range(1,5):
if not param[i].isdigit():
param[i] = 1
else:
param[i] = int(param[i])
__tmigrate(vm, consoles, param, offline=offline)
def tmigrate_offline(vm, consoles, parms):
"""
Tests whether the virtio-{console,port} are able to survive the offline
migration.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param parms: test parameters, multiple recievers allowed.
'[{serialport,console}]:$no_migrations:send_buf_len:recv_buf_len:
loopback_buf_len;...'
"""
_tmigrate(vm, consoles, parms, offline=True)
def tmigrate_online(vm, consoles, parms):
"""
Tests whether the virtio-{console,port} are able to survive the online
migration.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param parms: test parameters, multiple recievers allowed.
'[{serialport,console}]:$no_migrations:send_buf_len:recv_buf_len:
loopback_buf_len;...'
"""
_tmigrate(vm, consoles, parms, offline=False)
def _virtio_dev_create(vm, ports_name, pciid, id, console="no"):
"""
Add virtio serialport device.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param ports_name: Structure of ports.
@param pciid: Id of virtio-serial-pci device.
@param id: Id of port.
@param console: if "yes" inicialize console.
"""
port = "serialport-"
port_type = "virtserialport"
if console == "yes":
port = "console-"
port_type = "virtconsole"
port += "%d%d" % (pciid, id)
ret = vm[0].monitors[0].cmd("device_add %s,"
"bus=virtio-serial-pci%d.0,"
"id=%s,"
"name=%s"
% (port_type, pciid, port, port))
ports_name.append([ port, console])
if ret != "":
logging.error(ret)
def _virtio_dev_del(vm, ports_name, pciid, id):
"""
Del virtio serialport device.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param ports_name: Structure of ports.
@param pciid: Id of virtio-serial-pci device.
@param id: Id of port.
"""
port = filter(lambda x: x[0].endswith("-%d%d" % (pciid, id)),
ports_name)
ret = vm[0].monitors[0].cmd("device_del %s"
% (port[0][0]))
ports_name.remove(port[0])
if ret != "":
logging.error(ret)
def thotplug(vm, consoles, console="no", timeout=1):
"""
Try hotplug function of virtio-consoles ports.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles.
@param console: If "yes" inicialize console.
@param timeout: Timeout between hotplug operations.
"""
logging.info("Timeout between hotplug operations t=%fs" % timeout)
_reset_vm(vm, consoles, 1, 1)
ports_name = []
ports_name.append(['serialport-1','no'])
ports_name.append(['console-0','yes'])
logging.info("Test correct initialization of hotplug ports")
for id in range(1,5): #count of pci device
ret = vm[0].monitors[0].cmd("device_add virtio-serial-pci,"
"id=virtio-serial-pci%d" % (id))
if ret != "":
logging.error(ret)
for i in range(id*5+5): #max port 30
_virtio_dev_create(vm, ports_name, id, i, console)
time.sleep(timeout)
# Test correct initialization of hotplug ports
time.sleep(10) # Timeout for port initialization
_init_guest(vm, 10)
on_guest('virt.init(%s)' % (ports_name), vm, 10)
logging.info("Delete ports when ports are used")
# Delete ports when ports are used.
if not consoles[0][0].is_open:
consoles[0][0].open()
if not consoles[1][0].is_open:
consoles[1][0].open()
on_guest("virt.loopback(['%s'], ['%s'], 1024,"
"virt.LOOP_POLL)" % (consoles[0][0].name,
consoles[1][0].name), vm, 10)
exit_event = threading.Event()
send = ThSend(consoles[0][0].sock, "Data", exit_event, quiet = True)
recv = ThRecv(consoles[1][0].sock, exit_event, quiet = True)
send.start()
time.sleep(2)
recv.start()
# Try to delete ports under load
ret = vm[0].monitors[0].cmd("device_del serialport-1")
ret += vm[0].monitors[0].cmd("device_del console-0")
ports_name.remove(['serialport-1','no'])
ports_name.remove(['console-0','yes'])
if ret != "":
logging.error(ret)
exit_event.set()
send.join()
recv.join()
on_guest("virt.exit_threads()", vm, 10)
on_guest('guest_exit()', vm, 10)
logging.info("Trying to add maximum count of ports to one pci device")
# Try to add ports
for i in range(30): # max port 30
_virtio_dev_create(vm, ports_name, 0, i, console)
time.sleep(timeout)
_init_guest(vm, 10)
time.sleep(10)
on_guest('virt.init(%s)' % (ports_name), vm, 20)
on_guest('guest_exit()', vm, 10)
logging.info("Trying delete and add again part of ports")
# Try to delete ports
for i in range(25): # max port 30
_virtio_dev_del(vm, ports_name, 0, i)
time.sleep(timeout)
_init_guest(vm, 10)
on_guest('virt.init(%s)' % (ports_name), vm, 10)
on_guest('guest_exit()', vm, 10)
# Try to add ports
for i in range(5): # max port 30
_virtio_dev_create(vm, ports_name, 0, i, console)
time.sleep(timeout)
_init_guest(vm, 10)
on_guest('virt.init(%s)' % (ports_name), vm, 10)
on_guest('guest_exit()', vm, 10)
logging.info("Trying to add and delete one port 100 times")
# Try 100 times add and delete one port.
for i in range(100):
_virtio_dev_del(vm, ports_name, 0, 0)
time.sleep(timeout)
_virtio_dev_create(vm, ports_name, 0, 0, console)
time.sleep(timeout)
_init_guest(vm, 10)
on_guest('virt.init(%s)' % (ports_name), vm, 10)
on_guest('guest_exit()', vm, 10)
def thotplug_no_timeout(vm, consoles, console="no"):
"""
Start hotplug test without any timeout.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
@param console: If "yes" inicialize console.
"""
thotplug(vm, consoles, console, 0)
def thotplug_virtio_pci(vm, consoles):
"""
Test hotplug of virtio-serial-pci.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close before rmmod.
"""
vm[0].destroy(gracefully = False)
(vm, consoles) = _vm_create(1, 1, False)
id = 1
ret = vm[0].monitors[0].cmd("device_add virtio-serial-pci,"
"id=virtio-serial-pci%d" % (id))
time.sleep(10)
ret += vm[0].monitors[0].cmd("device_del virtio-serial-pci%d" % (id))
time.sleep(10)
ret += vm[0].monitors[0].cmd("device_add virtio-serial-pci,"
"id=virtio-serial-pci%d" % (id))
if ret != "":
logging.error(ret)
def tloopback(vm, consoles, params):
"""
Virtio console loopback subtest.
Creates loopback on the vm machine between send_pt and recv_pts
ports and sends length amount of data through this connection.
It validates the correctness of the data sent.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@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;...'
"""
# PREPARE
for param in params.split(';'):
if not param:
continue
logging.info("test_loopback: params: %s", param)
param = param.split(':')
idx_serialport = 0
idx_console = 0
buf_len = []
if (param[0].startswith('console')):
send_pt = consoles[0][idx_console]
idx_console += 1
else:
send_pt = consoles[1][idx_serialport]
idx_serialport += 1
if (len(param[0].split('@')) == 2):
buf_len.append(int(param[0].split('@')[1]))
else:
buf_len.append(1024)
recv_pts = []
for parm in param[1:]:
if (parm.isdigit()):
buf_len.append(int(parm))
break # buf_len is the last portion of param
if (parm.startswith('console')):
recv_pts.append(consoles[0][idx_console])
idx_console += 1
else:
recv_pts.append(consoles[1][idx_serialport])
idx_serialport += 1
if (len(parm[0].split('@')) == 2):
buf_len.append(int(parm[0].split('@')[1]))
else:
buf_len.append(1024)
# There must be sum(idx_*) consoles + last item as loopback buf_len
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")
threads = []
queues = []
for i in range(0, len(recv_pts)):
queues.append(deque())
tmp = "'%s'" % recv_pts[0].name
for recv_pt in recv_pts[1:]:
tmp += ", '%s'" % (recv_pt.name)
on_guest("virt.loopback(['%s'], [%s], %d, virt.LOOP_POLL)"
% (send_pt.name, tmp, buf_len[-1]), vm, 10)
exit_event = threading.Event()
# TEST
thread = ThSendCheck(send_pt, exit_event, queues,
buf_len[0])
thread.start()
threads.append(thread)
for i in range(len(recv_pts)):
thread = ThRecvCheck(recv_pts[i], queues[i], exit_event,
buf_len[i + 1])
thread.start()
threads.append(thread)
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])
# Read-out all remaining data
for recv_pt in recv_pts:
while select.select([recv_pt.sock], [], [], 0.1)[0]:
recv_pt.sock.recv(1024)
_guest_exit_threads(vm, [send_pt], recv_pts)
del exit_event
del threads[:]
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, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param params: test parameters:
'$console_type@$buffer_length:$test_duration;...'
"""
for param in params.split(';'):
if not param:
continue
logging.info("test_perf: params: %s", param)
param = param.split(':')
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 = 1024
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()
time_slice = float(duration) / 100
# HOST -> GUEST
on_guest('virt.loopback(["%s"], [], %d, virt.LOOP_NONE)' %
(port.name, buf_len), vm, 10)
thread = ThSend(port.sock, data, exit_event)
stats = array.array('f', [])
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(time_slice)
_time = time.time() - _time - duration
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.name, select.POLLIN), vm, 10)[0]:
time.sleep(1)
_guest_exit_threads(vm, [port], [])
if (_time > time_slice):
logging.error(
"Test ran %fs longer which is more than one time slice", _time)
else:
logging.debug("Test ran %fs longer", _time)
stats = process_stats(stats[1:], time_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])
del thread
# GUEST -> HOST
exit_event.clear()
stats = array.array('f', [])
on_guest("virt.send_loop_init('%s', %d)" % (port.name, buf_len),
vm, 30)
thread = ThRecv(port.sock, exit_event, buf_len)
thread.start()
loads.start()
on_guest("virt.send_loop()", vm, 10)
_time = time.time()
for i in range(100):
stats.append(thread.idx)
time.sleep(time_slice)
_time = time.time() - _time - duration
logging.info("\n" + loads.get_cpu_status_string()[:-1])
logging.info("\n" + loads.get_mem_status_string()[:-1])
on_guest("virt.exit_threads()", vm, 10)
exit_event.set()
thread.join()
if (_time > time_slice): # Deviation is higher than 1 time_slice
logging.error(
"Test ran %fs longer which is more than one time slice", _time)
else:
logging.debug("Test ran %fs longer", _time)
stats = process_stats(stats[1:], time_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])
del thread
del exit_event
def _clean_ports(vm, consoles):
"""
Read all data from all ports, in both sides of each port.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be clean.
"""
for ctype in consoles:
for port in ctype:
openned = port.is_open
port.clean_port()
on_guest("virt.clean_port('%s'),1024" % port.name, vm, 10)
if not openned:
port.close()
on_guest("virt.close('%s'),1024" % port.name, vm, 10)
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 vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be clean.
"""
# Check if python is still alive
logging.info("CLEANING")
match, tmp = _on_guest("is_alive()", vm, 10)
if (match is None) or (match != 0):
logging.error("Python died/is stucked/have remaining threads")
logging.debug(tmp)
try:
kernel_bug = _search_kernel_crashlog(vm[0].serial_console, 10)
if kernel_bug is not None:
logging.error(kernel_bug)
raise error.TestFail("Kernel crash.")
if vm[4] == True:
raise error.TestFail("Kernel crash.")
match, tmp = _on_guest("guest_exit()", vm, 10)
if (match is None) or (match == 0):
vm[1].close()
vm[1] = virt_test_utils.wait_for_login(vm[0], 0,
float(params.get("boot_timeout", 5)),
0, 10)
on_guest("killall -9 python "
"&& echo -n PASS: python killed"
"|| echo -n PASS: python was already dead",
vm, 10)
init_guest(vm, consoles)
_clean_ports(vm, consoles)
except (error.TestFail, aexpect.ExpectError,
Exception), inst:
logging.error(inst)
logging.error("Virtio-console driver is irreparably"
" blocked. Every comd end with sig KILL."
"Trying to reboot vm to continue testing...")
try:
vm[0].destroy(gracefully = True)
(vm[0], vm[1], vm[3]) = _restore_vm()
except (kvm_monitor.MonitorProtocolError):
logging.error("Qemu is blocked. Monitor no longer "
"communicates")
vm[0].destroy(gracefully = False)
os.system("kill -9 %d" % (vm[0].get_pid()))
(vm[0], vm[1], vm[3]) = _restore_vm()
init_guest(vm, consoles)
cname = ""
try:
cname = consoles[0][0].name
except (IndexError):
cname = consoles[1][0].name
match = _on_guest("virt.clean_port('%s'),1024" %
cname, vm, 10)[0]
if (match is None) or (match != 0):
raise error.TestFail("Virtio-console driver is irreparably "
"blocked. Every comd ended with sig "
"KILL. The restart didn't help")
_clean_ports(vm, consoles)
def _reset_vm(vm, consoles, no_console=1, no_serialport=1):
"""
Destroy and reload vm.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be close and than renew.
@param no_console: Number of desired virtconsoles.
@param no_serialport: Number of desired virtserialports.
"""
vm[0].destroy(gracefully=False)
shutil.rmtree(vm[2]) # Remove virtio sockets tmp directory
(_vm, _consoles) = _vm_create(no_console, no_serialport)
consoles[:] = _consoles[:]
vm[:] = _vm[:]
def clean_reload_vm(vm, consoles, expected=False):
"""
Reloads and boots the damaged vm
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Consoles which should be clean.
"""
if expected:
logging.info("Scheduled vm reboot")
else:
logging.info("SCHWARZENEGGER is CLEANING")
_reset_vm(vm, consoles, len(consoles[0]), len(consoles[1]))
init_guest(vm, consoles)
def test_smoke(test, vm, consoles, params, global_params):
"""
Virtio console smoke test.
Tests the basic functionalities (poll, read/write with and without
connected host, etc.
@param test: Main test object.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param params: Test parameters '$console_type:$data;...'
@param global_params: Params defined by tests_base.conf.
"""
# PREPARE
if (global_params.get('smoke_test') == "yes"):
for param in params.split(';'):
if not param:
continue
headline = "test_smoke: params: %s" % (param)
logging.info(headline)
param = param.split(':')
if len(param) > 1:
data = param[1]
else:
data = "Smoke test data"
param = (param[0] == 'serialport')
send_pt = consoles[param][0]
recv_pt = consoles[param][1]
subtest.headline(headline)
subtest.do_test(tcheck_zero_sym, [vm], cleanup=False)
subtest.do_test(topen, [vm, send_pt], True)
subtest.do_test(tclose, [vm, send_pt], True)
subtest.do_test(tmulti_open, [vm, send_pt])
subtest.do_test(tpolling, [vm, send_pt])
subtest.do_test(tsigio, [vm, send_pt])
subtest.do_test(tlseek, [vm, send_pt])
subtest.do_test(trw_host_offline, [vm, send_pt])
subtest.do_test(trw_host_offline_big_data, [vm, send_pt],
cleanup=False)
subtest.do_test(trw_notconnect_guest,
[vm, send_pt, consoles])
subtest.do_test(trw_nonblocking_mode, [vm, send_pt])
subtest.do_test(trw_blocking_mode, [vm, send_pt])
subtest.do_test(tbasic_loopback, [vm, send_pt, recv_pt, data],
True)
def test_multiport(test, vm, consoles, params, global_params):
"""
This is group of test which test virtio_console in maximal load and
with multiple ports.
@param test: Main test object.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param params: Test parameters '$console_type:$data;...'
@param global_params: Params defined by tests_base.conf.
"""
subtest.headline("test_multiport:")
# Test Loopback
if (global_params.get('loopback_test') == "yes"):
subtest.do_test(tloopback, [vm, consoles, params[0]])
# Test Performance
if (global_params.get('perf_test') == "yes"):
subtest.do_test(tperf, [vm, consoles, params[1]])
def test_destructive(test, vm, consoles, global_params, params):
"""
This is group of tests which might be destructive.
@param test: Main test object.
@param vm: Target virtual machine [vm, session, tmp_dir, ser_session].
@param consoles: Field of virtio ports with the minimum of 2 items.
@param global_params: Params defined by tests_base.conf.
@param params: Dictionary of subtest params from tests_base.conf.
"""
subtest.headline("test_destructive:")
# Uses stronger clean up function
(_cleanup_func, _cleanup_args) = subtest.get_cleanup_func()
subtest.set_cleanup_func(clean_reload_vm, [vm, consoles])
if (global_params.get('rmmod_test') == "yes"):
subtest.do_test(trmmod,[vm, consoles])
if (global_params.get('max_ports_test') == "yes"):
subtest.do_test(tmax_serial_ports, [vm, consoles])
subtest.do_test(tmax_console_ports, [vm, consoles])
subtest.do_test(tmax_mix_serial_conosle_port, [vm, consoles])
if (global_params.get('shutdown_test') == "yes"):
subtest.do_test(tshutdown, [vm, consoles])
if (global_params.get('migrate_offline_test') == "yes"):
subtest.do_test(tmigrate_offline,
[vm, consoles, params['tmigrate_offline_params']])
if (global_params.get('migrate_online_test') == "yes"):
subtest.do_test(tmigrate_online,
[vm, consoles, params['tmigrate_online_params']])
if (global_params.get('hotplug_serial_test') == "yes"):
subtest.do_test(thotplug, [vm, consoles])
subtest.do_test(thotplug_no_timeout, [vm, consoles])
if (global_params.get('hotplug_console_test') == "yes"):
subtest.do_test(thotplug, [vm, consoles, "yes"])
subtest.do_test(thotplug_no_timeout, [vm, consoles, "yes"])
if (global_params.get('hotplug_pci_test') == "yes"):
subtest.do_test(thotplug_virtio_pci, [vm, consoles])
subtest.set_cleanup_func(_cleanup_func, _cleanup_args)
# INITIALIZE
if "extra_params" in params:
standard_extra_params = params['extra_params']
else:
standard_extra_params = ""
tsmoke_params = params.get('virtio_console_smoke', '')
tloopback_params = params.get('virtio_console_loopback', '')
tperf_params = params.get('virtio_console_perf', '')
tmigrate_offline_params = params.get('virtio_console_migration_offline', '')
tmigrate_online_params = params.get('virtio_console_migration_online', '')
# destructive params
tdestructive_params = {}
tdestructive_params['tmigrate_offline_params'] = tmigrate_offline_params
tdestructive_params['tmigrate_online_params'] = tmigrate_online_params
no_serialports = int(params.get('virtio_console_no_serialports', 0))
no_consoles = int(params.get('virtio_console_no_consoles', 0))
# consoles required for Smoke test
if tsmoke_params.count('serialport'):
no_serialports = max(2, no_serialports)
if tsmoke_params.count('console'):
no_consoles = max(2, no_consoles)
# consoles required for Loopback test
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 tperf_params.count('serialport'):
no_serialports = max(1, no_serialports)
if tperf_params.count('console'):
no_consoles = max(1, no_consoles)
# consoles required for Migration offline test
if tmigrate_offline_params.count('serial'):
no_serialports = max(2, no_serialports)
if tmigrate_offline_params.count('console'):
no_consoles = max(2, no_consoles)
if tmigrate_online_params.count('serial'):
no_serialports = max(2, no_serialports)
if tmigrate_online_params.count('console'):
no_consoles = max(2, 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 virtio_console_guest.py into guests
pwd = os.path.join(os.environ['AUTODIR'], 'tests/kvm')
vksmd_src = os.path.join(pwd, "scripts/virtio_console_guest.py")
dst_dir = "/tmp"
vm[0].copy_files_to(vksmd_src, dst_dir)
# ACTUAL TESTING
# Defines all available consoles; tests udev and sysfs
subtest = SubTest()
try:
init_guest(vm, consoles)
subtest.set_cleanup_func(clean_ports, [vm, consoles])
# Test Smoke
test_smoke(subtest, vm, consoles, tsmoke_params, params)
# Test multiport functionality and performance.
test_multiport(subtest, vm, consoles, [tloopback_params, tperf_params],
params)
#Test destructive test.
test_destructive(subtest, vm, consoles, params, tdestructive_params)
finally:
logging.info(("Summary: %d tests passed %d test failed :\n" %
(subtest.passed, subtest.failed)) +
subtest.get_text_result())
if subtest.is_failed():
raise error.TestFail("%d out of %d virtio console tests failed" %
(subtest.failed, (subtest.passed+subtest.failed)))
# CLEANUP
vm[1].close()
vm[0].destroy(gracefully=False)
shutil.rmtree(vm[2])