Added new autotests and refactored the autotest script.
[ovirt-node-image.git] / autotest.sh
blob12d3e3040aea19cf8ecdb2747a5f1a1c23bc7813
1 #!/bin/bash
3 # oVirt node image autotest script
5 # Copyright (C) 2009 Red Hat, Inc.
6 # Written by Darryl L. Pierce <dpierce@redhat.com>
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; version 2 of the License.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 # MA 02110-1301, USA. A copy of the GNU General Public License is
21 # also available at http://www.gnu.org/copyleft/gpl.html.
23 # To include autotesting on the build system, you need to insert the
24 # following snippet *BEFORE* the text that reads "Output Stages":
25 # ---8<[begin]---
26 # # Integration test
27 # {
28 # name = integration
29 # label = Test group
30 # module = Test::AutoBuild::Stage::Test
31 # # Don't abort entire cycle if the module test fails
32 # critical = 0
33 # }
34 # ---8<[end]---
36 # This will, for each module whose autobuild.sh is run, to have a matching
37 # autotest.sh to run as well.
39 # To run these tests locally, you will need to open port 69 TCP and UDP and have
40 # an ISO file.
42 ME=$(basename "$0")
43 warn() { printf '%s: %s\n' "$ME" "$*" >&2; }
44 die() { warn "$*"; exit 1; }
45 debug() { if $debugging; then log "[DEBUG] %s" "$*"; fi }
47 trap '__st=$?; cleanup_after_testing; exit $__st' 1 2 3 13 15
48 trap 'cleanup_after_testing' 0
50 # set -e
51 # set -u
53 log () {
54 date=$(date)
55 printf "${date} $*\n"
58 usage () {
59 cat <<EOF
60 Usage: $ME [-n test_name] [LOGFILE]
61 -i: set the ISO filename (defualt: ovirt-node-image.iso)
62 -n: the name of the specific autotest to run (default: run all autotests)
63 -d: enable more verbose output (default: disabled)
64 -v: enable tracing (default: disabled)
65 -w: launch virt-viewer for each VM (default: no window shown)
66 -h: display this help and exit
67 EOF
70 # $1 - the test function to call
71 execute_test () {
72 local testname=$1
74 if [ -z $testname ]; then die "Missing test name"; fi
76 log "Executing test: $testname"
78 eval $testname
80 rc=$?
81 log "Completed test: $testname [result=$rc]"
83 if [ $rc -ne 0 ]; then
84 log "Build fails smoke tests."
85 exit 1
88 return $rc
91 # setup a node for pxeboot
92 # $1 - the working directory
93 # $2 - kernel arguments; if present then they replace all default flags
94 setup_pxeboot () {
95 local workdir=$1
96 local kernelargs=$2
97 local pxedefault=$workdir/tftpboot/pxelinux.cfg/default
99 debug "setup for pxeboot: isofile=${isofile} workdir=${workdir} kernelargs='${kernelargs}' pxedefault=${pxedefault}"
100 (cd $workdir && sudo livecd-iso-to-pxeboot $isofile) > /dev/null 2>&1
101 sudo chmod -R 777 $workdir
103 # set default kernel arguments if none were provided
104 # the defaults boot in standalone mode
105 if [ -z "$kernelargs" ]; then
106 kernelargs="ovirt_standalone"
109 local definition="DEFAULT pxeboot"
110 definition="${definition}\nTIMEOUT 20"
111 definition="${definition}\nPROMPT 0"
112 definition="${definition}\nLABEL pxeboot"
113 definition="${definition}\n KERNEL vmlinuz0"
114 definition="${definition}\n IPAPPEND 2"
115 definition="${definition}\n APPEND rootflags=loop initrd=initrd0.img root=/${isoname} rootfstype=auto console=tty0 check console=ttyS0,115200n8 $kernelargs"
117 debug "pxeboot definition=\n${definition}"
118 sudo bash -c "printf \"${definition}\" > $pxedefault"
121 # Starts a simple instance of dnsmasq.
122 # $1 - the iface on which dnsmasq works
123 # $2 - the root for tftp files
124 # $3 - the mac address for the node (ignored if blank)
125 # $4 - the nodename
126 start_dnsmasq () {
127 local iface=$1
128 local tftproot=$2
129 local macaddress=$3
130 local nodename=$4
131 local pidfile=$2/dnsmasq.pid
133 stop_dnsmasq
134 debug "Starting dnsmasq"
135 dns_startup="sudo /usr/sbin/dnsmasq --read-ethers
136 --dhcp-range=${NETWORK}.100,${NETWORK}.254,255.255.255.0,24h
137 --conf-file=
138 --interface=${iface}
139 --bind-interfaces
140 --except-interface=lo
141 --dhcp-boot=tftpboot/pxelinux.0
142 --enable-tftp
143 --tftp-root=${tftproot}
144 --log-facility=/tmp/dnsmasq-${nodename}.log
145 --log-queries
146 --log-dhcp
147 --pid-file=${pidfile}"
148 if [ -n "$macaddress" ]; then
149 dns_startup="${dns_startup} --dhcp-host=${macaddress},${NODE_ADDRESS}"
151 # start dnsmasq
152 eval $dns_startup
153 debug "pidfile=$pidfile"
154 DNSMASQ_PID=$(sudo cat $pidfile)
155 debug "DNSMASQ_PID=${DNSMASQ_PID}"
158 # Kills the running instance of dnsmasq.
159 stop_dnsmasq () {
160 if [ -n "${DNSMASQ_PID-}" -a "${DNSMASQ_PID-}" != "0" ]; then
161 local check=$(ps -ef | awk "/${DNSMASQ_PID}/"' { if ($2 ~ '"${DNSMASQ_PID}"') print $2 }')
163 if [[ "${check}" == "${DNSMASQ_PID}" ]]; then
164 sudo kill -9 $DNSMASQ_PID
165 return
168 DNSMASQ_PID="0"
171 # Creates a virt network.
172 # $1 - the node name
173 # $2 - the network interface name
174 # $3 - use DHCP (any value)
175 # $4 - start dnsmsq (def. false)
176 start_networking () {
177 local nodename=$1
178 local ifacename=$2
179 local use_dhcp=${3-false}
180 local start_dnsmasq=${4-false}
181 local workdir=$5
182 local definition=""
183 local network=$NETWORK
184 local xmlfile=$(mktemp)
186 debug "start_networking ()"
187 for var in nodename ifacename use_dhcp start_dnsmasq workdir network xmlfile; do
188 eval debug "::$var: \$$var"
189 done
191 definition="<network>\n<name>${ifacename}</name>\n<forward mode='nat' />\n<bridge name='${ifacename}' stp='on' forwardDelay='0' />"
192 definition="${definition}\n<ip address='${network}.1' netmask='255.255.255.0'>"
193 if $use_dhcp; then
194 definition="${definition}\n<dhcp>\n<range start='${network}.100' end='${network}.199' />\n</dhcp>"
196 definition="${definition}\n</ip>\n</network>"
198 debug "Saving network definition file to: ${xmlfile}\n"
199 sudo printf "${definition}" > $xmlfile
200 sudo virsh net-define $xmlfile > /dev/null 2>&1
201 debug "Starting network."
202 sudo virsh net-start $ifacename > /dev/null 2>&1
204 if [ "${use_dhcp}" == "false" ]; then
205 if $start_dnsmasq; then
206 start_dnsmasq $ifacename $workdir "" $nodename
211 # Destroys the test network interface
212 # $1 - the network name
213 # $2 - stop dnsmasq (def. false)
214 stop_networking () {
215 local networkname=${1-}
216 local stop_dnsmasq=${2-true}
218 # if no network was supplied, then check for the global network
219 if [ -z "$networkname" ]; then
220 networkname=${NETWORK_NAME-}
223 if [ -n "${networkname}" ]; then
224 debug "Destroying network interface: ${networkname}"
225 check=$(sudo virsh net-list --all)
226 if [[ "${check}" =~ "${networkname}" ]]; then
227 if [[ "{$check}" =~ active ]]; then
228 sudo virsh net-destroy $networkname > /dev/null 2>&1
230 sudo virsh net-undefine $networkname > /dev/null 2>&1
234 if $stop_dnsmasq; then
235 stop_dnsmasq
239 # creates a HD disk file
240 # $1 - filename for disk file
241 # $2 - size (##M or ##G)
242 create_hard_disk () {
243 local filename=$1
244 local size=$2
246 debug "Creating hard disk: filename=${filename} size=${size}"
247 sudo qemu-img create -f raw $filename "${size}M" > /dev/null 2>&1
248 sudo chcon -t virt_image_t $filename > /dev/null 2>&1
251 # Creates the XML for a virtual machine.
252 # $1 - the file to write the xml
253 # $2 - the node name
254 # $3 - memory size (in kb)
255 # $4 - boot device
256 # $5 - the local hard disk (if blank then no disk is used)
257 # $6 - the cdrom disk (if blank then no cdrom is used)
258 # $7 - the network bridge (if blank then 'default' is used)
259 # $8 - optional arguments
260 define_node () {
261 local filename=$1
262 local nodename=$2
263 local memory=$3
264 local boot_device=$4
265 local harddrive=$5
266 local cddrive=$6
267 local bridge=${7-default}
268 local options=${8-}
269 local result=""
271 # flexible options
272 # define defaults, then allow the caller to override them as needed
273 local arch=$(uname -i)
274 local emulator=$(which qemu-kvm)
275 local serial="true"
276 local vncport="-1"
277 local bootdev='hd'
279 # first destroy the node
280 destroy_node $nodename
282 if [ -n "$options" ]; then eval "$options"; fi
284 debug "define_node ()"
285 for var in filename nodename memory harddrive cddrive bridge options arch emulator serial vncport bootdev; do
286 eval debug "::$var: \$$var"
287 done
289 result="<domain type='kvm'>\n<name>${nodename}</name>\n<memory>${memory}</memory>\n <vcpu>1</vcpu>"
291 # begin the os section
292 # inject the boot device
293 result="${result}\n<os>\n<type arch='${arch}' machine='pc'>hvm</type>"
294 result="${result}\n<boot dev='${boot_device}' />"
295 result="${result}\n</os>"
297 # virtual machine features
298 result="${result}\n<features>"
299 result="${result}\n<acpi />"
300 if [ -z "${noapic-}" ]; then result="${result}\n<apic />"; fi
301 result="${result}\n<pae /></features>"
302 result="${result}\n<clock offset='utc' />"
303 result="${result}\n<on_poweroff>destroy</on_poweroff>"
304 result="${result}\n<on_reboot>restart</on_reboot>"
305 result="${result}\n<on_crash>restart</on_crash>"
307 # add devices
308 result="${result}\n<devices>"
309 result="${result}\n<emulator>${emulator}</emulator>"
310 # inject the hard disk if defined
311 if [ -n "$harddrive" ]; then
312 debug "Adding a hard drive to the node"
313 result="${result}\n<disk type='file' device='disk'>"
314 result="${result}\n<source file='$harddrive' />"
315 result="${result}\n<target dev='vda' bus='virtio' />"
316 result="${result}\n</disk>"
318 # inject the cdrom drive if defined
319 if [ -n "$cddrive" ]; then
320 debug "Adding a CDROM drive to the node"
321 result="${result}\n<disk type='file' device='cdrom'>"
322 result="${result}\n<source file='${cddrive}' />"
323 result="${result}\n<target dev='hdc' bus='ide' />"
324 result="${result}\n</disk>"
326 # inject the bridge network
327 result="${result}\n<interface type='network'>"
328 result="${result}\n<source network='${bridge}' />"
329 result="${result}\n</interface>"
330 # inject the serial port
331 if [ -n "$serial" ]; then
332 result="${result}\n<serial type='pty' />"
334 # inject the vnc port
335 if [ -n "$vncport" ]; then
336 result="${result}\n<console type='pty' />"
337 result="${result}\n<graphics type='vnc' port='${vncport}' autoport='yes' keyman='en-us' />"
339 # finish the device section
340 result="${result}\n</devices>"
342 result="${result}\n</domain>"
344 debug "Node definition: ${filename}"
345 sudo printf "$result" > $filename
347 # now define the vm
348 sudo virsh define $filename > /dev/null 2>&1
350 if [ $? != 0 ]; then die "Unable to define virtual machine: $nodename"; fi
353 # $1 - the node name
354 # $2 - the boot device (def. "hd")
355 # $3 - the memory size in kb (def. 524288)
356 # $4 - hard disk size (if blank then no hard disk)
357 # $5 - the cd drive image file (if blank then no cd drive)
358 # $6 - option arguments
359 configure_node () {
360 local nodename=$1
361 local boot_device=$2
362 local memory=$3
363 local hdsize=$4
364 local hdfile=""
365 local cdfile=$5
366 local args=$6
367 local nodefile=$(mktemp)
369 if [ -z "${boot_device}" ]; then boot_device="hd"; fi
370 if [ -z "${memory}" ]; then memory="524288"; fi
372 debug "configure_node ()"
373 for var in nodename boot_device memory hdsize hdfile cdfile args nodefile; do
374 eval debug "::$var: \$$var"
375 done
377 # create the hard disk file
378 if [ -n "${hdsize}" ]; then
379 hdfile=$(mktemp)
380 create_hard_disk $hdfile $hdsize
383 define_node $nodefile $nodename "${memory}" "${boot_device}" "${hdfile}" "${cdfile}" $IFACE_NAME "${args}"
386 # $1 - the node name
387 # $2 - undefine the node (def. true)
388 destroy_node () {
389 local nodename=$1
390 local undefine=${2-true}
392 if [ -n "${nodename}" ]; then
393 check=$(sudo virsh list --all)
394 if [[ "${check}" =~ "${nodename}" ]]; then
395 if [[ "${check}" =~ running ]]; then
396 sudo virsh destroy $nodename > /dev/null 2>&1
398 if $undefine; then
399 sudo virsh undefine $nodename > /dev/null 2>&1
405 # for each test created, add it to the follow array:
406 tests=''; testcount=0;
408 # $1 - test name
409 add_test () {
410 tests[$testcount]=$1
411 testcount=$testcount+1
414 # $1 - node name
415 start_virt_viewer () {
416 local nodename=$1
418 sudo virt-viewer $nodename > /dev/null 2>&1&
421 # $1 - the node's name
422 # $2 - kernel arguments
423 # $3 - working directory
424 boot_with_pxe () {
425 local nodename=$1
426 local kernel_args=$2
427 local workdir=$3
429 debug "boot_with_pxe ()"
430 debug "- workdir: ${workdir}"
431 debug "- nodename: ${nodename}"
432 debug "- kernel_args: ${kernel_args}"
434 setup_pxeboot $workdir "${kernel_args}"
436 sudo virsh start $nodename > /dev/null 2>&1
437 if $show_viewer; then
438 start_virt_viewer $nodename
442 # $1 - the node's name
443 boot_from_hd () {
444 local nodename=$1
446 debug "boot_from_hd ()"
447 debug "::nodename: ${nodename}"
449 sudo virsh start $nodename > /dev/null 2>&1
450 if $show_viewer; then
451 start_virt_viewer $nodename
455 # $1 - the node name
456 # $2 - the old boot device
457 # $3 - the new boot device
458 substitute_boot_device () {
459 local nodename=$1
460 local old_device=$2
461 local new_device=$3
462 local new_node_file=$(mktemp)
464 if [ -n "${nodename}" ]; then
465 local xml=$(sudo virsh dumpxml $nodename | sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/")
467 sudo printf "${xml}" > $new_node_file
469 sudo virsh define $new_node_file
473 add_test "test_stateless_pxe_with_nohd"
474 test_stateless_pxe_with_nohd () {
475 local nodename="${vm_prefix}-stateless-pxe-nohd"
476 local workdir=$(mktemp -d)
478 start_networking $nodename $IFACE_NAME false true $workdir
480 configure_node "${nodename}" "network" "" "" "" "local noapic=true"
481 boot_with_pxe "${nodename}" "firstboot=no" "${workdir}"
483 expect -c '
484 set timeout 120
485 log_file -noappend stateless-pxe-nohd.log
487 spawn sudo virsh console '"${nodename}"'
489 expect {
490 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
491 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
492 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
493 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
494 -exact "login:" { send_log "\n\nMarker 5\n\n"; exit }
495 timeout {
496 send_log "\nMarker not found.\n\n"
497 exit 1
498 } eof {
499 send_log "Unexpected end of file."
500 exit 2
503 send_log \"\n\nUnexpected end of interaction.\n\n\"
504 exit 3
508 result=$?
510 destroy_node $nodename
511 stop_networking $IFACE_NAME true
513 return $result
516 add_test "test_stateless_pxe"
517 test_stateless_pxe () {
518 local nodename="${vm_prefix}-stateless-pxe"
519 local workdir=$(mktemp -d)
521 start_networking $nodename $IFACE_NAME false true $workdir
523 configure_node "${nodename}" "network" "" "10000" "" "local noapic=true"
524 boot_with_pxe "${nodename}" "firstboot=no" "${workdir}"
526 expect -c '
527 set timeout 120
529 log_file -noappend stateless-pxe.log
531 spawn sudo virsh console '"${nodename}"'
533 expect {
534 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
535 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
536 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
537 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
538 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
539 timeout {
540 send_log "\nMarker not found.\n\n"
541 exit 1
542 } eof {
543 send_log "Unexpected end of file."
544 exit 2
548 send_log "\n\nUnexpected end of interaction.\n\n"
549 exit 3'
550 result=$?
552 destroy_node $nodename
553 stop_networking $IFACE_NAME true
555 return $result
558 add_test "test_stateful_pxe"
559 test_stateful_pxe () {
560 local nodename="${vm_prefix}-stateful-pxe"
561 local workdir=$(mktemp -d)
562 local ipaddress=${NODE_ADDRESS}
564 for var in nodename workdir ipaddress; do
565 eval debug "::\$$var: $var"
566 done
568 start_networking $nodename $IFACE_NAME false true $workdir
570 configure_node "${nodename}" "network" "" "10000" "" "local noapic=true"
571 boot_with_pxe "${nodename}" "ovirt_standalone ovirt_init=/dev/vda ovirt_local_boot ip=${ipaddress}" ${workdir}
573 # verify the booting and installation
574 expect -c '
575 set timeout 120
576 log_file -noappend stateful-pxe.log
578 spawn sudo virsh console '"${nodename}"'
580 expect {
581 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
582 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
583 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
584 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
585 -exact "Starting ovirt-firstpost:" { send_log "\n\nMarker 5\n\n"; exp_continue }
586 -exact "Starting partitioning of /dev/vda" { send_log "\n\nMarker 6\n\n"; exp_continue }
587 -exact "Restarting system" { send_log "\n\nMarker 7\n\n"; exit }
588 timeout {
589 send_log "\nMarker not found.\n\n"
590 exit 1
591 } eof {
592 send_log "Unexpected end of file."
593 exit 2
597 send_log "\n\nUnexpected end of interaction.\n\n"
598 exit 3'
599 result=$?
601 # only continue if we're in a good state
602 if [ $result -eq 0 ]; then
603 destroy_node "${nodename}" false
604 substitute_boot_device "${nodename}" "network" "hd"
605 boot_from_hd "${nodename}"
607 expect -c '
608 set timeout 120
609 log_file stateful-pxe.log
611 send_log "Restarted node, booting from hard disk.\n"
613 spawn sudo virsh console '"${nodename}"'
615 expect {
616 -re "localhost.*login:" { send_log "\n\nLogin marker found\n\n"; exit }
618 timeout {
619 send_log "\nMarker not found.\n\n"
620 exit 1
621 } eof {
622 send_log "Unexpected end of file."
623 exit 2
627 send_log "\n\nUnexpected end of interaction.\n\n"
629 exit 3
632 expect -c '
633 set timeout 3
634 log_file stateful-pxe.log
636 spawn ping -c 3 '"${ipaddress}"'
638 expect {
639 -exact "64 bytes from '"${ipaddress}"'" { send_log "\n\nGot ping response!\n"; send_log "\n\nNetworking verified!\n"; exit }
641 timeout {
642 send_log "\nMarker not found.\n\n"
643 exit 1
644 } eof {
645 send_log "Unexpected end of file."
646 exit 2
650 send_log "\n\nUnexpected end of interaction.\n\n"
652 exit 3'
654 result=$?
657 destroy_node $nodename
658 stop_networking $IFACE_NAME true
660 return $result
664 # configures the environment for testing
665 setup_for_testing () {
666 debug "isofile=${isofile}"
667 debug "isoname=${isoname}"
668 IFACE_NAME=testbr$$
669 debug "IFACE_NAME=${IFACE_NAME}"
670 NETWORK=192.168.$(echo "scale=0; print $$ % 255" | bc -l)
671 debug "NETWORK=${NETWORK}"
672 NODE_ADDRESS=$NETWORK.100
673 debug "NODE_ADDRESS=${NODE_ADDRESS}"
674 DNSMASQ_PID=0
677 # cleans up any loose ends
678 cleanup_after_testing () {
679 stop_dnsmasq
680 stop_networking
681 # destroy any running vms
682 vm_list=$(sudo virsh list --all | awk '/'${vm_prefix}-'/ { print $2 }')
683 test -n "$vm_list" && for vm in $vm_list; do
684 destroy_node $vm
685 done
688 # check commandline options
689 test=''
690 debugging=false
691 isofile="${PWD}/ovirt-node-image.iso"
692 show_viewer=false
693 vm_prefix="$$"
695 while getopts di:n:vwh c; do
696 case $c in
697 d) debugging=true;;
698 i) isofile=($OPTARG);;
699 n) tests=($OPTARG);;
700 v) set -v;;
701 w) show_viewer=true;;
702 h) usage; exit 0;;
703 '?') die "invalid option \`-$OPTARG'";;
704 :) die "missing argument to \`-$OPTARG' option";;
705 *) die "internal error";;
706 esac
707 done
709 isoname=$(basename $isofile)
710 isofile="$(cd `dirname $isofile`; pwd)/${isoname}"
712 shift $(($OPTIND - 1))
714 set +u
715 if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi
716 set -u
718 log "Logging results to file: ${RESULTS}"
720 setup_for_testing
722 log "Begin Testing: ${isoname}"
724 for test in ${tests}; do
725 execute_test $test
726 done
728 log "End Testing: ${isoname}"
729 } | sudo tee --append $RESULTS