3 # oVirt node 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
46 warn
() { printf '%s: %s\n' "$ME" "$*" >&2; }
47 die
() { warn
"$*"; exit 1; }
48 debug
() { if $debugging; then log
"[DEBUG] %s" "$*"; fi }
50 trap '__st=$?; cleanup_after_testing; exit $__st' 1 2 3 13 15
51 trap 'cleanup_after_testing' 0
63 Usage: $ME [-n test_name] [LOGFILE]
64 -i: set the ISO filename (defualt: ovirt-node-image.iso)
65 -n: the name of the specific autotest to run (default: run all autotests)
66 -d: enable more verbose output (default: disabled)
67 -t: change the timeout between markers (in ms, default: 120)
68 -v: enable tracing (default: disabled)
69 -w: launch virt-viewer for each VM (default: no window shown)
70 -h: display this help and exit
74 # $1 - the test function to call
78 if [ -z $testname ]; then die
"Missing test name"; fi
80 log
"Executing test: $testname"
85 log
"Completed test: $testname [result=$rc]"
87 if [ $rc -ne 0 ]; then
88 log
"Build fails smoke tests."
94 # setup a node for pxeboot
95 # $1 - the working directory
96 # $2 - kernel arguments; if present then they replace all default flags
100 local pxedefault
=$workdir/tftpboot
/pxelinux.cfg
/default
102 debug
"setup for pxeboot: isofile=${isofile} workdir=${workdir} kernelargs='${kernelargs}' pxedefault=${pxedefault}"
103 (cd $workdir && sudo livecd-iso-to-pxeboot
$isofile) > /dev
/null
2>&1
104 sudo
chmod -R 777 $workdir
106 # set default kernel arguments if none were provided
107 # the defaults boot in standalone mode
108 if [ -z "$kernelargs" ]; then
109 kernelargs
="standalone"
112 local definition
="DEFAULT pxeboot"
113 definition
="${definition}\nTIMEOUT 20"
114 definition
="${definition}\nPROMPT 0"
115 definition
="${definition}\nLABEL pxeboot"
116 definition
="${definition}\n KERNEL vmlinuz0"
117 definition
="${definition}\n IPAPPEND 2"
118 definition
="${definition}\n APPEND rootflags=loop initrd=initrd0.img root=/${isoname} rootfstype=auto console=tty0 check console=ttyS0,115200n8 $kernelargs"
120 debug
"pxeboot definition=\n${definition}"
121 sudo bash
-c "printf \"${definition}\" > $pxedefault"
124 # Starts a simple instance of dnsmasq.
125 # $1 - the iface on which dnsmasq works
126 # $2 - the root for tftp files
127 # $3 - the mac address for the node (ignored if blank)
134 local pidfile
=$2/dnsmasq.pid
137 debug
"Starting dnsmasq"
138 dns_startup
="sudo /usr/sbin/dnsmasq --read-ethers
139 --dhcp-range=${NETWORK}.100,${NETWORK}.254,255.255.255.0,24h
143 --except-interface=lo
144 --dhcp-boot=tftpboot/pxelinux.0
146 --tftp-root=${tftproot}
147 --log-facility=$WORKDIR/dnsmasq-${nodename}.log
150 --pid-file=${pidfile}"
151 if [ -n "$macaddress" ]; then
152 dns_startup
="${dns_startup} --dhcp-host=${macaddress},${NODE_ADDRESS}"
156 debug
"pidfile=$pidfile"
157 DNSMASQ_PID
=$
(sudo
cat $pidfile)
158 debug
"DNSMASQ_PID=${DNSMASQ_PID}"
161 # Kills the running instance of dnsmasq.
163 if [ -n "${DNSMASQ_PID-}" -a "${DNSMASQ_PID-}" != "0" ]; then
164 local check
=$
(ps
-ef |
awk "/${DNSMASQ_PID}/"' { if ($2 ~ '"${DNSMASQ_PID}"') print $2 }')
166 if [[ "${check}" == "${DNSMASQ_PID}" ]]; then
167 sudo kill -9 $DNSMASQ_PID
174 # Creates a virt network.
176 # $2 - the network interface name
177 # $3 - use DHCP (any value)
178 # $4 - start dnsmsq (def. false)
179 start_networking () {
182 local use_dhcp=${3-false}
183 local start_dnsmasq=${4-false}
186 local network=$NETWORK
187 local xmlfile=$WORKDIR/$nodename-$ifacename.xml
189 debug "start_networking
()"
190 for var in nodename ifacename use_dhcp start_dnsmasq workdir network xmlfile; do
191 eval debug "::$var: \$
$var"
194 definition="<network
>\n<name
>${ifacename}</name
>\n<forward mode
='nat' />\n<bridge name
='${ifacename}' stp
='on' forwardDelay
='0' />"
195 definition="${definition}\n<ip address
='${network}.1' netmask
='255.255.255.0'>"
197 definition="${definition}\n<dhcp>\n<range start='${network}.100' end='${network}.199' />\n</dhcp>"
199 definition="${definition}\n</ip>\n</network>"
201 debug "Saving network definition file to: ${xmlfile}\n"
202 sudo printf "${definition}" > $xmlfile
203 sudo virsh net-define $xmlfile > /dev/null 2>&1
204 debug "Starting network."
205 sudo virsh net-start $ifacename > /dev/null 2>&1
207 if [ "${use_dhcp}" == "false" ]; then
208 if $start_dnsmasq; then
209 start_dnsmasq $ifacename $workdir "" $nodename
214 # Destroys the test network interface
215 # $1 - the network name
216 # $2 - stop dnsmasq (def. false)
218 local networkname=${1-}
219 local stop_dnsmasq=${2-true}
221 # if no network was supplied, then check for the global network
222 if [ -z "$networkname" ]; then
223 networkname=${NETWORK_NAME-}
226 # exit if preserve was enabled
227 if $preserve_vm; then return; fi
229 if [ -n "${networkname}" ]; then
230 debug "Destroying network interface: ${networkname}"
231 check=$(sudo virsh net-list --all)
232 if [[ "${check}" =~ "${networkname}" ]]; then
233 if [[ "{$check}" =~ active ]]; then
234 sudo virsh net-destroy $networkname > /dev/null 2>&1
236 sudo virsh net-undefine $networkname > /dev/null 2>&1
240 if $stop_dnsmasq; then
245 # creates a HD disk file
246 # $1 - filename for disk file
247 # $2 - size (##M or ##G)
248 create_hard_disk () {
252 debug "Creating hard disk: filename=${filename} size=${size}"
253 sudo qemu-img create -f raw $filename "${size}M" > /dev/null 2>&1
254 sudo chcon -t virt_image_t $filename > /dev/null 2>&1
257 # Creates the XML for a virtual machine.
258 # $1 - the file to write the xml
260 # $3 - memory size (in kb)
262 # $5 - the local hard disk (if blank then no disk is used)
263 # $6 - the cdrom disk (if blank then no cdrom is used)
264 # $7 - the network bridge (if blank then 'default
' is used)
265 # $8 - optional arguments
273 local bridge=${7-default}
278 # define defaults, then allow the caller to override them as needed
279 local arch=$(uname -i)
284 # first destroy the node
285 destroy_node $nodename
287 if [ -n "$options" ]; then eval "$options"; fi
289 debug "define_node ()"
290 for var in filename nodename memory harddrive cddrive bridge options arch serial vncport bootdev; do
291 eval debug "::$var: \$$var"
294 result="<domain type='kvm
'>\n<name>${nodename}</name>\n<memory>${memory}</memory>\n <vcpu>1</vcpu>"
296 # begin the os section
297 # inject the boot device
298 result="${result}\n<os>\n<type arch='${arch}' machine='pc
'>hvm</type>"
299 result="${result}\n<boot dev='${boot_device}' />"
300 result="${result}\n</os>"
302 # virtual machine features
303 result="${result}\n<features>"
304 result="${result}\n<acpi />"
305 if [ -z "${noapic-}" ]; then result="${result}\n<apic />"; fi
306 result="${result}\n<pae /></features>"
307 result="${result}\n<clock offset='utc
' />"
308 result="${result}\n<on_poweroff>destroy</on_poweroff>"
309 result="${result}\n<on_reboot>restart</on_reboot>"
310 result="${result}\n<on_crash>restart</on_crash>"
313 result="${result}\n<devices>"
314 # inject the hard disk if defined
315 if [ -n "$harddrive" ]; then
316 debug "Adding a hard drive to the node"
317 result="${result}\n<disk type='file' device='disk
'>"
318 result="${result}\n<source file='$harddrive' />"
319 result="${result}\n<target dev='vda
' bus='virtio
' />"
320 result="${result}\n</disk>"
322 # inject the cdrom drive if defined
323 if [ -n "$cddrive" ]; then
324 debug "Adding a CDROM drive to the node"
325 result="${result}\n<disk type='file' device='cdrom
'>"
326 result="${result}\n<source file='${cddrive}' />"
327 result="${result}\n<target dev='hdc
' bus='ide
' />"
328 result="${result}\n</disk>"
330 # inject the bridge network
331 result="${result}\n<interface type='network
'>"
332 result="${result}\n<source network='${bridge}' />"
333 result="${result}\n</interface>"
334 # inject the serial port
335 if [ -n "$serial" ]; then
336 result="${result}\n<serial type='pty
' />"
338 # inject the vnc port
339 if [ -n "$vncport" ]; then
340 result="${result}\n<console type='pty
' />"
341 result="${result}\n<graphics type='vnc
' port='${vncport}' autoport='yes' keyman='en-us
' />"
343 # finish the device section
344 result="${result}\n</devices>"
346 result="${result}\n</domain>"
348 debug "Node definition: ${filename}"
349 sudo printf "$result" > $filename
352 sudo virsh define $filename > /dev/null 2>&1
354 if [ $? != 0 ]; then die "Unable to define virtual machine: $nodename"; fi
358 # $2 - the boot device (def. "hd")
359 # $3 - the memory size in kb (def. 524288)
360 # $4 - hard disk size (if blank then no hard disk)
361 # $5 - the cd drive image file (if blank then no cd drive)
362 # $6 - option arguments
371 local nodefile=$WORKDIR/$nodename.xml
373 if [ -z "${boot_device}" ]; then boot_device="hd"; fi
374 if [ -z "${memory}" ]; then memory="524288"; fi
376 debug "configure_node ()"
377 for var in nodename boot_device memory hdsize hdfile cdfile args nodefile; do
378 eval debug "::$var: \$$var"
381 # create the hard disk file
382 if [ -n "${hdsize}" ]; then
383 hdfile=$WORKDIR/$nodename-hd.img
384 create_hard_disk $hdfile $hdsize
387 define_node $nodefile $nodename "${memory}" "${boot_device}" "${hdfile}" "${cdfile}" $IFACE_NAME "${args}"
391 # $2 - undefine the node (def. true)
394 local undefine=${2-true}
396 # if preserving nodes then exit
397 if $preserve_vm; then return; fi
399 if [ -n "${nodename}" ]; then
400 check=$(sudo virsh list --all)
401 if [[ "${check}" =~ "${nodename}" ]]; then
402 if [[ "${check}" =~ running ]]; then
403 sudo virsh destroy $nodename > /dev/null 2>&1
406 sudo virsh undefine $nodename > /dev/null 2>&1
412 # for each test created, add it to the follow array:
413 tests=''; testcount=0;
421 start_virt_viewer () {
424 sudo virt-viewer $nodename > /dev/null 2>&1&
427 # $1 - the node's name
428 # $2 - kernel arguments
429 # $3 - working directory
435 debug
"boot_with_pxe ()"
436 debug
"- workdir: ${workdir}"
437 debug
"- nodename: ${nodename}"
438 debug
"- kernel_args: ${kernel_args}"
440 setup_pxeboot
$workdir "${kernel_args}"
442 sudo virsh start
$nodename > /dev
/null
2>&1
443 if $show_viewer; then
444 start_virt_viewer
$nodename
448 # $1 - the node's name
452 debug
"boot_from_hd ()"
453 debug
"::nodename: ${nodename}"
455 sudo virsh start
$nodename > /dev
/null
2>&1
456 if $show_viewer; then
457 start_virt_viewer
$nodename
462 # $2 - the old boot device
463 # $3 - the new boot device
464 substitute_boot_device
() {
468 local new_node_file
=$WORKDIR/$nodename-new.xml
470 if [ -n "${nodename}" ]; then
471 local xml
=$
(sudo virsh dumpxml
$nodename |
sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/")
473 sudo
printf "${xml}" > $new_node_file
475 sudo virsh define
$new_node_file
479 add_test
"test_stateless_pxe"
480 test_stateless_pxe
() {
481 local nodename
="${vm_prefix}-stateless-pxe"
482 local workdir
=$WORKDIR
484 start_networking
$nodename $IFACE_NAME false true
$workdir
486 configure_node
"${nodename}" "network" "" "10000" "" "local noapic=true"
487 boot_with_pxe
"${nodename}" "standalone firstboot=no" "${workdir}"
490 set timeout '${timeout_period}'
492 log_file -noappend stateless-pxe.log
494 spawn sudo virsh console '"${nodename}"'
497 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
498 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
499 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
500 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
501 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
503 send_log "\nTimeout waiting for marker..\n\n"
506 send_log "Unexpected end of file."
511 send_log "\n\nUnexpected end of interaction.\n\n"
515 destroy_node
$nodename
516 stop_networking
$IFACE_NAME true
521 add_test
"test_stateless_pxe_with_nohd"
522 test_stateless_pxe_with_nohd
() {
523 local nodename
="${vm_prefix}-stateless-pxe-nohd"
524 local workdir
=$WORKDIR
526 start_networking
$nodename $IFACE_NAME false true
$workdir
528 configure_node
"${nodename}" "network" "" "" "" "local noapic=true"
529 boot_with_pxe
"${nodename}" "firstboot=no" "${workdir}"
532 set timeout '${timeout_period}'
534 log_file -noappend stateless-pxe.log
536 spawn sudo virsh console '"${nodename}"'
539 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
540 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
541 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
542 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
543 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
545 send_log "\nTimeout waiting for marker..\n\n"
548 send_log "Unexpected end of file."
553 send_log "\n\nUnexpected end of interaction.\n\n"
558 destroy_node
$nodename
559 stop_networking
$IFACE_NAME true
564 add_test
"test_stateful_pxe"
565 test_stateful_pxe
() {
566 local nodename
="${vm_prefix}-stateful-pxe"
567 local workdir
=$WORKDIR
568 local ipaddress
=${NODE_ADDRESS}
570 for var
in nodename workdir ipaddress
; do
571 eval debug
"::\$$var: $var"
574 start_networking
$nodename $IFACE_NAME false true
$workdir
576 configure_node
"${nodename}" "network" "" "10000" "" "local noapic=true"
577 boot_with_pxe
"${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress}" ${workdir}
579 # verify the booting and installation
581 set timeout '${timeout_period}'
582 log_file -noappend stateful-pxe.log
584 spawn sudo virsh console '"${nodename}"'
587 -exact "Linux version
" { send_log "\n\nMarker
1\n\n"; exp_continue }
588 -exact "Starting ovirt-early
:" { send_log "\n\nMarker
2\n\n"; exp_continue }
589 -exact "Starting ovirt
:" { send_log "\n\nMarker
3\n\n"; exp_continue }
590 -exact "Starting ovirt-post
:" { send_log "\n\nMarker
4\n\n"; exp_continue }
591 -exact "Starting ovirt-firstpost
:" { send_log "\n\nMarker
5\n\n"; exp_continue }
592 -exact "Starting partitioning of
/dev
/vda
" { send_log "\n\nMarker
6\n\n"; exp_continue }
593 -exact "Restarting system
" { send_log "\n\nMarker
7\n\n"; exit }
595 send_log "\nTimeout waiting
for marker..
\n\n"
598 send_log "Unexpected end of
file.
"
603 send_log "\n\nUnexpected end of interaction.
\n\n"
607 # only continue if we're in a good state
608 if [ $result -eq 0 ]; then
609 destroy_node "${nodename}" false
610 substitute_boot_device "${nodename}" "network
" "hd
"
611 boot_from_hd "${nodename}"
614 set timeout '${timeout_period}'
615 log_file stateful-pxe.log
617 send_log "Restarted node
, booting from hard disk.
\n"
619 spawn sudo virsh console '"${nodename}"'
622 -re "localhost.
*login
:" { send_log "\n\nLogin marker found
\n\n"; exit }
625 send_log "\nMarker not found.
\n\n"
628 send_log "Unexpected end of
file.
"
633 send_log "\n\nUnexpected end of interaction.
\n\n"
640 log_file stateful-pxe.log
642 spawn ping -c 3 '"${ipaddress}"'
645 -exact "64 bytes from
'"${ipaddress}"'" { send_log "\n\nGot
ping response
!\n"; send_log "\n\nNetworking verified
!\n"; exit }
648 send_log "\nMarker not found.
\n\n"
651 send_log "Unexpected end of
file.
"
656 send_log "\n\nUnexpected end of interaction.
\n\n"
663 destroy_node $nodename
664 stop_networking $IFACE_NAME true
670 # configures the environment for testing
671 setup_for_testing () {
672 debug "WORKDIR
=${WORKDIR}"
673 debug "isofile
=${isofile}"
674 debug "isoname
=${isoname}"
676 debug "IFACE_NAME
=${IFACE_NAME}"
677 NETWORK=192.168.$(echo "scale
=0; print $$
% 255" | bc -l)
678 debug "NETWORK
=${NETWORK}"
679 NODE_ADDRESS=$NETWORK.100
680 debug "NODE_ADDRESS
=${NODE_ADDRESS}"
682 debug "preserve_vm
=${preserve_vm}"
685 # cleans up any loose ends
686 cleanup_after_testing () {
690 # destroy any running vms
691 vm_list=$(sudo virsh list --all | awk '/'${vm_prefix}-'/ { print $2 }')
692 test -n "$vm_list" && for vm in $vm_list; do
695 stop_networking "${IFACE_NAME}" true
697 # do not delete the work directory if preserve was specified
698 if $preserve_vm; then return; fi
703 # check commandline options
706 isofile="${PWD}/ovirt-node-image.iso
"
712 while getopts di:n:pt:vwh c; do
715 i) isofile=($OPTARG);;
717 p) preserve_vm=true;;
718 t) timeout_period=($OPTARG);;
720 w) show_viewer=true;;
722 '?') die "invalid option \
`-$OPTARG'";;
723 :) die "missing argument to \`-$OPTARG' option";;
724 *) die "internal error";;
728 isoname=$(basename $isofile)
729 isofile="$(cd `dirname $isofile`; pwd)/${isoname}"
731 if ! [ -s "${isofile}" ]; then
732 die "Missing or invalid file: ${isofile}"
735 shift $(($OPTIND - 1))
738 if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi
741 result_file=$WORKDIR/results.log
742 debug "result_file=${result_file}"
744 log "Logging results to file: ${RESULTS}"
748 log "Begin Testing: ${isoname}"
749 log "Tests: ${tests}"
750 log "Timeout: ${timeout_period} ms"
752 for test in ${tests}; do
756 cleanup_after_testing
758 if [ $result != 0 ]; then
759 echo "${result}" > $result_file
764 log "End Testing: ${isoname}"
766 } | sudo tee --append $RESULTS
768 if [ -s "$result_file" ]; then
769 exit $(cat $result_file)