Moved all temporary files into a single work directory to clean up.
[ovirt-node-image.git] / autotest.sh
blobb0f0ff9b4a86030c545104a0e9f5f6be11c4fa43
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 WORKDIR=$(mktemp -d)
44 warn() { printf '%s: %s\n' "$ME" "$*" >&2; }
45 die() { warn "$*"; exit 1; }
46 debug() { if $debugging; then log "[DEBUG] %s" "$*"; fi }
48 trap '__st=$?; cleanup_after_testing; exit $__st' 1 2 3 13 15
49 trap 'cleanup_after_testing' 0
51 # set -e
52 # set -u
54 log () {
55 date=$(date)
56 printf "${date} $*\n"
59 usage () {
60 cat <<EOF
61 Usage: $ME [-n test_name] [LOGFILE]
62 -i: set the ISO filename (defualt: ovirt-node-image.iso)
63 -n: the name of the specific autotest to run (default: run all autotests)
64 -d: enable more verbose output (default: disabled)
65 -v: enable tracing (default: disabled)
66 -w: launch virt-viewer for each VM (default: no window shown)
67 -h: display this help and exit
68 EOF
71 # $1 - the test function to call
72 execute_test () {
73 local testname=$1
75 if [ -z $testname ]; then die "Missing test name"; fi
77 log "Executing test: $testname"
79 eval $testname
81 rc=$?
82 log "Completed test: $testname [result=$rc]"
84 if [ $rc -ne 0 ]; then
85 log "Build fails smoke tests."
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="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=$WORKDIR/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=$WORKDIR/$nodename-$ifacename.xml
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=$WORKDIR/$nodename.xml
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=$WORKDIR/$nodename-hd.img
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="${tests} $1"
413 # $1 - node name
414 start_virt_viewer () {
415 local nodename=$1
417 sudo virt-viewer $nodename > /dev/null 2>&1&
420 # $1 - the node's name
421 # $2 - kernel arguments
422 # $3 - working directory
423 boot_with_pxe () {
424 local nodename=$1
425 local kernel_args=$2
426 local workdir=$3
428 debug "boot_with_pxe ()"
429 debug "- workdir: ${workdir}"
430 debug "- nodename: ${nodename}"
431 debug "- kernel_args: ${kernel_args}"
433 setup_pxeboot $workdir "${kernel_args}"
435 sudo virsh start $nodename > /dev/null 2>&1
436 if $show_viewer; then
437 start_virt_viewer $nodename
441 # $1 - the node's name
442 boot_from_hd () {
443 local nodename=$1
445 debug "boot_from_hd ()"
446 debug "::nodename: ${nodename}"
448 sudo virsh start $nodename > /dev/null 2>&1
449 if $show_viewer; then
450 start_virt_viewer $nodename
454 # $1 - the node name
455 # $2 - the old boot device
456 # $3 - the new boot device
457 substitute_boot_device () {
458 local nodename=$1
459 local old_device=$2
460 local new_device=$3
461 local new_node_file=$WORKDIR/$nodename-new.xml
463 if [ -n "${nodename}" ]; then
464 local xml=$(sudo virsh dumpxml $nodename | sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/")
466 sudo printf "${xml}" > $new_node_file
468 sudo virsh define $new_node_file
472 add_test "test_stateless_pxe"
473 test_stateless_pxe () {
474 local nodename="${vm_prefix}-stateless-pxe"
475 local workdir=$WORKDIR
477 start_networking $nodename $IFACE_NAME false true $workdir
479 configure_node "${nodename}" "network" "" "10000" "" "local noapic=true"
480 boot_with_pxe "${nodename}" "standalone firstboot=no" "${workdir}"
482 expect -c '
483 set timeout 120
485 log_file -noappend stateless-pxe.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 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
495 timeout {
496 send_log "\nTimeout waiting for marker..\n\n"
497 exit 1
498 } eof {
499 send_log "Unexpected end of file."
500 exit 2
504 send_log "\n\nUnexpected end of interaction.\n\n"
505 exit 3'
506 result=$?
508 destroy_node $nodename
509 stop_networking $IFACE_NAME true
511 return $result
514 add_test "test_stateless_pxe_with_nohd"
515 test_stateless_pxe_with_nohd () {
516 local nodename="${vm_prefix}-stateless-pxe-nohd"
517 local workdir=$WORKDIR
519 start_networking $nodename $IFACE_NAME false true $workdir
521 configure_node "${nodename}" "network" "" "" "" "local noapic=true"
522 boot_with_pxe "${nodename}" "firstboot=no" "${workdir}"
524 expect -c '
525 set timeout 120
527 log_file -noappend stateless-pxe.log
529 spawn sudo virsh console '"${nodename}"'
531 expect {
532 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
533 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
534 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
535 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
536 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
537 timeout {
538 send_log "\nTimeout waiting for marker..\n\n"
539 exit 1
540 } eof {
541 send_log "Unexpected end of file."
542 exit 2
546 send_log "\n\nUnexpected end of interaction.\n\n"
547 exit 3'
549 result=$?
551 destroy_node $nodename
552 stop_networking $IFACE_NAME true
554 return $result
557 add_test "test_stateful_pxe"
558 test_stateful_pxe () {
559 local nodename="${vm_prefix}-stateful-pxe"
560 local workdir=$WORKDIR
561 local ipaddress=${NODE_ADDRESS}
563 for var in nodename workdir ipaddress; do
564 eval debug "::\$$var: $var"
565 done
567 start_networking $nodename $IFACE_NAME false true $workdir
569 configure_node "${nodename}" "network" "" "10000" "" "local noapic=true"
570 boot_with_pxe "${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress}" ${workdir}
572 # verify the booting and installation
573 expect -c '
574 set timeout 120
575 log_file -noappend stateful-pxe.log
577 spawn sudo virsh console '"${nodename}"'
579 expect {
580 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
581 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
582 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
583 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
584 -exact "Starting ovirt-firstpost:" { send_log "\n\nMarker 5\n\n"; exp_continue }
585 -exact "Starting partitioning of /dev/vda" { send_log "\n\nMarker 6\n\n"; exp_continue }
586 -exact "Restarting system" { send_log "\n\nMarker 7\n\n"; exit }
587 timeout {
588 send_log "\nTimeout waiting for marker..\n\n"
589 exit 1
590 } eof {
591 send_log "Unexpected end of file."
592 exit 2
596 send_log "\n\nUnexpected end of interaction.\n\n"
597 exit 3'
598 result=$?
600 # only continue if we're in a good state
601 if [ $result -eq 0 ]; then
602 destroy_node "${nodename}" false
603 substitute_boot_device "${nodename}" "network" "hd"
604 boot_from_hd "${nodename}"
606 expect -c '
607 set timeout 120
608 log_file stateful-pxe.log
610 send_log "Restarted node, booting from hard disk.\n"
612 spawn sudo virsh console '"${nodename}"'
614 expect {
615 -re "localhost.*login:" { send_log "\n\nLogin marker found\n\n"; exit }
617 timeout {
618 send_log "\nMarker not found.\n\n"
619 exit 1
620 } eof {
621 send_log "Unexpected end of file."
622 exit 2
626 send_log "\n\nUnexpected end of interaction.\n\n"
628 exit 3
631 expect -c '
632 set timeout 3
633 log_file stateful-pxe.log
635 spawn ping -c 3 '"${ipaddress}"'
637 expect {
638 -exact "64 bytes from '"${ipaddress}"'" { send_log "\n\nGot ping response!\n"; send_log "\n\nNetworking verified!\n"; exit }
640 timeout {
641 send_log "\nMarker not found.\n\n"
642 exit 1
643 } eof {
644 send_log "Unexpected end of file."
645 exit 2
649 send_log "\n\nUnexpected end of interaction.\n\n"
651 exit 3'
653 result=$?
656 destroy_node $nodename
657 stop_networking $IFACE_NAME true
659 return $result
663 # configures the environment for testing
664 setup_for_testing () {
665 debug "WORKDIR=${WORKDIR}"
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 debug "Cleaning up"
680 stop_dnsmasq
681 stop_networking
682 # destroy any running vms
683 vm_list=$(sudo virsh list --all | awk '/'${vm_prefix}-'/ { print $2 }')
684 test -n "$vm_list" && for vm in $vm_list; do
685 destroy_node $vm
686 done
687 stop_networking
688 rm -rf $WORKDIR
691 # check commandline options
692 test=''
693 debugging=false
694 isofile="${PWD}/ovirt-node-image.iso"
695 show_viewer=false
696 vm_prefix="$$"
698 while getopts di:n:vwh c; do
699 case $c in
700 d) debugging=true;;
701 i) isofile=($OPTARG);;
702 n) tests=($OPTARG);;
703 v) set -v;;
704 w) show_viewer=true;;
705 h) usage; exit 0;;
706 '?') die "invalid option \`-$OPTARG'";;
707 :) die "missing argument to \`-$OPTARG' option";;
708 *) die "internal error";;
709 esac
710 done
712 isoname=$(basename $isofile)
713 isofile="$(cd `dirname $isofile`; pwd)/${isoname}"
715 shift $(($OPTIND - 1))
717 set +u
718 if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi
719 set -u
721 result_file=$WORKDIR/results.log
722 debug "result_file=${result_file}"
724 log "Logging results to file: ${RESULTS}"
726 setup_for_testing
728 log "Begin Testing: ${isoname}"
729 log "Tests: ${tests}"
731 for test in ${tests}; do
732 execute_test $test
733 result=$?
735 if [ $result != 0 ]; then
736 echo "${result}" > $result_file
737 break
739 done
741 log "End Testing: ${isoname}"
743 } | sudo tee --append $RESULTS
745 if [ -s "$result_file" ]; then
746 exit $(cat $result_file)