[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 1/1] integrate qemu-test into kvm-userspace
From: |
Ryan Harper |
Subject: |
[Qemu-devel] [PATCH 1/1] integrate qemu-test into kvm-userspace |
Date: |
Fri, 25 Jul 2008 11:53:25 -0500 |
diff --git a/Makefile b/Makefile
index 2c54e95..1c1223e 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ rpmrelease = devel
sane-arch = $(subst i386,x86,$(subst x86_64,x86,$(subst s390x,s390,$(ARCH))))
-.PHONY: kernel user libkvm qemu bios vgabios extboot clean libfdt
+.PHONY: kernel user libkvm qemu bios vgabios extboot clean libfdt test
all: libkvm qemu
ifneq '$(filter $(ARCH), x86_64 i386 ia64)' ''
@@ -104,9 +104,12 @@ srpm:
$(RM) $(tmpspec)
clean:
- for i in $(if $(WANT_MODULE), kernel) user libkvm qemu libfdt; do \
+ for i in $(if $(WANT_MODULE), kernel) user libkvm qemu libfdt test; do \
make -C $$i clean; \
done
distclean: clean
rm -f config.mak user/config.mak
+
+test: all
+ $(MAKE) -C test $@
diff --git a/test/CONFIG b/test/CONFIG
new file mode 100644
index 0000000..8509f22
--- /dev/null
+++ b/test/CONFIG
@@ -0,0 +1,56 @@
+###############################################################################
+# QEMU-TEST options and defaults
+#
+###############################################################################
+[options]
+debug=1
+logfile=qemu-test.log
+serial=/dev/stdout
+
+###############################################################################
+# !!!!WARNING!!!!: don't change the defaults, instead add an override for your
+# distro, see below
+###############################################################################
+[defaults]
+username=root
+password=
+login_prompt=^(\S+) login:
+shell_prompt=(%(username)s@)*%(hostname)s.*#\ $
+
+###############################################################################
+# OVERRIDES
+#
+# Distro overrides section, to override the default values as specified in the
+# [defaults] section, add the same option name and a new value, e.g, overriding
+# the default username:
+#
+# [rhel]
+# username=foobar
+#
+#
+# Fields:
+# username:
+# password:
+# login_prompt:
+# shell_prompt:
+# banner:
+# add a regular expression to match the output from your distro's welcome
banner
+# (/etc/issue) the option name will be used for overriding the default section.
+# For example, if you add the following option:
+#
+###############################################################################
+
+[rhel]
+banner=^Red Hat Enterprise Linux
+shell_prompt=^\[%(username)address@hidden(hostname)s.*\]#\ $
+
+[opensuse]
+banner=^Welcome to openSUSE
+shell_prompt=%(hostname)s:.*#\ $
+
+[ubuntu]
+banner=^Ubuntu
+shell_prompt=^%(username)address@hidden(hostname)s:.*#\ $
+
+[fedora]
+banner=^Fedora
diff --git a/test/HOWTO b/test/HOWTO
new file mode 100644
index 0000000..d7d1543
--- /dev/null
+++ b/test/HOWTO
@@ -0,0 +1,33 @@
+Configure your Guest - FIXME: more details
+-------------------
+1. Configure guest to support serial login
+
+
+2. Create an image file in qemu-test/images:
+
+For example:
+
+% cat images/rhel5.2
+DISK="/home/rharper/work/images/rhel5.2_x86_64_10G.qcow2"
+COMMAND="qemu-system-x86_64 -m 512 -smp 2 -net tap -net nic,model=e1000 -net
nic,model=rtl8139 -drive file=${DISK},if=ide,boot=on -vnc none"
+
+You can omit the COMMAND variable and a default qemu command will be run
instead
+(see run.sh). For each guest image or config add additional files in
+qemu-test/images.
+
+3. Run the test and observe the results:
+
+% make && sudo make test
+<snip massive amount of output>
+************************************************************
+Results: 5 passed, 4 FAILED
+passed: e820_memory.py:fedora-9-x86_64
+passed: host_shutdown.py:fedora-9-x86_64
+passed: networking.py:fedora-9-x86_64
+passed: reboot.py:fedora-9-x86_64
+passed: smp_hotplug.py:fedora-9-x86_64
+FAILED: Test:host_reset.py,Image:fedora-9-x86_64
+FAILED: Test:migrate.py,Image:fedora-9-x86_64
+FAILED: Test:timedrift.py,Image:fedora-9-x86_64
+FAILED: Test:writeverify.py,Image:fedora-9-x86_64
+************************************************************
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 0000000..7694695
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,21 @@
+include ../qemu/config-host.mak
+
+modules: ../kernel/kvm.ko ../kernel/kvm-intel.ko ../kernel/kvm-amd.ko
+ echo "Reloading kvm modules..."
+ if grep -q kvm_intel /proc/modules; then rmmod kvm-intel; else rmmod
kvm-amd; fi
+ if grep -q kvm /proc/modules; then rmmod kvm; fi
+ insmod ../kernel/kvm.ko && sleep 1
+ if grep -q Intel /proc/cpuinfo; then insmod ../kernel/kvm-intel.ko;
else insmod ../kernel/kvm-amd.ko; fi
+
+setup: ../qemu/pc-bios ../qemu/keymaps
+ cd ../qemu/pc-bios && ln -sf ../keymaps
+
+clean:
+ rm -f ../qemu/pc-bios/keymaps
+
+test: all
+
+all: ../qemu/config-host.mak setup modules
+ ./run.sh QEMU="../qemu/$(TARGET_DIRS)/qemu-system-$(ARCH) -L
../qemu/pc-bios" \
+ TESTS="$(TESTS)" ARGS="$(ARGS)"
+
diff --git a/test/THEORY b/test/THEORY
new file mode 100644
index 0000000..16d5c48
--- /dev/null
+++ b/test/THEORY
@@ -0,0 +1,4 @@
+Each test should work with as many guests as possible with the least number
+of parameters required.
+
+Minimum guest requirements (only configuration)
diff --git a/test/TODO b/test/TODO
new file mode 100644
index 0000000..69353d3
--- /dev/null
+++ b/test/TODO
@@ -0,0 +1,31 @@
+* improve failure detection
+ o oops/soft lockup detection
+ o freeze detection
+ - hard lockup detection (100% cpu usage, min timeout expired)
+ - soft lockup detection (0% cpu usage, min timeout expired)
+ - qemu freeze detection (timeout in monitor command)
+* detect guest type
+ o automagically select appropriate defaults for prompt
+* lots more tests
+ o device validation
+ o disk emu/paravirt verification
+ o pause/resume
+ o savevm/loadvm
+* randomize sockets
+* redirect serial output to file, print to screen as option
+* write up on how to configure various distros to be ready to run any test
+* rewrite networking.py to parse info out of sysfs
+* pass test name to launch for logging
+
+
+#### Completed #####
+2008-07-23
+* make check|test integration into kvm-userspace tree
+* fedora guest support
+
+2008-07-16
+* integrate config file
+ o support per-distro overrides
+* ubuntu guest support
+* rhel5 guest support
+* opensuse geust support
diff --git a/test/e820_memory.py b/test/e820_memory.py
new file mode 100644
index 0000000..f02d85d
--- /dev/null
+++ b/test/e820_memory.py
@@ -0,0 +1,114 @@
+# Copyright IBM Corp. 2008
+# Authors: Anthony Liguori <address@hidden>
+# : Ryan Harper <address@hidden
+#
+
+from qemu.test import launch, TimeoutException
+import sys
+import ConfigParser, os
+
+
+def check_table_entry(e820, entry, field, value):
+ v1 = e820[entry][field]
+ v2 = value
+ # third field is a string, so strip to trim newlines and such
+ if field == 2:
+ v1 = v1.strip()
+ v2 = v2.strip()
+ if v1 != v2:
+ print 'e820 entry %d field %d should be "%s", found [%s]' %(
+ entry, field, str(value), e820[entry][field])
+ return 1
+ print 'e820[%d][%d] OK' %(entry, field)
+ return 0
+
+
+def compare_table_entry(e820_a, e820_b, entry, field):
+ v1 = e820_a[entry][field]
+ v2 = e820_b[entry][field]
+ # third field is a string, so strip to trim newlines and such
+ if field == 2:
+ v1 = v1.strip()
+ v2 = v2.strip()
+ if v1 != v2:
+ print 'e820 table entry %d field %d do not match' %(entry, field)
+ print '[%s] != [%s]' %(v1, v2)
+ return 1
+
+ print 'e820[%d][%d] match' %(entry, field)
+ return 0
+
+
+def run_test(vm, memory_size):
+ vm.wait_for_boot()
+
+ vm.login()
+
+ e820 = []
+ output = vm.guest_command('dmesg | grep BIOS-e820')
+ for line in output.split('\n'):
+ # handle timestamps in dmesg
+ if line.startswith('['):
+ _, line = line.split('] ', 1)
+
+ words = line.split(' ', 5)
+ start = long('0x%s' % words[2], 0)
+ end = long('0x%s' % words[4], 0)
+ e820.append((start, end, words[5]))
+
+ if len(e820) != 6:
+ print 'Unusual number of e820 entries: %d' % len(e820)
+ return 1
+
+ e820_static = [(0x00000, 0x09fc00, '(usable)'),
+ (0x9fc00, 0x0a0000, '(reserved)'),
+ (0xe8000, 0x100000, '(reserved)')]
+
+ for i in range(3):
+ if compare_table_entry(e820_static, e820, i, 0): return 1
+ if compare_table_entry(e820_static, e820, i, 1): return 1
+ if compare_table_entry(e820_static, e820, i, 2): return 1
+
+ if check_table_entry(e820, 3, 0, 0x100000): return 1
+ if check_table_entry(e820, 3, 2, '(usable)'): return 1
+ if check_table_entry(e820, 4, 2, '(ACPI data)'): return 1
+
+ if (e820[4][1] - e820[4][0]) != (64 << 10):
+ print 'e820 entry 4 has invalid size'
+ return 1
+
+ if e820[4][1] != memory_size:
+ print 'e820 entry 4 has invalid start address'
+ return 1
+
+ if check_table_entry(e820, 5, 0, 0xfffbd000): return 1
+ if check_table_entry(e820, 5, 1, 0x100000000): return 1
+ if check_table_entry(e820, 5, 2, '(reserved)'): return 1
+
+ return 0
+
+
+def main(args):
+ memory_size = 128 << 20
+ for i in range(len(args)):
+ if args[i] == '-m':
+ memory_size = long(args[i + 1]) << 20
+ break
+
+ vm = launch(*args)
+ err = 1
+ try:
+ err = run_test(vm, memory_size)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/host_reset.py b/test/host_reset.py
new file mode 100644
index 0000000..f1bf610
--- /dev/null
+++ b/test/host_reset.py
@@ -0,0 +1,38 @@
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+#
+
+from qemu.test import launch, TimeoutException
+import sys
+
+def run_test(vm):
+ vm.wait_for_boot()
+ vm.login()
+ vm.guest_command("echo pressing reset button")
+ output = vm.monitor_command('system_reset')
+ vm.wait_for_restart()
+ vm.wait_for_boot()
+
+ return 0
+
+
+def main(args):
+ err = 1
+ vm = launch(*args)
+
+ try:
+ err = run_test(vm)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/host_shutdown.py b/test/host_shutdown.py
new file mode 100644
index 0000000..eefe232
--- /dev/null
+++ b/test/host_shutdown.py
@@ -0,0 +1,35 @@
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+
+from qemu.test import launch, TimeoutException
+import sys
+
+def run_test(vm):
+ vm.wait_for_boot()
+ vm.login()
+ vm.guest_command("echo pressing power button")
+ output = vm.monitor_command('system_powerdown')
+ if vm.wait_for_shutdown(): return 1
+
+ return 0
+
+
+def main(args):
+ err = 1
+ vm = launch(*args)
+
+ try:
+ err = run_test(vm)
+ finally:
+ vm.monitor_command('quit')
+ vm.wait_for_quit()
+
+ return err
+
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/migrate.py b/test/migrate.py
new file mode 100644
index 0000000..b13714d
--- /dev/null
+++ b/test/migrate.py
@@ -0,0 +1,55 @@
+# Copyright IBM Corp. 2008
+# Authors: Anthony Liguori <address@hidden>
+#
+
+from qemu.test import launch
+import sys, time
+
+def test_ping(vm):
+ #gw_info = vm.get_gateway_info()
+ #if vm.ping(gw_info['address']): return False
+ output = vm.guest_command('ping -c 1 10.0.1.1')
+ _, stats, __ = output.rsplit('\n', 2)
+ words = stats.split(' ')
+ if words[0] != words[3]:
+ return False
+ return True
+
+def main(args):
+ A = launch(*args)
+ A.wait_for_boot()
+ A.login('root', '')
+
+ for i in range(10):
+ B = launch(*(args + ['-incoming', 'tcp://localhost:1025']))
+
+ B.booted = A.booted
+ B.logged_in = A.logged_in
+ B.hostname = A.hostname
+ B.username = A.username
+ B.password = A.password
+ B.prompt = A.prompt
+
+ if not test_ping(A):
+ print 'Ping test failed before migration'
+ A.monitor_command('quit')
+ B.monitor_command('quit')
+ return 1
+
+ A.monitor_command('migrate tcp://localhost:1025')
+ A.monitor_command('quit')
+
+ if not test_ping(B):
+ print 'Ping test failed after migration'
+ B.monitor_command('quit')
+ return 1
+
+ A = B
+ B = None
+
+ B.monitor_command('quit')
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/test/networking.py b/test/networking.py
new file mode 100644
index 0000000..18ca242
--- /dev/null
+++ b/test/networking.py
@@ -0,0 +1,80 @@
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+#
+
+from qemu.test import launch, TimeoutException
+import sys
+
+def run_test(vm, args):
+ configured_nics = []
+ detected_nics = []
+ nic_static = { 'Unknown device 1af4:1000': 'virtio',
+ 'Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139 C+
(rev 20)': 'rtl8319',
+ 'Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev
20)': 'rtl8319',
+ 'Intel Corporation 82540EM Gigabit Ethernet Controller (rev
03)': 'e1000' }
+ drivers_static = { 'e1000': 'e1000', 'virtio_net': 'virtio', '8139cp':
'rtl8139' }
+ devices = []
+
+ vm.wait_for_boot()
+ vm.login()
+
+ for i in range(len(args)):
+ if args[i].startswith('nic') and 'model=' in args[i]:
+ configured_nics.append(args[i].split('model=')[1].split(',')[0])
+
+ if len(configured_nics) < 1:
+ print "this test requires a configured nic"
+ print "try again with: '-net nic,model=<ethernet device>'"
+ return 1
+
+ output = vm.guest_command('lspci -v | grep Ethernet')
+ for line in output.split('\n'):
+ device = " ".join(line.split()[3:]).replace('\r','')
+ if device in nic_static.keys():
+ detected_nics.append(nic_static[device])
+
+ if len(detected_nics) != len(configured_nics):
+ print 'Failed to detect all configured nics'
+ print 'Configured nics: %s' %(configured_nics)
+ print 'Detected nics: %s' %(detected_nics)
+ return 1
+
+ # build interface to driver mapping array
+ output = vm.guest_command('for d in `ls -1 /sys/class/net/ | grep eth`; do
driver=$(basename `readlink /sys/class/net/$d/device/driver/module`); echo
${driver}:$d; done');
+ for line in output.split('\n'):
+ (driver,device) = line.split(':')
+ devices.append((device,driver))
+
+ gw_info = vm.get_gateway_info()
+
+ # if device is configured and connected to a gateway, do a ping check
+ # FIXME multi-gateway setups not tested
+ for (device,driver) in devices:
+ dev_info = vm.get_netinfo(device)
+ if dev_info['state'] == 'up':
+ if vm.ping(gw_info['address'], iface=device): return 1
+ else:
+ print "device %s, driver %s not enabled in guest, skipping..."
%(device, driver)
+
+ return 0
+
+
+def main(args):
+ err = 1
+ vm = launch(*args)
+
+ try:
+ err = run_test(vm, args)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/qemu/__init__.py b/test/qemu/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/qemu/test.py b/test/qemu/test.py
new file mode 100644
index 0000000..a603c34
--- /dev/null
+++ b/test/qemu/test.py
@@ -0,0 +1,418 @@
+# Copyright IBM Corp. 2008
+# Authors: Anthony Liguori <address@hidden>
+# : Ryan Harper <address@hidden
+#
+
+import os, sys, socket, re, time, select, ConfigParser
+
+class TimeoutException(Exception):
+ def __init__(self, message):
+ Exception.__init__(self)
+ self.message = message
+
+
+class QEMUInstance(object):
+ def __init__(self, pid, serial_fd, monitor_fd, configfile=None):
+ self.configfile = None
+ self.DEBUG = None
+ self.logfile = None
+ self.logfd = None
+ self.pid = pid
+ self.serial = serial_fd
+ self.serialfile = None
+ self.serialoutfd = None
+ self.monitor = monitor_fd
+ self.booted = False
+ self.logged_in = False
+ self.distro = None
+ self.login_prompt = None
+ self.shell_prompt = None
+ self.username = None
+ self.hostname = None
+ self.prompt = None
+
+ # parse config file, check if custom path has been provided
+ if configfile:
+ self.configfile = configfile
+ else:
+ # look for CONFIG file in current dir
+ self.configfile = os.path.join(os.getcwd(),'CONFIG')
+
+ # fire up the parser
+ try:
+ self.config = ConfigParser.ConfigParser()
+ self.config.readfp(open(self.configfile))
+ except Exception, e:
+ print 'Failed to parse config: %s'%(self.configfile)
+ print e.message
+ self.quit()
+
+ # fetch common options from config
+ if self.config.has_option("options", "debug"):
+ self.DEBUG = self.config.get("options", "debug")
+ if self.config.has_option("options", "logfile"):
+ self.logfile = self.config.get("options", "logfile")
+ if self.config.has_option("options", "serialfile"):
+ self.serialfile = self.config.get("options", "serialfile")
+
+ # dump header if we're debuggin
+ if self.DEBUG:
+ self.logfd = open(self.logfile, "a")
+
self.log("****************************************************************")
+ self.log("qemu-test v0.1 init")
+
self.log("****************************************************************")
+
+ #if self.serialfile:
+ # self.serialoutfd = open(self.serialfile, "a")
+ # self.log("Writing serial output to %s", self.serialfile)
+
+
+ # wait for qemu monitor to come up -- ensure it's working
+ self.wait_for_monitor_prompt()
+
+
+ def log(self, message):
+ if self.DEBUG:
+ self.logfd.write('[%s] %s\n' %(time.time(), message))
+ self.logfd.flush()
+
+ ###########################################################
+ # waiting methods
+ ###########################################################
+
+ # base method which reads from the serial line checking to see
+ # the current input matches a designated regular expression
+ # also provide two timers, total (timeout) and activity timeout
+ # (min_activity). The latter is specified when the caller
+ # wants to ensure some output is being generated with some
+ # frequency. Return 'message' in the exception if either
+ # timer fails
+ def wait_for_rexpr(self, rexpr, timeout=None, min_activity=None,
+ message=None):
+ output = ''
+ m = None
+ self.log("wait_for_rexpr(%s) ->"%(rexpr))
+
+ if message == None:
+ message = "rexpr `%s'\n" % rexpr
+
+ if timeout != None:
+ end_time = time.time() + timeout
+ else:
+ end_time = None
+
+ if min_activity == None:
+ min_activity = timeout
+
+ while m == None:
+ now = time.time()
+ if end_time != None and end_time < now:
+ raise TimeoutException(message)
+
+ if end_time != None or min_activity != None:
+ if end_time == None:
+ wait = min_activity
+ else:
+ wait = min(min_activity, end_time - now)
+
+ rdfds, _, __ = select.select([self.serial], [], [], wait)
+ if self.serial not in rdfds:
+ raise TimeoutException(message)
+
+ ch = self.serial.recv(1)
+ if ch == '':
+ break
+ # TODO: support configuring output to file
+ sys.stdout.write(ch)
+ sys.stdout.flush()
+ output += ch
+ if output.find('\n') != -1:
+ _, line = output.rsplit('\n', 1)
+ #self.log("searching line: [%s] -> [%s]"%(rexpr, line))
+ m = re.search(rexpr, line)
+ self.log("wait_for_rexpr() <-")
+ return output, m
+
+
+ def wait_for_monitor_prompt(self):
+ info = ''
+ self.log("wait_for_monitor_prompt() ->")
+ while not info.endswith('\n(qemu) '):
+ ch = self.monitor.recv(1)
+ if ch == '':
+ break
+ #self.log("got: %s"%(ch))
+ info += ch
+
+ self.log("wait_for_monitor_prompt() <-")
+ return info
+
+
+ def wait_for_linux_password(self, rexpr='^Password: ', timeout=None):
+ self.wait_for_rexpr(rexpr, timeout=timeout)
+
+
+ def wait_for_boot(self, timeout=None):
+ try:
+ self.log("wait_for_boot() ->")
+ self.detect_distro()
+ self.log("after distro detect")
+ if not self.login_prompt:
+ self.login_prompt = self.getconfig("login_prompt")
+ output, m = self.wait_for_rexpr(self.login_prompt, timeout,
min_activity=60,
+ message="Linux login prompt")
+ self.hostname = m.group(1)
+ self.booted = True
+ self.log("wait_for_boot() <-")
+ except Exception, e:
+ print "wait_for_boot error: %s" % e.message
+
+
+ def detect_distro(self, timeout=None):
+ self.log("detect_distro() ->")
+ if self.distro == None:
+ match_map = []
+ self.log("distro not set yet")
+ # list potential distros to check for based on config overrides
+ distros = filter(lambda x: self.config.has_option(x, 'banner'),
+ self.config.sections())
+ self.log("distros in config: %s"%(distros))
+
+ # keep a position mapping
+ for d in distros:
+ match_map.append(d)
+
+ banner = "|".join(map(lambda y: "(%s)" % self.config.get(y,
'banner',
+ raw=True), distros))
+ self.log("distro rexpr: [%s]"%(banner))
+ try:
+ output, m = self.wait_for_rexpr(banner, timeout,
min_activity=20,
+ message="Detect distro banner")
+
+ # groups returns a tuple of which parts of the regular
expression
+ # were involved in the match, extracting which group was
+ # not None will give us an index into our regrexpress for each
+ # distro and we can determine which distro we detected
+ matchlist = list(m.groups())
+ for x in range(len(matchlist)):
+ if matchlist[x] != None:
+ self.distro = match_map[x]
+
+ self.log("detected distro: %s"%(self.distro))
+ except Exception, e:
+ self.log("waiting for distro rexpr FAIL: %s"%(e.message))
+ self.distro = 'defaults'
+ # kick the console to spit out a new login prompt
+ # as we probably ate it looking for a banner
+ self.send_to_guest("\n")
+
+
+ self.log("detect_distro() <-")
+
+ def wait_for_shutdown(self, rexpr='^Power down.', timeout=180):
+ output, m = self.wait_for_rexpr(rexpr, timeout, min_activity=60,
+ message="Linux shutdown message")
+ self.booted = False
+
+
+ def wait_for_restart(self, rexpr='^Restarting system.', timeout=180):
+ output, m = self.wait_for_rexpr(rexpr, timeout, min_activity=60,
+ message="Linux shutdown message")
+ self.booted = False
+
+
+ def wait_for_quit(self):
+ self.log("waidpid() on PID=%d"%(self.pid))
+ os.waitpid(self.pid, 0)
+
+
+ def getconfig(self, field):
+ def __getconfig(self, section, field):
+ self.log("getconfig: fetching section:%s field:%s "%(section,
+ field))
+ try:
+ return self.config.get(section, field, raw=True)
+ except Exception, e:
+ self.log("getconfig: %s"%(e.message))
+ pass
+ return None
+
+ self.log("getconfig() ->")
+ # try looking in distro override section, otherwise defaults
+ value = __getconfig(self, self.distro, field)
+ if value == None:
+ value = __getconfig(self, "defaults", field)
+
+ self.log("getconfig() <-")
+ return value
+
+
+ def login(self):
+ self.log("login() ->")
+ if not self.prompt:
+ username = self.getconfig("username")
+ password = self.getconfig("password")
+ prompt = self.getconfig("shell_prompt")
+
+ if username != 'root':
+ raise Exception("login requires username=root");
+
+ if not self.booted:
+ self.wait_for_boot()
+
+ self.username = username
+ self.password = password
+ self.prompt = prompt % {'username': username, 'hostname':
self.hostname}
+
+
+ # XXX: maybe I should check for self.booted here as well
+ self.send_to_guest('%s\n' % self.username)
+ self.wait_for_rexpr('^Password: ')
+ # There seems to be a race with the password entry
+ time.sleep(1)
+ self.send_to_guest('%s\n' % self.password)
+ self.wait_for_rexpr(self.prompt, timeout=10)
+ self.logged_in = True
+
+
+ def send_to_guest(self, data):
+ self.serial.sendall(data)
+
+
+ def __sanitize(self, string):
+ return string.replace('\r','')
+
+
+ # common networking helpers
+ def get_gateway_info(self):
+ cmd = "route -n | grep ^0 | awk '{print $8\":\"$2}'"
+ output = self.__sanitize(self.guest_command(cmd))
+ output = output.split(":")
+ return { 'device':output[0], 'address': output[1] }
+
+
+ def get_netinfo(self, devicename):
+ d = self.__sanitize(devicename)
+ netinfo = {}
+ c = "ifconfig %s | grep [A-Za-z]" %(d)
+ ifconfig_raw = self.guest_command(c).split('\n')
+ if 'error' in ifconfig_raw:
+ return netinfo
+
+ netinfo['device'] = d
+ netinfo['macaddr'] = ifconfig_raw[0].split()[-1:]
+ #FIXME: counting # of lines of ifconfig output isn't a good way
+ # to determine if the device is configured
+ if len(ifconfig_raw) >= 8:
+ netinfo['address'] = ifconfig_raw[1].split()[1].split(':')[1]
+ netinfo['netmask'] = ifconfig_raw[1].split()[-1:][0].split(':')[1]
+ if 'UP' in ifconfig_raw[3]:
+ netinfo['state'] = 'up'
+ else:
+ netinfo['state'] = 'down'
+ netinfo['mtu'] = ifconfig_raw[3].split()[4].split(':')[1]
+ else:
+ # device not configured
+ netinfo['state'] = 'down'
+
+ return netinfo
+
+
+ def ping(self, address, iface=None):
+ cmd = "ping -c 5 %s -w 10" %(address)
+ if iface:
+ iface = self.__sanitize(iface)
+ cmd = "%s -I %s" %(cmd, iface)
+ cmd = "%s; echo $?" %(cmd)
+ raw = self.guest_command(cmd).split('\n')
+ # extract the return code, dropping any empty entries from the list
+ rc = filter(lambda x: len(x) > 0, raw)[-1]
+ return int(rc)
+
+
+ def shutdown(self):
+ self.guest_command("halt; echo shutdown")
+
+
+ def quit(self):
+ self.monitor_command("quit")
+ self.wait_for_quit()
+
+ def guest_command(self, command, timeout=300):
+ self.log("guest_command(command=%s) ->"%(command))
+ self.send_to_guest('%s\n' % command)
+ output, _ = self.wait_for_rexpr('%s' % self.prompt, timeout,
+ message="Guest command: %s"%(command))
+ try:
+ output, _ = output.rsplit('\n', 1)
+ _, output = output.split('\n', 1)
+ except Exception, e:
+ pass
+ self.log("guest_command() <-")
+ return output.rsplit('\r', 1)[0]
+
+
+ def monitor_command(self, command):
+ self.log("monitor_command(command=%s) ->"%(command))
+ self.monitor.sendall('%s\n' % command)
+ info = self.wait_for_monitor_prompt()
+ self.log("info = %s"%(info))
+ index = info.find('\n')
+ if index != -1:
+ info = info[index+1:]
+ index = info.rfind('\n')
+ if index != -1:
+ info = info[0:index]
+ self.log("monitor_command() <-")
+ return info
+
+
+def launch(executable, *args):
+ def __extract_config(args):
+ if 'config=' in args:
+ configfile = args.split("config=")[1].split()[0]
+ newargs = " ".join(args.split("config=")[1].split()[1:])
+ return (configfile, newargs)
+
+ return (None, args)
+
+
+ (config, args) = __extract_config(args)
+
+ serial_path = os.tmpnam()
+ monitor_path = os.tmpnam()
+
+ try:
+ os.unlink(serial_path)
+ except Exception, e:
+ pass
+ try:
+ os.unlink(monitor_path)
+ except Exception, e:
+ pass
+
+ serial = socket.socket(socket.AF_UNIX)
+ serial.bind(serial_path)
+ serial.listen(1)
+
+ monitor = socket.socket(socket.AF_UNIX)
+ monitor.bind(monitor_path)
+ monitor.listen(1)
+
+ pid = os.fork()
+ if pid == 0:
+ os.execvp(executable, (executable, '-serial', 'unix:%s' % serial_path,
+ '-monitor', 'unix:%s' % monitor_path) + args)
+ sys.exit(1)
+
+ serial_fd, _ = serial.accept()
+ monitor_fd, _ = monitor.accept()
+
+ os.unlink(serial_path)
+ os.unlink(monitor_path)
+
+ serial.close()
+ monitor.close()
+
+ return QEMUInstance(pid, serial_fd, monitor_fd, config)
+
diff --git a/test/reboot.py b/test/reboot.py
new file mode 100644
index 0000000..ded7980
--- /dev/null
+++ b/test/reboot.py
@@ -0,0 +1,42 @@
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+#
+
+from qemu.test import launch, TimeoutException
+import sys
+
+def run_test(vm):
+ reboots=10
+ vm.wait_for_boot()
+ vm.login()
+
+ for x in range(1,reboots+1):
+ # background the reboot command so we match the prompt string
+ vm.guest_command("echo Reboot %s; reboot&"%(x))
+ vm.wait_for_restart()
+ vm.wait_for_boot()
+ vm.login()
+
+ return 0
+
+
+def main(args):
+ err = 1
+ vm = launch(*args)
+
+ try:
+ err = run_test(vm)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/run.sh b/test/run.sh
new file mode 100755
index 0000000..bc2f1b7
--- /dev/null
+++ b/test/run.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+#
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+#
+
+
+while [ $# -gt 0 ]; do
+ var=${1%=*}
+ value=${1#*=}
+ export ${var}="${value}"
+ shift;
+done
+
+[ -z "${QEMU}" ] && { QEMU="qemu-system-x86_64"; }
+[ -z "${TESTS}" ] && { TESTS="`ls *.py`"; }
+[ -z "${ARGS}" ] && { ARGS="-m 512 -smp 2 -net tap -net nic,model=e1000 -net
nic,model=rtl8139 -snapshot -vnc none"; }
+
+mkdir -p $PWD/images
+DISKS=( `ls $PWD/images` )
+# error out if we don't have any images defined
+[ address@hidden -le 0 ] && {
+ echo "No test images defined in $PWD/images"
+ exit 0;
+}
+
+declare -a PASS
+declare -a FAIL
+for t in address@hidden; do
+ for d in address@hidden; do
+ . $PWD/images/${d}
+ echo "Running Test:${t} on Image:${d}"
+ if [ -z "${COMMAND}" ]; then
+ COMMAND="${QEMU} ${ARGS} -drive
file=${DISK},if=ide,boot=on"
+ echo "Using default command: ${COMMAND}"
+ else
+ # filter out QEMU from COMMAND, image files should use
+ # QEMU if they need to specify
+ COMMAND="${QEMU} $(echo $COMMAND | fmt -w 1 | grep -v
qemu-system | fmt -w 255)"
+ echo "Using override command: ${COMMAND}"
+ fi
+ sleep 2
+ sudo python ${t} ${COMMAND}
+ rv="$?"
+ if [ "${rv}" == "0" ]; then
+ address@hidden"${t}:${d}"
+ else
+ address@hidden"Test:${t},Image:${d}"
+ fi
+ done
+done
+
+echo ""
+echo "************************************************************"
+echo "Results: address@hidden passed, address@hidden FAILED"
+for f in address@hidden; do
+ echo "passed: ${f}"
+done
+for f in address@hidden; do
+ echo "FAILED: ${f}"
+done
+echo "************************************************************"
diff --git a/test/smp_hotplug.py b/test/smp_hotplug.py
new file mode 100644
index 0000000..671e8cf
--- /dev/null
+++ b/test/smp_hotplug.py
@@ -0,0 +1,69 @@
+# Copyright IBM Corp. 2008
+# Authors: Anthony Liguori <address@hidden>
+#
+
+from qemu.test import launch, TimeoutException
+import sys, time
+
+def count_guest_cpus(vm):
+ output = vm.guest_command('grep "^processor" /proc/cpuinfo')
+ return len(output.split('\n'))
+
+def hotplug_cpu(vm, cpu, value):
+ vm.guest_command('echo %d | sudo dd
of=/sys/devices/system/cpu/cpu%d/online' %
+ (value, cpu))
+
+def run_test(vm, n_cpus):
+ vm.wait_for_boot()
+ output = vm.monitor_command('info cpus')
+
+ if n_cpus != len(output.split('\n')):
+ print 'info cpus shows the wrong number of cpus'
+ return 1
+
+ vm.login()
+
+ if count_guest_cpus(vm) != n_cpus:
+ print 'guest cannot see all of the cpus'
+ return 1
+
+ for i in range(1, n_cpus):
+ hotplug_cpu(vm, i, 0)
+ if count_guest_cpus(vm) != n_cpus - i:
+ print 'failed to hot unplug cpu %d' % i
+ return 1
+ # give the scheduler a chance to react
+ time.sleep(1)
+
+ for i in range(1, n_cpus):
+ hotplug_cpu(vm, i, 1)
+ if count_guest_cpus(vm) != i + 1:
+ print 'failed to hot plug cpu %d' % i
+ return 1
+ # give the scheduler a chance to react
+ time.sleep(1)
+
+def main(args):
+ for i in range(len(args)):
+ if args[i] == '-smp':
+ n_cpus = int(args[i + 1])
+ break
+
+ vm = launch(*args)
+
+ err = 1
+ try:
+ err = run_test(vm, n_cpus)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/timedrift.py b/test/timedrift.py
new file mode 100644
index 0000000..4e9f541
--- /dev/null
+++ b/test/timedrift.py
@@ -0,0 +1,67 @@
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+#
+
+from qemu.test import launch, TimeoutException
+import sys, time
+
+def run_test(vm):
+ badclocks = []
+ vm.wait_for_boot()
+ vm.login()
+
+ # calculate how much time it takes to run a very simple guest command
+ length = 600
+ now = time.time()
+ vm.guest_command("echo true")
+ end = time.time()
+ overhead = int(end - now)
+ # obviously it takes *some* amount of time to issue the command
+ if overhead == 0:
+ overhead = 1
+ vm.guest_command("echo calculated overhead: %s seconds"%(overhead))
+
+ available = vm.guest_command("cat
/sys/devices/system/clocksource/clocksource0/available_clocksource")
+ clocksource = vm.guest_command("cat
/sys/devices/system/clocksource/clocksource0/current_clocksource")
+ for clock in available.split():
+ if clock == "jiffies":
+ vm.guest_command("echo skipping crappy clock=jiffies")
+ continue
+ vm.guest_command("echo switching clocksource to %s"%(clock))
+ vm.guest_command("echo %s | dd
of=/sys/devices/system/clocksource/clocksource0/current_clocksource" %(clock))
+ now = time.time()
+ vm.guest_command("date -u +%s; sleep 600; date -u +%s", timeout=1200)
+ end = time.time()
+ delta = int(end - now)
+ vm.guest_command("echo %s took %s seconds to complete"%(clock, delta))
+
+ if (delta-length) > overhead:
+ badclocks.append(clock)
+
+ if len(badclocks) > 0:
+ print "Bad clocks: %s"%(badclocks)
+ return 1
+
+ return 0
+
+
+def main(args):
+ err = 1
+ vm = launch(*args)
+
+ try:
+ err = run_test(vm)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)
diff --git a/test/writeverify.py b/test/writeverify.py
new file mode 100644
index 0000000..ba072dd
--- /dev/null
+++ b/test/writeverify.py
@@ -0,0 +1,57 @@
+# Copyright IBM Corp. 2008
+# Authors: Ryan Harper <address@hidden
+#
+
+from qemu.test import launch, TimeoutException
+import sys
+
+def run_test(vm):
+ # definitions
+ fio_cmd = 'fio --thread --bs=64k --direct=1 --ioengine=sync --verify=md5
--verify_pattern=0xaa555aa5 --verify_interval=1000 --size=%(size)s
--filename=%(filename)s --name=write-phase --rw=write --fill_device=1
--do_verify=0 --name=verify-phase --stonewall --create_serialize=0 --rw=read
--do_verify=1'
+
+ write_size = '4g'
+ write_file = '/dev/vda'
+ vm.wait_for_boot()
+ vm.login()
+
+ # make sure we have fio installed
+ output = vm.guest_command('which fio')
+
+ # find mounted path on virtio device or unmounted device
+ cmd = fio_cmd % { 'size': write_size, 'filename': write_file }
+ cmd = cmd + "; echo $?"
+ raw = vm.guest_command(cmd, timeout=None).split('\n')
+ rc = filter(lambda x: len(x) > 0, raw)[-1]
+ return int(rc)
+
+
+def main(args):
+ err = 1
+ virtio_device = 0
+
+ #for a in args:
+ # if 'file=' in a: and 'if=virtio' in a:
+ # virtio_device=1
+ # break;
+
+
+ # scan args for virtio blk device
+ # create one and attach if not present
+ vm = launch(*args)
+
+ try:
+ err = run_test(vm)
+ finally:
+ vm.shutdown()
+ vm.wait_for_shutdown()
+ vm.wait_for_quit()
+
+ return err
+
+
+if __name__ == '__main__':
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except TimeoutException, e:
+ print 'Timeout occurred waiting for %s' % e.message
+ sys.exit(1)