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 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
239 # creates a HD disk file
240 # $1 - filename for disk file
241 # $2 - size (##M or ##G)
242 create_hard_disk () {
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
254 # $3 - memory size (in kb)
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
267 local bridge=${7-default}
272 # define defaults, then allow the caller to override them as needed
273 local arch=$(uname -i)
274 local emulator=$(which qemu-kvm)
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"
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>"
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
348 sudo virsh define $filename > /dev/null 2>&1
350 if [ $? != 0 ]; then die "Unable to define virtual machine: $nodename"; fi
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
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"
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}"
387 # $2 - undefine the node (def. true)
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
399 sudo virsh undefine $nodename > /dev/null 2>&1
405 # for each test created, add it to the follow array:
406 tests=''; testcount=0;
414 start_virt_viewer () {
417 sudo virt-viewer $nodename > /dev/null 2>&1&
420 # $1 - the node's name
421 # $2 - kernel arguments
422 # $3 - working directory
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
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
455 # $2 - the old boot device
456 # $3 - the new boot device
457 substitute_boot_device
() {
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}"
485 log_file -noappend stateless-pxe.log
487 spawn sudo virsh console '"${nodename}"'
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 }
496 send_log "\nTimeout waiting for marker..\n\n"
499 send_log "Unexpected end of file."
504 send_log "\n\nUnexpected end of interaction.\n\n"
508 destroy_node
$nodename
509 stop_networking
$IFACE_NAME true
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}"
527 log_file -noappend stateless-pxe.log
529 spawn sudo virsh console '"${nodename}"'
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 }
538 send_log "\nTimeout waiting for marker..\n\n"
541 send_log "Unexpected end of file."
546 send_log "\n\nUnexpected end of interaction.\n\n"
551 destroy_node
$nodename
552 stop_networking
$IFACE_NAME true
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"
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
575 log_file -noappend stateful-pxe.log
577 spawn sudo virsh console '"${nodename}"'
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 }
588 send_log "\nTimeout waiting
for marker..
\n\n"
591 send_log "Unexpected end of
file.
"
596 send_log "\n\nUnexpected end of interaction.
\n\n"
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}"
608 log_file stateful-pxe.log
610 send_log "Restarted node
, booting from hard disk.
\n"
612 spawn sudo virsh console '"${nodename}"'
615 -re "localhost.
*login
:" { send_log "\n\nLogin marker found
\n\n"; exit }
618 send_log "\nMarker not found.
\n\n"
621 send_log "Unexpected end of
file.
"
626 send_log "\n\nUnexpected end of interaction.
\n\n"
633 log_file stateful-pxe.log
635 spawn ping -c 3 '"${ipaddress}"'
638 -exact "64 bytes from
'"${ipaddress}"'" { send_log "\n\nGot
ping response
!\n"; send_log "\n\nNetworking verified
!\n"; exit }
641 send_log "\nMarker not found.
\n\n"
644 send_log "Unexpected end of
file.
"
649 send_log "\n\nUnexpected end of interaction.
\n\n"
656 destroy_node $nodename
657 stop_networking $IFACE_NAME true
663 # configures the environment for testing
664 setup_for_testing () {
665 debug "WORKDIR
=${WORKDIR}"
666 debug "isofile
=${isofile}"
667 debug "isoname
=${isoname}"
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}"
677 # cleans up any loose ends
678 cleanup_after_testing () {
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
691 # check commandline options
694 isofile="${PWD}/ovirt-node-image.iso
"
698 while getopts di:n:vwh c; do
701 i) isofile=($OPTARG);;
704 w) show_viewer=true;;
706 '?') die "invalid option \
`-$OPTARG'";;
707 :) die "missing argument to \`-$OPTARG' option";;
708 *) die "internal error";;
712 isoname=$(basename $isofile)
713 isofile="$(cd `dirname $isofile`; pwd)/${isoname}"
715 shift $(($OPTIND - 1))
718 if [ $# -gt 0 -a -n "$1" ]; then RESULTS=$1; else RESULTS=autotest.log; fi
721 result_file=$WORKDIR/results.log
722 debug "result_file=${result_file}"
724 log "Logging results to file: ${RESULTS}"
728 log "Begin Testing: ${isoname}"
729 log "Tests: ${tests}"
731 for test in ${tests}; do
735 if [ $result != 0 ]; then
736 echo "${result}" > $result_file
741 log "End Testing: ${isoname}"
743 } | sudo tee --append $RESULTS
745 if [ -s "$result_file" ]; then
746 exit $(cat $result_file)