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":
30 # module = Test::AutoBuild::Stage::Test
31 # # Don't abort entire cycle if the module test fails
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
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
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 -t: change the timeout between markers (in ms, default: 120)
66 -v: enable tracing (default: disabled)
67 -w: launch virt-viewer for each VM (default: no window shown)
68 -h: display this help and exit
72 # $1 - the test function to call
76 if [ -z $testname ]; then die
"Missing test name"; fi
78 log
"Executing test: $testname"
83 log
"Completed test: $testname [result=$rc]"
85 if [ $rc -ne 0 ]; then
86 log
"Build fails smoke tests."
92 # setup a node for pxeboot
93 # $1 - the working directory
94 # $2 - kernel arguments; if present then they replace all default flags
98 local pxedefault
=$workdir/tftpboot
/pxelinux.cfg
/default
100 debug
"setup for pxeboot: isofile=${isofile} workdir=${workdir} kernelargs='${kernelargs}' pxedefault=${pxedefault}"
101 (cd $workdir && sudo livecd-iso-to-pxeboot
$isofile) > /dev
/null
2>&1
102 sudo
chmod -R 777 $workdir
104 # set default kernel arguments if none were provided
105 # the defaults boot in standalone mode
106 if [ -z "$kernelargs" ]; then
107 kernelargs
="standalone"
110 local definition
="DEFAULT pxeboot"
111 definition
="${definition}\nTIMEOUT 20"
112 definition
="${definition}\nPROMPT 0"
113 definition
="${definition}\nLABEL pxeboot"
114 definition
="${definition}\n KERNEL vmlinuz0"
115 definition
="${definition}\n IPAPPEND 2"
116 definition
="${definition}\n APPEND rootflags=loop initrd=initrd0.img root=/${isoname} rootfstype=auto console=tty0 check console=ttyS0,115200n8 $kernelargs"
118 debug
"pxeboot definition=\n${definition}"
119 sudo bash
-c "printf \"${definition}\" > $pxedefault"
122 # Starts a simple instance of dnsmasq.
123 # $1 - the iface on which dnsmasq works
124 # $2 - the root for tftp files
125 # $3 - the mac address for the node (ignored if blank)
132 local pidfile
=$2/dnsmasq.pid
135 debug
"Starting dnsmasq"
136 dns_startup
="sudo /usr/sbin/dnsmasq --read-ethers
137 --dhcp-range=${NETWORK}.100,${NETWORK}.254,255.255.255.0,24h
141 --except-interface=lo
142 --dhcp-boot=tftpboot/pxelinux.0
144 --tftp-root=${tftproot}
145 --log-facility=$WORKDIR/dnsmasq-${nodename}.log
148 --pid-file=${pidfile}"
149 if [ -n "$macaddress" ]; then
150 dns_startup
="${dns_startup} --dhcp-host=${macaddress},${NODE_ADDRESS}"
154 debug
"pidfile=$pidfile"
155 DNSMASQ_PID
=$
(sudo
cat $pidfile)
156 debug
"DNSMASQ_PID=${DNSMASQ_PID}"
159 # Kills the running instance of dnsmasq.
161 if [ -n "${DNSMASQ_PID-}" -a "${DNSMASQ_PID-}" != "0" ]; then
162 local check
=$
(ps
-ef |
awk "/${DNSMASQ_PID}/"' { if ($2 ~ '"${DNSMASQ_PID}"') print $2 }')
164 if [[ "${check}" == "${DNSMASQ_PID}" ]]; then
165 sudo kill -9 $DNSMASQ_PID
172 # Creates a virt network.
174 # $2 - the network interface name
175 # $3 - use DHCP (any value)
176 # $4 - start dnsmsq (def. false)
177 start_networking () {
180 local use_dhcp=${3-false}
181 local start_dnsmasq=${4-false}
184 local network=$NETWORK
185 local xmlfile=$WORKDIR/$nodename-$ifacename.xml
187 debug "start_networking
()"
188 for var in nodename ifacename use_dhcp start_dnsmasq workdir network xmlfile; do
189 eval debug "::$var: \$
$var"
192 definition="<network
>\n<name
>${ifacename}</name
>\n<forward mode
='nat' />\n<bridge name
='${ifacename}' stp
='on' forwardDelay
='0' />"
193 definition="${definition}\n<ip address
='${network}.1' netmask
='255.255.255.0'>"
195 definition="${definition}\n<dhcp>\n<range start='${network}.100' end='${network}.199' />\n</dhcp>"
197 definition="${definition}\n</ip>\n</network>"
199 debug "Saving network definition file to: ${xmlfile}\n"
200 sudo printf "${definition}" > $xmlfile
201 sudo virsh net-define $xmlfile > /dev/null 2>&1
202 debug "Starting network."
203 sudo virsh net-start $ifacename > /dev/null 2>&1
205 if [ "${use_dhcp}" == "false" ]; then
206 if $start_dnsmasq; then
207 start_dnsmasq $ifacename $workdir "" $nodename
212 # Destroys the test network interface
213 # $1 - the network name
214 # $2 - stop dnsmasq (def. false)
216 local networkname=${1-}
217 local stop_dnsmasq=${2-true}
219 # if no network was supplied, then check for the global network
220 if [ -z "$networkname" ]; then
221 networkname=${NETWORK_NAME-}
224 # exit if preserve was enabled
225 if $preserve_vm; then return; fi
227 if [ -n "${networkname}" ]; then
228 debug "Destroying network interface: ${networkname}"
229 check=$(sudo virsh net-list --all)
230 if [[ "${check}" =~ "${networkname}" ]]; then
231 if [[ "{$check}" =~ active ]]; then
232 sudo virsh net-destroy $networkname > /dev/null 2>&1
234 sudo virsh net-undefine $networkname > /dev/null 2>&1
238 if $stop_dnsmasq; then
243 # creates a HD disk file
244 # $1 - filename for disk file
245 # $2 - size (##M or ##G)
246 create_hard_disk () {
250 debug "Creating hard disk: filename=${filename} size=${size}"
251 sudo qemu-img create -f raw $filename "${size}M" > /dev/null 2>&1
252 sudo chcon -t virt_image_t $filename > /dev/null 2>&1
255 # Creates the XML for a virtual machine.
256 # $1 - the file to write the xml
258 # $3 - memory size (in kb)
260 # $5 - the local hard disk (if blank then no disk is used)
261 # $6 - the cdrom disk (if blank then no cdrom is used)
262 # $7 - the network bridge (if blank then 'default
' is used)
263 # $8 - optional arguments
271 local bridge=${7-default}
276 # define defaults, then allow the caller to override them as needed
277 local arch=$(uname -i)
282 # first destroy the node
283 destroy_node $nodename
285 if [ -n "$options" ]; then eval "$options"; fi
287 debug "define_node ()"
288 for var in filename nodename memory harddrive cddrive bridge options arch serial vncport bootdev; do
289 eval debug "::$var: \$$var"
292 result="<domain type='kvm
'>\n<name>${nodename}</name>\n<memory>${memory}</memory>\n <vcpu>1</vcpu>"
294 # begin the os section
295 # inject the boot device
296 result="${result}\n<os>\n<type arch='${arch}' machine='pc
'>hvm</type>"
297 result="${result}\n<boot dev='${boot_device}' />"
298 result="${result}\n</os>"
300 # virtual machine features
301 result="${result}\n<features>"
302 result="${result}\n<acpi />"
303 if [ -z "${noapic-}" ]; then result="${result}\n<apic />"; fi
304 result="${result}\n<pae /></features>"
305 result="${result}\n<clock offset='utc
' />"
306 result="${result}\n<on_poweroff>destroy</on_poweroff>"
307 result="${result}\n<on_reboot>restart</on_reboot>"
308 result="${result}\n<on_crash>restart</on_crash>"
311 result="${result}\n<devices>"
312 # inject the hard disk if defined
313 if [ -n "$harddrive" ]; then
314 debug "Adding a hard drive to the node"
315 result="${result}\n<disk type='file' device='disk
'>"
316 result="${result}\n<source file='$harddrive' />"
317 result="${result}\n<target dev='vda
' bus='virtio
' />"
318 result="${result}\n</disk>"
320 # inject the cdrom drive if defined
321 if [ -n "$cddrive" ]; then
322 debug "Adding a CDROM drive to the node"
323 result="${result}\n<disk type='file' device='cdrom
'>"
324 result="${result}\n<source file='${cddrive}' />"
325 result="${result}\n<target dev='hdc
' bus='ide
' />"
326 result="${result}\n</disk>"
328 # inject the bridge network
329 result="${result}\n<interface type='network
'>"
330 result="${result}\n<source network='${bridge}' />"
331 result="${result}\n</interface>"
332 # inject the serial port
333 if [ -n "$serial" ]; then
334 result="${result}\n<serial type='pty
' />"
336 # inject the vnc port
337 if [ -n "$vncport" ]; then
338 result="${result}\n<console type='pty
' />"
339 result="${result}\n<graphics type='vnc
' port='${vncport}' autoport='yes' keyman='en-us
' />"
341 # finish the device section
342 result="${result}\n</devices>"
344 result="${result}\n</domain>"
346 debug "Node definition: ${filename}"
347 sudo printf "$result" > $filename
350 sudo virsh define $filename > /dev/null 2>&1
352 if [ $? != 0 ]; then die "Unable to define virtual machine: $nodename"; fi
356 # $2 - the boot device (def. "hd")
357 # $3 - the memory size in kb (def. 524288)
358 # $4 - hard disk size (if blank then no hard disk)
359 # $5 - the cd drive image file (if blank then no cd drive)
360 # $6 - option arguments
369 local nodefile=$WORKDIR/$nodename.xml
371 if [ -z "${boot_device}" ]; then boot_device="hd"; fi
372 if [ -z "${memory}" ]; then memory="524288"; fi
374 debug "configure_node ()"
375 for var in nodename boot_device memory hdsize hdfile cdfile args nodefile; do
376 eval debug "::$var: \$$var"
379 # create the hard disk file
380 if [ -n "${hdsize}" ]; then
381 hdfile=$WORKDIR/$nodename-hd.img
382 create_hard_disk $hdfile $hdsize
385 define_node $nodefile $nodename "${memory}" "${boot_device}" "${hdfile}" "${cdfile}" $IFACE_NAME "${args}"
389 # $2 - undefine the node (def. true)
392 local undefine=${2-true}
394 # if preserving nodes then exit
395 if $preserve_vm; then return; fi
397 if [ -n "${nodename}" ]; then
398 check=$(sudo virsh list --all)
399 if [[ "${check}" =~ "${nodename}" ]]; then
400 if [[ "${check}" =~ running ]]; then
401 sudo virsh destroy $nodename > /dev/null 2>&1
404 sudo virsh undefine $nodename > /dev/null 2>&1
410 # for each test created, add it to the follow array:
411 tests=''; testcount=0;
419 start_virt_viewer () {
422 sudo virt-viewer $nodename > /dev/null 2>&1&
425 # $1 - the node's name
426 # $2 - kernel arguments
427 # $3 - working directory
433 debug
"boot_with_pxe ()"
434 debug
"- workdir: ${workdir}"
435 debug
"- nodename: ${nodename}"
436 debug
"- kernel_args: ${kernel_args}"
438 setup_pxeboot
$workdir "${kernel_args}"
440 sudo virsh start
$nodename > /dev
/null
2>&1
441 if $show_viewer; then
442 start_virt_viewer
$nodename
446 # $1 - the node's name
450 debug
"boot_from_hd ()"
451 debug
"::nodename: ${nodename}"
453 sudo virsh start
$nodename > /dev
/null
2>&1
454 if $show_viewer; then
455 start_virt_viewer
$nodename
460 # $2 - the old boot device
461 # $3 - the new boot device
462 substitute_boot_device
() {
466 local new_node_file
=$WORKDIR/$nodename-new.xml
468 if [ -n "${nodename}" ]; then
469 local xml
=$
(sudo virsh dumpxml
$nodename |
sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/")
471 sudo
printf "${xml}" > $new_node_file
473 sudo virsh define
$new_node_file
477 add_test
"test_stateless_pxe"
478 test_stateless_pxe
() {
479 local nodename
="${vm_prefix}-stateless-pxe"
480 local workdir
=$WORKDIR
482 start_networking
$nodename $IFACE_NAME false true
$workdir
484 configure_node
"${nodename}" "network" "" "10000" "" "local noapic=true"
485 boot_with_pxe
"${nodename}" "standalone firstboot=no" "${workdir}"
488 set timeout '${timeout_period}'
490 log_file -noappend stateless-pxe.log
492 spawn sudo virsh console '"${nodename}"'
495 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
496 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
497 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
498 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
499 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
501 send_log "\nTimeout waiting for marker..\n\n"
504 send_log "Unexpected end of file."
509 send_log "\n\nUnexpected end of interaction.\n\n"
513 destroy_node
$nodename
514 stop_networking
$IFACE_NAME true
519 add_test
"test_stateless_pxe_with_nohd"
520 test_stateless_pxe_with_nohd
() {
521 local nodename
="${vm_prefix}-stateless-pxe-nohd"
522 local workdir
=$WORKDIR
524 start_networking
$nodename $IFACE_NAME false true
$workdir
526 configure_node
"${nodename}" "network" "" "" "" "local noapic=true"
527 boot_with_pxe
"${nodename}" "firstboot=no" "${workdir}"
530 set timeout '${timeout_period}'
532 log_file -noappend stateless-pxe.log
534 spawn sudo virsh console '"${nodename}"'
537 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
538 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
539 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
540 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
541 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
543 send_log "\nTimeout waiting for marker..\n\n"
546 send_log "Unexpected end of file."
551 send_log "\n\nUnexpected end of interaction.\n\n"
556 destroy_node
$nodename
557 stop_networking
$IFACE_NAME true
562 add_test
"test_stateful_pxe"
563 test_stateful_pxe
() {
564 local nodename
="${vm_prefix}-stateful-pxe"
565 local workdir
=$WORKDIR
566 local ipaddress
=${NODE_ADDRESS}
568 for var
in nodename workdir ipaddress
; do
569 eval debug
"::\$$var: $var"
572 start_networking
$nodename $IFACE_NAME false true
$workdir
574 configure_node
"${nodename}" "network" "" "10000" "" "local noapic=true"
575 boot_with_pxe
"${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress}" ${workdir}
577 # verify the booting and installation
579 set timeout '${timeout_period}'
580 log_file -noappend stateful-pxe.log
582 spawn sudo virsh console '"${nodename}"'
585 -exact "Linux version
" { send_log "\n\nMarker
1\n\n"; exp_continue }
586 -exact "Starting ovirt-early
:" { send_log "\n\nMarker
2\n\n"; exp_continue }
587 -exact "Starting ovirt
:" { send_log "\n\nMarker
3\n\n"; exp_continue }
588 -exact "Starting ovirt-post
:" { send_log "\n\nMarker
4\n\n"; exp_continue }
589 -exact "Starting ovirt-firstpost
:" { send_log "\n\nMarker
5\n\n"; exp_continue }
590 -exact "Starting partitioning of
/dev
/vda
" { send_log "\n\nMarker
6\n\n"; exp_continue }
591 -exact "Restarting system
" { send_log "\n\nMarker
7\n\n"; exit }
593 send_log "\nTimeout waiting
for marker..
\n\n"
596 send_log "Unexpected end of
file.
"
601 send_log "\n\nUnexpected end of interaction.
\n\n"
605 # only continue if we're in a good state
606 if [ $result -eq 0 ]; then
607 destroy_node "${nodename}" false
608 substitute_boot_device "${nodename}" "network
" "hd
"
609 boot_from_hd "${nodename}"
612 set timeout '${timeout_period}'
613 log_file stateful-pxe.log
615 send_log "Restarted node
, booting from hard disk.
\n"
617 spawn sudo virsh console '"${nodename}"'
620 -re "localhost.
*login
:" { send_log "\n\nLogin marker found
\n\n"; exit }
623 send_log "\nMarker not found.
\n\n"
626 send_log "Unexpected end of
file.
"
631 send_log "\n\nUnexpected end of interaction.
\n\n"
638 log_file stateful-pxe.log
640 spawn ping -c 3 '"${ipaddress}"'
643 -exact "64 bytes from
'"${ipaddress}"'" { send_log "\n\nGot
ping response
!\n"; send_log "\n\nNetworking verified
!\n"; exit }
646 send_log "\nMarker not found.
\n\n"
649 send_log "Unexpected end of
file.
"
654 send_log "\n\nUnexpected end of interaction.
\n\n"
661 destroy_node $nodename
662 stop_networking $IFACE_NAME true
668 # configures the environment for testing
669 setup_for_testing () {
670 debug "WORKDIR
=${WORKDIR}"
671 debug "isofile
=${isofile}"
672 debug "isoname
=${isoname}"
674 debug "IFACE_NAME
=${IFACE_NAME}"
675 NETWORK=192.168.$(echo "scale
=0; print $$
% 255" | bc -l)
676 debug "NETWORK
=${NETWORK}"
677 NODE_ADDRESS=$NETWORK.100
678 debug "NODE_ADDRESS
=${NODE_ADDRESS}"
680 debug "preserve_vm
=${preserve_vm}"
683 # cleans up any loose ends
684 cleanup_after_testing () {
688 # destroy any running vms
689 vm_list=$(sudo virsh list --all | awk '/'${vm_prefix}-'/ { print $2 }')
690 test -n "$vm_list" && for vm in $vm_list; do
695 # do not delete the work directory if preserve was specified
696 if $preserve_vm; then return; fi
701 # check commandline options
704 isofile="${PWD}/ovirt-node-image.iso
"
710 while getopts di:n:pt:vwh c; do
713 i) isofile=($OPTARG);;
715 p) preserve_vm=true;;
716 t) timeout_period=($OPTARG);;
718 w) show_viewer=true;;
720 '?') die "invalid option \
`-$OPTARG'";;
721 :) die "missing argument to \`-$OPTARG' option";;
722 *) die "internal error";;
726 isoname=$(basename $isofile)
727 isofile="$(cd `dirname $isofile`; pwd)/${isoname}"
729 if ! [ -s "${isofile}" ]; then
730 die "Missing or invalid file: ${isofile}"
733 shift $(($OPTIND - 1))
736 if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi
739 result_file=$WORKDIR/results.log
740 debug "result_file=${result_file}"
742 log "Logging results to file: ${RESULTS}"
746 log "Begin Testing: ${isoname}"
747 log "Tests: ${tests}"
748 log "Timeout: ${timeout_period} ms"
750 for test in ${tests}; do
754 if [ $result != 0 ]; then
755 echo "${result}" > $result_file
760 log "End Testing: ${isoname}"
762 } | sudo tee --append $RESULTS
764 if [ -s "$result_file" ]; then
765 exit $(cat $result_file)