[Ovirt-devel] [PATCH] Introduces automated testing.

Darryl L. Pierce dpierce at redhat.com
Tue Mar 31 16:35:32 UTC 2009


This patch creates a virtual bridge, then runs an instance of dnsmasq on
it. A virtual machine is then launched and PXE boots the node.

Two tests are run in this mannner: one which performs a stateless boot
of the node via PXE. The second performs a stateful install of the node
via PXE.

Signed-off-by: Darryl L. Pierce <dpierce at redhat.com>
---
 autobuild.sh |   16 ++
 autotest.sh  |  567 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 583 insertions(+), 0 deletions(-)
 create mode 100755 autotest.sh

diff --git a/autobuild.sh b/autobuild.sh
index e10ec6a..7ca8ca0 100755
--- a/autobuild.sh
+++ b/autobuild.sh
@@ -18,6 +18,14 @@
 # MA  02110-1301, USA.  A copy of the GNU General Public License is
 # also available at http://www.gnu.org/copyleft/gpl.html.
 
+ME=$(basename "$0")
+warn() { printf '%s: %s\n' "$ME" "$*" >&2; }
+die() { warn "$*"; exit 1; }
+
+# trap '__st=$?; stop_log; exit $__st' 0
+trap '__st=$?; exit $__st' 0
+trap 'exit $?' 1 2 13 15
+
 echo "Running oVirt node image Autobuild"
 
 set -e
@@ -45,3 +53,11 @@ if [ -e /usr/bin/rpmbuild ]; then
            --define "ovirt_local_repo file://$AUTOBUILD_PACKAGE_ROOT/rpm/RPMS" \
            -ta --clean *.tar.gz
 fi
+
+#if [ -x ./autotest.sh ]; then
+#    echo "Testing the build."
+#    ./autotest.sh $1
+#else
+#    echo "NO AUTOTEST FOUND!"
+#    exit 1
+#fi
diff --git a/autotest.sh b/autotest.sh
new file mode 100755
index 0000000..0dbe934
--- /dev/null
+++ b/autotest.sh
@@ -0,0 +1,567 @@
+#!/bin/sh
+#
+# oVirt node image autotest script
+#
+# Copyright (C) 2009 Red Hat, Inc.
+# Written by Darryl L. Pierce <dpierce at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+# To include autotesting on the build system, you need to insert the
+# following snippet *BEFORE* the text that reads "Output Stages":
+# ---8<[begin]---
+#  # Integration test
+#  {
+#    name = integration
+#    label = Test group
+#    module = Test::AutoBuild::Stage::Test
+#    # Don't abort entire cycle if the module test fails
+#    critical = 0
+#  }
+# ---8<[end]---
+#
+# This will, for each module whose autobuild.sh is run, to have a matching
+# autotest.sh to run as well.
+#
+# To run the test locally, all that's needed is for an ISO file named ovirt-node-image.iso
+# be present in the local directory. This will then be put through its paces with test
+# results being send to stdout.
+
+ME=$(basename "$0")
+warn() { printf '%s: %s\n' "$ME" "$*" >&2; }
+die() {  warn "$*"; exit 1; }
+
+# trap '__st=$?; stop_log; exit $__st' 0
+trap '__st=$?; cleanup; exit $__st' 0
+trap 'cleanup; exit $?' 1 2 13 15
+
+test -n "$1" && RESULTS=$1 || RESULTS=autotest.log
+
+echo "Running oVirt node image Autotest"
+
+set -e
+# set -v
+
+OVIRT_NODE_IMAGE_ISO=$PWD/ovirt-node-image.iso
+
+if [ ! -f $OVIRT_NODE_IMAGE_ISO ]; then
+    die "Missing ovirt-node-image.iso file!"
+fi
+
+log () {
+    local text="`date` $*"
+    printf "${text}\n"
+    # sudo bash -c "printf \"[$$] ${text}\n\" >> ${RESULTS}"
+}
+
+cleanup () {
+    destroy_node
+    stop_dnsmasq
+    destroy_test_iface
+}
+
+# Creates a HD disk file.
+# $1 - filename for disk file
+# $2 - size
+create_hard_disk () {
+    local filename=$1
+    local size=$2
+
+    sudo qemu-img create -f raw $filename $size
+    sudo chcon -t virt_image_t $filename
+}
+
+# Creates the XML for a virtual machine.
+# $1 - the file to write the xml
+# $2 - the node name
+# $3 - memory size (in bytes)
+# $4 - the local hard disk (if blank then no disk is used)
+# $5 - the cdrom disk (if blank then no cdrom is used)
+# $6 - the network bridge (if blank then 'default' is used)
+# $7 - optional arguments
+define_node () {
+    local filename=$1
+    local nodename=$2
+    local memory=$3
+    local harddrive=$4
+    local cddrive=$5
+    local bridge=$6
+    local options=$7
+    local result=""
+
+    # flexible options
+    # define defaults, then allow the caller to override them as needed
+    local arch=$(uname -i)
+    local emulator=$(which qemu-kvm)
+    local serial="true"
+    local vncport="-1"
+    local bootdev='hd'
+
+    # if a cdrom was defined, then assume it's the boot device
+    if [ -n "$cddrive" ]; then bootdev='cdrom'; fi
+
+    if [ -n "$options" ]; then eval "$options"; fi
+
+    result="<domain type='kvm'>\n<name>${nodename}</name>\n<memory>${memory}</memory>\n <vcpu>1</vcpu>"
+
+    # begin the os section
+    # inject the boot device
+    result="${result}\n<os>\n<type arch='${arch}' machine='pc'>hvm</type>"
+    result="${result}\n<boot dev='${bootdev}' />"
+    result="${result}\n</os>"
+
+    # virtual machine features
+    result="${result}\n<features>"
+    result="${result}\n<acpi />"
+    if [ -z "${noapic}" ]; then result="${result}\n<apic />"; fi
+    result="${result}\n<pae /></features>"
+    result="${result}\n<clock offset='utc' />"
+    result="${result}\n<on_poweroff>destroy</on_poweroff>"
+    result="${result}\n<on_reboot>restart</on_reboot>"
+    result="${result}\n<on_crash>restart</on_crash>"
+
+    # add devices
+    result="${result}\n<devices>"
+    result="${result}\n<emulator>${emulator}</emulator>"
+    # inject the hard disk if defined
+    if [ -n "$harddrive" ]; then
+	result="${result}\n<disk type='file' device='disk'>"
+	result="${result}\n<source file='$harddrive' />"
+	result="${result}\n<target dev='vda' bus='virtio' />"
+	result="${result}\n</disk>"
+    fi
+    # inject the cdrom drive if defined
+    if [ -n "$cddrive" ]; then
+	result="${result}\n<disk type='file' device='cdrom'>"
+	result="${result}\n<source file='${cddrive}' />"
+	result="${result}\n<target dev='hdc' bus='ide' />"
+	result="${result}\n</disk>"
+    fi
+    # inject the bridge network
+    result="${result}\n<interface type='network'>"
+    result="${result}\n<source network='${bridge}' />"
+    result="${result}\n</interface>"
+    # inject the serial port
+    if [ -n "$serial" ]; then
+	result="${result}\n<serial type='pty' />"
+    fi
+    # inject the vnc port
+    if [ -n "$vncport" ]; then
+	result="${result}\n<console type='pty' />"
+	result="${result}\n<graphics type='vnc' port='${vncport}' autoport='yes' keyman='en-us' />"
+    fi
+    # finish the device section
+    result="${result}\n</devices>"
+
+    result="${result}\n</domain>"
+
+    log "Saving node definition to file: ${filename}"
+    sudo printf "$result" > $filename
+
+    # now define the vm
+    sudo virsh define $filename
+    NODENAME=$nodename
+    log "Defined VM: name=${NODENAME}"
+
+    if [ $? != 0 ]; then die "Unable to define virtual machine: $nodename"; fi
+}
+
+# Returns the mac address for the given node.
+# $1 - the node name
+# $2 - the variable name to set
+get_mac_address () {
+    local nodename=$1
+    local varname=$2
+
+    if [ -z "$nodename" ]; then die "Cannot get mac address for node with a name"; fi
+
+    address=$(sudo virsh dumpxml $nodename|awk '/<mac address/ {
+              match($0,"mac address='"'"'(.*)'"'"'",data); print data[1]}')
+
+    if [ -z "$varname" ]; then die "Cannot set unnamed varilable"; fi
+    eval $varname="$address"
+}
+
+# Starts the named node.
+# $1 - the node name
+start_node () {
+    local nodename=$1
+
+    if [ -z "$nodename" ]; then die "Cannot start node without a name"; fi
+
+    sudo virsh start $nodename
+}
+
+# Destroys any existing instance of the given node.
+# $1 - the node name
+destroy_node () {
+    local nodename=$1
+
+    if [ -z "${nodename}" ]; then
+	nodename=$NODENAME
+    fi
+
+    if [ -n "${nodename}" ]; then
+	log "Destroying VM: ${nodename}"
+	check=$(sudo virsh list --all)
+	if [[ "${check}" =~ "${nodename}" ]]; then
+	    if [[ "${check}" =~ running ]]; then
+		sudo virsh destroy $nodename
+	    fi
+	    sudo virsh undefine $nodename
+	fi
+    fi
+}
+
+# PXE boots a node.
+# $1 - the ISO file
+# $2 - the working directory
+# $3 - kernel arguments; if present then they replace all default flags
+setup_pxeboot () {
+    local isofile=$1
+    local workdir=$2
+    local kernelargs=$3
+    local pxedefault=$workdir/tftpboot/pxelinux.cfg/default
+
+    (cd $workdir && sudo livecd-iso-to-pxeboot $isofile)
+    sudo chmod -R 777 $workdir
+
+    # set default kernel arguments if none were provided
+    # the defaults boot in standalone mode
+    if [ -z "$kernelargs" ]; then
+	kernelargs="ovirt_standalone"
+    fi
+
+    local definition="DEFAULT pxeboot\nTIMEOUT 20\nPROMPT 0\nLABEL pxeboot\n     KERNEL vmlinuz0\n     IPAPPEND 2\n     APPEND rootflags=loop BOOTIF=link|eth*|<MAC> initrd=initrd0.img root=/ovirt-node-image.iso rootfstype=auto console=ttyS0,115200n8 $kernelargs\n"
+
+    sudo bash -c "printf \"${definition}\" > $pxedefault"
+}
+
+# Launches the node as a virtual machine.
+# $1 - the node name
+# $2 - the ISO filename
+# $3 - the hard disk file
+# $4 - the memory size (in MB)
+# $5 - the network bridge to use
+# $6 - kernel arguments
+# $7 - verification method
+pxeboot_node_vm () {
+    local nodename=$1
+    local isofile=$2
+    local diskfile=$3
+    local memsize=$4
+    local bridge=$5
+    local kernel_args=$6
+    local verify_method=$7
+    local xmlfile=$(mktemp)
+    local tftproot=$(mktemp -d)
+    local node_mac_address=""
+    local return_code=0
+
+    destroy_node $nodename
+
+    log "Beginning pxeboot for $nodename"
+    # setup the dnsmasq instance with the iso setup
+    setup_pxeboot "$isofile" "$tftproot" "$kernel_args"
+    create_test_iface $bridge
+    define_node $xmlfile $nodename $memsize "$diskfile" "" $bridge "local bootdev='network'; local noapic='yes'"
+    get_mac_address $nodename "node_mac_address"
+    start_dnsmasq $bridge $tftproot $node_mac_address
+    start_node $nodename
+    if [ -n "$verify_method" ]; then
+	eval $verify_method
+	return_code=$?
+    fi
+    destroy_node $nodename
+    stop_dnsmasq
+    destroy_test_iface $bridge
+    log "Finished pxeboot for $nodename (RC=${return_code})"
+
+    if [ $return_code != 0 ]; then
+	log "Test ended in failure"
+    fi
+
+    test $return_code == 0 && return 0 || exit 1
+}
+
+# Launches the node as a virtual machine with a CDROM.
+# $1 - the node name
+# $2 - the ISO filename
+# $3 - the disk file to use
+# $4 - the memory size (in MB)
+# $5 - the network bridge
+cdrom_boot_node_vm () {
+    local nodename=$1
+    local isofile=$2
+    local diskfile=$3
+    local memsize=$4
+    local bridge=$5
+    local xmlfile=$(mktemp)
+
+    destroy_node $nodename
+
+    log "Beginning cdrom boot for $nodename"
+    create_test_iface $bridge "yes"
+    define_node $xmlfile $nodename $memsize "$diskfile" "$isofile" $bridge
+    start_node $nodename
+    # TODO make sure the node's booted
+    sleep 300
+    # TODO verify the node's running
+    destroy_node $nodename
+    destroy_test_iface $bridge
+    log "Finished cdrom booting for $nodename"
+}
+
+# Creates a virt network.
+# $1 - the network interface name
+# $2 - use DHCP (any value)
+create_test_iface () {
+    local name=$1
+    local dhcp=$2
+    local definition=$(mktemp)
+    local network=$NETWORK
+    local definition=""
+    local xmlfile=$(mktemp)
+
+    destroy_test_iface $name
+    NETWORK_NAME=$name
+
+    log "Creating network definition file: $definition"
+    definition="<network>\n<name>${name}</name>\n<forward mode='nat' />\n<bridge name='${name}' stp='on' forwardDelay='0' />"
+    definition="${definition}\n<ip address='${network}.1' netmask='255.255.255.0'>"
+    if [ -n "$dhcp" ]; then
+	definition="${definition}\n<dhcp>\n<range start='${network}.100' end='${network}.199' />\n</dhcp>"
+    fi
+    definition="${definition}\n</ip>\n</network>"
+
+    printf "Saving network definition file to: ${xmlfile}\n"
+    sudo printf "${definition}" > $xmlfile
+    sudo virsh net-define $xmlfile
+    log "Starting network"
+    sudo virsh net-start $name
+}
+
+# Destroys the test network interface
+# $1 - the network name
+destroy_test_iface () {
+    local networkname=$1
+
+    # if no network was supplied, then check for the global network
+    if [ -z "$networkname" ]; then
+	networkname=$NETWORK_NAME
+    fi
+
+    if [ -n "${networkname}" ]; then
+	log "Destroying network interface: ${networkname}"
+	check=$(sudo virsh net-list --all)
+	if [[ "${check}" =~ "${networkname}" ]]; then
+	    log "- found existing instance"
+	    if [[ "{$check}" =~ active ]]; then
+		log "- shutting down current instance"
+		sudo virsh net-destroy $networkname
+	    fi
+	    log "- undefining previous instance"
+	    sudo virsh net-undefine $networkname
+	fi
+
+        # ensure the bridge interface was destroyed
+	check=$(sudo /sbin/ifconfig)
+	if [[ "${check}" =~ "${networkname}" ]]; then
+	    sudo /sbin/ifconfig $networkname down
+	fi
+    fi
+}
+
+# Starts a simple instance of dnsmasq.
+# $1 - the iface on which dnsmasq works
+# $2 - the root for tftp files
+# $3 - the mac address for the node (ignored if blank)
+start_dnsmasq () {
+    local iface=$1
+    local tftproot=$2
+    local macaddress=$3
+    local pidfile=$2/dnsmasq.pid
+
+    stop_dnsmasq
+    log "Starting dnsmasq"
+    dns_startup="sudo /usr/sbin/dnsmasq --read-ethers
+                                        --dhcp-range=${NETWORK}.100,${NETWORK}.254,255.255.255.0,24h
+                                        --interface=${iface}
+                                        --bind-interfaces
+                                        --except-interface=lo
+                                        --dhcp-boot=tftpboot/pxelinux.0
+                                        --enable-tftp
+                                        --tftp-root=${tftproot}
+                                        --log-queries
+                                        --log-dhcp
+                                        --pid-file=${pidfile}"
+    if [ -n "$macaddress" ]; then
+	dns_startup="${dns_startup} --dhcp-host=${macaddress},${NODE_ADDRESS}"
+   fi
+    # start dnsmasq
+    eval $dns_startup
+    DNSMASQ_PID=$(sudo cat $pidfile)
+}
+
+# Kills the running instance of dnsmasq.
+stop_dnsmasq () {
+    if [ -n "$DNSMASQ_PID" -a "$DNSMASQ_PID" != "0" ]; then
+	local check=$(ps -ef | awk "/${DNSMASQ_PID}/"' { if ($2 ~ '"${DNSMASQ_PID}"') print $2 }')
+
+	if [[ "${check}" == "${DNSMASQ_PID}" ]]; then
+	    log "Killing dnsmasq"
+	    sudo kill -9 $DNSMASQ_PID
+	    return
+	fi
+    fi
+    log "No running instance of dnsmasq found."
+    DNSMASQ_PID="0"
+}
+
+# Boots a node via CDROM.
+# $1 - the node name
+# $2 - the network to use
+# $3 - the working directory
+# $4 - the ISO file to use as CDROM
+cdrom_boot () {
+    local nodename=$1
+    local network=$2
+    local workdir=$3
+    local isofile=$4
+    local diskfile=$workdir/ovirt-harddisk.img
+
+    create_hard_disk $diskfile "10G"
+    cdrom_boot_node_vm $nodename $isofile $diskfile "512" $network
+}
+
+# verify that a node has booted properly
+# $1 - the node's name
+# $2 - the logfile to use
+verify_pxeboot_stateless_standalone () {
+    local nodename=$1
+    local port=$(sudo virsh ttyconsole $nodename)
+    local logfile=$2
+
+    log "Verifying the node is booted correctly"
+    local script='
+log_file -noappend '"${logfile}"'
+set timeout 60
+expect {
+    "Linux version" {send_log "\n\n***\nGot first boot marker\n\n"}
+     timeout {send_log "\n\n***\nDid not receive in time\n\n"
+              exit 1}
+}
+expect {
+    -re "Kernel command line.*ovirt_standalone" {send_log "\n\n***\nGot kernel arguments marker\n\n"}
+    timeout {
+       send_log "\n\n***\nDid not receive in time\n\n"
+       exit 2
+    }
+}
+expect {
+    "Starting ovirt-early:" {send_log "\n\n***\nGot ovirt-early marker\n\n"}
+    timeout {send_log "\n\n***\nDid not receive in time\n\n"
+             exit 3}
+}
+expect {
+    "Starting ovirt:" {send_log "\n\n***\nGot ovirt marker\n\n"}
+    timeout {send_log "\n\n***\nDid not receive in time\n\n"
+             exit 4}
+}
+expect {
+    "Starting ovirt-post:"  {send_log "\n\n***\nGot ovirt-post marker\n\n"}
+    timeout {send_log "\n\n***\nDid not receive in time\n\n"
+             exit 5}
+}
+expect {
+    "Starting ovirt-firstboot:" {send_log "\n\n***\nGot ovirt-firstboot marker\n\n"}
+    timeout {send_log "\n\n***\nDid not receive in time\n\n"
+             exit 6}
+}'
+
+    sudo bash -c "/usr/bin/expect -c '${script}' < ${port}"
+    result=$?
+    printf "result=${result}\n"
+}
+
+# Verify that a stateful node has booted properly.
+# $1 - the node's name
+# $2 - the logfile for recording the transcript
+verify_pxeboot_stateful_standalone () {
+    local nodename=$1
+    local port=$(sudo virsh ttyconsole $nodename)
+    local logfile=$2
+
+    # leverage the existing stateless test
+    verify_pxeboot_stateless_standalone $nodename
+    log "Verifying the node is booted correctly"
+    local script='
+log_file -noappend '"${logfile}"'
+set timeout 180
+expect {
+    -re "login:$" {send_log "\n\n***\nGot login prompt!\n\n"}
+    timeout {send_log "\n\n***\nDid not receive in time\n\n"
+             exit 7}
+}'
+    sudo bash -c "/usr/bin/expect -c '${script}' < ${port}"
+}
+
+# TEST: Performs a PXE boot of the node as a standalone, stateless instance.
+test_pxeboot_stateless_standalone () {
+    local nodename="pxe_stateless_standalone-$$"
+    local hdfile=$(mktemp)
+
+    log "TEST: Booting a stateless standalone node via PXE."
+    create_hard_disk $hdfile "10G"
+    pxeboot_node_vm $nodename $OVIRT_NODE_IMAGE_ISO "${hdfile}" "524288" \
+	            $IFACE_NAME "ovirt_standalone OVIRT_FIRSTBOOT=no" \
+                    "verify_pxeboot_stateless_standalone $nodename 'pxeboot_stateless_standalone.log'"
+}
+
+# TEST: Performs a PXE boot of the node as a standalone instance. The node then performs a full install
+test_pxeboot_stateful_standalone () {
+    local nodename="pxe_stateful_standalone-$$"
+    local hdfile=$(mktemp)
+
+    log "TEST: Installing a stateful standalone node via PXE."
+    create_hard_disk $hdfile "10G"
+    pxeboot_node_vm $nodename $OVIRT_NODE_IMAGE_ISO "${hdfile}" "524288" \
+	            $IFACE_NAME "ovirt_standalone OVIRT_FIRSTBOOT=no ovirt_init=/dev/vda" \
+	            "verify_pxeboot_stateful_standalone $nodename 'pxeboot_stateful_standalone.log'"
+}
+
+# TEST: Performs a CDROM boot of the node as a standalone, stateless instance
+test_cdrom_stateless_standalone () {
+    local nodename="stateless_cdrom_standalone-$$"
+
+    log "TEST: Booting a stateless node from CDROM."
+    cdrom_boot $nodename "$IFACE_NAME" "$TFTP_ROOT_DIR" "$OVIRT_NODE_IMAGE_ISO"
+}
+
+# automated testing entry points
+{
+    IFACE_NAME=testbr$$
+    NODENAME=
+    NETWORK=192.168.$(echo "scale=0; print $$ % 255" | bc -l)
+    NODE_ADDRESS=$NETWORK.100
+    DNSMASQ_PID=0
+
+    log "Starting tests"
+    log "Using network: ${NETWORK}.0"
+
+    test_pxeboot_stateless_standalone
+    test_pxeboot_stateful_standalone
+} | sudo tee --append $RESULTS
-- 
1.5.5.6




More information about the ovirt-devel mailing list