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 -v: enable tracing (default: disabled)
66 -w: launch virt-viewer for each VM (default: no window shown)
67 -h: display this help and exit
71 # $1 - the test function to call
75 if [ -z $testname ]; then die
"Missing test name"; fi
77 log
"Executing test: $testname"
82 log
"Completed test: $testname [result=$rc]"
84 if [ $rc -ne 0 ]; then
85 log
"Build fails smoke tests."
91 # setup a node for pxeboot
92 # $1 - the working directory
93 # $2 - kernel arguments; if present then they replace all default flags
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)
131 local pidfile
=$2/dnsmasq.pid
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
140 --except-interface=lo
141 --dhcp-boot=tftpboot/pxelinux.0
143 --tftp-root=${tftproot}
144 --log-facility=$WORKDIR/dnsmasq-${nodename}.log
147 --pid-file=${pidfile}"
148 if [ -n "$macaddress" ]; then
149 dns_startup
="${dns_startup} --dhcp-host=${macaddress},${NODE_ADDRESS}"
153 debug
"pidfile=$pidfile"
154 DNSMASQ_PID
=$
(sudo
cat $pidfile)
155 debug
"DNSMASQ_PID=${DNSMASQ_PID}"
158 # Kills the running instance of 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
171 # Creates a virt network.
173 # $2 - the network interface name
174 # $3 - use DHCP (any value)
175 # $4 - start dnsmsq (def. false)
176 start_networking () {
179 local use_dhcp=${3-false}
180 local start_dnsmasq=${4-false}
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"
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'>"
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)
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 # exit if preserve was enabled
224 if $preserve_vm; then return; fi
226 if [ -n "${networkname}" ]; then
227 debug "Destroying network interface: ${networkname}"
228 check=$(sudo virsh net-list --all)
229 if [[ "${check}" =~ "${networkname}" ]]; then
230 if [[ "{$check}" =~ active ]]; then
231 sudo virsh net-destroy $networkname > /dev/null 2>&1
233 sudo virsh net-undefine $networkname > /dev/null 2>&1
237 if $stop_dnsmasq; then
242 # creates a HD disk file
243 # $1 - filename for disk file
244 # $2 - size (##M or ##G)
245 create_hard_disk () {
249 debug "Creating hard disk: filename=${filename} size=${size}"
250 sudo qemu-img create -f raw $filename "${size}M" > /dev/null 2>&1
251 sudo chcon -t virt_image_t $filename > /dev/null 2>&1
254 # Creates the XML for a virtual machine.
255 # $1 - the file to write the xml
257 # $3 - memory size (in kb)
259 # $5 - the local hard disk (if blank then no disk is used)
260 # $6 - the cdrom disk (if blank then no cdrom is used)
261 # $7 - the network bridge (if blank then 'default
' is used)
262 # $8 - optional arguments
270 local bridge=${7-default}
275 # define defaults, then allow the caller to override them as needed
276 local arch=$(uname -i)
281 # first destroy the node
282 destroy_node $nodename
284 if [ -n "$options" ]; then eval "$options"; fi
286 debug "define_node ()"
287 for var in filename nodename memory harddrive cddrive bridge options arch serial vncport bootdev; do
288 eval debug "::$var: \$$var"
291 result="<domain type='kvm
'>\n<name>${nodename}</name>\n<memory>${memory}</memory>\n <vcpu>1</vcpu>"
293 # begin the os section
294 # inject the boot device
295 result="${result}\n<os>\n<type arch='${arch}' machine='pc
'>hvm</type>"
296 result="${result}\n<boot dev='${boot_device}' />"
297 result="${result}\n</os>"
299 # virtual machine features
300 result="${result}\n<features>"
301 result="${result}\n<acpi />"
302 if [ -z "${noapic-}" ]; then result="${result}\n<apic />"; fi
303 result="${result}\n<pae /></features>"
304 result="${result}\n<clock offset='utc
' />"
305 result="${result}\n<on_poweroff>destroy</on_poweroff>"
306 result="${result}\n<on_reboot>restart</on_reboot>"
307 result="${result}\n<on_crash>restart</on_crash>"
310 result="${result}\n<devices>"
311 # inject the hard disk if defined
312 if [ -n "$harddrive" ]; then
313 debug "Adding a hard drive to the node"
314 result="${result}\n<disk type='file' device='disk
'>"
315 result="${result}\n<source file='$harddrive' />"
316 result="${result}\n<target dev='vda
' bus='virtio
' />"
317 result="${result}\n</disk>"
319 # inject the cdrom drive if defined
320 if [ -n "$cddrive" ]; then
321 debug "Adding a CDROM drive to the node"
322 result="${result}\n<disk type='file' device='cdrom
'>"
323 result="${result}\n<source file='${cddrive}' />"
324 result="${result}\n<target dev='hdc
' bus='ide
' />"
325 result="${result}\n</disk>"
327 # inject the bridge network
328 result="${result}\n<interface type='network
'>"
329 result="${result}\n<source network='${bridge}' />"
330 result="${result}\n</interface>"
331 # inject the serial port
332 if [ -n "$serial" ]; then
333 result="${result}\n<serial type='pty
' />"
335 # inject the vnc port
336 if [ -n "$vncport" ]; then
337 result="${result}\n<console type='pty
' />"
338 result="${result}\n<graphics type='vnc
' port='${vncport}' autoport='yes' keyman='en-us
' />"
340 # finish the device section
341 result="${result}\n</devices>"
343 result="${result}\n</domain>"
345 debug "Node definition: ${filename}"
346 sudo printf "$result" > $filename
349 sudo virsh define $filename > /dev/null 2>&1
351 if [ $? != 0 ]; then die "Unable to define virtual machine: $nodename"; fi
355 # $2 - the boot device (def. "hd")
356 # $3 - the memory size in kb (def. 524288)
357 # $4 - hard disk size (if blank then no hard disk)
358 # $5 - the cd drive image file (if blank then no cd drive)
359 # $6 - option arguments
368 local nodefile=$WORKDIR/$nodename.xml
370 if [ -z "${boot_device}" ]; then boot_device="hd"; fi
371 if [ -z "${memory}" ]; then memory="524288"; fi
373 debug "configure_node ()"
374 for var in nodename boot_device memory hdsize hdfile cdfile args nodefile; do
375 eval debug "::$var: \$$var"
378 # create the hard disk file
379 if [ -n "${hdsize}" ]; then
380 hdfile=$WORKDIR/$nodename-hd.img
381 create_hard_disk $hdfile $hdsize
384 define_node $nodefile $nodename "${memory}" "${boot_device}" "${hdfile}" "${cdfile}" $IFACE_NAME "${args}"
388 # $2 - undefine the node (def. true)
391 local undefine=${2-true}
393 # if preserving nodes then exit
394 if $preserve_vm; then return; fi
396 if [ -n "${nodename}" ]; then
397 check=$(sudo virsh list --all)
398 if [[ "${check}" =~ "${nodename}" ]]; then
399 if [[ "${check}" =~ running ]]; then
400 sudo virsh destroy $nodename > /dev/null 2>&1
403 sudo virsh undefine $nodename > /dev/null 2>&1
409 # for each test created, add it to the follow array:
410 tests=''; testcount=0;
418 start_virt_viewer () {
421 sudo virt-viewer $nodename > /dev/null 2>&1&
424 # $1 - the node's name
425 # $2 - kernel arguments
426 # $3 - working directory
432 debug
"boot_with_pxe ()"
433 debug
"- workdir: ${workdir}"
434 debug
"- nodename: ${nodename}"
435 debug
"- kernel_args: ${kernel_args}"
437 setup_pxeboot
$workdir "${kernel_args}"
439 sudo virsh start
$nodename > /dev
/null
2>&1
440 if $show_viewer; then
441 start_virt_viewer
$nodename
445 # $1 - the node's name
449 debug
"boot_from_hd ()"
450 debug
"::nodename: ${nodename}"
452 sudo virsh start
$nodename > /dev
/null
2>&1
453 if $show_viewer; then
454 start_virt_viewer
$nodename
459 # $2 - the old boot device
460 # $3 - the new boot device
461 substitute_boot_device
() {
465 local new_node_file
=$WORKDIR/$nodename-new.xml
467 if [ -n "${nodename}" ]; then
468 local xml
=$
(sudo virsh dumpxml
$nodename |
sed "s/boot dev='"${old_device}"'/boot dev='"${new_device}"'/")
470 sudo
printf "${xml}" > $new_node_file
472 sudo virsh define
$new_node_file
476 add_test
"test_stateless_pxe"
477 test_stateless_pxe
() {
478 local nodename
="${vm_prefix}-stateless-pxe"
479 local workdir
=$WORKDIR
481 start_networking
$nodename $IFACE_NAME false true
$workdir
483 configure_node
"${nodename}" "network" "" "10000" "" "local noapic=true"
484 boot_with_pxe
"${nodename}" "standalone firstboot=no" "${workdir}"
489 log_file -noappend stateless-pxe.log
491 spawn sudo virsh console '"${nodename}"'
494 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
495 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
496 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
497 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
498 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
500 send_log "\nTimeout waiting for marker..\n\n"
503 send_log "Unexpected end of file."
508 send_log "\n\nUnexpected end of interaction.\n\n"
512 destroy_node
$nodename
513 stop_networking
$IFACE_NAME true
518 add_test
"test_stateless_pxe_with_nohd"
519 test_stateless_pxe_with_nohd
() {
520 local nodename
="${vm_prefix}-stateless-pxe-nohd"
521 local workdir
=$WORKDIR
523 start_networking
$nodename $IFACE_NAME false true
$workdir
525 configure_node
"${nodename}" "network" "" "" "" "local noapic=true"
526 boot_with_pxe
"${nodename}" "firstboot=no" "${workdir}"
531 log_file -noappend stateless-pxe.log
533 spawn sudo virsh console '"${nodename}"'
536 -exact "Linux version" { send_log "\n\nMarker 1\n\n"; exp_continue }
537 -exact "Starting ovirt-early:" { send_log "\n\nMarker 2\n\n"; exp_continue }
538 -exact "Starting ovirt:" { send_log "\n\nMarker 3\n\n"; exp_continue }
539 -exact "Starting ovirt-post:" { send_log "\n\nMarker 4\n\n"; exp_continue }
540 -re "localhost.*login:" { send_log "\n\nMarker 5\n\n"; exit }
542 send_log "\nTimeout waiting for marker..\n\n"
545 send_log "Unexpected end of file."
550 send_log "\n\nUnexpected end of interaction.\n\n"
555 destroy_node
$nodename
556 stop_networking
$IFACE_NAME true
561 add_test
"test_stateful_pxe"
562 test_stateful_pxe
() {
563 local nodename
="${vm_prefix}-stateful-pxe"
564 local workdir
=$WORKDIR
565 local ipaddress
=${NODE_ADDRESS}
567 for var
in nodename workdir ipaddress
; do
568 eval debug
"::\$$var: $var"
571 start_networking
$nodename $IFACE_NAME false true
$workdir
573 configure_node
"${nodename}" "network" "" "10000" "" "local noapic=true"
574 boot_with_pxe
"${nodename}" "standalone storage_init=/dev/vda local_boot ip=${ipaddress}" ${workdir}
576 # verify the booting and installation
579 log_file -noappend stateful-pxe.log
581 spawn sudo virsh console '"${nodename}"'
584 -exact "Linux version
" { send_log "\n\nMarker
1\n\n"; exp_continue }
585 -exact "Starting ovirt-early
:" { send_log "\n\nMarker
2\n\n"; exp_continue }
586 -exact "Starting ovirt
:" { send_log "\n\nMarker
3\n\n"; exp_continue }
587 -exact "Starting ovirt-post
:" { send_log "\n\nMarker
4\n\n"; exp_continue }
588 -exact "Starting ovirt-firstpost
:" { send_log "\n\nMarker
5\n\n"; exp_continue }
589 -exact "Starting partitioning of
/dev
/vda
" { send_log "\n\nMarker
6\n\n"; exp_continue }
590 -exact "Restarting system
" { send_log "\n\nMarker
7\n\n"; exit }
592 send_log "\nTimeout waiting
for marker..
\n\n"
595 send_log "Unexpected end of
file.
"
600 send_log "\n\nUnexpected end of interaction.
\n\n"
604 # only continue if we're in a good state
605 if [ $result -eq 0 ]; then
606 destroy_node "${nodename}" false
607 substitute_boot_device "${nodename}" "network
" "hd
"
608 boot_from_hd "${nodename}"
612 log_file stateful-pxe.log
614 send_log "Restarted node
, booting from hard disk.
\n"
616 spawn sudo virsh console '"${nodename}"'
619 -re "localhost.
*login
:" { send_log "\n\nLogin marker found
\n\n"; exit }
622 send_log "\nMarker not found.
\n\n"
625 send_log "Unexpected end of
file.
"
630 send_log "\n\nUnexpected end of interaction.
\n\n"
637 log_file stateful-pxe.log
639 spawn ping -c 3 '"${ipaddress}"'
642 -exact "64 bytes from
'"${ipaddress}"'" { send_log "\n\nGot
ping response
!\n"; send_log "\n\nNetworking verified
!\n"; exit }
645 send_log "\nMarker not found.
\n\n"
648 send_log "Unexpected end of
file.
"
653 send_log "\n\nUnexpected end of interaction.
\n\n"
660 destroy_node $nodename
661 stop_networking $IFACE_NAME true
667 # configures the environment for testing
668 setup_for_testing () {
669 debug "WORKDIR
=${WORKDIR}"
670 debug "isofile
=${isofile}"
671 debug "isoname
=${isoname}"
673 debug "IFACE_NAME
=${IFACE_NAME}"
674 NETWORK=192.168.$(echo "scale
=0; print $$
% 255" | bc -l)
675 debug "NETWORK
=${NETWORK}"
676 NODE_ADDRESS=$NETWORK.100
677 debug "NODE_ADDRESS
=${NODE_ADDRESS}"
679 debug "preserve_vm
=${preserve_vm}"
682 # cleans up any loose ends
683 cleanup_after_testing () {
687 # destroy any running vms
688 vm_list=$(sudo virsh list --all | awk '/'${vm_prefix}-'/ { print $2 }')
689 test -n "$vm_list" && for vm in $vm_list; do
694 # do not delete the work directory if preserve was specified
695 if $preserve_vm; then return; fi
700 # check commandline options
703 isofile="${PWD}/ovirt-node-image.iso
"
708 while getopts di:n:pvwh c; do
711 i) isofile=($OPTARG);;
713 p) preserve_vm=true;;
715 w) show_viewer=true;;
717 '?') die "invalid option \
`-$OPTARG'";;
718 :) die "missing argument to \`-$OPTARG' option";;
719 *) die "internal error";;
723 isoname=$(basename $isofile)
724 isofile="$(cd `dirname $isofile`; pwd)/${isoname}"
726 if ! [ -s "${isofile}" ]; then
727 die "Missing or invalid file: ${isofile}"
730 shift $(($OPTIND - 1))
733 if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi
736 result_file=$WORKDIR/results.log
737 debug "result_file=${result_file}"
739 log "Logging results to file: ${RESULTS}"
743 log "Begin Testing: ${isoname}"
744 log "Tests: ${tests}"
746 for test in ${tests}; do
750 if [ $result != 0 ]; then
751 echo "${result}" > $result_file
756 log "End Testing: ${isoname}"
758 } | sudo tee --append $RESULTS
760 if [ -s "$result_file" ]; then
761 exit $(cat $result_file)